@@ -21,18 +21,32 @@ function GroupDetails() {
2121 const { groupId } = Route . useParams ( ) ;
2222 const group = useQuery ( api . group . getById , { id : groupId as Id < "groups" > } ) ;
2323 const removeParticipant = useMutation ( api . group . removeParticipant ) ;
24+ const leaveGroup = useMutation ( api . group . leaveGroup ) ;
2425 const [ inviteCopied , setInviteCopied ] = useState ( false ) ;
2526 const [ removingUserId , setRemovingUserId ] = useState < string | null > ( null ) ;
27+ const [ isLeavingGroup , setIsLeavingGroup ] = useState ( false ) ;
2628 const userId = useMemo ( ( ) => {
2729 if ( typeof window === "undefined" ) return null ;
2830 const user = localStorage . getItem ( "user" ) ;
2931 return user ? JSON . parse ( user ) . _id : null ;
3032 } , [ ] ) ;
31- const isOwner = useMemo ( ( ) => userId && group && String ( group . createdBy ) === String ( userId ) , [ userId , group ] ) ;
32- const inviteLink = useMemo ( ( ) => group ? `${ window . location . origin } /groups/join?groupId=${ group . _id } ` : "" , [ group ] ) ;
33+ const isOwner = useMemo (
34+ ( ) => userId && group && String ( group . createdBy ) === String ( userId ) ,
35+ [ userId , group ]
36+ ) ;
37+ const inviteLink = useMemo (
38+ ( ) =>
39+ group ? `${ window . location . origin } /groups/join?groupId=${ group . _id } ` : "" ,
40+ [ group ]
41+ ) ;
3342
34- async function handleRemoveParticipant ( participantId : string , participantName : string ) {
35- if ( ! confirm ( `Tem certeza que deseja remover ${ participantName } do grupo?` ) ) {
43+ async function handleRemoveParticipant (
44+ participantId : string ,
45+ participantName : string
46+ ) {
47+ if (
48+ ! confirm ( `Tem certeza que deseja remover ${ participantName } do grupo?` )
49+ ) {
3650 return ;
3751 }
3852
@@ -50,6 +64,26 @@ function GroupDetails() {
5064 }
5165 }
5266
67+ async function handleLeaveGroup ( ) {
68+ if ( ! confirm ( "Tem certeza que deseja sair deste grupo?" ) ) {
69+ return ;
70+ }
71+
72+ setIsLeavingGroup ( true ) ;
73+ try {
74+ await leaveGroup ( {
75+ groupId : groupId as Id < "groups" > ,
76+ userId : userId as Id < "users" > ,
77+ } ) ;
78+ navigate ( { to : "/groups" } ) ;
79+ } catch ( error : any ) {
80+ console . error ( "Error leaving group:" , error ) ;
81+ alert ( error . message || "Erro ao sair do grupo. Tente novamente." ) ;
82+ } finally {
83+ setIsLeavingGroup ( false ) ;
84+ }
85+ }
86+
5387 if ( group === undefined ) {
5488 return (
5589 < div className = "space-y-4 p-4" >
@@ -59,18 +93,18 @@ function GroupDetails() {
5993 ) ;
6094 }
6195
62- if ( ! group ) {
63- return < div className = "p-4" > Group not found</ div > ;
64- }
96+ if ( ! group ) {
97+ return < div className = "p-4" > Group not found</ div > ;
98+ }
6599
66100 return (
67101 < main className = "flex flex-col justify-center items-center min-h-screen p-4 pt-16" >
68102 < div className = "w-full max-w-md mb-4" >
69- < button
103+ < button
70104 onClick = { ( ) => navigate ( { to : "/groups" } ) }
71105 className = "flex items-center gap-2 text-[#7c6a5c] hover:text-[#bfa98a] mb-3 transition-colors cursor-pointer"
72106 >
73- < ArrowLeft size = { 20 } />
107+ < ArrowLeft size = { 20 } />
74108 < span className = "font-medium" > Voltar</ span >
75109 </ button >
76110 < div className = "text-center mb-2" >
@@ -83,36 +117,73 @@ function GroupDetails() {
83117 < div className = "bg-[#f8f3ed] p-8 flex flex-col gap-4 rounded-xl shadow w-full max-w-md" >
84118 < div className = "flex items-center justify-between mb-2" >
85119 { isOwner && (
86- < button title = "Editar nome" className = "text-[#7c6a5c] hover:text-[#bfa98a]" >
87- < svg width = "20" height = "20" fill = "none" stroke = "currentColor" strokeWidth = "2" viewBox = "0 0 24 24" > < path d = "M12 20h9" /> < path d = "M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z" /> </ svg >
120+ < button
121+ title = "Editar nome"
122+ className = "text-[#7c6a5c] hover:text-[#bfa98a]"
123+ >
124+ < svg
125+ width = "20"
126+ height = "20"
127+ fill = "none"
128+ stroke = "currentColor"
129+ strokeWidth = "2"
130+ viewBox = "0 0 24 24"
131+ >
132+ < path d = "M12 20h9" />
133+ < path d = "M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
134+ </ svg >
88135 </ button >
89136 ) }
90137 </ div >
91138 < Card className = "bg-white border-none shadow-none p-4 flex flex-col items-center" >
92- < div className = "text-[#7c6a5c] text-sm mb-1" > Estoque Combinado Atual</ div >
93- < div className = "text-3xl font-bold text-green-700 mb-1" > { group . stock ?. toLocaleString ( ) } sacas</ div >
139+ < div className = "text-[#7c6a5c] text-sm mb-1" >
140+ Estoque Combinado Atual
141+ </ div >
142+ < div className = "text-3xl font-bold text-green-700 mb-1" >
143+ { group . stock ?. toLocaleString ( ) } sacas
144+ </ div >
94145 </ Card >
95146 < Card className = "bg-white border-none shadow-none p-4" >
96- < div className = "font-semibold text-[#7c6a5c] mb-2" > Participantes ({ group . participantsFull ?. length ?? 0 } )</ div >
147+ < div className = "font-semibold text-[#7c6a5c] mb-2" >
148+ Participantes ({ group . participantsFull ?. length ?? 0 } )
149+ </ div >
97150 < div className = "flex flex-col gap-2" >
98151 { group . participantsFull ?. map ( ( p : any ) => (
99- < div key = { p . _id } className = "flex items-center gap-3 bg-[#f5ede3] rounded-lg px-3 py-2" >
152+ < div
153+ key = { p . _id }
154+ className = "flex items-center gap-3 bg-[#f5ede3] rounded-lg px-3 py-2"
155+ >
100156 < Avatar name = { p . name ?? p . email ?? "?" } />
101- < span className = "font-medium text-[#7c6a5c] flex-1" > { p . name ?? p . email } { String ( p . _id ) === String ( userId ) ? " (Você)" : "" } </ span >
157+ < span className = "font-medium text-[#7c6a5c] flex-1" >
158+ { p . name ?? p . email }
159+ { String ( p . _id ) === String ( userId ) ? " (Você)" : "" }
160+ </ span >
102161 { isOwner && String ( p . _id ) !== String ( userId ) && (
103162 < button
104163 title = "Remover participante"
105164 className = "text-[#bfa98a] hover:text-red-500 transition-colors disabled:opacity-50"
106- onClick = { ( ) => handleRemoveParticipant ( p . _id , p . name ?? p . email ?? "participante" ) }
165+ onClick = { ( ) =>
166+ handleRemoveParticipant (
167+ p . _id ,
168+ p . name ?? p . email ?? "participante"
169+ )
170+ }
107171 disabled = { removingUserId === p . _id }
108172 >
109173 { removingUserId === p . _id ? (
110174 < div className = "w-5 h-5 border-2 border-[#bfa98a] border-t-transparent rounded-full animate-spin" />
111175 ) : (
112- < svg width = "20" height = "20" fill = "none" stroke = "currentColor" strokeWidth = "2" viewBox = "0 0 24 24" >
113- < circle cx = "12" cy = "12" r = "10" />
114- < line x1 = "15" y1 = "9" x2 = "9" y2 = "15" />
115- < line x1 = "9" y1 = "9" x2 = "15" y2 = "15" />
176+ < svg
177+ width = "20"
178+ height = "20"
179+ fill = "none"
180+ stroke = "currentColor"
181+ strokeWidth = "2"
182+ viewBox = "0 0 24 24"
183+ >
184+ < circle cx = "12" cy = "12" r = "10" />
185+ < line x1 = "15" y1 = "9" x2 = "9" y2 = "15" />
186+ < line x1 = "9" y1 = "9" x2 = "15" y2 = "15" />
116187 </ svg >
117188 ) }
118189 </ button >
@@ -122,24 +193,43 @@ function GroupDetails() {
122193 </ div >
123194 </ Card >
124195 < Card className = "bg-white border-none shadow-none p-4" >
125- < div className = "font-semibold text-green-700 mb-1" > Link de convite</ div >
196+ < div className = "font-semibold text-green-700 mb-1" >
197+ Link de convite
198+ </ div >
126199 < div className = "text-sm text-[#7c6a5c] mb-2" > { inviteLink } </ div >
127200 < Button
128201 variant = "secondary"
129- className = "w-full bg-[#ffa726] text-white font-semibold"
202+ className = "w-full bg-[#ffa726] text-white font-semibold hover:bg-[#ff9800] transition-all cursor-pointer shadow-sm hover:shadow-md "
130203 onClick = { async ( ) => {
131204 await navigator . clipboard . writeText ( inviteLink ) ;
132205 setInviteCopied ( true ) ;
133206 setTimeout ( ( ) => setInviteCopied ( false ) , 2000 ) ;
134207 } }
135208 >
136- { inviteCopied ? "Link copiado!" : "Convidar" }
209+ { inviteCopied ? "✓ Link copiado!" : "📋 Convidar" }
137210 </ Button >
138211 </ Card >
212+ { ! isOwner && (
213+ < Button
214+ variant = "outline"
215+ className = "w-full border-2 border-amber-600 text-amber-600 bg-white hover:bg-amber-50 hover:border-amber-700 transition-all font-semibold cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
216+ onClick = { handleLeaveGroup }
217+ disabled = { isLeavingGroup }
218+ >
219+ { isLeavingGroup ? (
220+ < >
221+ < div className = "w-4 h-4 border-2 border-amber-600 border-t-transparent rounded-full animate-spin mr-2" />
222+ Saindo...
223+ </ >
224+ ) : (
225+ "Sair do Grupo"
226+ ) }
227+ </ Button >
228+ ) }
139229 { isOwner && (
140230 < Button
141231 variant = "destructive"
142- className = "w-full border-2 border-[#bfa98a] text-[#bfa98a] bg-white hover:bg-[#f5ede3] mt-2 "
232+ className = "w-full border-2 border-red-500 text-red-600 bg-white hover:bg-red-50 hover:border-red-600 transition-all font-semibold cursor-pointer "
143233 >
144234 Encerrar Grupo
145235 </ Button >
0 commit comments