22
33import { useEffect , useState } from "react" ;
44import Image from "next/image" ;
5+ import UserInactiveModal from "@/components/modals/UserInactiveModal" ;
56import Badges from "@/components/shared/Badges" ;
67import Box from "@/components/shared/dashboard/Box" ;
78import { Skeleton } from "@/components/ui/skeleton/skeleton" ;
@@ -14,145 +15,169 @@ import { cn } from "@codevs/ui";
1415import { Badge } from "@codevs/ui/badge" ;
1516
1617import {
17- fetchUserAvailabilityStatus ,
18- updateUserAvailabilityStatus ,
18+ fetchUserAvailabilityStatus ,
19+ updateUserAvailabilityStatus ,
1920} from "../actions" ;
2021import DashboardCertificate from "./DashboardCertificate" ;
2122
2223export default function DashboardProfile ( ) {
23- const { user } = useUserStore ( ) ;
24- const isLoading = ! user ;
25-
26- const [ active , setActive ] = useState < boolean | null > ( null ) ;
27- const [ activeloading , setActiveLoading ] = useState < boolean > ( false ) ;
28-
29- useEffect ( ( ) => {
30- if ( ! user ) return ;
31-
32- const fetchStatus = async ( ) => {
33- try {
34- const res = await fetchUserAvailabilityStatus ( user ?. id ) ;
35- setActive ( res ) ;
36- } catch ( error ) {
37- console . error ( error ) ;
38- }
39- } ;
40-
41- fetchStatus ( ) ;
42- } , [ user ] ) ;
43-
44- const triggerRefresh = useSidebarStore ( ( state ) => state . triggerRefresh ) ;
45-
46- const handleStatusSwitch = async ( ) => {
47- if ( ! user ) return ;
48-
49- setActiveLoading ( true ) ;
50-
51- try {
52- await updateUserAvailabilityStatus ( {
53- userId : user ?. id ,
54- status : ! active ,
55- } ) ;
56-
57- setActive ( ( prev ) => ! prev ) ;
58- await useUserStore . getState ( ) . hydrate ( ) ;
59- } catch ( error ) {
60- console . error ( error ) ;
61- } finally {
62- setActiveLoading ( false ) ;
63- triggerRefresh ( ) ;
64- }
65- } ;
66-
67- return (
68- < >
69- { ! isLoading ? (
70- < Box className = "relative flex-1 overflow-hidden" >
71- { /* Background decoration */ }
72- < div className = "absolute inset-0 bg-gradient-to-br from-customBlue-50/30 to-purple-50/30 dark:from-customBlue-950/10 dark:to-purple-950/10" />
73- < div className = "absolute -top-4 -left-4 h-32 w-32 rounded-full bg-gradient-to-br from-yellow-400/10 to-orange-400/10 blur-2xl" />
74-
75- { user && < DashboardCertificate /> }
76- < div className = "relative mx-auto flex flex-col items-center gap-4" >
77- < div className = "text-center" >
78- < h2 className = "mt-2 sm:mt-4 text-2xl sm:text-3xl font-bold bg-gradient-to-r from-purple-600 to-customBlue-600 bg-clip-text text-transparent md:mt-0" >
79- Hello, { user ?. first_name ?? "" } !
80- </ h2 >
81- < p className = "text-xs sm:text-sm text-gray-500 dark:text-gray-400" > Ready to level up today?</ p >
82- </ div >
83-
84- < div className = "relative" >
85- { /* Profile picture with enhanced styling */ }
86- < div className = "relative group" >
87- < div className = "absolute inset-0 bg-gradient-to-br from-customBlue-500 to-purple-500 rounded-2xl blur-sm opacity-75 group-hover:opacity-100 transition-opacity" > </ div >
88- < div className = "relative bg-white dark:bg-gray-800 p-1 rounded-2xl" >
89- < Image
90- alt = { `Profile picture of ${ user ?. first_name } ${ user ?. last_name || '' } , ${ user ?. display_position || 'team member' } ` }
91- src = { user ?. image_url ? `${ user . image_url } ` : defaultAvatar }
92- width = { 100 }
93- height = { 100 }
94- title = { `${ user ?. first_name } 's Profile Picture` }
95- className = "h-[100px] w-[100px] rounded-xl object-cover transition-transform duration-300 group-hover:scale-105"
96- />
97- </ div >
98- { /* Online status indicator */ }
99- < div className = "absolute -bottom-1 -right-1 h-6 w-6 bg-white dark:bg-gray-800 rounded-full flex items-center justify-center" >
100- < div className = "h-4 w-4 bg-green-500 rounded-full animate-pulse" > </ div >
101- </ div >
102- </ div >
103- </ div >
104-
105- < div className = "text-center" >
106- < p className = "text-lg font-semibold text-gray-800 dark:text-gray-200" > { user ?. display_position } </ p >
107- < div className = "mt-2" >
108- < Badges />
109- </ div >
110- </ div >
111- </ div >
112-
113- { /* Enhanced status switch */ }
114- < div className = "absolute right-2 top-2 sm:right-4 sm:top-4" >
115- { active !== null && (
116- < div className = "flex flex-col items-end gap-1 sm:gap-2" >
117- < Badge className = { cn (
118- "text-xs sm:text-sm font-medium transition-all duration-300 px-2 py-1" ,
119- active
120- ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 border border-green-200 dark:border-green-800"
121- : "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400 border border-red-200 dark:border-red-800"
122- ) } >
123- < div className = "flex items-center gap-1" >
124- < div className = { cn (
125- "h-1.5 w-1.5 rounded-full" ,
126- active ? "bg-green-600 dark:bg-green-400 animate-pulse" : "bg-red-600 dark:bg-red-400"
127- ) } />
128- < span className = "hidden sm:inline" > { active ? "Active" : "Inactive" } </ span >
129- < span className = "sm:hidden" > { active ? "On" : "Off" } </ span >
130- </ div >
131- </ Badge >
132-
133- < SwitchStatusButton
134- isActive = { active }
135- disabled = { activeloading }
136- handleSwitch = { handleStatusSwitch }
137- />
138-
139- < p className = "hidden sm:block text-[10px] sm:text-xs text-gray-500 dark:text-gray-400 text-center max-w-[80px] leading-tight" >
140- { active ? "Available for work" : "Away" }
141- </ p >
142- </ div >
143- ) }
144- </ div >
145- </ Box >
146- ) : (
147- < Box className = "flex-1" >
148- < div className = "mx-auto flex flex-col items-center gap-3" >
149- < Skeleton className = "h-8 w-full" />
150- < Skeleton className = "h-32 w-full" />
151- < Skeleton className = "h-8 w-full" />
152- < Skeleton className = "h-24 w-full" />
153- </ div >
154- </ Box >
155- ) }
156- </ >
157- ) ;
24+ const { user } = useUserStore ( ) ;
25+ const isLoading = ! user ;
26+
27+ const [ active , setActive ] = useState < boolean | null > ( null ) ;
28+ const [ activeloading , setActiveLoading ] = useState < boolean > ( false ) ;
29+ const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
30+
31+ useEffect ( ( ) => {
32+ if ( ! user ) return ;
33+
34+ const fetchStatus = async ( ) => {
35+ try {
36+ const res = await fetchUserAvailabilityStatus ( user ?. id ) ;
37+ setActive ( res ) ;
38+ } catch ( error ) {
39+ console . error ( error ) ;
40+ }
41+ } ;
42+
43+ fetchStatus ( ) ;
44+ } , [ user ] ) ;
45+
46+ const triggerRefresh = useSidebarStore ( ( state ) => state . triggerRefresh ) ;
47+
48+ const handleStatusSwitch = async ( ) => {
49+ if ( ! user ) return ;
50+
51+ setActiveLoading ( true ) ;
52+
53+ // if inactive, it cannot be set to active from here
54+ if ( active === false ) {
55+ setActiveLoading ( false ) ;
56+ setIsModalOpen ( true ) ;
57+ return ;
58+ }
59+
60+ try {
61+ await updateUserAvailabilityStatus ( {
62+ userId : user ?. id ,
63+ status : ! active ,
64+ } ) ;
65+
66+ setActive ( ( prev ) => ! prev ) ;
67+ await useUserStore . getState ( ) . hydrate ( ) ;
68+ } catch ( error ) {
69+ console . error ( error ) ;
70+ } finally {
71+ setActiveLoading ( false ) ;
72+ triggerRefresh ( ) ;
73+ }
74+ } ;
75+
76+ return (
77+ < >
78+ < UserInactiveModal
79+ isOpen = { isModalOpen }
80+ onClose = { ( ) => setIsModalOpen ( false ) }
81+ />
82+ { ! isLoading ? (
83+ < Box className = "relative flex-1 overflow-hidden" >
84+ { /* Background decoration */ }
85+ < div className = "from-customBlue-50/30 dark:from-customBlue-950/10 absolute inset-0 bg-gradient-to-br to-purple-50/30 dark:to-purple-950/10" />
86+ < div className = "absolute -left-4 -top-4 h-32 w-32 rounded-full bg-gradient-to-br from-yellow-400/10 to-orange-400/10 blur-2xl" />
87+
88+ { user && < DashboardCertificate /> }
89+ < div className = "relative mx-auto flex flex-col items-center gap-4" >
90+ < div className = "text-center" >
91+ < h2 className = "to-customBlue-600 mt-2 bg-gradient-to-r from-purple-600 bg-clip-text text-2xl font-bold text-transparent sm:mt-4 sm:text-3xl md:mt-0" >
92+ Hello, { user ?. first_name ?? "" } !
93+ </ h2 >
94+ < p className = "text-xs text-gray-500 dark:text-gray-400 sm:text-sm" >
95+ Ready to level up today?
96+ </ p >
97+ </ div >
98+
99+ < div className = "relative" >
100+ { /* Profile picture with enhanced styling */ }
101+ < div className = "group relative" >
102+ < div className = "from-customBlue-500 absolute inset-0 rounded-2xl bg-gradient-to-br to-purple-500 opacity-75 blur-sm transition-opacity group-hover:opacity-100" > </ div >
103+ < div className = "relative rounded-2xl bg-white p-1 dark:bg-gray-800" >
104+ < Image
105+ alt = { `Profile picture of ${ user ?. first_name } ${ user ?. last_name || "" } , ${ user ?. display_position || "team member" } ` }
106+ src = { user ?. image_url ? `${ user . image_url } ` : defaultAvatar }
107+ width = { 100 }
108+ height = { 100 }
109+ title = { `${ user ?. first_name } 's Profile Picture` }
110+ className = "h-[100px] w-[100px] rounded-xl object-cover transition-transform duration-300 group-hover:scale-105"
111+ />
112+ </ div >
113+ { /* Online status indicator */ }
114+ < div className = "absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-white dark:bg-gray-800" >
115+ < div className = "h-4 w-4 animate-pulse rounded-full bg-green-500" > </ div >
116+ </ div >
117+ </ div >
118+ </ div >
119+
120+ < div className = "text-center" >
121+ < p className = "text-lg font-semibold text-gray-800 dark:text-gray-200" >
122+ { user ?. display_position }
123+ </ p >
124+ < div className = "mt-2" >
125+ < Badges />
126+ </ div >
127+ </ div >
128+ </ div >
129+
130+ { /* Enhanced status switch */ }
131+ < div className = "absolute right-2 top-2 sm:right-4 sm:top-4" >
132+ { active !== null && (
133+ < div className = "flex flex-col items-end gap-1 sm:gap-2" >
134+ < Badge
135+ className = { cn (
136+ "px-2 py-1 text-xs font-medium transition-all duration-300 sm:text-sm" ,
137+ active
138+ ? "border border-green-200 bg-green-100 text-green-800 dark:border-green-800 dark:bg-green-900/30 dark:text-green-400"
139+ : "border border-red-200 bg-red-100 text-red-800 dark:border-red-800 dark:bg-red-900/30 dark:text-red-400" ,
140+ ) }
141+ >
142+ < div className = "flex items-center gap-1" >
143+ < div
144+ className = { cn (
145+ "h-1.5 w-1.5 rounded-full" ,
146+ active
147+ ? "animate-pulse bg-green-600 dark:bg-green-400"
148+ : "bg-red-600 dark:bg-red-400" ,
149+ ) }
150+ />
151+ < span className = "hidden sm:inline" >
152+ { active ? "Active" : "Inactive" }
153+ </ span >
154+ < span className = "sm:hidden" > { active ? "On" : "Off" } </ span >
155+ </ div >
156+ </ Badge >
157+
158+ < SwitchStatusButton
159+ isActive = { active }
160+ disabled = { activeloading }
161+ handleSwitch = { handleStatusSwitch }
162+ />
163+
164+ < p className = "hidden max-w-[80px] text-center text-[10px] leading-tight text-gray-500 dark:text-gray-400 sm:block sm:text-xs" >
165+ { active ? "Available for work" : "Away" }
166+ </ p >
167+ </ div >
168+ ) }
169+ </ div >
170+ </ Box >
171+ ) : (
172+ < Box className = "flex-1" >
173+ < div className = "mx-auto flex flex-col items-center gap-3" >
174+ < Skeleton className = "h-8 w-full" />
175+ < Skeleton className = "h-32 w-full" />
176+ < Skeleton className = "h-8 w-full" />
177+ < Skeleton className = "h-24 w-full" />
178+ </ div >
179+ </ Box >
180+ ) }
181+ </ >
182+ ) ;
158183}
0 commit comments