From e8a55b38d0027ebbb59ab991ffab704ec2a446eb Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Mon, 22 Jul 2024 16:26:19 +0800 Subject: [PATCH] feat(console): support multiple app secrets --- .../CreateSecretModal.tsx | 148 ++++++++++++++++ .../EndpointsAndCredentials.tsx | 160 +++++++++++++++++- .../ProtectedAppSettings/index.tsx | 2 +- .../index.module.scss | 14 ++ .../ApplicationDetailsContent/index.tsx | 6 +- .../admin-console/application-details.ts | 22 ++- 6 files changed, 340 insertions(+), 12 deletions(-) create mode 100644 packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/CreateSecretModal.tsx diff --git a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/CreateSecretModal.tsx b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/CreateSecretModal.tsx new file mode 100644 index 000000000..4d3129096 --- /dev/null +++ b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/CreateSecretModal.tsx @@ -0,0 +1,148 @@ +import { type ApplicationSecret } from '@logto/schemas'; +import { addDays, format } from 'date-fns'; +import { useCallback, useEffect, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; +import ReactModal from 'react-modal'; + +import Button from '@/ds-components/Button'; +import DangerousRaw from '@/ds-components/DangerousRaw'; +import FormField from '@/ds-components/FormField'; +import ModalLayout from '@/ds-components/ModalLayout'; +import Select from '@/ds-components/Select'; +import TextInput from '@/ds-components/TextInput'; +import useApi from '@/hooks/use-api'; +import * as modalStyles from '@/scss/modal.module.scss'; +import { trySubmitSafe } from '@/utils/form'; + +type FormData = { name: string; expiration: string }; + +type Props = { + readonly appId: string; + readonly isOpen: boolean; + readonly onClose: (createdAppSecret?: ApplicationSecret) => void; +}; + +const days = Object.freeze([7, 30, 180, 365]); +const neverExpires = '-1'; + +function CreateSecretModal({ appId, isOpen, onClose }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { + register, + control, + watch, + formState: { errors, isSubmitting }, + handleSubmit, + reset, + } = useForm({ defaultValues: { name: '', expiration: String(days[0]) } }); + const onCloseHandler = useCallback( + (created?: ApplicationSecret) => { + reset(); + onClose(created); + }, + [onClose, reset] + ); + const api = useApi(); + const expirationDays = watch('expiration'); + const [expirationDate, setExpirationDate] = useState(); + + useEffect(() => { + const setDate = () => { + if (expirationDays === neverExpires) { + setExpirationDate(undefined); + } else { + setExpirationDate(addDays(new Date(), Number(expirationDays))); + } + }; + const interval = setInterval(setDate, 1000); + setDate(); + + return () => { + clearInterval(interval); + }; + }, [expirationDays]); + + const submit = handleSubmit( + trySubmitSafe(async ({ expiration, ...rest }) => { + const createdData = await api + .post(`api/applications/${appId}/secrets`, { + json: { ...rest, expiresAt: expirationDate?.valueOf() }, + }) + .json(); + toast.success( + t('organization_template.roles.create_modal.created', { name: createdData.name }) + ); + onCloseHandler(createdData); + }) + ); + + return ( + { + onCloseHandler(); + }} + > + + } + onClose={onCloseHandler} + > + + + + ( + + {t('application_details.secrets.create_modal.expiration_description', { + date: format(expirationDate, 'Pp'), + })} + + ) : ( + 'application_details.secrets.create_modal.expiration_description_never' + ) + } + > +