Skip to content

Commit 3369b12

Browse files
committed
Add modals for RegistrationKeys
1 parent a23bded commit 3369b12

5 files changed

Lines changed: 396 additions & 1 deletion

File tree

src/contexts/api/auth/types/RegistrationKey.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,15 @@ export interface RegistrationKey {
77
expiresAt: Date;
88
permanent: boolean;
99
}
10+
11+
export function newUninitializedRegistrationKey(): RegistrationKey {
12+
return {
13+
id: 0,
14+
key: '',
15+
description: '',
16+
createdAt: new Date(0),
17+
updatedAt: new Date(0),
18+
expiresAt: new Date(0),
19+
permanent: false,
20+
};
21+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { AuthContext } from '@luna/contexts/api/auth/AuthContext';
2+
import {
3+
newUninitializedRegistrationKey,
4+
RegistrationKey,
5+
} from '@luna/contexts/api/auth/types';
6+
import {
7+
Button,
8+
Checkbox,
9+
DateInput,
10+
Input,
11+
Modal,
12+
ModalBody,
13+
ModalContent,
14+
ModalFooter,
15+
ModalHeader,
16+
} from '@heroui/react';
17+
import { useCallback, useContext, useEffect, useState } from 'react';
18+
import { CreateOrUpdateRegistrationKeyPayload } from '@luna/contexts/api/auth/types/CreateOrUpdateRegistrationKeyPayload';
19+
import { ZonedDateTime } from '@internationalized/date';
20+
21+
export interface RegistrationKeyAddModalProps {
22+
isOpen: boolean;
23+
setOpen: (show: boolean) => void;
24+
onSuccess: () => void;
25+
}
26+
27+
export function RegistrationKeyAddModal({
28+
isOpen,
29+
setOpen,
30+
onSuccess,
31+
}: RegistrationKeyAddModalProps) {
32+
const [regKey, setRegKey] = useState<RegistrationKey>(
33+
newUninitializedRegistrationKey()
34+
);
35+
const [expirationDate, setExpirationDate] = useState<ZonedDateTime | null>(
36+
null
37+
);
38+
const [generate, setGenerate] = useState(true);
39+
40+
// initialize/reset modal state
41+
useEffect(() => {
42+
if (!isOpen) return;
43+
setRegKey(newUninitializedRegistrationKey());
44+
}, [isOpen]);
45+
46+
const auth = useContext(AuthContext);
47+
48+
const addRegKey = useCallback(async () => {
49+
if (generate) {
50+
// this is a bit hacky but it achieves the same key format as before (6 groups of 4 alphanumeric characters)
51+
const randomPart = window.crypto
52+
.randomUUID()
53+
.replaceAll('-', '')
54+
.slice(0, 24)
55+
.toUpperCase()
56+
.match(/.{1,4}/g)
57+
?.join('-');
58+
regKey.key += '_' + randomPart;
59+
}
60+
61+
const payload: CreateOrUpdateRegistrationKeyPayload = {
62+
key: regKey.key,
63+
description: regKey.description,
64+
expires_at: regKey.expiresAt,
65+
permanent: regKey.permanent,
66+
};
67+
const result = await auth.createRegistrationKey(payload);
68+
if (result.ok) {
69+
console.log('added registration key:', payload);
70+
onSuccess();
71+
} else {
72+
console.log('failed to add registration key:', result.error);
73+
}
74+
// TODO: UI feedback from the request (success, error)
75+
setOpen(false);
76+
}, [setOpen, regKey, generate, onSuccess, auth]);
77+
78+
return (
79+
<Modal isOpen={isOpen} onOpenChange={setOpen}>
80+
<ModalContent>
81+
{onClose => (
82+
<>
83+
<ModalHeader>Add Registration Key</ModalHeader>
84+
<ModalBody>
85+
{/* TODO: generate key with prefix */}
86+
<Input
87+
label="Key / Prefix"
88+
value={regKey.key}
89+
onValueChange={key => {
90+
if (!regKey) return;
91+
setRegKey({ ...regKey, key });
92+
}}
93+
/>
94+
<Checkbox
95+
isSelected={generate}
96+
onValueChange={generate => {
97+
setGenerate(generate);
98+
}}
99+
>
100+
Generate Random Key with Prefix
101+
</Checkbox>
102+
<Input
103+
label="Description"
104+
value={regKey.description}
105+
onValueChange={description => {
106+
if (!regKey) return;
107+
setRegKey({ ...regKey, description });
108+
}}
109+
/>
110+
<DateInput
111+
className="max-w-md"
112+
granularity="minute"
113+
label="Expires At"
114+
value={expirationDate}
115+
onChange={setExpirationDate}
116+
/>
117+
<Checkbox
118+
isSelected={regKey.permanent}
119+
onValueChange={permanent => {
120+
if (!regKey) return;
121+
setRegKey({ ...regKey, permanent });
122+
}}
123+
>
124+
Permanent Key
125+
</Checkbox>
126+
</ModalBody>
127+
<ModalFooter>
128+
<Button color="success" onPress={addRegKey}>
129+
Add
130+
</Button>
131+
<Button onPress={onClose}>Cancel</Button>
132+
</ModalFooter>
133+
</>
134+
)}
135+
</ModalContent>
136+
</Modal>
137+
);
138+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { AuthContext } from '@luna/contexts/api/auth/AuthContext';
2+
import {
3+
newUninitializedRegistrationKey,
4+
RegistrationKey,
5+
} from '@luna/contexts/api/auth/types';
6+
import {
7+
Button,
8+
Modal,
9+
ModalBody,
10+
ModalContent,
11+
ModalFooter,
12+
ModalHeader,
13+
} from '@heroui/react';
14+
import { useCallback, useContext, useEffect, useState } from 'react';
15+
16+
export interface RegistrationKeyDeleteModalProps {
17+
id: number;
18+
isOpen: boolean;
19+
setOpen: (open: boolean) => void;
20+
onSuccess: () => void;
21+
}
22+
23+
export function RegistrationKeyDeleteModal({
24+
id,
25+
isOpen,
26+
setOpen,
27+
onSuccess,
28+
}: RegistrationKeyDeleteModalProps) {
29+
const [regKey, setRegKey] = useState<RegistrationKey>(
30+
newUninitializedRegistrationKey()
31+
);
32+
const auth = useContext(AuthContext);
33+
34+
// initialize modal state
35+
useEffect(() => {
36+
if (!isOpen) return;
37+
38+
const fetchRegistrationKey = async () => {
39+
const regKeyResult = await auth.getRegistrationKeyById(id);
40+
if (regKeyResult.ok) {
41+
setRegKey(regKeyResult.value);
42+
} else {
43+
console.log('Fetching registration key failed:', regKeyResult.error);
44+
setRegKey(newUninitializedRegistrationKey());
45+
}
46+
};
47+
fetchRegistrationKey();
48+
}, [id, isOpen, auth]);
49+
50+
const deleteRegistrationKey = useCallback(async () => {
51+
const result = await auth.deleteRegistrationKey(regKey.id);
52+
if (result.ok) {
53+
console.log('Deleted registration key: ', id);
54+
onSuccess();
55+
} else {
56+
console.log('Deleting registration key', id, 'failed:', result.error);
57+
}
58+
// TODO: feedback from the request (success, error)
59+
setOpen(false);
60+
}, [auth, id, setOpen, regKey.id, onSuccess]);
61+
62+
return (
63+
<Modal isOpen={isOpen} onOpenChange={setOpen}>
64+
<ModalContent>
65+
{onClose => (
66+
<>
67+
<ModalHeader>Delete Registration Key</ModalHeader>
68+
<ModalBody>
69+
<span>
70+
Do you really want to delete the registration key{' '}
71+
<b>
72+
{regKey.key} (ID: {regKey.id})
73+
</b>
74+
?
75+
</span>
76+
</ModalBody>
77+
<ModalFooter>
78+
<Button color="danger" onPress={deleteRegistrationKey}>
79+
Delete
80+
</Button>
81+
<Button onPress={onClose}>Cancel</Button>
82+
</ModalFooter>
83+
</>
84+
)}
85+
</ModalContent>
86+
</Modal>
87+
);
88+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { AuthContext } from '@luna/contexts/api/auth/AuthContext';
2+
3+
import {
4+
newUninitializedRegistrationKey,
5+
RegistrationKey,
6+
} from '@luna/contexts/api/auth/types';
7+
import {
8+
Button,
9+
Checkbox,
10+
DateInput,
11+
Input,
12+
Modal,
13+
ModalBody,
14+
ModalContent,
15+
ModalFooter,
16+
ModalHeader,
17+
} from '@heroui/react';
18+
import { useCallback, useContext, useEffect, useState } from 'react';
19+
import { CreateOrUpdateRegistrationKeyPayload } from '@luna/contexts/api/auth/types/CreateOrUpdateRegistrationKeyPayload';
20+
import { ZonedDateTime, parseAbsoluteToLocal } from '@internationalized/date';
21+
22+
export interface RegistrationKeyEditModalProps {
23+
id: number;
24+
isOpen: boolean;
25+
setOpen: (open: boolean) => void;
26+
onSuccess: () => void;
27+
}
28+
29+
export function RegistrationKeyEditModal({
30+
id,
31+
isOpen,
32+
setOpen,
33+
onSuccess,
34+
}: RegistrationKeyEditModalProps) {
35+
const [regKey, setRegKey] = useState<RegistrationKey>(
36+
newUninitializedRegistrationKey()
37+
);
38+
const [expirationDate, setExpirationDate] = useState<ZonedDateTime | null>();
39+
40+
const auth = useContext(AuthContext);
41+
42+
// initialize modal state
43+
useEffect(() => {
44+
if (!isOpen) return;
45+
const fetchRegistrationKey = async () => {
46+
const regKeyResult = await auth.getRegistrationKeyById(id);
47+
if (regKeyResult.ok) {
48+
setRegKey(regKeyResult.value);
49+
setExpirationDate(
50+
parseAbsoluteToLocal(regKeyResult.value.expiresAt.toISOString())
51+
);
52+
} else {
53+
setRegKey(newUninitializedRegistrationKey());
54+
}
55+
};
56+
fetchRegistrationKey();
57+
}, [id, isOpen, auth]);
58+
59+
const editRegistrationKey = useCallback(async () => {
60+
const expiresAt = expirationDate?.toDate();
61+
62+
const payload: CreateOrUpdateRegistrationKeyPayload = {
63+
key: regKey.key,
64+
description: regKey.description,
65+
expires_at: expiresAt ?? regKey.expiresAt,
66+
permanent: regKey.permanent,
67+
};
68+
const result = await auth.updateRegistrationKey(id, payload);
69+
if (result.ok) {
70+
console.log('Updated registration key', id, ':', payload);
71+
onSuccess();
72+
} else {
73+
console.log('Update registration key failed:', result.error);
74+
}
75+
76+
// TODO: feedback from the request (success, error)
77+
setOpen(false);
78+
}, [
79+
expirationDate,
80+
regKey.key,
81+
regKey.description,
82+
regKey.expiresAt,
83+
regKey.permanent,
84+
auth,
85+
id,
86+
setOpen,
87+
onSuccess,
88+
]);
89+
90+
return (
91+
<Modal isOpen={isOpen} onOpenChange={setOpen}>
92+
<ModalContent>
93+
{onClose => (
94+
<>
95+
<ModalHeader>Edit Registration Key</ModalHeader>
96+
<ModalBody>
97+
<Input label="ID" value={id.toString()} isDisabled />
98+
<Input label="Key" value={regKey.key} isDisabled />
99+
<Input
100+
label="Description"
101+
value={regKey.description}
102+
onValueChange={description => {
103+
setRegKey({ ...regKey, description });
104+
}}
105+
/>
106+
107+
<DateInput
108+
className="max-w-md"
109+
granularity="minute"
110+
label="Expires At"
111+
value={expirationDate}
112+
onChange={setExpirationDate}
113+
/>
114+
115+
<Checkbox
116+
isSelected={regKey.permanent}
117+
onValueChange={permanent => {
118+
setRegKey({ ...regKey, permanent });
119+
}}
120+
>
121+
Permanent Registration Key
122+
</Checkbox>
123+
</ModalBody>
124+
<ModalFooter>
125+
<Button color="warning" onPress={editRegistrationKey}>
126+
Save
127+
</Button>
128+
<Button onPress={onClose}>Cancel</Button>
129+
</ModalFooter>
130+
</>
131+
)}
132+
</ModalContent>
133+
</Modal>
134+
);
135+
}

0 commit comments

Comments
 (0)