Spaces:
Runtime error
Runtime error
Commit ·
14b8081
1
Parent(s): 694e696
feat: transition to google-only authentication and streamline UI
Browse files- dev.log +43 -23
- src/app/dashboard/page.tsx +5 -0
- src/app/login/page.tsx +1 -1
- src/app/register/page.tsx +1 -1
- src/components/auth/auto-claimer.tsx +38 -0
- src/components/auth/sign-in-form.tsx +25 -61
- src/components/auth/sign-up-form.tsx +27 -74
- src/lib/auth.ts +14 -4
dev.log
CHANGED
|
@@ -2,36 +2,56 @@
|
|
| 2 |
> vault@0.1.0 dev
|
| 3 |
> next dev --turbopack
|
| 4 |
|
| 5 |
-
⚠ Mismatching @next/swc version, detected: 15.5.7 while Next.js is on 15.5.11. Please ensure these match
|
| 6 |
[@sentry/nextjs] DEPRECATION WARNING: automaticVercelMonitors is deprecated and will be removed in a future version. Use webpack.automaticVercelMonitors instead. (Not supported with Turbopack.)
|
| 7 |
[@sentry/nextjs] DEPRECATION WARNING: reactComponentAnnotation is deprecated and will be removed in a future version. Use webpack.reactComponentAnnotation instead. (Not supported with Turbopack.)
|
| 8 |
-
⚠ Mismatching @next/swc version, detected: 15.5.7 while Next.js is on 15.5.11. Please ensure these match
|
| 9 |
[@sentry/nextjs] DEPRECATION WARNING: automaticVercelMonitors is deprecated and will be removed in a future version. Use webpack.automaticVercelMonitors instead. (Not supported with Turbopack.)
|
| 10 |
[@sentry/nextjs] DEPRECATION WARNING: reactComponentAnnotation is deprecated and will be removed in a future version. Use webpack.reactComponentAnnotation instead. (Not supported with Turbopack.)
|
| 11 |
-
▲ Next.js 15.5.
|
| 12 |
- Local: http://localhost:3000
|
| 13 |
-
- Network: http://192.168.1.
|
| 14 |
- Environments: .env.local
|
| 15 |
- Experiments (use with caution):
|
|
|
|
| 16 |
· clientTraceMetadata
|
| 17 |
|
| 18 |
✓ Starting...
|
| 19 |
-
|
| 20 |
-
✓ Compiled instrumentation
|
| 21 |
-
✓ Compiled
|
| 22 |
-
✓
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
> vault@0.1.0 dev
|
| 3 |
> next dev --turbopack
|
| 4 |
|
|
|
|
| 5 |
[@sentry/nextjs] DEPRECATION WARNING: automaticVercelMonitors is deprecated and will be removed in a future version. Use webpack.automaticVercelMonitors instead. (Not supported with Turbopack.)
|
| 6 |
[@sentry/nextjs] DEPRECATION WARNING: reactComponentAnnotation is deprecated and will be removed in a future version. Use webpack.reactComponentAnnotation instead. (Not supported with Turbopack.)
|
|
|
|
| 7 |
[@sentry/nextjs] DEPRECATION WARNING: automaticVercelMonitors is deprecated and will be removed in a future version. Use webpack.automaticVercelMonitors instead. (Not supported with Turbopack.)
|
| 8 |
[@sentry/nextjs] DEPRECATION WARNING: reactComponentAnnotation is deprecated and will be removed in a future version. Use webpack.reactComponentAnnotation instead. (Not supported with Turbopack.)
|
| 9 |
+
▲ Next.js 15.5.12 (Turbopack)
|
| 10 |
- Local: http://localhost:3000
|
| 11 |
+
- Network: http://192.168.1.225:3000
|
| 12 |
- Environments: .env.local
|
| 13 |
- Experiments (use with caution):
|
| 14 |
+
· serverActions
|
| 15 |
· clientTraceMetadata
|
| 16 |
|
| 17 |
✓ Starting...
|
| 18 |
+
✓ Compiled instrumentation Node.js in 473ms
|
| 19 |
+
✓ Compiled instrumentation Edge in 151ms
|
| 20 |
+
✓ Compiled middleware in 80ms
|
| 21 |
+
✓ Ready in 1849ms
|
| 22 |
+
○ Compiling /login ...
|
| 23 |
+
✓ Compiled /login in 2.3s
|
| 24 |
+
GET /login 200 in 2700ms
|
| 25 |
+
✓ Compiled /favicon.ico in 166ms
|
| 26 |
+
GET /favicon.ico?favicon.0b3bf435.ico 200 in 428ms
|
| 27 |
+
○ Compiling /api/auth/[...all] ...
|
| 28 |
+
✓ Compiled /api/auth/[...all] in 715ms
|
| 29 |
+
POST /api/auth/sign-in/social 200 in 2621ms
|
| 30 |
+
GET /api/auth/callback/google?state=1ytLvxWE8WL44ljrBpjooa5ktCWdMu0l&code=4/0ASc3gC0LWU5y8kD2WHo1zGejy6ln-RUjUSLcGVGrYUEC6xo84ddALNF_6LzH2IEXKsm2Yg&scope=email%20profile%20https://www.googleapis.com/auth/youtube.readonly%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/userinfo.email%20openid&authuser=0&prompt=consent 302 in 2140ms
|
| 31 |
+
GET /api/auth/get-session 200 in 634ms
|
| 32 |
+
○ Compiling /dashboard ...
|
| 33 |
+
✓ Compiled /dashboard in 990ms
|
| 34 |
+
[DashboardPage] userId: pvcRv8Ruf0FqiKwPLSUYjpMhBkTnyhJw
|
| 35 |
+
GET /dashboard 200 in 3728ms
|
| 36 |
+
GET /favicon.ico?favicon.0b3bf435.ico 200 in 234ms
|
| 37 |
+
GET /api/auth/get-session 200 in 619ms
|
| 38 |
+
GET /api/auth/get-session 200 in 1383ms
|
| 39 |
+
POST /dashboard 200 in 1568ms
|
| 40 |
+
GET /api/auth/get-session 200 in 587ms
|
| 41 |
+
POST /dashboard 200 in 604ms
|
| 42 |
+
✓ Compiled in 76ms
|
| 43 |
+
GET /api/auth/get-session 200 in 1479ms
|
| 44 |
+
✓ Compiled /dashboard in 29ms
|
| 45 |
+
GET /api/auth/get-session 200 in 653ms
|
| 46 |
+
[DashboardPage] userId: pvcRv8Ruf0FqiKwPLSUYjpMhBkTnyhJw
|
| 47 |
+
[DashboardPage] userId: pvcRv8Ruf0FqiKwPLSUYjpMhBkTnyhJw
|
| 48 |
+
GET /dashboard 200 in 2634ms
|
| 49 |
+
GET /dashboard 200 in 2544ms
|
| 50 |
+
✓ Compiled in 171ms
|
| 51 |
+
GET /api/auth/get-session 200 in 599ms
|
| 52 |
+
[DashboardPage] userId: pvcRv8Ruf0FqiKwPLSUYjpMhBkTnyhJw
|
| 53 |
+
GET /dashboard 200 in 769ms
|
| 54 |
+
✓ Compiled in 259ms
|
| 55 |
+
GET /api/auth/get-session 200 in 557ms
|
| 56 |
+
[DashboardPage] userId: pvcRv8Ruf0FqiKwPLSUYjpMhBkTnyhJw
|
| 57 |
+
GET /dashboard 200 in 820ms
|
src/app/dashboard/page.tsx
CHANGED
|
@@ -14,6 +14,8 @@ import { KeyMetricsRow } from "@/features/analytics/components/key-metrics-row";
|
|
| 14 |
import { TopEvergreenVideos } from "@/features/analytics/components/top-evergreen-videos";
|
| 15 |
import { QuickAnalyzeTool } from "@/features/vault/components/quick-analyze-tool";
|
| 16 |
import { UserNav } from "@/components/auth/user-nav";
|
|
|
|
|
|
|
| 17 |
|
| 18 |
export default async function DashboardPage({
|
| 19 |
searchParams,
|
|
@@ -130,6 +132,9 @@ export default async function DashboardPage({
|
|
| 130 |
|
| 131 |
return (
|
| 132 |
<div className="max-w-7xl mx-auto px-4 py-8 space-y-8 animate-in fade-in duration-500">
|
|
|
|
|
|
|
|
|
|
| 133 |
<div className="flex justify-between items-start">
|
| 134 |
<div className="space-y-1">
|
| 135 |
<Link href="/" className="inline-block text-primary font-bold text-xl tracking-tight mb-1">Vault</Link>
|
|
|
|
| 14 |
import { TopEvergreenVideos } from "@/features/analytics/components/top-evergreen-videos";
|
| 15 |
import { QuickAnalyzeTool } from "@/features/vault/components/quick-analyze-tool";
|
| 16 |
import { UserNav } from "@/components/auth/user-nav";
|
| 17 |
+
import { AutoClaimer } from "@/components/auth/auto-claimer";
|
| 18 |
+
import { Suspense } from "react";
|
| 19 |
|
| 20 |
export default async function DashboardPage({
|
| 21 |
searchParams,
|
|
|
|
| 132 |
|
| 133 |
return (
|
| 134 |
<div className="max-w-7xl mx-auto px-4 py-8 space-y-8 animate-in fade-in duration-500">
|
| 135 |
+
<Suspense fallback={null}>
|
| 136 |
+
<AutoClaimer />
|
| 137 |
+
</Suspense>
|
| 138 |
<div className="flex justify-between items-start">
|
| 139 |
<div className="space-y-1">
|
| 140 |
<Link href="/" className="inline-block text-primary font-bold text-xl tracking-tight mb-1">Vault</Link>
|
src/app/login/page.tsx
CHANGED
|
@@ -9,7 +9,7 @@ export default function LoginPage() {
|
|
| 9 |
<div className="space-y-2 text-center">
|
| 10 |
<Link href="/" className="inline-block text-primary font-bold text-xl tracking-tight mb-2">Vault</Link>
|
| 11 |
<h1 className="text-3xl font-bold">Login</h1>
|
| 12 |
-
<p className="text-gray-500 dark:text-gray-400">
|
| 13 |
</div>
|
| 14 |
<Suspense fallback={<div className="h-64 flex items-center justify-center">Loading...</div>}>
|
| 15 |
<SignInForm />
|
|
|
|
| 9 |
<div className="space-y-2 text-center">
|
| 10 |
<Link href="/" className="inline-block text-primary font-bold text-xl tracking-tight mb-2">Vault</Link>
|
| 11 |
<h1 className="text-3xl font-bold">Login</h1>
|
| 12 |
+
<p className="text-gray-500 dark:text-gray-400">Continue with your Google account to access your vault</p>
|
| 13 |
</div>
|
| 14 |
<Suspense fallback={<div className="h-64 flex items-center justify-center">Loading...</div>}>
|
| 15 |
<SignInForm />
|
src/app/register/page.tsx
CHANGED
|
@@ -9,7 +9,7 @@ export default function RegisterPage() {
|
|
| 9 |
<div className="space-y-2 text-center">
|
| 10 |
<Link href="/" className="inline-block text-primary font-bold text-xl tracking-tight mb-2">Vault</Link>
|
| 11 |
<h1 className="text-3xl font-bold">Sign Up</h1>
|
| 12 |
-
<p className="text-gray-500 dark:text-gray-400">
|
| 13 |
</div>
|
| 14 |
<Suspense fallback={<div className="h-64 flex items-center justify-center">Loading...</div>}>
|
| 15 |
<SignUpForm />
|
|
|
|
| 9 |
<div className="space-y-2 text-center">
|
| 10 |
<Link href="/" className="inline-block text-primary font-bold text-xl tracking-tight mb-2">Vault</Link>
|
| 11 |
<h1 className="text-3xl font-bold">Sign Up</h1>
|
| 12 |
+
<p className="text-gray-500 dark:text-gray-400">Sign up with your Google account to start your vault</p>
|
| 13 |
</div>
|
| 14 |
<Suspense fallback={<div className="h-64 flex items-center justify-center">Loading...</div>}>
|
| 15 |
<SignUpForm />
|
src/components/auth/auto-claimer.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useEffect, useState } from "react";
|
| 4 |
+
import { useSearchParams } from "next/navigation";
|
| 5 |
+
import { claimVideo } from "@/actions/claim-video";
|
| 6 |
+
import { toast } from "sonner";
|
| 7 |
+
|
| 8 |
+
export function AutoClaimer() {
|
| 9 |
+
const searchParams = useSearchParams();
|
| 10 |
+
const videoId = searchParams.get("videoId");
|
| 11 |
+
const [processed, setProcessed] = useState(false);
|
| 12 |
+
|
| 13 |
+
useEffect(() => {
|
| 14 |
+
if (videoId && !processed) {
|
| 15 |
+
console.log(`[AutoClaimer] Found videoId ${videoId} in URL, attempting to claim...`);
|
| 16 |
+
setProcessed(true);
|
| 17 |
+
|
| 18 |
+
const performClaim = async () => {
|
| 19 |
+
try {
|
| 20 |
+
const result = await claimVideo(videoId);
|
| 21 |
+
if (result.success) {
|
| 22 |
+
toast.success("Video successfully claimed!");
|
| 23 |
+
} else {
|
| 24 |
+
console.error("[AutoClaimer] Claim failed:", result.error);
|
| 25 |
+
toast.error(result.error || "Failed to claim video");
|
| 26 |
+
}
|
| 27 |
+
} catch (err) {
|
| 28 |
+
console.error("[AutoClaimer] Error claiming video:", err);
|
| 29 |
+
toast.error("An unexpected error occurred while claiming your video");
|
| 30 |
+
}
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
performClaim();
|
| 34 |
+
}
|
| 35 |
+
}, [videoId, processed]);
|
| 36 |
+
|
| 37 |
+
return null;
|
| 38 |
+
}
|
src/components/auth/sign-in-form.tsx
CHANGED
|
@@ -3,48 +3,25 @@
|
|
| 3 |
import { useState } from "react";
|
| 4 |
import { signIn } from "@/lib/auth-client";
|
| 5 |
import { Button } from "@/components/ui/button";
|
| 6 |
-
import { Input } from "@/components/ui/input";
|
| 7 |
-
import { Label } from "@/components/ui/label";
|
| 8 |
-
import { useRouter } from "next/navigation";
|
| 9 |
import { Loader2 } from "lucide-react";
|
| 10 |
import { useSearchParams } from "next/navigation";
|
| 11 |
-
import { claimVideo } from "@/actions/claim-video";
|
| 12 |
|
| 13 |
export function SignInForm() {
|
| 14 |
-
const [email, setEmail] = useState("");
|
| 15 |
-
const [password, setPassword] = useState("");
|
| 16 |
const [loading, setLoading] = useState(false);
|
| 17 |
const [error, setError] = useState("");
|
| 18 |
-
const router = useRouter();
|
| 19 |
const searchParams = useSearchParams();
|
| 20 |
const videoId = searchParams.get("videoId");
|
| 21 |
|
| 22 |
-
const
|
| 23 |
-
e.preventDefault();
|
| 24 |
setLoading(true);
|
| 25 |
setError("");
|
| 26 |
-
await signIn.
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
callbackURL: "/dashboard",
|
| 30 |
}, {
|
| 31 |
onRequest: () => {
|
| 32 |
setLoading(true);
|
| 33 |
},
|
| 34 |
-
onSuccess: async () => {
|
| 35 |
-
console.log("[SignInForm] Login success");
|
| 36 |
-
if (videoId) {
|
| 37 |
-
console.log(`[SignInForm] Found videoId ${videoId}, claiming...`);
|
| 38 |
-
try {
|
| 39 |
-
const result = await claimVideo(videoId);
|
| 40 |
-
console.log(`[SignInForm] Claim result:`, result);
|
| 41 |
-
} catch (err) {
|
| 42 |
-
console.error("[SignInForm] Claim failed:", err);
|
| 43 |
-
}
|
| 44 |
-
}
|
| 45 |
-
setLoading(false);
|
| 46 |
-
router.push("/dashboard");
|
| 47 |
-
},
|
| 48 |
onError: (ctx) => {
|
| 49 |
setLoading(false);
|
| 50 |
setError(ctx.error.message);
|
|
@@ -54,41 +31,28 @@ export function SignInForm() {
|
|
| 54 |
|
| 55 |
return (
|
| 56 |
<div className="grid gap-6">
|
| 57 |
-
|
| 58 |
-
<div className="
|
| 59 |
-
|
| 60 |
-
<Label htmlFor="email">Email</Label>
|
| 61 |
-
<Input
|
| 62 |
-
id="email"
|
| 63 |
-
placeholder="name@example.com"
|
| 64 |
-
type="email"
|
| 65 |
-
autoCapitalize="none"
|
| 66 |
-
autoComplete="email"
|
| 67 |
-
autoCorrect="off"
|
| 68 |
-
required
|
| 69 |
-
value={email}
|
| 70 |
-
onChange={(e) => setEmail(e.target.value)}
|
| 71 |
-
/>
|
| 72 |
-
</div>
|
| 73 |
-
<div className="grid gap-2">
|
| 74 |
-
<Label htmlFor="password">Password</Label>
|
| 75 |
-
<Input
|
| 76 |
-
id="password"
|
| 77 |
-
type="password"
|
| 78 |
-
required
|
| 79 |
-
value={password}
|
| 80 |
-
onChange={(e) => setPassword(e.target.value)}
|
| 81 |
-
/>
|
| 82 |
-
</div>
|
| 83 |
-
{error && <p className="text-sm text-red-500">{error}</p>}
|
| 84 |
-
<Button disabled={loading}>
|
| 85 |
-
{loading && (
|
| 86 |
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
| 87 |
-
)}
|
| 88 |
-
Sign In
|
| 89 |
-
</Button>
|
| 90 |
</div>
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
</div>
|
| 93 |
);
|
| 94 |
}
|
|
|
|
| 3 |
import { useState } from "react";
|
| 4 |
import { signIn } from "@/lib/auth-client";
|
| 5 |
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
|
|
|
|
| 6 |
import { Loader2 } from "lucide-react";
|
| 7 |
import { useSearchParams } from "next/navigation";
|
|
|
|
| 8 |
|
| 9 |
export function SignInForm() {
|
|
|
|
|
|
|
| 10 |
const [loading, setLoading] = useState(false);
|
| 11 |
const [error, setError] = useState("");
|
|
|
|
| 12 |
const searchParams = useSearchParams();
|
| 13 |
const videoId = searchParams.get("videoId");
|
| 14 |
|
| 15 |
+
const handleGoogleSignIn = async () => {
|
|
|
|
| 16 |
setLoading(true);
|
| 17 |
setError("");
|
| 18 |
+
await signIn.social({
|
| 19 |
+
provider: "google",
|
| 20 |
+
callbackURL: videoId ? `/dashboard?videoId=${videoId}` : "/dashboard",
|
|
|
|
| 21 |
}, {
|
| 22 |
onRequest: () => {
|
| 23 |
setLoading(true);
|
| 24 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
onError: (ctx) => {
|
| 26 |
setLoading(false);
|
| 27 |
setError(ctx.error.message);
|
|
|
|
| 31 |
|
| 32 |
return (
|
| 33 |
<div className="grid gap-6">
|
| 34 |
+
{error && (
|
| 35 |
+
<div className="bg-destructive/15 text-destructive text-sm p-3 rounded-lg border border-destructive/20 mb-2">
|
| 36 |
+
{error}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
</div>
|
| 38 |
+
)}
|
| 39 |
+
|
| 40 |
+
<Button
|
| 41 |
+
variant="outline"
|
| 42 |
+
type="button"
|
| 43 |
+
disabled={loading}
|
| 44 |
+
onClick={handleGoogleSignIn}
|
| 45 |
+
className="w-full h-11 text-base glass border-white/10 hover:bg-white/5 transition-all duration-300"
|
| 46 |
+
>
|
| 47 |
+
{loading ? (
|
| 48 |
+
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
| 49 |
+
) : (
|
| 50 |
+
<svg className="mr-2 h-5 w-5" aria-hidden="true" focusable="false" data-prefix="fab" data-icon="google" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 488 512">
|
| 51 |
+
<path fill="currentColor" d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"></path>
|
| 52 |
+
</svg>
|
| 53 |
+
)}
|
| 54 |
+
Continue with Google
|
| 55 |
+
</Button>
|
| 56 |
</div>
|
| 57 |
);
|
| 58 |
}
|
src/components/auth/sign-up-form.tsx
CHANGED
|
@@ -1,51 +1,27 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
-
import {
|
| 5 |
-
import { signUp } from "@/lib/auth-client";
|
| 6 |
import { Button } from "@/components/ui/button";
|
| 7 |
-
import { Input } from "@/components/ui/input";
|
| 8 |
-
import { Label } from "@/components/ui/label";
|
| 9 |
import { Loader2 } from "lucide-react";
|
|
|
|
| 10 |
|
| 11 |
export function SignUpForm() {
|
| 12 |
-
const [name, setName] = useState("");
|
| 13 |
-
const [email, setEmail] = useState("");
|
| 14 |
-
const [password, setPassword] = useState("");
|
| 15 |
const [loading, setLoading] = useState(false);
|
| 16 |
const [error, setError] = useState("");
|
| 17 |
-
const router = useRouter();
|
| 18 |
const searchParams = useSearchParams();
|
| 19 |
const videoId = searchParams.get("videoId");
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
const handleSubmit = async (e: React.FormEvent) => {
|
| 24 |
-
e.preventDefault();
|
| 25 |
setLoading(true);
|
| 26 |
setError("");
|
| 27 |
-
await
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
name,
|
| 31 |
}, {
|
| 32 |
onRequest: () => {
|
| 33 |
setLoading(true);
|
| 34 |
},
|
| 35 |
-
onSuccess: async () => {
|
| 36 |
-
if (videoId) {
|
| 37 |
-
console.log(`[SignUpForm] Success. Triggering claim for videoId: ${videoId}`);
|
| 38 |
-
try {
|
| 39 |
-
const { claimVideo } = await import("@/actions/claim-video");
|
| 40 |
-
const result = await claimVideo(videoId);
|
| 41 |
-
console.log(`[SignUpForm] Claim result:`, result);
|
| 42 |
-
} catch (err) {
|
| 43 |
-
console.error("Failed to claim video:", err);
|
| 44 |
-
}
|
| 45 |
-
}
|
| 46 |
-
setLoading(false);
|
| 47 |
-
router.push("/dashboard");
|
| 48 |
-
},
|
| 49 |
onError: (ctx) => {
|
| 50 |
setLoading(false);
|
| 51 |
setError(ctx.error.message);
|
|
@@ -55,51 +31,28 @@ export function SignUpForm() {
|
|
| 55 |
|
| 56 |
return (
|
| 57 |
<div className="grid gap-6">
|
| 58 |
-
|
| 59 |
-
<div className="
|
| 60 |
-
|
| 61 |
-
<Label htmlFor="name">Name</Label>
|
| 62 |
-
<Input
|
| 63 |
-
id="name"
|
| 64 |
-
placeholder="John Doe"
|
| 65 |
-
required
|
| 66 |
-
value={name}
|
| 67 |
-
onChange={(e) => setName(e.target.value)}
|
| 68 |
-
/>
|
| 69 |
-
</div>
|
| 70 |
-
<div className="grid gap-2">
|
| 71 |
-
<Label htmlFor="email">Email</Label>
|
| 72 |
-
<Input
|
| 73 |
-
id="email"
|
| 74 |
-
placeholder="name@example.com"
|
| 75 |
-
type="email"
|
| 76 |
-
autoCapitalize="none"
|
| 77 |
-
autoComplete="email"
|
| 78 |
-
autoCorrect="off"
|
| 79 |
-
required
|
| 80 |
-
value={email}
|
| 81 |
-
onChange={(e) => setEmail(e.target.value)}
|
| 82 |
-
/>
|
| 83 |
-
</div>
|
| 84 |
-
<div className="grid gap-2">
|
| 85 |
-
<Label htmlFor="password">Password</Label>
|
| 86 |
-
<Input
|
| 87 |
-
id="password"
|
| 88 |
-
type="password"
|
| 89 |
-
required
|
| 90 |
-
value={password}
|
| 91 |
-
onChange={(e) => setPassword(e.target.value)}
|
| 92 |
-
/>
|
| 93 |
-
</div>
|
| 94 |
-
{error && <p className="text-sm text-red-500">{error}</p>}
|
| 95 |
-
<Button disabled={loading}>
|
| 96 |
-
{loading && (
|
| 97 |
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
| 98 |
-
)}
|
| 99 |
-
Sign Up
|
| 100 |
-
</Button>
|
| 101 |
</div>
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</div>
|
| 104 |
);
|
| 105 |
}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
+
import { signIn } from "@/lib/auth-client";
|
|
|
|
| 5 |
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
|
| 6 |
import { Loader2 } from "lucide-react";
|
| 7 |
+
import { useSearchParams } from "next/navigation";
|
| 8 |
|
| 9 |
export function SignUpForm() {
|
|
|
|
|
|
|
|
|
|
| 10 |
const [loading, setLoading] = useState(false);
|
| 11 |
const [error, setError] = useState("");
|
|
|
|
| 12 |
const searchParams = useSearchParams();
|
| 13 |
const videoId = searchParams.get("videoId");
|
| 14 |
|
| 15 |
+
const handleGoogleSignIn = async () => {
|
|
|
|
|
|
|
|
|
|
| 16 |
setLoading(true);
|
| 17 |
setError("");
|
| 18 |
+
await signIn.social({
|
| 19 |
+
provider: "google",
|
| 20 |
+
callbackURL: videoId ? `/dashboard?videoId=${videoId}` : "/dashboard",
|
|
|
|
| 21 |
}, {
|
| 22 |
onRequest: () => {
|
| 23 |
setLoading(true);
|
| 24 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
onError: (ctx) => {
|
| 26 |
setLoading(false);
|
| 27 |
setError(ctx.error.message);
|
|
|
|
| 31 |
|
| 32 |
return (
|
| 33 |
<div className="grid gap-6">
|
| 34 |
+
{error && (
|
| 35 |
+
<div className="bg-destructive/15 text-destructive text-sm p-3 rounded-lg border border-destructive/20 mb-2">
|
| 36 |
+
{error}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
</div>
|
| 38 |
+
)}
|
| 39 |
+
|
| 40 |
+
<Button
|
| 41 |
+
variant="outline"
|
| 42 |
+
type="button"
|
| 43 |
+
disabled={loading}
|
| 44 |
+
onClick={handleGoogleSignIn}
|
| 45 |
+
className="w-full h-11 text-base glass border-white/10 hover:bg-white/5 transition-all duration-300"
|
| 46 |
+
>
|
| 47 |
+
{loading ? (
|
| 48 |
+
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
| 49 |
+
) : (
|
| 50 |
+
<svg className="mr-2 h-5 w-5" aria-hidden="true" focusable="false" data-prefix="fab" data-icon="google" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 488 512">
|
| 51 |
+
<path fill="currentColor" d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"></path>
|
| 52 |
+
</svg>
|
| 53 |
+
)}
|
| 54 |
+
Continue with Google
|
| 55 |
+
</Button>
|
| 56 |
</div>
|
| 57 |
);
|
| 58 |
}
|
src/lib/auth.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
import { betterAuth } from "better-auth";
|
| 2 |
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
| 3 |
-
import { db } from "./db";
|
| 4 |
import * as schema from "./db/schema";
|
| 5 |
|
| 6 |
export const auth = betterAuth({
|
| 7 |
database: drizzleAdapter(db, {
|
| 8 |
-
provider: "pg",
|
| 9 |
schema: {
|
| 10 |
user: schema.users,
|
| 11 |
session: schema.sessions,
|
|
@@ -14,10 +14,15 @@ export const auth = betterAuth({
|
|
| 14 |
},
|
| 15 |
}),
|
| 16 |
account: {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
encryptOAuthTokens: false,
|
| 18 |
},
|
|
|
|
| 19 |
emailAndPassword: {
|
| 20 |
-
enabled:
|
| 21 |
},
|
| 22 |
socialProviders: {
|
| 23 |
google: {
|
|
@@ -31,5 +36,10 @@ export const auth = betterAuth({
|
|
| 31 |
}
|
| 32 |
}
|
| 33 |
}
|
| 34 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
});
|
|
|
|
| 1 |
import { betterAuth } from "better-auth";
|
| 2 |
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
| 3 |
+
import { db } from "./db";
|
| 4 |
import * as schema from "./db/schema";
|
| 5 |
|
| 6 |
export const auth = betterAuth({
|
| 7 |
database: drizzleAdapter(db, {
|
| 8 |
+
provider: "pg",
|
| 9 |
schema: {
|
| 10 |
user: schema.users,
|
| 11 |
session: schema.sessions,
|
|
|
|
| 14 |
},
|
| 15 |
}),
|
| 16 |
account: {
|
| 17 |
+
accountLinking: {
|
| 18 |
+
enabled: true,
|
| 19 |
+
trustedProviders: ["google"],
|
| 20 |
+
},
|
| 21 |
encryptOAuthTokens: false,
|
| 22 |
},
|
| 23 |
+
// Transitioning to Google-only authentication
|
| 24 |
emailAndPassword: {
|
| 25 |
+
enabled: false,
|
| 26 |
},
|
| 27 |
socialProviders: {
|
| 28 |
google: {
|
|
|
|
| 36 |
}
|
| 37 |
}
|
| 38 |
}
|
| 39 |
+
},
|
| 40 |
+
advanced: {
|
| 41 |
+
// Help resolve state mismatch on localhost
|
| 42 |
+
useSecureCookies: false, // Ensure cookies work on HTTP localhost
|
| 43 |
+
},
|
| 44 |
+
trustedOrigins: ["http://localhost:3000", "http://127.0.0.1:3000"]
|
| 45 |
});
|