1- import { useEffect , useState } from "react" ;
1+ import { ChangeEvent , useCallback , useEffect , useMemo , useState } from "react" ;
22import styles from "./list.module.css" ;
33import { useTerminology } from "../../../hooks/useTerminology" ;
4+ import { generateSlug , randomSuffix } from "~/admin/utils/helper" ;
45import {
56 Button ,
67 Field ,
@@ -14,49 +15,35 @@ import {
1415} from "@raystack/apsara" ;
1516import { Cross1Icon } from "@radix-ui/react-icons" ;
1617import { ImageUpload } from "~/client/components/image-upload" ;
18+ import { debounce } from "lodash" ;
1719import { z } from "zod" ;
1820import { Controller , useForm } from "react-hook-form" ;
1921import { zodResolver } from "@hookform/resolvers/zod" ;
2022import { useMutation } from "@connectrpc/connect-query" ;
2123import { Code } from "@connectrpc/connect" ;
2224import { AdminServiceQueries } from "@raystack/proton/frontier" ;
2325
24- const orgCreateSchema = z
25- . object ( {
26- avatar : z . string ( ) . optional ( ) ,
27- title : z . string ( ) . min ( 1 , "Title is required" ) ,
28- name : z
29- . string ( )
30- . min ( 1 , "URL is required" )
31- . regex (
32- / ^ [ a - z 0 - 9 - ] + $ / ,
33- "Use only lowercase letters, numbers, and hyphens" ,
34- ) ,
35- orgOwnerEmail : z
36- . string ( )
37- . min ( 1 , "Owner email is required" )
38- . email ( "Enter a valid email address" ) ,
39- size : z
40- . string ( )
41- . min ( 1 , "Size is required" )
42- . refine (
43- ( value ) => Number . isInteger ( Number ( value ) ) && Number ( value ) > 0 ,
44- "Enter a valid size greater than 0" ,
45- )
46- . transform ( ( value ) => parseInt ( value ) ) ,
47- type : z . string ( ) . min ( 1 , "Industry is required" ) ,
48- otherType : z . string ( ) . optional ( ) ,
49- country : z . string ( ) . min ( 1 , "Country is required" ) ,
50- } )
51- . refine ( ( data ) => data . type !== "other" || Boolean ( data . otherType ?. trim ( ) ) , {
52- message : "Please specify the industry" ,
53- path : [ "otherType" ] ,
54- } ) ;
26+ const orgCreateSchema = z . object ( {
27+ avatar : z . string ( ) . optional ( ) ,
28+ title : z . string ( ) . min ( 1 , "Title is required" ) ,
29+ name : z
30+ . string ( )
31+ . min ( 1 , "URL is required" )
32+ . min ( 3 , "URL not valid, Min 3 characters allowed" )
33+ . max ( 50 , "URL not valid, Max 50 characters allowed" )
34+ . regex (
35+ / ^ [ a - z A - Z 0 - 9 _ - ] { 3 , 50 } $ / ,
36+ "Only numbers, letters, '-', and '_' are allowed. Spaces are not allowed." ,
37+ ) ,
38+ orgOwnerEmail : z
39+ . string ( )
40+ . min ( 1 , "Owner email is required" )
41+ . email ( "Enter a valid email address" ) ,
42+ country : z . string ( ) . min ( 1 , "Country is required" ) ,
43+ } ) ;
5544
5645type OrgCreateSchema = z . infer < typeof orgCreateSchema > ;
5746
58- const otherTypePrefix = "Other - " ;
59-
6047export type CreateOrganizationPanelProps = {
6148 open ?: boolean ;
6249 onClose : ( ) => void ;
@@ -69,14 +56,12 @@ export type CreateOrganizationPanelProps = {
6956export function CreateOrganizationPanel ( {
7057 open = false ,
7158 onClose,
72- organizationTypes = [ ] ,
7359 appUrl = "" ,
7460 countries : countriesProp = [ ] ,
7561 onSuccess,
7662} : CreateOrganizationPanelProps ) {
7763 const t = useTerminology ( ) ;
7864 const [ countries , setCountries ] = useState < string [ ] > ( countriesProp ) ;
79- const industries = organizationTypes ;
8065
8166 useEffect ( ( ) => {
8267 if ( countriesProp . length > 0 ) {
@@ -88,22 +73,41 @@ export function CreateOrganizationPanel({
8873 handleSubmit,
8974 control,
9075 setError,
91- formState : { isSubmitting , errors } ,
92- watch ,
76+ setValue ,
77+ formState : { isSubmitting , errors , dirtyFields } ,
9378 register,
9479 } = useForm < OrgCreateSchema > ( {
9580 defaultValues : {
9681 avatar : "" ,
9782 title : "" ,
9883 name : "" ,
9984 orgOwnerEmail : "" ,
100- type : "" ,
101- otherType : "" ,
10285 country : "" ,
10386 } ,
10487 resolver : zodResolver ( orgCreateSchema ) ,
10588 } ) ;
10689
90+ const uniqueSuffix = useMemo ( ( ) => randomSuffix ( ) , [ ] ) ;
91+
92+ const debouncedGenerateSlug = useMemo (
93+ ( ) =>
94+ debounce ( ( title : string ) => {
95+ const baseSlug = generateSlug ( title ) ;
96+ setValue ( "name" , baseSlug ? `${ baseSlug } -${ uniqueSuffix } ` : "" ) ;
97+ } , 300 ) ,
98+ [ setValue , uniqueSuffix ] ,
99+ ) ;
100+
101+ useEffect ( ( ) => ( ) => debouncedGenerateSlug . cancel ( ) , [ debouncedGenerateSlug ] ) ;
102+
103+ const handleTitleChange = useCallback (
104+ ( e : ChangeEvent < HTMLInputElement > ) => {
105+ if ( dirtyFields . name ) return ;
106+ debouncedGenerateSlug ( e . target . value ) ;
107+ } ,
108+ [ debouncedGenerateSlug , dirtyFields . name ] ,
109+ ) ;
110+
107111 const { mutateAsync : createOrganization , isPending } = useMutation (
108112 AdminServiceQueries . adminCreateOrganization ,
109113 {
@@ -127,10 +131,6 @@ export function CreateOrganizationPanel({
127131 title : data . title ,
128132 orgOwnerEmail : data . orgOwnerEmail ,
129133 metadata : {
130- size : data . size . toString ( ) ,
131- type : data . otherType
132- ? `${ otherTypePrefix } ${ data . otherType } `
133- : data . type ,
134134 country : data . country ,
135135 } ,
136136 } ;
@@ -145,8 +145,6 @@ export function CreateOrganizationPanel({
145145 }
146146 }
147147
148- const showOtherTypeField = watch ( "type" ) === "other" ;
149-
150148 return (
151149 < Drawer open = { open } onOpenChange = { ( open ) => ! open && onClose ( ) } >
152150 < Drawer . Content showCloseButton = { false } className = { styles [ "drawer-content" ] } >
@@ -169,6 +167,7 @@ export function CreateOrganizationPanel({
169167 < form
170168 className = { styles [ "side-panel-form" ] }
171169 onSubmit = { handleSubmit ( onSubmit ) }
170+ noValidate
172171 >
173172 < Flex
174173 direction = "column"
@@ -192,14 +191,16 @@ export function CreateOrganizationPanel({
192191 error = { errors . title ?. message }
193192 required
194193 >
195- < Input { ...register ( "title" ) } />
194+ < Input
195+ { ...register ( "title" , { onChange : handleTitleChange } ) }
196+ />
196197 </ Field >
197198 < Field
198- label = { `${ t . organization ( { case : "capital" } ) } owner` }
199+ label = { `${ t . organization ( { case : "capital" } ) } owner email ` }
199200 error = { errors . orgOwnerEmail ?. message }
200201 required
201202 >
202- < Input { ...register ( "orgOwnerEmail" ) } type = "email" />
203+ < Input { ...register ( "orgOwnerEmail" ) } type = "email" />
203204 </ Field >
204205 < Field
205206 label = { `${ t . organization ( { case : "capital" } ) } URL` }
@@ -209,57 +210,6 @@ export function CreateOrganizationPanel({
209210 >
210211 < Input { ...register ( "name" ) } prefix = { appUrl } />
211212 </ Field >
212- < Field
213- label = { `${ t . organization ( { case : "capital" } ) } size` }
214- error = { errors . size ?. message }
215- required
216- >
217- < Input { ...register ( "size" ) } type = "number" min = { 1 } />
218- </ Field >
219- < Controller
220- name = "type"
221- control = { control }
222- render = { ( { field } ) => {
223- return (
224- < Field
225- label = { `${ t . organization ( { case : "capital" } ) } industry` }
226- error = { errors . type ?. message }
227- required
228- >
229- < Select
230- { ...field }
231- value = { field ?. value ?. toString ( ) }
232- onValueChange = { ( value ) => {
233- field ?. onChange ( { target : { value } } ) ;
234- } }
235- >
236- < Select . Trigger >
237- < Select . Value
238- placeholder = "Select an industry"
239- id = "org-type-select"
240- />
241- </ Select . Trigger >
242- < Select . Content className = { styles [ "select-content" ] } >
243- { industries . map ( ( industry ) => (
244- < Select . Item key = { industry } value = { industry } >
245- { industry }
246- </ Select . Item >
247- ) ) }
248- < Select . Item value = "other" > Other</ Select . Item >
249- </ Select . Content >
250- </ Select >
251- </ Field >
252- ) ;
253- } }
254- />
255- { showOtherTypeField ? (
256- < Field
257- label = { `${ t . organization ( { case : "capital" } ) } industry (other)` }
258- error = { errors . otherType ?. message }
259- >
260- < Input { ...register ( "otherType" ) } />
261- </ Field >
262- ) : null }
263213 < Controller
264214 name = "country"
265215 control = { control }
0 commit comments