11import React from "react" ;
2+ // mobx
3+ import { observer } from "mobx-react" ;
24import Link from "next/link" ;
35import { useParams } from "next/navigation" ;
4- import { Briefcase , Hotel , Users } from "lucide-react" ;
6+ import { Briefcase , Check , Hotel , Users , X } from "lucide-react" ;
57// plane ui
68import { EUserPermissions , EUserPermissionsLevel } from "@plane/constants" ;
9+ import { useLocalStorage } from "@plane/hooks" ;
710import { useTranslation } from "@plane/i18n" ;
811// helpers
12+ import { cn } from "@plane/utils" ;
913import { getFileURL } from "@/helpers/file.helper" ;
1014// hooks
11- import { useCommandPalette , useEventTracker , useUser , useUserPermissions } from "@/hooks/store" ;
15+ import { useCommandPalette , useEventTracker , useProject , useUser , useUserPermissions } from "@/hooks/store" ;
1216// plane web constants
1317
14- export const NoProjectsEmptyState = ( ) => {
18+ export const NoProjectsEmptyState = observer ( ( ) => {
1519 // navigation
1620 const { workspaceSlug } = useParams ( ) ;
1721 // store hooks
1822 const { allowPermissions } = useUserPermissions ( ) ;
1923 const { toggleCreateProjectModal } = useCommandPalette ( ) ;
2024 const { setTrackElement } = useEventTracker ( ) ;
2125 const { data : currentUser } = useUser ( ) ;
26+ const { joinedProjectIds } = useProject ( ) ;
27+ // local storage
28+ const { storedValue, setValue } = useLocalStorage ( `quickstart-guide-${ workspaceSlug } ` , {
29+ hide : false ,
30+ visited_members : false ,
31+ visited_workspace : false ,
32+ visited_profile : false ,
33+ } ) ;
2234 const { t } = useTranslation ( ) ;
2335 // derived values
2436 const canCreateProject = allowPermissions (
@@ -31,7 +43,8 @@ export const NoProjectsEmptyState = () => {
3143 id : "create-project" ,
3244 title : "home.empty.create_project.title" ,
3345 description : "home.empty.create_project.description" ,
34- icon : < Briefcase className = "w-[40px] h-[40px] text-custom-primary-100" /> ,
46+ icon : < Briefcase className = "size-10" /> ,
47+ flag : "projects" ,
3548 cta : {
3649 text : "home.empty.create_project.cta" ,
3750 onClick : ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
@@ -47,7 +60,8 @@ export const NoProjectsEmptyState = () => {
4760 id : "invite-team" ,
4861 title : "home.empty.invite_team.title" ,
4962 description : "home.empty.invite_team.description" ,
50- icon : < Users className = "w-[40px] h-[40px] text-custom-primary-100" /> ,
63+ icon : < Users className = "size-10" /> ,
64+ flag : "visited_members" ,
5165 cta : {
5266 text : "home.empty.invite_team.cta" ,
5367 link : `/${ workspaceSlug } /settings/members` ,
@@ -57,7 +71,8 @@ export const NoProjectsEmptyState = () => {
5771 id : "configure-workspace" ,
5872 title : "home.empty.configure_workspace.title" ,
5973 description : "home.empty.configure_workspace.description" ,
60- icon : < Hotel className = "w-[40px] h-[40px] text-custom-primary-100" /> ,
74+ icon : < Hotel className = "size-10" /> ,
75+ flag : "visited_workspace" ,
6176 cta : {
6277 text : "home.empty.configure_workspace.cta" ,
6378 link : "settings" ,
@@ -85,44 +100,94 @@ export const NoProjectsEmptyState = () => {
85100 </ span >
86101 </ Link >
87102 ) ,
103+ flag : "visited_profile" ,
88104 cta : {
89105 text : "home.empty.personalize_account.cta" ,
90106 link : "/profile" ,
91107 } ,
92108 } ,
93109 ] ;
110+ const isComplete = ( type : string ) => {
111+ switch ( type ) {
112+ case "projects" :
113+ return joinedProjectIds ?. length > 0 ;
114+ case "visited_members" :
115+ return storedValue ?. visited_members ;
116+ case "visited_workspace" :
117+ return storedValue ?. visited_workspace ;
118+ case "visited_profile" :
119+ return storedValue ?. visited_profile ;
120+ }
121+ } ;
122+
123+ if ( storedValue ?. hide ) return null ;
94124
95125 return (
96- < div className = "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4" >
97- { EMPTY_STATE_DATA . map ( ( item ) => (
98- < div
99- key = { item . id }
100- className = "flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
126+ < div >
127+ < div className = "flex items-center justify-between mb-4" >
128+ < div className = "text-base font-semibold text-custom-text-350" > { t ( "home.empty.quickstart_guide" ) } </ div >
129+ < button
130+ className = "text-custom-text-300 font-medium text-sm flex items-center gap-1"
131+ onClick = { ( ) => {
132+ if ( ! storedValue ) return ;
133+ setValue ( { ...storedValue , hide : true } ) ;
134+ } }
101135 >
102- < div className = "grid place-items-center bg-custom-primary-100/10 rounded-full size-24 mb-3" >
103- < span className = "text-3xl my-auto" > { item . icon } </ span >
104- </ div >
105- < h3 className = "text-lg font-medium text-custom-text-100 mb-2" > { t ( item . title ) } </ h3 >
106- < p className = "text-sm text-custom-text-200 mb-4 w-[80%] flex-1" > { t ( item . description ) } </ p >
107-
108- { item . cta . link ? (
109- < Link
110- href = { item . cta . link }
111- className = "text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
112- >
113- { t ( item . cta . text ) }
114- </ Link >
115- ) : (
116- < button
117- type = "button"
118- className = "text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
119- onClick = { item . cta . onClick }
136+ < X className = "size-4" />
137+ { t ( "home.empty.not_right_now" ) }
138+ </ button >
139+ </ div >
140+ < div className = "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4" >
141+ { EMPTY_STATE_DATA . map ( ( item ) => {
142+ const isStateComplete = isComplete ( item . flag ) ;
143+ return (
144+ < div
145+ key = { item . id }
146+ className = "flex flex-col items-center justify-center p-6 bg-custom-background-100 rounded-lg text-center border border-custom-border-200/40"
120147 >
121- { t ( item . cta . text ) }
122- </ button >
123- ) }
124- </ div >
125- ) ) }
148+ < div
149+ className = { cn (
150+ "grid place-items-center bg-custom-background-90 rounded-full size-20 mb-3 text-custom-text-400" ,
151+ {
152+ "text-custom-primary-100 bg-custom-primary-100/10" : ! isStateComplete ,
153+ }
154+ ) }
155+ >
156+ < span className = "text-3xl my-auto" > { item . icon } </ span >
157+ </ div >
158+ < h3 className = "text-base font-medium text-custom-text-100 mb-2" > { t ( item . title ) } </ h3 >
159+ < p className = "text-sm text-custom-text-300 mb-2" > { t ( item . description ) } </ p >
160+ { isStateComplete ? (
161+ < div className = "flex items-center gap-2 bg-[#17a34a] rounded-full p-1" >
162+ < Check className = "size-3 text-custom-primary-100 text-white" />
163+ </ div >
164+ ) : item . cta . link ? (
165+ < Link
166+ href = { item . cta . link }
167+ onClick = { ( ) => {
168+ if ( ! storedValue ) return ;
169+ setValue ( {
170+ ...storedValue ,
171+ [ item . flag ] : true ,
172+ } ) ;
173+ } }
174+ className = "text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
175+ >
176+ { t ( item . cta . text ) }
177+ </ Link >
178+ ) : (
179+ < button
180+ type = "button"
181+ className = "text-custom-primary-100 hover:text-custom-primary-200 text-sm font-medium"
182+ onClick = { item . cta . onClick }
183+ >
184+ { t ( item . cta . text ) }
185+ </ button >
186+ ) }
187+ </ div >
188+ ) ;
189+ } ) }
190+ </ div >
126191 </ div >
127192 ) ;
128- } ;
193+ } ) ;
0 commit comments