@@ -12,9 +12,79 @@ import { PromptCollection } from "@/components/PromptCollection";
1212import { usePrompts } from "@/lib/hooks/usePrompts" ;
1313import { useAuth } from "@/components/AuthProvider" ;
1414
15+ const ONBOARDING_KEY = ( userId : string ) => `closednote_onboarded_${ userId } ` ;
16+
17+ function WelcomeBanner ( { userName, onDismiss } : { userName : string ; onDismiss : ( ) => void } ) {
18+ return (
19+ < div className = "max-w-xl mx-auto py-16 px-4" >
20+ < p className = "text-xs font-medium tracking-widest text-neutral-400 dark:text-neutral-500 uppercase mb-3" >
21+ You're in
22+ </ p >
23+ < h1 className = "text-3xl font-semibold text-neutral-900 dark:text-neutral-100 mb-3" >
24+ Welcome{ userName ? `, ${ userName } ` : "" } .
25+ </ h1 >
26+ < p className = "text-neutral-500 dark:text-neutral-400 text-sm leading-relaxed mb-10" >
27+ closedNote is where you save, version, and refine your prompts. Here's how to get started.
28+ </ p >
29+
30+ < ol className = "space-y-5 mb-10" >
31+ < li className = "flex gap-4" >
32+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 text-xs font-semibold flex items-center justify-center" >
33+ 1
34+ </ span >
35+ < div >
36+ < p className = "text-sm font-medium text-neutral-900 dark:text-neutral-100" > Save a prompt</ p >
37+ < p className = "text-sm text-neutral-500 dark:text-neutral-400 mt-0.5" >
38+ Paste or type any prompt, give it a title, and hit save. Takes 10 seconds.
39+ </ p >
40+ </ div >
41+ </ li >
42+ < li className = "flex gap-4" >
43+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 text-xs font-semibold flex items-center justify-center" >
44+ 2
45+ </ span >
46+ < div >
47+ < p className = "text-sm font-medium text-neutral-900 dark:text-neutral-100" > Edit freely, versions save automatically</ p >
48+ < p className = "text-sm text-neutral-500 dark:text-neutral-400 mt-0.5" >
49+ Every time you update a prompt, the previous version is kept. Restore any version in one click.
50+ </ p >
51+ </ div >
52+ </ li >
53+ < li className = "flex gap-4" >
54+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 text-xs font-semibold flex items-center justify-center" >
55+ 3
56+ </ span >
57+ < div >
58+ < p className = "text-sm font-medium text-neutral-900 dark:text-neutral-100" > Refine with AI when you need it</ p >
59+ < p className = "text-sm text-neutral-500 dark:text-neutral-400 mt-0.5" >
60+ Open any prompt and ask AI to improve it. Add your OpenAI key in Settings to unlock GPT-4o.
61+ </ p >
62+ </ div >
63+ </ li >
64+ </ ol >
65+
66+ < div className = "flex items-center gap-4" >
67+ < Link
68+ href = "/prompts/new"
69+ onClick = { onDismiss }
70+ className = "inline-flex items-center px-5 py-2.5 bg-neutral-900 hover:bg-neutral-800 dark:bg-neutral-100 dark:hover:bg-neutral-200 dark:text-neutral-900 text-white font-medium rounded-full transition-colors text-sm"
71+ >
72+ + Create your first prompt
73+ </ Link >
74+ < button
75+ onClick = { onDismiss }
76+ className = "text-sm text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300 transition-colors"
77+ >
78+ Skip for now
79+ </ button >
80+ </ div >
81+ </ div >
82+ ) ;
83+ }
84+
1585function DashboardContent ( ) {
1686 const { prompts : allPrompts , loading, error } = usePrompts ( ) ;
17- const { loading : authLoading } = useAuth ( ) ;
87+ const { user , loading : authLoading } = useAuth ( ) ;
1888 const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
1989 const [ filters , setFilters ] = useState < {
2090 query : string ;
@@ -23,6 +93,7 @@ function DashboardContent() {
2393 const [ activeCollection , setActiveCollection ] = useState <
2494 string | undefined
2595 > ( ) ;
96+ const [ showWelcome , setShowWelcome ] = useState ( false ) ;
2697
2798 useEffect ( ( ) => {
2899 if ( typeof window !== "undefined" ) {
@@ -36,6 +107,21 @@ function DashboardContent() {
36107 setFilters ( ( prev ) => ( { ...prev , query : searchQuery } ) ) ;
37108 } , [ searchQuery ] ) ;
38109
110+ // Show welcome only once per account, on first login with no prompts
111+ useEffect ( ( ) => {
112+ if ( ! user || authLoading || loading ) return ;
113+ if ( allPrompts . length > 0 ) return ;
114+ const key = ONBOARDING_KEY ( user . id ) ;
115+ if ( ! localStorage . getItem ( key ) ) {
116+ setShowWelcome ( true ) ;
117+ }
118+ } , [ user , authLoading , loading , allPrompts . length ] ) ;
119+
120+ function dismissWelcome ( ) {
121+ if ( user ) localStorage . setItem ( ONBOARDING_KEY ( user . id ) , "1" ) ;
122+ setShowWelcome ( false ) ;
123+ }
124+
39125 const filteredPrompts = useMemo ( ( ) => {
40126 return filterPrompts ( allPrompts , {
41127 query : filters . query || undefined ,
@@ -80,18 +166,25 @@ function DashboardContent() {
80166 ) ) }
81167 </ div >
82168 ) : allPrompts . length === 0 ? (
83- < div className = "max-w-sm mx-auto text-center py-20" >
84- < p className = "text-neutral-900 dark:text-neutral-100 font-medium mb-2" > No prompts yet</ p >
85- < p className = "text-sm text-neutral-500 dark:text-neutral-400 mb-6" >
86- Create your first one and it'll show up here.
87- </ p >
88- < Link
89- href = "/prompts/new"
90- className = "inline-flex items-center px-5 py-2.5 bg-neutral-900 hover:bg-neutral-800 dark:bg-neutral-100 dark:hover:bg-neutral-200 dark:text-neutral-900 text-white font-medium rounded-full transition-colors text-sm"
91- >
92- + New prompt
93- </ Link >
94- </ div >
169+ showWelcome ? (
170+ < WelcomeBanner
171+ userName = { user ?. displayName ?. split ( " " ) [ 0 ] ?? "" }
172+ onDismiss = { dismissWelcome }
173+ />
174+ ) : (
175+ < div className = "max-w-sm mx-auto text-center py-20" >
176+ < p className = "text-neutral-900 dark:text-neutral-100 font-medium mb-2" > No prompts yet</ p >
177+ < p className = "text-sm text-neutral-500 dark:text-neutral-400 mb-6" >
178+ Create your first one and it'll show up here.
179+ </ p >
180+ < Link
181+ href = "/prompts/new"
182+ className = "inline-flex items-center px-5 py-2.5 bg-neutral-900 hover:bg-neutral-800 dark:bg-neutral-100 dark:hover:bg-neutral-200 dark:text-neutral-900 text-white font-medium rounded-full transition-colors text-sm"
183+ >
184+ + New prompt
185+ </ Link >
186+ </ div >
187+ )
95188 ) : (
96189 < div className = "max-w-5xl mx-auto w-full" >
97190 < div >
0 commit comments