Skip to content
This repository was archived by the owner on Dec 6, 2025. It is now read-only.

Commit 3530351

Browse files
authored
feat: add login and organization creation page with route guarding (#1197)
1 parent 176e0e0 commit 3530351

13 files changed

Lines changed: 589 additions & 132 deletions

File tree

customer/api/mutations/customer_mutations.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,48 @@
11
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
22
import { QueryKeys } from '@/api/mutations/query_keys'
33
import type { Customer } from '@/api/dataclasses/customer'
4+
import { CustomerAPI } from '@/api/services/customer'
5+
46

5-
// TODO delete later
6-
let customerData: Customer = {
7-
uuid: 'customer',
8-
name: 'Beispiel Krankenhaus',
9-
address: {
10-
city: 'Aachen',
11-
postalCode: '52062',
12-
street: 'Test Street',
13-
houseNumber: '42',
14-
houseNumberAdditional: '',
15-
country: 'Germany'
16-
},
17-
email: 'test@helpwave.de',
18-
creationDate: new Date(2025, 1, 1),
19-
phoneNumber: '+49 123 456789',
20-
websiteURL: 'https://helpwave.de',
21-
}
227

238
export const useCustomerMyselfQuery = () => {
249
return useQuery({
2510
queryKey: [QueryKeys.customer],
2611
queryFn: async () => {
27-
// TODO do request here with auth data
28-
return customerData
12+
return await CustomerAPI.getMyself()
2913
},
3014
})
3115
}
3216

33-
export const useCustomerUpdateMutation = () => {
17+
export const useCustomerCreateMutation = () => {
3418
const queryClient = useQueryClient()
3519
return useMutation({
3620
mutationFn: async (customer: Customer) => {
37-
// TODO do request here
21+
return await CustomerAPI.create(customer)
22+
},
23+
onSuccess: () => {
24+
queryClient.refetchQueries([QueryKeys.customer]).catch(reason => console.error(reason))
25+
}
26+
})
27+
}
3828

39-
if (customer.uuid === customerData.uuid) {
40-
customerData = customer
41-
}
29+
export const useCustomerUpdateMutation = () => {
30+
const queryClient = useQueryClient()
31+
return useMutation({
32+
mutationFn: async (customer: Customer) => {
33+
return await CustomerAPI.update(customer)
34+
},
35+
onSuccess: () => {
36+
queryClient.refetchQueries([QueryKeys.customer]).catch(reason => console.error(reason))
37+
}
38+
})
39+
}
4240

43-
return customer
41+
export const useCustomerDeleteMutation = () => {
42+
const queryClient = useQueryClient()
43+
return useMutation({
44+
mutationFn: async (id: string) => {
45+
return await CustomerAPI.delete(id)
4446
},
4547
onSuccess: () => {
4648
queryClient.refetchQueries([QueryKeys.customer]).catch(reason => console.error(reason))

customer/api/services/customer.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// TODO delete later
2+
import type { Customer } from '@/api/dataclasses/customer'
3+
4+
let customerData: Customer | undefined = undefined
5+
6+
/*
7+
{
8+
uuid: 'customer',
9+
name: 'Beispiel Krankenhaus',
10+
address: {
11+
city: 'Aachen',
12+
postalCode: '52062',
13+
street: 'Test Street',
14+
houseNumber: '42',
15+
houseNumberAdditional: '',
16+
country: 'Germany'
17+
},
18+
email: 'test@helpwave.de',
19+
creationDate: new Date(2025, 1, 1),
20+
phoneNumber: '+49 123 456789',
21+
websiteURL: 'https://helpwave.de',
22+
}
23+
*/
24+
25+
export const CustomerAPI = {
26+
getMyself: async () => {
27+
return customerData
28+
},
29+
create: async (customer: Customer) => {
30+
// TODO do request here
31+
if (!customerData) {
32+
customerData = { ...customer, uuid: Date().toString() }
33+
} else {
34+
throw 'Organization already exists'
35+
}
36+
return customerData
37+
},
38+
update: async (customer: Customer) => {
39+
// TODO do request here
40+
41+
if (customer.uuid === customerData?.uuid) {
42+
customerData = customer
43+
}
44+
45+
return customer
46+
},
47+
delete: async (_: string) => {
48+
// TODO do request here
49+
50+
if (customerData) {
51+
customerData = undefined
52+
}else {
53+
throw 'You can only delete an already existing customer'
54+
}
55+
56+
return true
57+
},
58+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import type { Customer } from '@/api/dataclasses/customer'
2+
import type { Translation } from '@helpwave/common/hooks/useTranslation'
3+
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
4+
import { tw, tx } from '@twind/core'
5+
import { Input } from '@helpwave/common/components/user-input/Input'
6+
import { Button } from '@helpwave/common/components/Button'
7+
8+
type ContactInformationTranslation = {
9+
contactInfo: string,
10+
additionalInformation: string,
11+
name: string,
12+
websiteUrl: string,
13+
email: string,
14+
phone: string,
15+
address: string,
16+
street: string,
17+
houseNumber: string,
18+
houseNumberAdditional: string,
19+
postalCode: string,
20+
city: string,
21+
country: string,
22+
save: string,
23+
}
24+
25+
const defaultContactInformationTranslation: Translation<ContactInformationTranslation> = {
26+
en: {
27+
contactInfo: 'Contact Information',
28+
additionalInformation: 'Additional Information',
29+
name: 'Name',
30+
websiteUrl: 'Website URL',
31+
email: 'Email',
32+
phone: 'Phonenumber',
33+
address: 'Address',
34+
street: 'Street',
35+
houseNumber: 'Housenumber',
36+
houseNumberAdditional: 'Addition',
37+
postalCode: 'Postal code',
38+
city: 'City',
39+
country: 'Country',
40+
save: 'Save'
41+
},
42+
de: {
43+
contactInfo: 'Kontakt Informationen',
44+
additionalInformation: 'Zusätzliche Informationen',
45+
name: 'Name',
46+
websiteUrl: 'Website URL',
47+
email: 'Email',
48+
phone: 'Telefonnummer',
49+
address: 'Adresse',
50+
street: 'Straße',
51+
houseNumber: 'Hausnummer',
52+
houseNumberAdditional: 'Zusatz',
53+
postalCode: 'Postleitzahl',
54+
city: 'Stadt',
55+
country: 'Land',
56+
save: 'Speichern'
57+
}
58+
}
59+
60+
type ContactInformationFormProps = {
61+
value: Customer,
62+
onChange: (customer: Customer) => void,
63+
onSubmit: (customer: Customer) => void,
64+
className?: string,
65+
}
66+
67+
export const ContactInformationForm = ({ value, onChange, onSubmit, className }: ContactInformationFormProps) => {
68+
const translation = useTranslation(defaultContactInformationTranslation)
69+
return (
70+
<form className={tx('@(flex flex-col gap-y-1 max-w-[700px])', className)}>
71+
<h3 className={tw('font-space font-bold text-2xl')}>{translation.contactInfo}</h3>
72+
<Input
73+
value={value.name}
74+
onChange={name => onChange({ ...value, name })}
75+
label={{ name: translation.name }}
76+
/>
77+
<Input
78+
value={value.email}
79+
onChange={email => onChange({ ...value, email })}
80+
label={{ name: translation.email }}
81+
/>
82+
<Input
83+
value={value.phoneNumber ?? ''}
84+
onChange={phoneNumber => onChange({ ...value, phoneNumber })}
85+
label={{ name: translation.phone }}
86+
/>
87+
<div className={tw('flex flex-col gap-y-1')}>
88+
<h4 className={tw('font-space font-bold text-lg')}>{translation.address}</h4>
89+
<Input
90+
value={value.address.country ?? ''}
91+
onChange={country => onChange({ ...value, address: { ...value.address, country } })}
92+
label={{ name: translation.country }}
93+
/>
94+
<div className={tw('flex flex-row gap-x-1')}>
95+
<Input
96+
value={value.address.city ?? ''}
97+
onChange={city => onChange({ ...value, address: { ...value.address, city } })}
98+
label={{ name: translation.city }}
99+
/>
100+
<Input
101+
value={value.address.postalCode ?? ''}
102+
onChange={postalCode => onChange({
103+
...value,
104+
address: { ...value.address, postalCode }
105+
})}
106+
label={{ name: translation.postalCode }}
107+
containerClassName={tw('max-w-[180px]')}
108+
/>
109+
</div>
110+
<div className={tw('flex flex-row gap-x-1')}>
111+
<Input
112+
value={value.address.street ?? ''}
113+
onChange={street => onChange({
114+
...value,
115+
address: { ...value.address, street }
116+
})}
117+
label={{ name: translation.street }}
118+
/>
119+
<Input
120+
value={value.address.houseNumber ?? ''}
121+
onChange={houseNumber => onChange({
122+
...value,
123+
address: { ...value.address, houseNumber }
124+
})}
125+
label={{ name: translation.houseNumber }}
126+
containerClassName={tw('max-w-[180px]')}
127+
/>
128+
<Input
129+
value={value.address.houseNumberAdditional ?? ''}
130+
onChange={houseNumberAdditional => onChange({
131+
...value,
132+
address: { ...value.address, houseNumberAdditional }
133+
})}
134+
label={{ name: translation.houseNumberAdditional }}
135+
containerClassName={tw('max-w-[180px]')}
136+
/>
137+
</div>
138+
<div className={tw('flex flex-col gap-y-1')}>
139+
<h4 className={tw('font-space font-bold text-lg')}>{translation.additionalInformation}</h4>
140+
<Input
141+
value={value.websiteURL ?? ''}
142+
onChange={websiteURL => onChange({ ...value, websiteURL })}
143+
label={{ name: translation.websiteUrl }}
144+
/>
145+
</div>
146+
</div>
147+
148+
<div className={tw('flex flex-row justify-end')}>
149+
<Button onClick={() => onSubmit(value)}>{translation.save}</Button>
150+
</div>
151+
</form>
152+
)
153+
}

customer/components/layout/NavigationSidebar.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import { useState } from 'react'
99
import { Avatar } from '@helpwave/common/components/Avatar'
1010
import { LanguageModal } from '@helpwave/common/components/modals/LanguageModal'
1111
import { ArrowRightLeft } from 'lucide-react'
12+
import { useAuth } from '@/hooks/useAuth'
13+
import { Button } from '@helpwave/common/components/Button'
14+
import type { Translation } from '@helpwave/common/hooks/useTranslation'
15+
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
1216

1317
export type NavItem = {
1418
name: Record<Languages, string>,
@@ -22,13 +26,28 @@ export type NavSidebarProps = {
2226
className?: string,
2327
}
2428

29+
type NavigationSidebarTranslation = {
30+
logout: string,
31+
}
32+
33+
const defaultNavigationSidebarTranslation: Translation<NavigationSidebarTranslation> = {
34+
en: {
35+
logout: 'Logout'
36+
},
37+
de: {
38+
logout: 'Logout'
39+
},
40+
}
41+
2542
/**
2643
* A navigation sidebar component
2744
*/
2845
export const NavigationSidebar = ({ items, className }: NavSidebarProps) => {
2946
const { language } = useLanguage()
3047
const [isLanguageModalOpen, setIsLanguageModalOpen] = useState(false)
3148
const router = useRouter()
49+
const translation = useTranslation(defaultNavigationSidebarTranslation)
50+
const { identity, logout } = useAuth()
3251

3352
const width = 250
3453

@@ -65,10 +84,13 @@ export const NavigationSidebar = ({ items, className }: NavSidebarProps) => {
6584
{languagesLocalNames[language]}
6685
<ArrowRightLeft size={24}/>
6786
</button>
68-
<button className={tw('flex flex-row gap-x-2 items-center p-4 bg-gray-50 hover:bg-hw-primary-500/40')}>
69-
<Avatar avatarUrl="https://helpwave.de/favicon.ico" alt="" size="small"/>
70-
{'Max Mustermann'}
71-
</button>
87+
<div className={tw('flex flex-col p-4 gap-y-4 bg-gray-50')}>
88+
<div className={tw('flex flex-row gap-x-2 items-center')}>
89+
<Avatar avatarUrl="https://helpwave.de/favicon.ico" alt="" size="small"/>
90+
{identity?.name}
91+
</div>
92+
<Button onClick={logout} color="hw-negative">{translation.logout}</Button>
93+
</div>
7294
</div>
7395
</div>
7496
)

0 commit comments

Comments
 (0)