Før grafikk
This commit is contained in:
parent
0ca9151ed6
commit
be8da63df6
7 changed files with 57 additions and 49 deletions
|
|
@ -155,7 +155,13 @@
|
|||
"Bash(python3)",
|
||||
"Bash(docker --version)",
|
||||
"Bash(docker run --rm -v /opt/teecup/frontend:/app -w /app node:22-slim bash -c ' *)",
|
||||
"Bash(git -C /opt/teecup status --short frontend/)"
|
||||
"Bash(git -C /opt/teecup status --short frontend/)",
|
||||
"Bash(grep -n \"CORS\\\\|cors\\\\|allow_origin\" /opt/teecup/app/main.py *)",
|
||||
"Bash(git -C /opt/teecup ls-files frontend/.gitignore)",
|
||||
"Bash(git -C /opt/teecup rev-parse --show-toplevel)",
|
||||
"Bash(git -C /opt/teecup config --get core.excludesfile)",
|
||||
"Bash(git -C /opt/teecup ls-files .claude/)",
|
||||
"Bash(git -C /opt/teecup status --porcelain)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/opt/teeoff/deploy",
|
||||
|
|
|
|||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
|
|
@ -12,4 +12,7 @@ __v0_jsx-dev-runtime.ts
|
|||
# Common ignores
|
||||
node_modules
|
||||
.next/
|
||||
.pnpm-store/
|
||||
next-env.d.ts
|
||||
pnpm-workspace.yaml
|
||||
.DS_Store
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { Analytics } from '@vercel/analytics/next'
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
import { Nunito } from 'next/font/google'
|
||||
import './globals.css'
|
||||
|
|
@ -47,10 +46,7 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html lang="no" className={`${nunito.variable} bg-background`}>
|
||||
<body className="font-sans antialiased">
|
||||
{children}
|
||||
{process.env.NODE_ENV === 'production' && <Analytics />}
|
||||
</body>
|
||||
<body className="font-sans antialiased">{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export function LoginForm() {
|
|||
const [sent, setSent] = useState(false)
|
||||
const [sending, setSending] = useState(false)
|
||||
const [cooldown, setCooldown] = useState(0)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const emailValid = isValidEmail(email)
|
||||
const showError = touched && email.length > 0 && !emailValid
|
||||
|
|
@ -33,12 +34,25 @@ export function LoginForm() {
|
|||
}, [cooldown])
|
||||
|
||||
async function sendLink() {
|
||||
// Mock only — pretend to dispatch a magic link.
|
||||
setSending(true)
|
||||
await new Promise((r) => setTimeout(r, 700))
|
||||
setSending(false)
|
||||
setSent(true)
|
||||
setCooldown(RESEND_COOLDOWN)
|
||||
setError(null)
|
||||
try {
|
||||
// Alltid samme suksess-respons uansett om e-posten finnes (anti-
|
||||
// enumerering, se ADR-009) -- kun nettverks-/serverfeil havner i catch.
|
||||
const res = await fetch("/auth/request-link", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ email: email.trim(), locale: "nb" }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`request-link: ${res.status}`)
|
||||
setSent(true)
|
||||
setCooldown(RESEND_COOLDOWN)
|
||||
} catch {
|
||||
setError("Klarte ikke å sende lenken. Sjekk tilkoblingen og prøv igjen.")
|
||||
} finally {
|
||||
setSending(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
|
|
@ -65,6 +79,7 @@ export function LoginForm() {
|
|||
email={email}
|
||||
cooldown={cooldown}
|
||||
sending={sending}
|
||||
error={error}
|
||||
onResend={handleResend}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
|
|
@ -101,6 +116,12 @@ export function LoginForm() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p role="alert" className="text-center text-sm font-medium text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={sending}
|
||||
|
|
@ -122,12 +143,14 @@ function ConfirmationState({
|
|||
email,
|
||||
cooldown,
|
||||
sending,
|
||||
error,
|
||||
onResend,
|
||||
onReset,
|
||||
}: {
|
||||
email: string
|
||||
cooldown: number
|
||||
sending: boolean
|
||||
error: string | null
|
||||
onResend: () => void
|
||||
onReset: () => void
|
||||
}) {
|
||||
|
|
@ -157,6 +180,12 @@ function ConfirmationState({
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p role="alert" className="text-sm font-medium text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-1 w-full">
|
||||
{cooldown > 0 ? (
|
||||
<p className="text-sm text-muted-foreground" aria-live="polite">
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
// API-et proxyes server-side under samme opprinnelse (ingen CORS, cookien
|
||||
// fungerer uendret) -- se ADR-009/015. TEECUP_API_ORIGIN peker mot en lokal
|
||||
// backend i dev; i prod peker den mot teecup_api på det delte Docker-nettverket.
|
||||
const API_ORIGIN = process.env.TEECUP_API_ORIGIN || "http://localhost:8000"
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{ source: "/auth/:path*", destination: `${API_ORIGIN}/auth/:path*` },
|
||||
{ source: "/orgs/:path*", destination: `${API_ORIGIN}/orgs/:path*` },
|
||||
{ source: "/health", destination: `${API_ORIGIN}/health` },
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.5.0",
|
||||
"@vercel/analytics": "1.6.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.16.0",
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ importers:
|
|||
'@base-ui/react':
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@vercel/analytics':
|
||||
specifier: 1.6.1
|
||||
version: 1.6.1(next@16.2.6(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
|
|
@ -701,32 +698,6 @@ packages:
|
|||
'@types/validate-npm-package-name@4.0.2':
|
||||
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
|
||||
|
||||
'@vercel/analytics@1.6.1':
|
||||
resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==}
|
||||
peerDependencies:
|
||||
'@remix-run/react': ^2
|
||||
'@sveltejs/kit': ^1 || ^2
|
||||
next: '>= 13'
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
svelte: '>= 4'
|
||||
vue: ^3
|
||||
vue-router: ^4
|
||||
peerDependenciesMeta:
|
||||
'@remix-run/react':
|
||||
optional: true
|
||||
'@sveltejs/kit':
|
||||
optional: true
|
||||
next:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
svelte:
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
vue-router:
|
||||
optional: true
|
||||
|
||||
accepts@2.0.0:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
|
@ -2646,11 +2617,6 @@ snapshots:
|
|||
|
||||
'@types/validate-npm-package-name@4.0.2': {}
|
||||
|
||||
'@vercel/analytics@1.6.1(next@16.2.6(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
|
||||
optionalDependencies:
|
||||
next: 16.2.6(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
|
||||
accepts@2.0.0:
|
||||
dependencies:
|
||||
mime-types: 3.0.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue