1- import { Button , Stack , TextInput , Textarea , Group } from "@mantine/core" ;
2- import { useState , useEffect } from "react" ;
1+ import {
2+ Button ,
3+ Stack ,
4+ TextInput ,
5+ Textarea ,
6+ Group ,
7+ ActionIcon ,
8+ Select ,
9+ } from "@mantine/core" ;
10+ import { useEffect , useState } from "react" ;
311import usePost from "@/utils/usePost" ;
12+ import useForm from "@/hooks/generic/useForm" ;
13+ import { FaCheck , FaPlus , FaTrashAlt } from "react-icons/fa" ;
14+ import { SOCIAL_LINKS_DATA } from "@/utils/constants" ;
415
516type UpdateProfileForm = {
6- user : any ; // your user object, can type more strictly if you want
17+ user : any ;
718 onCancel : ( ) => void ;
8- onSuccess ?: ( ) => void ;
19+ refresh ?: ( ) => void ;
920} ;
1021
1122export const UpdateProfileForm = ( {
1223 user,
1324 onCancel,
14- onSuccess ,
25+ refresh = ( ) => { } ,
1526} : UpdateProfileForm ) => {
16- const [ username , setUsername ] = useState ( user ?. username || "" ) ;
17- const [ email , setEmail ] = useState ( user ?. email || "" ) ;
18- const [ biography , setBiography ] = useState ( user ?. biography || "" ) ;
19- const [ organisation , setOrganisation ] = useState ( user ?. organisation || "" ) ;
27+ const { form, onChange, updateFormValues, editedForm } = useForm ( ) ;
2028
2129 const { uploadData, submitting, success } = usePost ( ) ;
2230
31+ useEffect ( ( ) => {
32+ if ( user ) {
33+ updateFormValues ( {
34+ username : user ?. username || "" ,
35+ email : user ?. email || "" ,
36+ biography : user ?. biography || "" ,
37+ organisation : user ?. organisation || "" ,
38+ } ) ;
39+ }
40+ } , [ ] ) ;
41+
2342 useEffect ( ( ) => {
2443 if ( success ) {
25- if ( onSuccess ) {
26- onSuccess ( ) ;
27- }
44+ refresh ( ) ;
45+ onCancel ( ) ;
2846 }
29- } , [ success , onSuccess ] ) ;
47+ } , [ success ] ) ;
3048
3149 const handleSubmit = ( e : React . FormEvent ) => {
3250 e . preventDefault ( ) ;
@@ -39,12 +57,7 @@ export const UpdateProfileForm = ({
3957 api : "users" ,
4058 id : user . id ,
4159 method : "PATCH" ,
42- params : {
43- username,
44- email,
45- biography,
46- organisation,
47- } ,
60+ params : user ?. id ? editedForm : form ,
4861 } ) ;
4962 } ;
5063
@@ -55,34 +68,38 @@ export const UpdateProfileForm = ({
5568 label = "Username"
5669 placeholder = "Enter your username"
5770 description = "This will be your public username. Do not use spaces or special characters."
58- value = { username }
59- onChange = { ( e ) => setUsername ( e . currentTarget . value ) }
71+ name = "username"
72+ value = { form . username as string }
73+ onChange = { onChange }
6074 required
6175 />
6276
6377 < TextInput
6478 label = "Email"
6579 type = "email"
6680 placeholder = "Enter your email"
67- value = { email }
68- onChange = { ( e ) => setEmail ( e . currentTarget . value ) }
81+ name = "email"
82+ value = { form . email as string }
83+ onChange = { onChange }
6984 disabled
7085 required
7186 />
7287
7388 < TextInput
7489 label = "Organisation"
7590 placeholder = "Your organisation or company"
76- value = { organisation }
77- onChange = { ( e ) => setOrganisation ( e . currentTarget . value ) }
91+ name = "organisation"
92+ value = { form . organisation as string }
93+ onChange = { onChange }
7894 required
7995 />
8096
8197 < Textarea
8298 label = "Biography"
8399 placeholder = "Tell us about yourself"
84- value = { biography }
85- onChange = { ( e ) => setBiography ( e . currentTarget . value ) }
100+ name = "biography"
101+ value = { form . biography as string }
102+ onChange = { onChange }
86103 maxRows = { 3 }
87104 />
88105
@@ -95,6 +112,7 @@ export const UpdateProfileForm = ({
95112 loading = { submitting }
96113 variant = "filled"
97114 color = "dark"
115+ disabled = { user ?. id && Object . keys ( editedForm ) . length <= 0 }
98116 >
99117 Save
100118 </ Button >
@@ -103,3 +121,181 @@ export const UpdateProfileForm = ({
103121 </ form >
104122 ) ;
105123} ;
124+
125+ export const SocialMediaLinksForm = ( {
126+ user,
127+ onCancel,
128+ refresh = ( ) => { } ,
129+ } : {
130+ user : any ;
131+ onCancel : ( ) => void ;
132+ refresh ?: ( ) => void ;
133+ } ) => {
134+ const { uploadData, submitting, success } = usePost ( ) ;
135+
136+ const [ socialLinks , setSocialLinks ] = useState <
137+ { platform : string ; url : string } [ ]
138+ > ( [ ] ) ;
139+
140+ useEffect ( ( ) => {
141+ if ( user ) {
142+ const socialLinks = user ?. social_links || { } ;
143+ const links =
144+ Object . entries ( socialLinks ) . map ( ( [ key , value ] ) => ( {
145+ platform : key ,
146+ url : value as string ,
147+ } ) ) || [ ] ;
148+ setSocialLinks ( links ) ;
149+ }
150+ } , [ user ] ) ;
151+
152+ useEffect ( ( ) => {
153+ if ( success ) {
154+ refresh ( ) ;
155+ onCancel ( ) ;
156+ }
157+ } , [ success ] ) ;
158+
159+ const handleChange = ( index : number , field : string , value : string ) => {
160+ const newLinks = [ ...socialLinks ] ;
161+ if ( field === "url" ) {
162+ // Ensure URL starts with http:// or https://
163+ if (
164+ value &&
165+ ! value . startsWith ( "http://" ) &&
166+ ! value . startsWith ( "https://" )
167+ ) {
168+ const newValue = `https://${ value } ` ;
169+ ( newLinks [ index ] as any ) [ field ] = newValue ;
170+ } else {
171+ ( newLinks [ index ] as any ) [ field ] = value ;
172+ }
173+ } else {
174+ ( newLinks [ index ] as any ) [ field ] = value ;
175+ }
176+ setSocialLinks ( newLinks ) ;
177+ } ;
178+
179+ const addLink = ( ) => {
180+ setSocialLinks ( [ ...socialLinks , { platform : "" , url : "" } ] ) ;
181+ } ;
182+
183+ const removeLink = ( index : number ) => {
184+ const newLinks = [ ...socialLinks ] ;
185+ newLinks . splice ( index , 1 ) ;
186+ setSocialLinks ( newLinks ) ;
187+ } ;
188+
189+ const handleSubmit = ( e : React . FormEvent ) => {
190+ e . preventDefault ( ) ;
191+
192+ if ( ! user ?. id ) {
193+ return ;
194+ }
195+
196+ const socialLinksData = socialLinks . reduce (
197+ ( acc , link ) => {
198+ if ( link . platform && link . url ) {
199+ acc [ link . platform ] = link . url ;
200+ }
201+ return acc ;
202+ } ,
203+ { } as Record < string , string > ,
204+ ) ;
205+
206+ uploadData ( {
207+ api : "users" ,
208+ id : user . id ,
209+ method : "PATCH" ,
210+ params : { social_links : socialLinksData } ,
211+ } ) ;
212+ } ;
213+
214+ return (
215+ < form onSubmit = { handleSubmit } >
216+ < Stack gap = { 10 } >
217+ { socialLinks ?. map ( ( link , index ) => (
218+ < Group key = { index } align = "center" gap = "md" >
219+ < Select
220+ label = "Platform"
221+ placeholder = "e.g. Twitter"
222+ value = { link . platform }
223+ onChange = { ( e ) => handleChange ( index , "platform" , e || "" ) }
224+ data = { SOCIAL_LINKS_DATA }
225+ leftSection = {
226+ link . platform
227+ ? ( ( ) => {
228+ const platform = SOCIAL_LINKS_DATA . find (
229+ ( p ) => p . value === link . platform ,
230+ ) ;
231+ const IconComponent = platform ?. icon ;
232+ return IconComponent ? (
233+ < IconComponent size = { 16 } color = { platform ?. color } />
234+ ) : undefined ;
235+ } ) ( )
236+ : undefined
237+ }
238+ renderOption = { ( { option, checked } ) => {
239+ const platform = SOCIAL_LINKS_DATA . find (
240+ ( p ) => p . value === option . value ,
241+ ) ;
242+ const IconComponent = platform ?. icon ;
243+
244+ return (
245+ < Group flex = "1" gap = "xs" >
246+ { IconComponent && (
247+ < IconComponent size = { 16 } color = { platform ?. color } />
248+ ) }
249+ < span > { option . label } </ span >
250+ { checked && < FaCheck size = { 14 } color = "#228BE6" /> }
251+ </ Group >
252+ ) ;
253+ } }
254+ required
255+ />
256+ < TextInput
257+ label = "URL"
258+ placeholder = "https://twitter.com/yourhandle"
259+ value = { link . url }
260+ onChange = { ( e ) => handleChange ( index , "url" , e . target . value ) }
261+ flex = "1"
262+ required
263+ />
264+ < ActionIcon
265+ color = "red"
266+ variant = "filled"
267+ aria-label = "Delete"
268+ mt = "lg"
269+ size = "2.2rem"
270+ onClick = { ( ) => removeLink ( index ) }
271+ >
272+ < FaTrashAlt />
273+ </ ActionIcon >
274+ </ Group >
275+ ) ) }
276+ < Button
277+ variant = "outline"
278+ onClick = { addLink }
279+ disabled = { submitting }
280+ leftSection = { < FaPlus /> }
281+ >
282+ Add New Link
283+ </ Button >
284+
285+ < Group justify = "flex-end" mt = "md" >
286+ < Button variant = "outline" onClick = { onCancel } disabled = { submitting } >
287+ Cancel
288+ </ Button >
289+ < Button
290+ type = "submit"
291+ loading = { submitting }
292+ variant = "filled"
293+ color = "dark"
294+ >
295+ Save Links
296+ </ Button >
297+ </ Group >
298+ </ Stack >
299+ </ form >
300+ ) ;
301+ } ;
0 commit comments