Skip to content

Commit 23ffa8a

Browse files
refactor(resources/settings): migrate settings fetchers (#1058)
* refactor(resources/settings): migrate settings/api-keys fetchers * refactor(resources/settings): migrate settings/inboxes/inbox-users fetchers * refactor(resources/settings): migrate settings/inboxes fetchers * refactor(resources/settings): migrate settings/organization fetchers * refactor(resources/settings): migrate settings/tags fetchers * refactor(resources/settings): migrate settings/users fetchers * refactor(resources/settings): migrate settings/webhooks fetchers
1 parent 2532104 commit 23ffa8a

70 files changed

Lines changed: 3532 additions & 3025 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { FormErrorOrDescription } from '@app-builder/components/Form/Tanstack/FormErrorOrDescription';
2+
import { FormInput } from '@app-builder/components/Form/Tanstack/FormInput';
3+
import { FormLabel } from '@app-builder/components/Form/Tanstack/FormLabel';
4+
import { useLoaderRevalidator } from '@app-builder/contexts/LoaderRevalidatorContext';
5+
import { apiKeyRoleOptions } from '@app-builder/models/api-keys';
6+
import {
7+
CreateApiKeyPayload,
8+
createApiKeyPayloadSchema,
9+
useCreateApiKeyMutation,
10+
} from '@app-builder/queries/settings/api-keys/create-api-key';
11+
import { tKeyForApiKeyRole } from '@app-builder/services/i18n/translation-keys/api-key';
12+
import { getFieldErrors, handleSubmit } from '@app-builder/utils/form';
13+
import { useForm } from '@tanstack/react-form';
14+
import { useState } from 'react';
15+
import { useTranslation } from 'react-i18next';
16+
import { Button, ModalV2, Select } from 'ui-design-system';
17+
import { Icon } from 'ui-icons';
18+
19+
export function CreateApiKey() {
20+
const { t } = useTranslation(['settings']);
21+
const [open, setOpen] = useState(false);
22+
23+
return (
24+
<ModalV2.Root open={open} setOpen={setOpen}>
25+
<ModalV2.Trigger onClick={(e) => e.stopPropagation()} render={<Button type="button" />}>
26+
<Icon icon="plus" className="size-6" />
27+
{t('settings:api_keys.new_api_key')}
28+
</ModalV2.Trigger>
29+
<ModalV2.Content onClick={(e) => e.stopPropagation()}>
30+
<CreateApiKeyContent onSuccess={() => setOpen(false)} />
31+
</ModalV2.Content>
32+
</ModalV2.Root>
33+
);
34+
}
35+
36+
const CreateApiKeyContent = ({ onSuccess }: { onSuccess: () => void }) => {
37+
const { t } = useTranslation(['settings', 'common']);
38+
const createApiKeyMutation = useCreateApiKeyMutation();
39+
const revalidate = useLoaderRevalidator();
40+
41+
const form = useForm({
42+
onSubmit: ({ value, formApi }) => {
43+
if (formApi.state.isValid) {
44+
createApiKeyMutation.mutateAsync(value).then((res) => {
45+
if (!res) {
46+
onSuccess();
47+
}
48+
revalidate();
49+
});
50+
}
51+
},
52+
defaultValues: { description: '', role: 'API_CLIENT' } as CreateApiKeyPayload,
53+
validators: {
54+
onChange: createApiKeyPayloadSchema,
55+
onSubmit: createApiKeyPayloadSchema,
56+
},
57+
});
58+
59+
return (
60+
<form onSubmit={handleSubmit(form)}>
61+
<ModalV2.Title>{t('settings:api_keys.new_api_key')}</ModalV2.Title>
62+
<div className="bg-grey-100 flex flex-col gap-6 p-6">
63+
<form.Field name="description">
64+
{(field) => (
65+
<div className="group flex flex-col gap-2">
66+
<FormLabel name={field.name}>{t('settings:api_keys.description')}</FormLabel>
67+
<FormInput
68+
name={field.name}
69+
onBlur={field.handleBlur}
70+
onChange={(e) => field.handleChange(e.currentTarget.value)}
71+
valid={field.state.meta.errors.length === 0}
72+
type="text"
73+
/>
74+
<FormErrorOrDescription errors={getFieldErrors(field.state.meta.errors)} />
75+
</div>
76+
)}
77+
</form.Field>
78+
<form.Field name="role">
79+
{(field) => (
80+
<div className="group flex flex-col gap-2">
81+
<FormLabel name={field.name}>{t('settings:api_keys.role')}</FormLabel>
82+
<Select.Default
83+
name={field.name}
84+
disabled={apiKeyRoleOptions.length === 1}
85+
defaultValue={field.state.value}
86+
borderColor={field.state.meta.errors.length === 0 ? 'greyfigma-90' : 'redfigma-47'}
87+
>
88+
{apiKeyRoleOptions.map((role) => (
89+
<Select.DefaultItem key={role} value={role}>
90+
{t(tKeyForApiKeyRole(role))}
91+
</Select.DefaultItem>
92+
))}
93+
</Select.Default>
94+
<FormErrorOrDescription errors={getFieldErrors(field.state.meta.errors)} />
95+
</div>
96+
)}
97+
</form.Field>
98+
<div className="flex flex-1 flex-row gap-2">
99+
<ModalV2.Close render={<Button type="button" className="flex-1" variant="secondary" />}>
100+
{t('common:cancel')}
101+
</ModalV2.Close>
102+
<Button className="flex-1" variant="primary" type="submit">
103+
{t('settings:api_keys.create')}
104+
</Button>
105+
</div>
106+
</div>
107+
</form>
108+
);
109+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { useLoaderRevalidator } from '@app-builder/contexts/LoaderRevalidatorContext';
2+
import { type ApiKey } from '@app-builder/models/api-keys';
3+
import { useDeleteApiKeyMutation } from '@app-builder/queries/settings/api-keys/delete-api-key';
4+
import { useState } from 'react';
5+
import { useTranslation } from 'react-i18next';
6+
import { Button, Modal } from 'ui-design-system';
7+
import { Icon } from 'ui-icons';
8+
9+
export function DeleteApiKey({ apiKey }: { apiKey: ApiKey }) {
10+
const { t } = useTranslation(['settings']);
11+
const [open, setOpen] = useState(false);
12+
13+
return (
14+
<Modal.Root open={open} onOpenChange={setOpen}>
15+
<Modal.Trigger>
16+
<Icon
17+
icon="delete"
18+
className="size-6 shrink-0"
19+
aria-label={t('settings:api_keys.delete')}
20+
/>
21+
</Modal.Trigger>
22+
<Modal.Content>
23+
<DeleteApiKeyContent apiKey={apiKey} onSuccess={() => setOpen(false)} />
24+
</Modal.Content>
25+
</Modal.Root>
26+
);
27+
}
28+
29+
function DeleteApiKeyContent({ apiKey, onSuccess }: { apiKey: ApiKey; onSuccess: () => void }) {
30+
const { t } = useTranslation(['settings', 'common']);
31+
const deleteApiKeyMutation = useDeleteApiKeyMutation();
32+
const revalidate = useLoaderRevalidator();
33+
34+
const handleDeleteApiKey = () => {
35+
deleteApiKeyMutation.mutateAsync({ apiKeyId: apiKey.id }).then((res) => {
36+
if (!res) {
37+
onSuccess();
38+
}
39+
revalidate();
40+
});
41+
};
42+
43+
return (
44+
<>
45+
<Modal.Title>{t('settings:api_keys.delete')}</Modal.Title>
46+
<div className="flex flex-col gap-6 p-6">
47+
<div className="text-s flex flex-1 flex-col gap-4">
48+
<input name="apiKeyId" value={apiKey.id} type="hidden" />
49+
<p className="text-center">{t('settings:api_keys.delete.content')}</p>
50+
</div>
51+
<div className="flex flex-1 flex-row gap-2">
52+
<Modal.Close asChild>
53+
<Button className="flex-1" variant="secondary" name="cancel">
54+
{t('common:cancel')}
55+
</Button>
56+
</Modal.Close>
57+
<Button
58+
color="red"
59+
className="flex-1"
60+
variant="primary"
61+
name="delete"
62+
onClick={handleDeleteApiKey}
63+
>
64+
<Icon icon="delete" className="size-6" />
65+
{t('common:delete')}
66+
</Button>
67+
</div>
68+
</div>
69+
</>
70+
);
71+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { FormErrorOrDescription } from '@app-builder/components/Form/Tanstack/FormErrorOrDescription';
2+
import { FormInput } from '@app-builder/components/Form/Tanstack/FormInput';
3+
import { FormLabel } from '@app-builder/components/Form/Tanstack/FormLabel';
4+
import { useLoaderRevalidator } from '@app-builder/contexts/LoaderRevalidatorContext';
5+
import {
6+
CreateInboxPayload,
7+
createInboxPayloadSchema,
8+
createInboxRedirectRouteOptions,
9+
useCreateInboxMutation,
10+
} from '@app-builder/queries/settings/inboxes/create-inbox';
11+
import { getFieldErrors } from '@app-builder/utils/form';
12+
import { useForm } from '@tanstack/react-form';
13+
import { useQueryClient } from '@tanstack/react-query';
14+
import { useState } from 'react';
15+
import { useTranslation } from 'react-i18next';
16+
import { Button, HiddenInputs, ModalV2 } from 'ui-design-system';
17+
import { Icon } from 'ui-icons';
18+
19+
export function CreateInbox({
20+
redirectRoutePath,
21+
}: {
22+
redirectRoutePath?: (typeof createInboxRedirectRouteOptions)[number];
23+
}) {
24+
const { t } = useTranslation(['common', 'settings']);
25+
const [open, setOpen] = useState(false);
26+
27+
return (
28+
<ModalV2.Root open={open} setOpen={setOpen}>
29+
<ModalV2.Trigger
30+
onClick={(e) => e.stopPropagation()}
31+
render={<Button className="whitespace-nowrap" variant="secondary" />}
32+
>
33+
<Icon icon="new-inbox" className="size-5 shrink-0" />
34+
{t('settings:inboxes.new_inbox.create')}
35+
</ModalV2.Trigger>
36+
<ModalV2.Content onClick={(e) => e.stopPropagation()}>
37+
<CreateInboxContent redirectRoutePath={redirectRoutePath} setOpen={setOpen} />
38+
</ModalV2.Content>
39+
</ModalV2.Root>
40+
);
41+
}
42+
43+
export function CreateInboxContent({
44+
redirectRoutePath,
45+
setOpen,
46+
}: {
47+
redirectRoutePath?: (typeof createInboxRedirectRouteOptions)[number];
48+
setOpen: (open: boolean) => void;
49+
}) {
50+
const { t } = useTranslation(['common', 'settings']);
51+
const createInboxMutation = useCreateInboxMutation();
52+
const revalidate = useLoaderRevalidator();
53+
const queryClient = useQueryClient();
54+
55+
const form = useForm({
56+
defaultValues: { name: '', redirectRoute: redirectRoutePath } as CreateInboxPayload,
57+
validators: {
58+
onSubmit: createInboxPayloadSchema,
59+
},
60+
onSubmit: ({ value, formApi }) => {
61+
if (formApi.state.isValid) {
62+
createInboxMutation.mutateAsync(value).then((res) => {
63+
if (res?.success) {
64+
setOpen(false);
65+
queryClient.invalidateQueries({ queryKey: ['inboxes'] });
66+
revalidate();
67+
}
68+
});
69+
}
70+
},
71+
});
72+
73+
return (
74+
<form
75+
onSubmit={(e) => {
76+
e.preventDefault();
77+
e.stopPropagation();
78+
form.handleSubmit();
79+
}}
80+
>
81+
<ModalV2.Title>{t('settings:inboxes.new_inbox.explain')}</ModalV2.Title>
82+
<div className="flex flex-col gap-6 p-6">
83+
<HiddenInputs redirectRoute={redirectRoutePath} />
84+
<form.Field
85+
name="name"
86+
validators={{
87+
onBlur: createInboxPayloadSchema.shape.name,
88+
onChange: createInboxPayloadSchema.shape.name,
89+
}}
90+
>
91+
{(field) => (
92+
<div className="group flex flex-col gap-2">
93+
<FormLabel name={field.name}>{t('settings:inboxes.new_inbox.name')}</FormLabel>
94+
<FormInput
95+
name={field.name}
96+
onBlur={field.handleBlur}
97+
onChange={(e) => field.handleChange(e.currentTarget.value)}
98+
valid={field.state.meta.errors.length === 0}
99+
type="text"
100+
/>
101+
<FormErrorOrDescription errors={getFieldErrors(field.state.meta.errors)} />
102+
</div>
103+
)}
104+
</form.Field>
105+
<div className="flex flex-1 flex-row gap-2">
106+
<ModalV2.Close render={<Button className="flex-1" variant="secondary" />}>
107+
{t('common:cancel')}
108+
</ModalV2.Close>
109+
<Button
110+
className="flex-1"
111+
variant="primary"
112+
type="submit"
113+
name="create"
114+
disabled={createInboxMutation.isPending}
115+
>
116+
<Icon icon="new-inbox" className="size-5" />
117+
{t('settings:inboxes.new_inbox.create')}
118+
</Button>
119+
</div>
120+
</div>
121+
</form>
122+
);
123+
}

0 commit comments

Comments
 (0)