Skip to content

Commit 6098a80

Browse files
committed
feat: add UserInvitePage for inviting new users
1 parent deaa827 commit 6098a80

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"use client"
2+
3+
import React from "react";
4+
import {
5+
Button,
6+
Col,
7+
EmailInput,
8+
emailValidation,
9+
Flex,
10+
PasswordInput,
11+
passwordValidation,
12+
Spacing,
13+
SwitchInput,
14+
Text,
15+
TextInput,
16+
useForm,
17+
useService,
18+
useStore
19+
} from "@code0-tech/pictor";
20+
import Link from "next/link";
21+
import {useRouter, notFound} from "next/navigation";
22+
import {UserService} from "@edition/user/services/User.service";
23+
import {useUserSession} from "@edition/user/hooks/User.session.hook";
24+
import {addIslandSuccessNotification, addIslandErrorNotification} from "@code0-tech/pictor/dist/components/island/Island.hook";
25+
import {IconAt, IconLock, IconMail, IconUser} from "@tabler/icons-react";
26+
27+
export const UserInvitePage: React.FC = () => {
28+
29+
const currentSession = useUserSession()
30+
const userService = useService(UserService)
31+
const userStore = useStore(UserService)
32+
const currentUser = React.useMemo(() => userService.getById(currentSession?.user?.id), [userStore, currentSession])
33+
const router = useRouter()
34+
const [, startTransition] = React.useTransition()
35+
36+
const [inputs, validate] = useForm<{
37+
email: string | null,
38+
username: string | null,
39+
firstname: string | null,
40+
lastname: string | null,
41+
password: string | null,
42+
repeatPassword: string | null,
43+
admin: boolean | null,
44+
}>({
45+
useInitialValidation: false,
46+
initialValues: {
47+
email: null,
48+
username: null,
49+
firstname: null,
50+
lastname: null,
51+
password: null,
52+
repeatPassword: null,
53+
admin: false,
54+
},
55+
validate: {
56+
email: (value) => {
57+
if (!value) return "Email is required"
58+
if (!emailValidation(value)) return "Please provide a valid email"
59+
return null
60+
},
61+
username: (value) => {
62+
if (!value) return "Username is required"
63+
return null
64+
},
65+
password: passwordValidation,
66+
repeatPassword: (value, values) => {
67+
if (passwordValidation(value) != null) return passwordValidation(value)
68+
if (value != values?.password) return "Passwords do not match"
69+
return null
70+
}
71+
},
72+
onSubmit: (values) => {
73+
if (!values.email || !values.username || !values.password || !values.repeatPassword) return
74+
startTransition(async () => {
75+
await userService.usersCreate({
76+
email: values.email!!,
77+
username: values.username!!,
78+
firstname: values.firstname,
79+
lastname: values.lastname,
80+
password: values.password!!,
81+
passwordRepeat: values.repeatPassword!!,
82+
admin: values.admin,
83+
}).then(payload => {
84+
if (payload?.user) {
85+
router.push("/users")
86+
addIslandSuccessNotification({
87+
message: "Invited user",
88+
})
89+
}
90+
})
91+
})
92+
}
93+
})
94+
95+
if (currentUser && !currentUser.admin) {
96+
notFound()
97+
}
98+
99+
return <div style={{
100+
background: "#070514",
101+
height: "100%",
102+
padding: "2rem",
103+
borderTopLeftRadius: "1rem",
104+
borderTopRightRadius: "1rem"
105+
}}>
106+
<Flex mih={"100%"} miw={"100%"} align={"center"} justify={"center"}>
107+
<Col xs={4}>
108+
<Text size={"xl"} hierarchy={"primary"} display={"block"}>
109+
Invite a new user
110+
</Text>
111+
<Spacing spacing={"xs"}/>
112+
<Text size={"md"} hierarchy={"tertiary"} display={"block"}>
113+
Create a new user with access to your instance. They can log in with the credentials you provide.
114+
</Text>
115+
<Spacing spacing={"xl"}/>
116+
<Flex style={{gap: "1.3rem"}}>
117+
{/*@ts-ignore*/}
118+
<TextInput wrapperComponent={{style: {flex: 1}}} placeholder={"Firstname"} w={"100%"}
119+
left={<IconUser size={16}/>} leftType={"icon"}
120+
{...inputs.getInputProps("firstname")}/>
121+
{/*@ts-ignore*/}
122+
<TextInput wrapperComponent={{style: {flex: 1}}} placeholder={"Lastname"} w={"100%"}
123+
left={<IconUser size={16}/>} leftType={"icon"}
124+
{...inputs.getInputProps("lastname")}/>
125+
</Flex>
126+
<Spacing spacing={"md"}/>
127+
{/*@ts-ignore*/}
128+
<EmailInput w={"100%"} wrapperComponent={{style: {width: "100%"}}} placeholder={"Email"}
129+
left={<IconMail size={16}/>} leftType={"icon"}
130+
{...inputs.getInputProps("email")}/>
131+
<Spacing spacing={"md"}/>
132+
{/*@ts-ignore*/}
133+
<TextInput w={"100%"} wrapperComponent={{style: {width: "100%"}}} placeholder={"Username"}
134+
left={<IconAt size={16}/>} leftType={"icon"}
135+
{...inputs.getInputProps("username")}/>
136+
<Spacing spacing={"md"}/>
137+
<PasswordInput w={"100%"} placeholder={"Password"}
138+
left={<IconLock size={16}/>} leftType={"icon"}
139+
onChange={() => validate("password")}
140+
{...inputs.getInputProps("password")}/>
141+
<Spacing spacing={"md"}/>
142+
<PasswordInput w={"100%"} placeholder={"Repeat password"}
143+
left={<IconLock size={16}/>} leftType={"icon"}
144+
onChange={() => validate("repeatPassword")}
145+
{...inputs.getInputProps("repeatPassword")}/>
146+
<Spacing spacing={"xl"}/>
147+
<Flex justify={"space-between"} align={"center"}>
148+
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
149+
<Text size={"md"} hierarchy={"primary"}>Admin</Text>
150+
<Text size={"md"} hierarchy={"tertiary"}>Grant this user administrator privileges.</Text>
151+
</Flex>
152+
<SwitchInput w={"40px"} {...inputs.getInputProps("admin")}/>
153+
</Flex>
154+
<Spacing spacing={"xl"}/>
155+
<Flex style={{gap: "0.35rem"}} justify={"space-between"}>
156+
<Link href={`/users`}>
157+
<Button color={"primary"}>
158+
Go back to users
159+
</Button>
160+
</Link>
161+
<Button color={"success"} onClick={validate}>
162+
Invite user
163+
</Button>
164+
</Flex>
165+
</Col>
166+
</Flex>
167+
</div>
168+
}

0 commit comments

Comments
 (0)