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(python3)",
|
||||||
"Bash(docker --version)",
|
"Bash(docker --version)",
|
||||||
"Bash(docker run --rm -v /opt/teecup/frontend:/app -w /app node:22-slim bash -c ' *)",
|
"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": [
|
"additionalDirectories": [
|
||||||
"/opt/teeoff/deploy",
|
"/opt/teeoff/deploy",
|
||||||
|
|
|
||||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
|
|
@ -12,4 +12,7 @@ __v0_jsx-dev-runtime.ts
|
||||||
# Common ignores
|
# Common ignores
|
||||||
node_modules
|
node_modules
|
||||||
.next/
|
.next/
|
||||||
|
.pnpm-store/
|
||||||
|
next-env.d.ts
|
||||||
|
pnpm-workspace.yaml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Analytics } from '@vercel/analytics/next'
|
|
||||||
import type { Metadata, Viewport } from 'next'
|
import type { Metadata, Viewport } from 'next'
|
||||||
import { Nunito } from 'next/font/google'
|
import { Nunito } from 'next/font/google'
|
||||||
import './globals.css'
|
import './globals.css'
|
||||||
|
|
@ -47,10 +46,7 @@ export default function RootLayout({
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="no" className={`${nunito.variable} bg-background`}>
|
<html lang="no" className={`${nunito.variable} bg-background`}>
|
||||||
<body className="font-sans antialiased">
|
<body className="font-sans antialiased">{children}</body>
|
||||||
{children}
|
|
||||||
{process.env.NODE_ENV === 'production' && <Analytics />}
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export function LoginForm() {
|
||||||
const [sent, setSent] = useState(false)
|
const [sent, setSent] = useState(false)
|
||||||
const [sending, setSending] = useState(false)
|
const [sending, setSending] = useState(false)
|
||||||
const [cooldown, setCooldown] = useState(0)
|
const [cooldown, setCooldown] = useState(0)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
const emailValid = isValidEmail(email)
|
const emailValid = isValidEmail(email)
|
||||||
const showError = touched && email.length > 0 && !emailValid
|
const showError = touched && email.length > 0 && !emailValid
|
||||||
|
|
@ -33,12 +34,25 @@ export function LoginForm() {
|
||||||
}, [cooldown])
|
}, [cooldown])
|
||||||
|
|
||||||
async function sendLink() {
|
async function sendLink() {
|
||||||
// Mock only — pretend to dispatch a magic link.
|
|
||||||
setSending(true)
|
setSending(true)
|
||||||
await new Promise((r) => setTimeout(r, 700))
|
setError(null)
|
||||||
setSending(false)
|
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)
|
setSent(true)
|
||||||
setCooldown(RESEND_COOLDOWN)
|
setCooldown(RESEND_COOLDOWN)
|
||||||
|
} catch {
|
||||||
|
setError("Klarte ikke å sende lenken. Sjekk tilkoblingen og prøv igjen.")
|
||||||
|
} finally {
|
||||||
|
setSending(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
function handleSubmit(e: React.FormEvent) {
|
||||||
|
|
@ -65,6 +79,7 @@ export function LoginForm() {
|
||||||
email={email}
|
email={email}
|
||||||
cooldown={cooldown}
|
cooldown={cooldown}
|
||||||
sending={sending}
|
sending={sending}
|
||||||
|
error={error}
|
||||||
onResend={handleResend}
|
onResend={handleResend}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
/>
|
/>
|
||||||
|
|
@ -101,6 +116,12 @@ export function LoginForm() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p role="alert" className="text-center text-sm font-medium text-destructive">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
|
|
@ -122,12 +143,14 @@ function ConfirmationState({
|
||||||
email,
|
email,
|
||||||
cooldown,
|
cooldown,
|
||||||
sending,
|
sending,
|
||||||
|
error,
|
||||||
onResend,
|
onResend,
|
||||||
onReset,
|
onReset,
|
||||||
}: {
|
}: {
|
||||||
email: string
|
email: string
|
||||||
cooldown: number
|
cooldown: number
|
||||||
sending: boolean
|
sending: boolean
|
||||||
|
error: string | null
|
||||||
onResend: () => void
|
onResend: () => void
|
||||||
onReset: () => void
|
onReset: () => void
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -157,6 +180,12 @@ function ConfirmationState({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p role="alert" className="text-sm font-medium text-destructive">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-1 w-full">
|
<div className="mt-1 w-full">
|
||||||
{cooldown > 0 ? (
|
{cooldown > 0 ? (
|
||||||
<p className="text-sm text-muted-foreground" aria-live="polite">
|
<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} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
typescript: {
|
|
||||||
ignoreBuildErrors: true,
|
|
||||||
},
|
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
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
|
export default nextConfig
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.5.0",
|
"@base-ui/react": "^1.5.0",
|
||||||
"@vercel/analytics": "1.6.1",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^1.16.0",
|
"lucide-react": "^1.16.0",
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,6 @@ importers:
|
||||||
'@base-ui/react':
|
'@base-ui/react':
|
||||||
specifier: ^1.5.0
|
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)
|
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:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
|
|
@ -701,32 +698,6 @@ packages:
|
||||||
'@types/validate-npm-package-name@4.0.2':
|
'@types/validate-npm-package-name@4.0.2':
|
||||||
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
|
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:
|
accepts@2.0.0:
|
||||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -2646,11 +2617,6 @@ snapshots:
|
||||||
|
|
||||||
'@types/validate-npm-package-name@4.0.2': {}
|
'@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:
|
accepts@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-types: 3.0.2
|
mime-types: 3.0.2
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue