1
- /* eslint-disable react/destructuring-assignment */
2
1
import React from 'react' ;
3
2
import styles from './OrganizationCard.module.css' ;
4
3
import { Button } from 'react-bootstrap' ;
@@ -10,15 +9,17 @@ import {
10
9
JOIN_PUBLIC_ORGANIZATION ,
11
10
SEND_MEMBERSHIP_REQUEST ,
12
11
} from 'GraphQl/Mutations/OrganizationMutations' ;
13
- import { useMutation , useQuery , ApolloError } from '@apollo/client' ;
12
+ import { useMutation , useQuery } from '@apollo/client' ;
14
13
import {
15
14
USER_JOINED_ORGANIZATIONS ,
16
15
USER_ORGANIZATION_CONNECTION ,
17
16
} from 'GraphQl/Queries/OrganizationQueries' ;
17
+ import useLocalStorage from 'utils/useLocalstorage' ;
18
18
import Avatar from 'components/Avatar/Avatar' ;
19
19
import { useNavigate } from 'react-router-dom' ;
20
+ import { ApolloError } from '@apollo/client' ;
20
21
21
- import { useLocalStorage } from 'utils/useLocalstorage' ;
22
+ const { getItem } = useLocalStorage ( ) ;
22
23
23
24
interface InterfaceOrganizationCardProps {
24
25
id : string ;
@@ -48,88 +49,103 @@ interface InterfaceOrganizationCardProps {
48
49
} [ ] ;
49
50
}
50
51
51
- function OrganizationCard ( {
52
- id,
53
- name,
54
- image,
55
- description,
56
- admins,
57
- members,
58
- address,
59
- membershipRequestStatus,
60
- userRegistrationRequired,
61
- membershipRequests,
62
- } : InterfaceOrganizationCardProps ) : JSX . Element {
63
- const { getItem } = useLocalStorage ( ) ;
64
- const userId = getItem ( 'userId' ) ;
65
-
52
+ /**
53
+ * Displays an organization card with options to join or manage membership.
54
+ *
55
+ * Shows the organization's name, image, description, address, number of admins and members,
56
+ * and provides buttons for joining, withdrawing membership requests, or visiting the organization page.
57
+ *
58
+ * @param props - The properties for the organization card.
59
+ * @param id - The unique identifier of the organization.
60
+ * @param name - The name of the organization.
61
+ * @param image - The URL of the organization's image.
62
+ * @param description - A description of the organization.
63
+ * @param admins - The list of admins with their IDs.
64
+ * @param members - The list of members with their IDs.
65
+ * @param address - The address of the organization including city, country code, line1, postal code, and state.
66
+ * @param membershipRequestStatus - The status of the membership request (accepted, pending, or empty).
67
+ * @param userRegistrationRequired - Indicates if user registration is required to join the organization.
68
+ * @param membershipRequests - The list of membership requests with user IDs.
69
+ *
70
+ * @returns The organization card component.
71
+ */
72
+ const userId : string | null = getItem ( 'userId' ) ;
73
+
74
+ function OrganizationCard ( props : InterfaceOrganizationCardProps ) : JSX . Element {
66
75
const { t } = useTranslation ( 'translation' , {
67
76
keyPrefix : 'users' ,
68
77
} ) ;
69
78
const { t : tCommon } = useTranslation ( 'common' ) ;
70
79
71
80
const navigate = useNavigate ( ) ;
72
81
82
+ // Mutations for handling organization memberships
73
83
const [ sendMembershipRequest ] = useMutation ( SEND_MEMBERSHIP_REQUEST , {
74
84
refetchQueries : [
75
- { query : USER_ORGANIZATION_CONNECTION , variables : { id } } ,
85
+ { query : USER_ORGANIZATION_CONNECTION , variables : { id : props . id } } ,
76
86
] ,
77
87
} ) ;
78
-
79
88
const [ joinPublicOrganization ] = useMutation ( JOIN_PUBLIC_ORGANIZATION , {
80
89
refetchQueries : [
81
- { query : USER_ORGANIZATION_CONNECTION , variables : { id } } ,
90
+ { query : USER_ORGANIZATION_CONNECTION , variables : { id : props . id } } ,
82
91
] ,
83
92
} ) ;
84
-
85
93
const [ cancelMembershipRequest ] = useMutation ( CANCEL_MEMBERSHIP_REQUEST , {
86
94
refetchQueries : [
87
- { query : USER_ORGANIZATION_CONNECTION , variables : { id } } ,
95
+ { query : USER_ORGANIZATION_CONNECTION , variables : { id : props . id } } ,
88
96
] ,
89
97
} ) ;
90
-
91
98
const { refetch } = useQuery ( USER_JOINED_ORGANIZATIONS , {
92
99
variables : { id : userId } ,
93
100
} ) ;
94
101
102
+ /**
103
+ * Handles joining the organization. Sends a membership request if registration is required,
104
+ * otherwise joins the public organization directly. Displays success or error messages.
105
+ */
95
106
async function joinOrganization ( ) : Promise < void > {
96
107
try {
97
- if ( userRegistrationRequired ) {
108
+ if ( props . userRegistrationRequired ) {
98
109
await sendMembershipRequest ( {
99
110
variables : {
100
- organizationId : id ,
111
+ organizationId : props . id ,
101
112
} ,
102
113
} ) ;
103
- toast . success ( t ( 'MembershipRequestSent' ) ) ;
114
+ toast . success ( t ( 'MembershipRequestSent' ) as string ) ;
104
115
} else {
105
116
await joinPublicOrganization ( {
106
117
variables : {
107
- organizationId : id ,
118
+ organizationId : props . id ,
108
119
} ,
109
120
} ) ;
110
- toast . success ( t ( 'orgJoined' ) ) ;
121
+ toast . success ( t ( 'orgJoined' ) as string ) ;
111
122
}
112
123
refetch ( ) ;
113
124
} catch ( error : unknown ) {
114
- if ( error instanceof ApolloError ) {
115
- const errorCode = error . graphQLErrors [ 0 ] ?. extensions ?. code ;
125
+ /* istanbul ignore next */
126
+ if ( error instanceof Error ) {
127
+ const apolloError = error as ApolloError ;
128
+ const errorCode = apolloError . graphQLErrors [ 0 ] ?. extensions ?. code ;
129
+
116
130
if ( errorCode === 'ALREADY_MEMBER' ) {
117
- toast . error ( t ( 'AlreadyJoined' ) ) ;
131
+ toast . error ( t ( 'AlreadyJoined' ) as string ) ;
118
132
} else {
119
- toast . error ( t ( 'errorOccured' ) ) ;
133
+ toast . error ( t ( 'errorOccured' ) as string ) ;
120
134
}
121
135
}
122
136
}
123
137
}
124
138
139
+ /**
140
+ * Handles withdrawing a membership request. Finds the request for the current user and cancels it.
141
+ */
125
142
async function withdrawMembershipRequest ( ) : Promise < void > {
143
+ const membershipRequest = props . membershipRequests . find (
144
+ ( request ) => request . user . _id === userId ,
145
+ ) ;
126
146
try {
127
- const membershipRequest = membershipRequests . find (
128
- ( request ) => request . user . _id === userId ,
129
- ) ;
130
-
131
147
if ( ! membershipRequest ) {
132
- toast . error ( t ( 'MembershipRequestNotFound' ) ) ;
148
+ toast . error ( t ( 'MembershipRequestNotFound' ) as string ) ;
133
149
return ;
134
150
}
135
151
@@ -138,84 +154,89 @@ function OrganizationCard({
138
154
membershipRequestId : membershipRequest . _id ,
139
155
} ,
140
156
} ) ;
141
- toast . success ( t ( 'MembershipRequestWithdrawn' ) ) ;
142
- } catch ( error : unknown ) {
143
- console . error ( error ) ;
144
- toast . error ( t ( 'errorOccured' ) ) ;
157
+
158
+ toast . success ( t ( 'MembershipRequestWithdrawn' ) as string ) ;
159
+ } catch ( error ) {
160
+ toast . error ( t ( 'errorOccured' ) as string ) ;
145
161
}
146
162
}
147
163
148
164
return (
149
- < div className = { styles . orgCard } >
150
- < div className = { styles . innerContainer } >
151
- < div className = { styles . orgImgContainer } >
152
- { image ? (
153
- < img src = { image } alt = { `${ name } image` } />
154
- ) : (
155
- < Avatar
156
- name = { name }
157
- alt = { `${ name } image` }
158
- dataTestId = "emptyContainerForImage"
159
- />
160
- ) }
161
- </ div >
162
- < div className = { styles . content } >
163
- < Tooltip title = { name } placement = "top-end" >
164
- < h4 className = { `${ styles . orgName } fw-semibold` } > { name } </ h4 >
165
- </ Tooltip >
166
- < h6 className = { `${ styles . orgdesc } fw-semibold` } >
167
- < span > { description } </ span >
168
- </ h6 >
169
- { address && address . city && (
170
- < div className = { styles . address } >
171
- < h6 className = "text-secondary" >
172
- < span className = "address-line" > { address . line1 } , </ span >
173
- < span className = "address-line" > { address . city } , </ span >
174
- < span className = "address-line" > { address . countryCode } </ span >
175
- </ h6 >
176
- </ div >
177
- ) }
178
- < h6 className = { styles . orgadmin } >
179
- { tCommon ( 'admins' ) } : < span > { admins ?. length } </ span >
180
- { tCommon ( 'members' ) } : < span > { members ?. length } </ span >
181
- </ h6 >
165
+ < >
166
+ < div className = { styles . orgCard } >
167
+ < div className = { styles . innerContainer } >
168
+ < div className = { styles . orgImgContainer } >
169
+ { props . image ? (
170
+ < img src = { props . image } alt = { `${ props . name } image` } />
171
+ ) : (
172
+ < Avatar
173
+ name = { props . name }
174
+ alt = { `${ props . name } image` }
175
+ dataTestId = "emptyContainerForImage"
176
+ />
177
+ ) }
178
+ </ div >
179
+ < div className = { styles . content } >
180
+ < Tooltip title = { props . name } placement = "top-end" >
181
+ < h4 className = { `${ styles . orgName } fw-semibold` } > { props . name } </ h4 >
182
+ </ Tooltip >
183
+ < h6 className = { `${ styles . orgdesc } fw-semibold` } >
184
+ < span > { props . description } </ span >
185
+ </ h6 >
186
+ { props . address && props . address . city && (
187
+ < div className = { styles . address } >
188
+ < h6 className = "text-secondary" >
189
+ < span className = "address-line" > { props . address . line1 } , </ span >
190
+ < span className = "address-line" > { props . address . city } , </ span >
191
+ < span className = "address-line" >
192
+ { props . address . countryCode }
193
+ </ span >
194
+ </ h6 >
195
+ </ div >
196
+ ) }
197
+ < h6 className = { styles . orgadmin } >
198
+ { tCommon ( 'admins' ) } : < span > { props . admins ?. length } </ span >
199
+ { tCommon ( 'members' ) } :{ ' ' }
200
+ < span > { props . members ?. length } </ span >
201
+ </ h6 >
202
+ </ div >
182
203
</ div >
204
+ { props . membershipRequestStatus === 'accepted' && (
205
+ < Button
206
+ variant = "success"
207
+ data-testid = "manageBtn"
208
+ className = { styles . joinedBtn }
209
+ onClick = { ( ) => {
210
+ navigate ( `/user/organization/${ props . id } ` ) ;
211
+ } }
212
+ >
213
+ { t ( 'visit' ) }
214
+ </ Button >
215
+ ) }
216
+
217
+ { props . membershipRequestStatus === 'pending' && (
218
+ < Button
219
+ variant = "danger"
220
+ onClick = { withdrawMembershipRequest }
221
+ data-testid = "withdrawBtn"
222
+ className = { styles . withdrawBtn }
223
+ >
224
+ { t ( 'withdraw' ) }
225
+ </ Button >
226
+ ) }
227
+
228
+ { props . membershipRequestStatus === '' && (
229
+ < Button
230
+ onClick = { joinOrganization }
231
+ data-testid = "joinBtn"
232
+ className = { styles . joinBtn }
233
+ variant = "outline-success"
234
+ >
235
+ { t ( 'joinNow' ) }
236
+ </ Button >
237
+ ) }
183
238
</ div >
184
- { membershipRequestStatus === 'accepted' && (
185
- < Button
186
- variant = "success"
187
- data-testid = "manageBtn"
188
- className = { styles . joinedBtn }
189
- onClick = { ( ) => {
190
- navigate ( `/user/organization/${ id } ` ) ;
191
- } }
192
- >
193
- { t ( 'visit' ) }
194
- </ Button >
195
- ) }
196
-
197
- { membershipRequestStatus === 'pending' && (
198
- < Button
199
- variant = "danger"
200
- onClick = { withdrawMembershipRequest }
201
- data-testid = "withdrawBtn"
202
- className = { styles . withdrawBtn }
203
- >
204
- { t ( 'withdraw' ) }
205
- </ Button >
206
- ) }
207
-
208
- { membershipRequestStatus === '' && (
209
- < Button
210
- onClick = { joinOrganization }
211
- data-testid = "joinBtn"
212
- className = { styles . joinBtn }
213
- variant = "outline-success"
214
- >
215
- { t ( 'joinNow' ) }
216
- </ Button >
217
- ) }
218
- </ div >
239
+ </ >
219
240
) ;
220
241
}
221
242
0 commit comments