Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39fa9ef19d | ||
|
|
ec7d0e496c | ||
|
|
67930e0c1a |
@ -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`)
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
57
app/auth/update-password/page.tsx
Normal file
57
app/auth/update-password/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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!,
|
||||
)
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
3
next-env.d.ts
vendored
@ -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.
|
||||
|
||||
@ -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
|
||||
7183
package-lock.json
generated
7183
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@ -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"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
14
public/sw.js
14
public/sw.js
@ -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))
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -15,7 +15,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user