Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d726595fc | ||
|
|
315c22de65 | ||
|
|
19a80d1c74 | ||
|
|
e85bdabf10 | ||
|
|
e2ae729a60 | ||
|
|
8b18dc55c8 | ||
|
|
3e0385d5d9 |
@@ -0,0 +1,116 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# Overrides the default run name (the tagged commit's message) so the Actions
|
||||||
|
# runs list shows "Release v0.1.0" instead.
|
||||||
|
run-name: "Release ${{ gitea.event.inputs.tag || gitea.ref_name }}"
|
||||||
|
|
||||||
|
# Publishes the games-frontend container image to the Gitea container registry
|
||||||
|
# AND creates a Gitea Release whose body is the annotated tag's message
|
||||||
|
# (generated locally by `make release VERSION=...` via git-cliff). Triggered
|
||||||
|
# by a version tag push, or manually via workflow_dispatch with a tag. Pushing
|
||||||
|
# to main does nothing, so work-in-progress commits never ship. Each release
|
||||||
|
# publishes two image tags: the version (e.g. v0.1.0) and "latest".
|
||||||
|
#
|
||||||
|
# The image carries no backend URL (Vite build runs with no .env); the
|
||||||
|
# backend is injected at container start from VITE_API_URL, so no
|
||||||
|
# build-args or secrets are needed here beyond the registry login.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Tag to release (e.g. v0.1.0)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
env:
|
||||||
|
# Resolve the target tag for both triggers: explicit input on manual
|
||||||
|
# dispatch, otherwise the pushed tag ref.
|
||||||
|
TAG: ${{ gitea.event.inputs.tag || gitea.ref_name }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Full history ensures the tag annotation (the release notes) is present.
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ gitea.event.inputs.tag || gitea.ref }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Gitea Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.linuxhg.com
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
# PAT stored as a repo Actions secret (auto GITHUB_TOKEN lacks package scope in Gitea)
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
# Publishes both the exact version (e.g. v0.1.0) and the movable "latest" tag.
|
||||||
|
# Deployments default to "latest" via ${IMAGE_TAG:-latest} in docker-compose.yml;
|
||||||
|
# pin or roll back by setting IMAGE_TAG in .env.
|
||||||
|
tags: |
|
||||||
|
git.linuxhg.com/games-database/games-frontend:${{ env.TAG }}
|
||||||
|
git.linuxhg.com/games-database/games-frontend:latest
|
||||||
|
|
||||||
|
- name: Create Gitea Release
|
||||||
|
env:
|
||||||
|
# REGISTRY_TOKEN is reused for release creation because Gitea's auto
|
||||||
|
# GITHUB_TOKEN cannot create releases on this instance. The PAT must
|
||||||
|
# carry write:repository scope. Idempotent: re-runs update an existing
|
||||||
|
# release for this tag instead of failing with 409. On any HTTP error
|
||||||
|
# the API response body is printed so a 403 names the missing scope.
|
||||||
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
REPO: ${{ gitea.repository }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${TAG:?TAG is required}"
|
||||||
|
API="https://git.linuxhg.com/api/v1/repos/${REPO}/releases"
|
||||||
|
AUTH="Authorization: token ${TOKEN}"
|
||||||
|
# Release body = the annotated tag's message (the git-cliff notes).
|
||||||
|
BODY="$(git tag -l --format='%(contents)' "${TAG}")"
|
||||||
|
|
||||||
|
# Tags containing a '-' (e.g. v0.1.0-rc1) are published as pre-releases.
|
||||||
|
PRE="false"; case "${TAG}" in *-*) PRE="true";; esac
|
||||||
|
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg t "${TAG}" --arg n "${TAG}" --arg b "${BODY}" --argjson p "${PRE}" \
|
||||||
|
'{tag_name:$t, name:$n, body:$b, draft:false, prerelease:$p}')
|
||||||
|
|
||||||
|
# POST/PATCH the release, surfacing Gitea's error message on failure
|
||||||
|
# (e.g. "token does not have write scope") instead of failing silently.
|
||||||
|
api_call() {
|
||||||
|
local method="$1" url="$2" resp code rbody
|
||||||
|
resp="$(curl -sS -w '\n%{http_code}' -X "${method}" \
|
||||||
|
-H "${AUTH}" -H "Content-Type: application/json" \
|
||||||
|
-d "${PAYLOAD}" "${url}")"
|
||||||
|
code="$(printf '%s' "${resp}" | tail -n1)"
|
||||||
|
rbody="$(printf '%s' "${resp}" | sed '$d')"
|
||||||
|
if [ "${code}" -ge 400 ]; then
|
||||||
|
echo "::error::Release API ${code} (${method} ${url}): ${rbody}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
EXISTING_ID="$(curl -sS -H "${AUTH}" "${API}/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null || true)"
|
||||||
|
if [ -n "${EXISTING_ID}" ]; then
|
||||||
|
api_call PATCH "${API}/${EXISTING_ID}"
|
||||||
|
echo "Updated existing release id=${EXISTING_ID} for ${TAG}"
|
||||||
|
else
|
||||||
|
api_call POST "${API}"
|
||||||
|
echo "Created new release for ${TAG}"
|
||||||
|
fi
|
||||||
+18
-1
@@ -1,2 +1,19 @@
|
|||||||
|
# Multi-stage: build the Vite app here so CI produces an env-independent
|
||||||
|
# image (no .env needed at build time — the backend URL is injected at
|
||||||
|
# container start, see docker-entrypoint.d/30-frontend-config.sh).
|
||||||
|
FROM oven/bun:1 AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json bun.lockb ./
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
FROM nginx:stable-alpine-perl
|
FROM nginx:stable-alpine-perl
|
||||||
COPY ./dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
# nginx's entrypoint auto-runs *.sh in /docker-entrypoint.d/ on start.
|
||||||
|
# Unaffected by mounting a custom /etc/nginx/nginx.conf (separate path).
|
||||||
|
COPY docker-entrypoint.d/ /docker-entrypoint.d/
|
||||||
|
RUN chmod +x /docker-entrypoint.d/*.sh
|
||||||
|
EXPOSE 80
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
.PHONY: help release
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "Available targets:"
|
||||||
|
@echo ""
|
||||||
|
@echo "Release:"
|
||||||
|
@echo " ./release v0.1.0 - Tag, push, and release (notes auto-generated from commits)"
|
||||||
|
@echo " make release VERSION=v0.1.0 - Same, explicit make form"
|
||||||
|
|
||||||
|
# Create an annotated version tag carrying auto-generated release notes (git-cliff)
|
||||||
|
# and push it. The tag push triggers .gitea/workflows/release.yml, which builds the
|
||||||
|
# image and publishes a Gitea Release whose body is this tag's message. Notes come
|
||||||
|
# entirely from Conventional Commits — no hand-written message required.
|
||||||
|
#
|
||||||
|
# git-cliff's --latest needs the tag to exist to scope the notes, so we create a
|
||||||
|
# throwaway lightweight tag, generate the notes, replace it with an annotated tag,
|
||||||
|
# then push. --cleanup=verbatim keeps the markdown "###" group headers (git's
|
||||||
|
# default cleanup would strip lines starting with "#").
|
||||||
|
#
|
||||||
|
# Requires git-cliff: https://git-cliff.org/install
|
||||||
|
# Usage: make release VERSION=v0.1.0
|
||||||
|
release:
|
||||||
|
@test -n "$(VERSION)" || { echo "Usage: make release VERSION=v0.1.0"; exit 1; }
|
||||||
|
@command -v git-cliff >/dev/null 2>&1 || { echo "git-cliff not found — install: https://git-cliff.org/install"; exit 1; }
|
||||||
|
@if git rev-parse "$(VERSION)" >/dev/null 2>&1; then echo "Tag $(VERSION) already exists locally — delete it first: git tag -d $(VERSION)"; exit 1; fi
|
||||||
|
@echo "Generating release notes for $(VERSION)..."
|
||||||
|
@git tag "$(VERSION)" HEAD && \
|
||||||
|
(git cliff --latest --config cliff.toml > .release-notes.tmp && git tag -d "$(VERSION)" >/dev/null) || \
|
||||||
|
{ git tag -d "$(VERSION)" >/dev/null 2>&1; rm -f .release-notes.tmp; echo "git-cliff failed"; exit 1; }
|
||||||
|
@git tag -a --cleanup=verbatim -F .release-notes.tmp "$(VERSION)" HEAD && rm -f .release-notes.tmp
|
||||||
|
@git push origin "$(VERSION)"
|
||||||
|
@echo "Pushed $(VERSION) — Gitea Actions will build the image and publish the Release."
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# git-cliff configuration — generates the body of each Gitea Release from
|
||||||
|
# Conventional Commits accumulated since the previous tag. Used locally by
|
||||||
|
# `make release` with --latest so only the current tag's section is emitted
|
||||||
|
# (no full history, no header — the Gitea Release title is the tag).
|
||||||
|
# Docs: https://git-cliff.org/docs/configuration
|
||||||
|
|
||||||
|
[changelog]
|
||||||
|
header = ""
|
||||||
|
body = """
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}\
|
||||||
|
### {{ group | upper_first }}
|
||||||
|
{% for commit in commits %}\
|
||||||
|
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{{ commit.message | upper_first }} ({{ commit.id | truncate(length=7, end="") }})
|
||||||
|
{% endfor %}\
|
||||||
|
{% endfor %}\
|
||||||
|
"""
|
||||||
|
trim = true
|
||||||
|
footer = ""
|
||||||
|
|
||||||
|
[git]
|
||||||
|
conventional_commits = true
|
||||||
|
filter_unconventional = false
|
||||||
|
require_conventional = false
|
||||||
|
split_commits = false
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "Features" },
|
||||||
|
{ message = "^fix", group = "Bug Fixes" },
|
||||||
|
{ message = "^perf", group = "Performance" },
|
||||||
|
{ message = "^refactor", group = "Refactor" },
|
||||||
|
{ message = "^docs", group = "Documentation" },
|
||||||
|
{ message = "^test", group = "Tests" },
|
||||||
|
{ message = "^chore|^ci", group = "Miscellaneous Tasks" },
|
||||||
|
{ message = ".*", group = "Other" },
|
||||||
|
]
|
||||||
|
filter_commits = false
|
||||||
|
tag_pattern = "v[0-9].*"
|
||||||
|
sort_commits = "oldest"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
# Prod run template. The backend URL is injected at container start
|
||||||
|
# (docker-entrypoint.d/30-frontend-config.sh), so the image carries no env.
|
||||||
|
# Set VITE_API_URL including the /api suffix, e.g.:
|
||||||
|
# VITE_API_URL=https://gamesapi.linuxhg.com/api
|
||||||
|
# Either inline below or via env_file .env (environment wins if both set).
|
||||||
|
# Traefik setups: drop ports, add your labels/networks like the existing
|
||||||
|
# games-frontend service; custom nginx.conf mounts keep working.
|
||||||
|
services:
|
||||||
|
games-frontend:
|
||||||
|
image: git.linuxhg.com/games-database/games-frontend:${IMAGE_TAG:-latest}
|
||||||
|
container_name: games-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- VITE_API_URL=
|
||||||
|
# env_file:
|
||||||
|
# - .env
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Generates runtime frontend config from container environment.
|
||||||
|
# Runs automatically at container start via nginx's /docker-entrypoint.d/.
|
||||||
|
# Lets one image talk to any backend: set VITE_API_URL on the service
|
||||||
|
# (compose environment or .env) and restart — no rebuild needed.
|
||||||
|
# The app (src/api/agent.ts) prefers this file, falling back to the
|
||||||
|
# build-time VITE_API_URL when it is absent (local dev, vite preview).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
CONFIG_FILE="/usr/share/nginx/html/config.js"
|
||||||
|
mkdir -p "$(dirname "${CONFIG_FILE}")"
|
||||||
|
|
||||||
|
if [ -z "${VITE_API_URL:-}" ]; then
|
||||||
|
echo "frontend-config: VITE_API_URL not set, writing empty config (build-time env applies)"
|
||||||
|
printf 'window.__APP_CONFIG__ = {};\n' > "${CONFIG_FILE}"
|
||||||
|
else
|
||||||
|
echo "frontend-config: writing config.js with VITE_API_URL=${VITE_API_URL}"
|
||||||
|
printf 'window.__APP_CONFIG__ = { VITE_API_URL: "%s" };\n' "${VITE_API_URL}" > "${CONFIG_FILE}"
|
||||||
|
fi
|
||||||
@@ -32,6 +32,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<!-- Runtime config (backend URL) injected at container start.
|
||||||
|
See docker-entrypoint.d/30-frontend-config.sh and public/config.js. -->
|
||||||
|
<script src="/config.js"></script>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Placeholder replaced at container start by
|
||||||
|
// docker-entrypoint.d/30-frontend-config.sh, which writes the real
|
||||||
|
// VITE_API_URL from the container environment. Served as /config.js
|
||||||
|
// (see index.html). Empty here so local builds fall back to the
|
||||||
|
// build-time import.meta.env value.
|
||||||
|
window.__APP_CONFIG__ = {};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# Project-attached wrapper around `make release` so you can run:
|
||||||
|
# ./release v0.1.0 (or) ./release 0.1.0
|
||||||
|
# instead of:
|
||||||
|
# make release VERSION=v0.1.0
|
||||||
|
# Lives in the repo (no machine-specific alias needed).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
[ "$#" -ge 1 ] || { echo "Usage: ./release v0.1.0" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Accept "0.1.0" or "v0.1.0"; ensure the tag starts with 'v' (the workflow
|
||||||
|
# only triggers on v* tags).
|
||||||
|
VERSION="v${1#v}"
|
||||||
|
|
||||||
|
exec make release "VERSION=${VERSION}"
|
||||||
+1
-1
@@ -58,7 +58,7 @@ function App() {
|
|||||||
<>
|
<>
|
||||||
<ToastContainer position='bottom-right' hideProgressBar />
|
<ToastContainer position='bottom-right' hideProgressBar />
|
||||||
{loggedIn ? <Header /> : ''}
|
{loggedIn ? <Header /> : ''}
|
||||||
<Box mt={loadedPreferences.get().stickyNav ? "20" : "4"} mb={'4'}>
|
<Box mt={loggedIn ? (loadedPreferences.get().stickyNav ? "20" : "4") : "0"} mb={loggedIn ? '4' : '0'}>
|
||||||
{/*<Box mt={loadedPreferences.get().stickyNav ? isSmallerThan768 ? "15%" : "9%" : "4"} mb={'4'}>*/}
|
{/*<Box mt={loadedPreferences.get().stickyNav ? isSmallerThan768 ? "15%" : "9%" : "4"} mb={'4'}>*/}
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/*' element={loggedIn ? <Navigate to='games' /> : <Home />} />
|
<Route path='/*' element={loggedIn ? <Navigate to='games' /> : <Home />} />
|
||||||
|
|||||||
+8
-1
@@ -11,7 +11,14 @@ const sleep = (delay: number) => {
|
|||||||
setTimeout(resolve, delay)
|
setTimeout(resolve, delay)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL
|
// Backend URL: container runtime config first (/config.js, written from
|
||||||
|
// VITE_API_URL at container start), build-time env as fallback for dev.
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__APP_CONFIG__?: { VITE_API_URL?: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
axios.defaults.baseURL = window.__APP_CONFIG__?.VITE_API_URL ?? import.meta.env.VITE_API_URL
|
||||||
|
|
||||||
axios.interceptors.request.use((config) => {
|
axios.interceptors.request.use((config) => {
|
||||||
const token = userToken.get()
|
const token = userToken.get()
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const ActiveFilters = () => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
><Link
|
><Link
|
||||||
|
color="inherit"
|
||||||
onMouseDown={() => setSearchModalOpen.set(true)}>{searchParams}</Link>
|
onMouseDown={() => setSearchModalOpen.set(true)}>{searchParams}</Link>
|
||||||
</Tag>
|
</Tag>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -191,7 +191,8 @@ export default function NavBar() {
|
|||||||
colorPalette={'blue'}
|
colorPalette={'blue'}
|
||||||
size={'md'}
|
size={'md'}
|
||||||
asChild
|
asChild
|
||||||
w={'70%'}
|
px={4}
|
||||||
|
borderWidth={0}
|
||||||
>
|
>
|
||||||
<RouteLink to="/games/create"><LuPlus/><Text mt="1">Create Game</Text></RouteLink>
|
<RouteLink to="/games/create"><LuPlus/><Text mt="1">Create Game</Text></RouteLink>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { VStack, Image, Button, Heading, Flex, Center } from '@chakra-ui/react'
|
|||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
<Flex align={'center'} bgImage={'linear-gradient(to bottom right, #314755, #26a0da)'} height={'100vh'}>
|
<Flex align={'center'} bgImage={'linear-gradient(to bottom right, #314755, #26a0da)'} minH={'100dvh'} w={'100%'}>
|
||||||
<Center w={'100%'}>
|
<Center w={'100%'}>
|
||||||
<VStack gap={4} align={'center'} margin={'auto'}>
|
<VStack gap={4} align={'center'} margin={'auto'}>
|
||||||
<Image src="/assets/Logo.svg" alt="Games Database Logo"/>
|
<Image src="/assets/Logo.svg" alt="Games Database Logo"/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { login } from '../../stateManagement/userState'
|
|||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Button,
|
Button,
|
||||||
Input, HStack, Center,
|
Input, Center, Flex,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import {Field} from '../ui/field'
|
import {Field} from '../ui/field'
|
||||||
import {useColorModeValue} from '../ui/color-mode'
|
import {useColorModeValue} from '../ui/color-mode'
|
||||||
@@ -35,7 +35,8 @@ export default function LoginForm() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container style={{ marginTop: '7rem', paddingBottom: '5rem' }}>
|
<Flex minH={'100dvh'} w={'100%'} align={'center'} justify={'center'}>
|
||||||
|
<Container maxW={'md'} w={'full'} py={'4'}>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Field label="Email" invalid={Boolean(errors.email)} errorText={errors.email?.type === 'required' ? errors.email.message?.toString() : errors.email?.type === 'pattern' ? 'Invalid Email Address' : undefined} mb={'3'}>
|
<Field label="Email" invalid={Boolean(errors.email)} errorText={errors.email?.type === 'required' ? errors.email.message?.toString() : errors.email?.type === 'pattern' ? 'Invalid Email Address' : undefined} mb={'3'}>
|
||||||
<Input
|
<Input
|
||||||
@@ -65,8 +66,7 @@ export default function LoginForm() {
|
|||||||
{...register('password', { required: 'You must specify a password' })}
|
{...register('password', { required: 'You must specify a password' })}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<br/>
|
<Center mt={'6'}>
|
||||||
<HStack>
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
colorPalette="blue"
|
colorPalette="blue"
|
||||||
@@ -75,11 +75,12 @@ export default function LoginForm() {
|
|||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</Center>
|
||||||
<Center mt={'5rem'}>
|
<Center mt={'5rem'}>
|
||||||
<Button asChild variant="outline"><Link to={'/forgotpassword'}>Forgot Password</Link></Button>
|
<Button asChild variant="outline"><Link to={'/forgotpassword'}>Forgot Password</Link></Button>
|
||||||
</Center>
|
</Center>
|
||||||
</form>
|
</form>
|
||||||
</Container>
|
</Container>
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,6 +349,11 @@ const config = defineConfig({
|
|||||||
bg: { base: 'gray.200', _dark: 'gray.800' },
|
bg: { base: 'gray.200', _dark: 'gray.800' },
|
||||||
fontSize: '1.0625rem',
|
fontSize: '1.0625rem',
|
||||||
},
|
},
|
||||||
|
// Native text-selection was near-invisible on dark surfaces.
|
||||||
|
// Match the hover-selection treatment (gray.200 / whiteAlpha.300).
|
||||||
|
'::selection': {
|
||||||
|
bg: { base: 'gray.200', _dark: 'whiteAlpha.300' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user