-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFirstTimePublicProfileBanner.tsx
More file actions
107 lines (93 loc) · 3.77 KB
/
Copy pathFirstTimePublicProfileBanner.tsx
File metadata and controls
107 lines (93 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"use client";
import { useState, useSyncExternalStore, useCallback } from "react";
import Link from "next/link";
import { X, Sparkles } from "lucide-react";
const STORAGE_KEY = "vcp_public_profile_intro_seen";
// Subscribe to localStorage changes (for cross-tab sync if needed)
function subscribe(callback: () => void) {
window.addEventListener("storage", callback);
return () => window.removeEventListener("storage", callback);
}
function getSnapshot(): boolean {
return localStorage.getItem(STORAGE_KEY) === "1";
}
function getServerSnapshot(): boolean {
return true; // On server, assume dismissed to avoid hydration mismatch
}
interface FirstTimePublicProfileBannerProps {
/** Whether user's public profile is already enabled */
profileEnabled: boolean;
/** Whether user has claimed a username */
hasUsername: boolean;
}
/**
* Dismissible onboarding banner shown after first VCP generation.
* Prompts users to enable their public profile.
* Uses localStorage to track dismissal.
*/
export function FirstTimePublicProfileBanner({
profileEnabled,
hasUsername,
}: FirstTimePublicProfileBannerProps) {
// Use useSyncExternalStore to safely read from localStorage
const dismissedFromStorage = useSyncExternalStore(
subscribe,
getSnapshot,
getServerSnapshot
);
const [localDismissed, setLocalDismissed] = useState(false);
const dismissed = dismissedFromStorage || localDismissed;
const dismiss = useCallback(() => {
localStorage.setItem(STORAGE_KEY, "1");
setLocalDismissed(true);
}, []);
// Don't render if dismissed or if profile already enabled
if (dismissed || profileEnabled) {
return null;
}
return (
<div className="relative overflow-hidden rounded-2xl border border-violet-300/50 bg-gradient-to-r from-violet-100 via-indigo-50 to-violet-100 px-6 py-5 shadow-sm">
{/* Decorative sparkle */}
<div className="absolute -right-4 -top-4 h-24 w-24 rounded-full bg-violet-200/30 blur-2xl" />
<div className="absolute -left-4 -bottom-4 h-20 w-20 rounded-full bg-indigo-200/30 blur-2xl" />
<div className="relative flex items-start gap-4">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-violet-500 to-indigo-500 shadow-sm">
<Sparkles className="h-5 w-5 text-white" />
</div>
<div className="min-w-0 flex-1">
<p className="font-semibold text-zinc-900">
Your Vibe Coding Profile is ready!
</p>
<p className="mt-1 text-sm text-zinc-600">
{hasUsername
? "Enable your public profile to share your VCP on Twitter, LinkedIn, and more."
: "Claim a username and enable your public profile to share your VCP with the world."}
</p>
<div className="mt-3 flex flex-wrap items-center gap-3">
<Link
href="/settings/public-profile"
className="inline-flex items-center gap-1.5 rounded-full bg-gradient-to-r from-violet-600 to-indigo-500 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:brightness-110"
>
{hasUsername ? "Enable Public Profile" : "Set Up Public Profile"}
</Link>
<button
type="button"
onClick={dismiss}
className="text-sm font-medium text-zinc-500 hover:text-zinc-700"
>
Maybe later
</button>
</div>
</div>
<button
type="button"
onClick={dismiss}
className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-zinc-400 transition hover:bg-zinc-200/50 hover:text-zinc-600"
aria-label="Dismiss"
>
<X size={16} />
</button>
</div>
</div>
);
}