Compare commits

..

3 Commits

Author SHA1 Message Date
Flatlogic Bot
39fa9ef19d Autosave: 20260408-235134 2026-04-08 23:51:34 +00:00
Flatlogic Bot
ec7d0e496c Autosave: 20260408-230340 2026-04-08 23:03:40 +00:00
Flatlogic Bot
67930e0c1a Autosave: 20260408-215335 2026-04-08 21:53:35 +00:00
16 changed files with 5390 additions and 2030 deletions

View File

@ -4,15 +4,21 @@ import { createClient } from "@/lib/supabase/server"
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get("code")
const type = searchParams.get("type")
const next = searchParams.get("next") ?? "/feed"
// If this is a password recovery flow, handle it specifically
if (type === 'recovery') {
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_SITE_URL}/auth/update-password`)
}
if (code) {
const supabase = await createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}${next}`)
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_SITE_URL}${next}`)
}
}
return NextResponse.redirect(`${origin}/auth/error`)
}
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_SITE_URL}/auth/error`)
}

View File

@ -6,15 +6,17 @@ import { useState } from "react"
import { useRouter } from "next/navigation"
import Link from "next/link"
import { createClient } from "@/lib/supabase/client"
import { Film, Eye, Loader2 } from "lucide-react"
import { Film, Eye, Loader2, Mail } from "lucide-react"
import { useLanguage } from "@/contexts/LanguageContext"
import { LanguageToggle } from "@/components/language-toggle"
import { toast } from "sonner"
export default function LoginPage() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const [resetLoading, setResetLoading] = useState(false)
const router = useRouter()
const { t } = useLanguage()
@ -22,6 +24,8 @@ export default function LoginPage() {
e.preventDefault()
setLoading(true)
setError(null)
console.log("Attempting login for:", email);
const supabase = createClient()
const { error } = await supabase.auth.signInWithPassword({
@ -30,7 +34,8 @@ export default function LoginPage() {
})
if (error) {
setError(t("auth.invalidCredentials"))
console.error("Login error:", error);
setError(error.message)
setLoading(false)
return
}
@ -39,6 +44,31 @@ export default function LoginPage() {
router.refresh()
}
async function handleResetPassword() {
if (!email) {
setError("Bitte gib deine E-Mail-Adresse oben ein.")
return
}
setResetLoading(true)
setError(null)
const supabase = createClient()
// Important: The redirectTo here should be the URL that Supabase sends the user to after they click the link in their email.
// If we want it to hit our app/auth/callback, it handles the recovery flow via the new logic.
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback?type=recovery`,
})
if (error) {
setError(error.message)
setResetLoading(false)
} else {
toast.success("E-Mail zum Zurücksetzen des Passworts wurde gesendet.")
setResetLoading(false)
}
}
return (
<main className="flex min-h-dvh flex-col items-center justify-center px-6">
<div className="w-full max-w-sm">
@ -108,6 +138,19 @@ export default function LoginPage() {
</button>
</form>
<button
onClick={handleResetPassword}
disabled={resetLoading}
className="mt-4 flex w-full items-center justify-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors disabled:opacity-50"
>
{resetLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Mail className="h-4 w-4" />
)}
Passwort vergessen?
</button>
<p className="mt-6 text-center text-sm text-muted-foreground">
{t("auth.dontHaveAccount")}{" "}
<Link href="/auth/sign-up" className="font-medium text-primary hover:underline">
@ -117,4 +160,4 @@ export default function LoginPage() {
</div>
</main>
)
}
}

View File

@ -0,0 +1,57 @@
"use client"
import React, { useState } from "react"
import { useRouter } from "next/navigation"
import { createClient } from "@/lib/supabase/client"
import { Loader2 } from "lucide-react"
import { toast } from "sonner"
export default function UpdatePasswordPage() {
const [password, setPassword] = useState("")
const [loading, setLoading] = useState(false)
const router = useRouter()
const supabase = createClient()
async function handleUpdatePassword(e: React.FormEvent) {
e.preventDefault()
setLoading(true)
const { error } = await supabase.auth.updateUser({
password: password,
})
if (error) {
toast.error(error.message)
} else {
toast.success("Passwort erfolgreich geändert.")
router.push("/feed")
router.refresh()
}
setLoading(false)
}
return (
<main className="flex min-h-dvh flex-col items-center justify-center px-6">
<div className="w-full max-w-sm">
<h1 className="mb-6 text-2xl font-bold">Neues Passwort festlegen</h1>
<form onSubmit={handleUpdatePassword} className="flex flex-col gap-4">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Neues Passwort"
required
className="h-12 w-full rounded-lg border border-input bg-background px-4 text-sm"
/>
<button
type="submit"
disabled={loading}
className="flex h-12 w-full items-center justify-center gap-2 rounded-lg bg-primary font-semibold text-primary-foreground disabled:opacity-50"
>
{loading ? <Loader2 className="h-5 w-5 animate-spin" /> : "Passwort speichern"}
</button>
</form>
</div>
</main>
)
}

View File

@ -6,7 +6,7 @@ export async function GET(request: NextRequest) {
const state = searchParams.get('state')
if (!code) {
return NextResponse.redirect('http://localhost:3000/auth/error?error=oauth_code_missing')
return NextResponse.redirect(new URL('/oauth/error?error=oauth_code_missing', request.url))
}
// Handle OAuth callback for MCP server
@ -21,22 +21,22 @@ export async function GET(request: NextRequest) {
body: JSON.stringify({
code,
state,
redirect_uri: 'http://localhost:3000/oauth/consent'
redirect_uri: new URL('/oauth/consent', request.url).toString()
})
})
const result = await response.json()
if (result.error) {
return NextResponse.redirect(`http://localhost:3000/auth/error?error=${result.error}`)
return NextResponse.redirect(new URL(`/oauth/error?error=${result.error}`, request.url))
}
// Store tokens securely (in production, use proper session management)
// For now, redirect back to app
return NextResponse.redirect('http://localhost:3000/auth/success?mcp_configured=true')
return NextResponse.redirect(new URL('/oauth/success?mcp_configured=true', request.url))
} catch (error) {
console.error('OAuth callback error:', error)
return NextResponse.redirect('http://localhost:3000/auth/error?error=oauth_callback_failed')
return NextResponse.redirect(new URL('/oauth/error?error=oauth_callback_failed', request.url))
}
}

View File

@ -1,8 +1,15 @@
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "https://ekbpexbhuochrplzorce.supabase.co";
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "sb_publishable__UII_iKx3pgvLQvc1xrN1w_qnwP6JOv" || process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY;
if (!url || !key) {
console.error("Supabase config missing!", { url: !!url, key: !!key });
}
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
url!,
key!,
)
}

View File

@ -7,8 +7,8 @@ export async function updateSession(request: NextRequest) {
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
process.env.NEXT_PUBLIC_SUPABASE_URL || "https://ekbpexbhuochrplzorce.supabase.co"!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "sb_publishable__UII_iKx3pgvLQvc1xrN1w_qnwP6JOv" || process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY!,
{
cookies: {
getAll() {

View File

@ -5,8 +5,8 @@ export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
process.env.NEXT_PUBLIC_SUPABASE_URL || "https://ekbpexbhuochrplzorce.supabase.co"!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "sb_publishable__UII_iKx3pgvLQvc1xrN1w_qnwP6JOv" || process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY!,
{
cookies: {
getAll() {

View File

@ -7,6 +7,6 @@ export async function middleware(request: NextRequest) {
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|manifest.json|icons/.*|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
'/((?!_next/static|_next/image|favicon.ico|manifest.json|sw.js|icons/.*|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
}

3
next-env.d.ts vendored
View File

@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -13,6 +13,11 @@ const nextConfig = {
},
],
},
devIndicators: {
appIsrStatus: false,
},
// Allow host for dev server to fix HMR/WebSocket issues
allowedDevOrigins: ['infocusmovieapp-3455.dev.appwizzy.dev'],
}
export default nextConfig
export default nextConfig

0
next.log Normal file
View File

7183
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,32 +4,79 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsx server.ts",
"build": "vite build",
"dev": "next dev -H 0.0.0.0 -p 3001",
"build": "next build",
"preview": "vite preview",
"clean": "rm -rf dist",
"lint": "tsc --noEmit"
"lint": "tsc --noEmit",
"start": "next dev -H 0.0.0.0 -p 3001"
},
"dependencies": {
"@google/genai": "^1.29.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@supabase/ssr": "^0.10.0",
"@supabase/supabase-js": "^2.99.1",
"@tailwindcss/vite": "^4.1.14",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.0.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "3.6.0",
"dotenv": "^17.3.1",
"embla-carousel-react": "^8.6.0",
"express": "^4.22.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"next": "^16.2.3",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-day-picker": "8.10.1",
"date-fns": "3.6.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.72.1",
"react-resizable-panels": "^4.9.0",
"recharts": "^3.8.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"vite": "^6.2.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.2",
"@types/express": "^4.17.25",
"@types/node": "^22.14.0",
"autoprefixer": "^10.4.21",
"tailwindcss": "^4.1.14",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tsx": "^4.21.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"

View File

@ -2,6 +2,7 @@
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -39,8 +39,10 @@ self.addEventListener("fetch", (event) => {
(cached) =>
cached ||
fetch(event.request).then((response) => {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone))
if (response.status === 200 && event.request.method === 'GET') {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone))
}
return response
})
)
@ -52,10 +54,12 @@ self.addEventListener("fetch", (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone))
if (response.status === 200 && event.request.method === 'GET') {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone))
}
return response
})
.catch(() => caches.match(event.request))
)
})
})

View File

@ -15,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{