mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): EditAccout form complete
This commit is contained in:
parent
494fcc0f9a
commit
5c7bf224c3
@ -1,24 +1,64 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useCallback, useEffect, useState } from 'react'
|
||||
import { FormContainer, FormContent, FormFooter, FormHeader } from '../components/FormLayout'
|
||||
import { ButtonComponent, CheckBoxComponent, TextBoxComponent } from '../components/editors'
|
||||
import { GetAccountResponse } from '../models/letsEncryptServer/account/responses/GetAccountResponse'
|
||||
import { useFormState } from '../hooks/useFormState'
|
||||
import { boolean, object, Schema, string } from 'zod'
|
||||
import { array, boolean, object, Schema, string } from 'zod'
|
||||
import { PlusIcon, TrashIcon } from 'lucide-react'
|
||||
import { getData, patchData } from '../axiosConfig'
|
||||
import { ApiRoutes, GetApiRoute } from '../AppMap'
|
||||
import { FieldContainer } from '../components/editors/FieldContainer'
|
||||
import { deepCopy, deepDelta, deltaHasOperations } from '../functions'
|
||||
import { PatchAccountRequest, PatchAccountRequestSchema } from '../models/letsEncryptServer/account/requests/PatchAccountRequest'
|
||||
import { addToast } from '../components/Toast/addToast'
|
||||
|
||||
|
||||
interface EditAccountHostnameFormProps {
|
||||
isDisabled: boolean
|
||||
hostname: string
|
||||
}
|
||||
|
||||
const EditAccountHostnameFormProto = (): EditAccountHostnameFormProps => ({
|
||||
isDisabled: false,
|
||||
hostname: ''
|
||||
})
|
||||
|
||||
const EditAccountHostnameFormSchema: Schema<EditAccountHostnameFormProps> = object({
|
||||
hostname: string(),
|
||||
isDisabled: boolean()
|
||||
})
|
||||
|
||||
interface EditAccountFormProps {
|
||||
isDisabled: boolean
|
||||
description: string
|
||||
disabled: boolean
|
||||
|
||||
contact: string
|
||||
contacts: string[]
|
||||
|
||||
hostname: string,
|
||||
hostnames: EditAccountHostnameFormProps[]
|
||||
}
|
||||
|
||||
const RegisterFormProto = (): EditAccountFormProps => ({
|
||||
isDisabled: false,
|
||||
description: '',
|
||||
disabled: false
|
||||
|
||||
contact: '',
|
||||
contacts: [],
|
||||
|
||||
hostname: '',
|
||||
hostnames: [],
|
||||
})
|
||||
|
||||
const RegisterFormSchema: Schema<EditAccountFormProps> = object({
|
||||
isDisabled: boolean(),
|
||||
description: string(),
|
||||
disabled: boolean()
|
||||
|
||||
contact: string(),
|
||||
contacts: array(string()),
|
||||
|
||||
hostname: string(),
|
||||
hostnames: array(EditAccountHostnameFormSchema)
|
||||
})
|
||||
|
||||
interface EditAccountProps {
|
||||
@ -41,14 +81,88 @@ const EditAccount: FC<EditAccountProps> = (props) => {
|
||||
formState,
|
||||
errors,
|
||||
formIsValid,
|
||||
handleInputChange
|
||||
handleInputChange,
|
||||
setInitialState
|
||||
} = useFormState<EditAccountFormProps>({
|
||||
initialState: RegisterFormProto(),
|
||||
validationSchema: RegisterFormSchema
|
||||
})
|
||||
|
||||
const [backupState, setBackupState] = useState<EditAccountFormProps>(RegisterFormProto())
|
||||
|
||||
const handleInitialization = useCallback((response: GetAccountResponse) => {
|
||||
const newState = {
|
||||
...RegisterFormProto(),
|
||||
isDisabled: response.isDisabled,
|
||||
description: response.description,
|
||||
contacts: response.contacts,
|
||||
hostnames: (response.hostnames ?? []).map(h => ({
|
||||
...EditAccountHostnameFormProto(),
|
||||
isDisabled: h.isDisabled,
|
||||
hostname: h.hostname
|
||||
}))
|
||||
}
|
||||
|
||||
setInitialState(newState)
|
||||
setBackupState(deepCopy(newState))
|
||||
}, [setInitialState])
|
||||
|
||||
useEffect(() => {
|
||||
getData<GetAccountResponse>(GetApiRoute(ApiRoutes.ACCOUNT_GET).route
|
||||
.replace('{accountId}', accountId)
|
||||
).then((response) => {
|
||||
if (!response) return
|
||||
|
||||
handleInitialization(response)
|
||||
})
|
||||
}, [accountId, handleInitialization])
|
||||
|
||||
|
||||
const mapFormStateToPatchRequest = (formState: EditAccountFormProps) : PatchAccountRequest => {
|
||||
const formStateCopy = deepCopy(formState)
|
||||
|
||||
const patchRequest: PatchAccountRequest = {
|
||||
isDisabled: formStateCopy.isDisabled,
|
||||
description: formStateCopy.description,
|
||||
contacts: formStateCopy.contacts,
|
||||
hostnames: formStateCopy.hostnames.map(h => ({
|
||||
hostname: h.hostname
|
||||
}))
|
||||
}
|
||||
|
||||
return patchRequest
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
// onSubmitted && onSubmitted(updatedEntity)
|
||||
if (!formIsValid) return
|
||||
|
||||
const fromFormState = mapFormStateToPatchRequest(formState)
|
||||
const fromBackupState = mapFormStateToPatchRequest(backupState)
|
||||
|
||||
const delta = deepDelta(fromBackupState, fromFormState)
|
||||
|
||||
if (!deltaHasOperations(delta)) {
|
||||
addToast('No changes detected', 'info')
|
||||
return
|
||||
}
|
||||
|
||||
const request = PatchAccountRequestSchema.safeParse(delta)
|
||||
if (!request.success) {
|
||||
request.error.issues.forEach(error => {
|
||||
addToast(error.message, 'error')
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
patchData<PatchAccountRequest, GetAccountResponse>(GetApiRoute(ApiRoutes.ACCOUNT_PATCH).route
|
||||
.replace('{accountId}', accountId), delta
|
||||
).then((response) => {
|
||||
if (!response) return
|
||||
|
||||
handleInitialization(response)
|
||||
onSubmitted?.(response)
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
@ -70,13 +184,100 @@ const EditAccount: FC<EditAccountProps> = (props) => {
|
||||
<CheckBoxComponent
|
||||
colspan={12}
|
||||
label={'Disabled'}
|
||||
value={formState.disabled}
|
||||
onChange={(e) => handleInputChange('disabled', e.target.checked)}
|
||||
errorText={errors.disabled}
|
||||
value={formState.isDisabled}
|
||||
onChange={(e) => handleInputChange('isDisabled', e.target.checked)}
|
||||
errorText={errors.isDisabled}
|
||||
/>
|
||||
|
||||
<h3 className={'col-span-12'}>Contacts:</h3>
|
||||
<ul className={'col-span-12'}>
|
||||
{formState.contacts
|
||||
.map((contact) => (
|
||||
<li key={contact} className={'grid grid-cols-12 gap-4 w-full pb-2'}>
|
||||
<div className={'col-span-10'}>
|
||||
{contact}
|
||||
</div>
|
||||
<ButtonComponent
|
||||
colspan={2}
|
||||
onClick={() => {
|
||||
const updatedContacts = formState.contacts.filter(c => c !== contact)
|
||||
handleInputChange('contacts', updatedContacts)
|
||||
}}
|
||||
|
||||
>
|
||||
<TrashIcon />
|
||||
</ButtonComponent>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<TextBoxComponent
|
||||
colspan={10}
|
||||
label={'New Contact'}
|
||||
value={formState.contact}
|
||||
onChange={(e) => {
|
||||
if (formState.contacts.includes(e.target.value))
|
||||
return
|
||||
|
||||
handleInputChange('contact', e.target.value)
|
||||
}}
|
||||
placeholder={'Add contact'}
|
||||
type={'text'}
|
||||
errorText={errors.contact}
|
||||
/>
|
||||
<FieldContainer colspan={2}>
|
||||
<ButtonComponent
|
||||
onClick={() => {
|
||||
handleInputChange('contacts', [...formState.contacts, formState.contact])
|
||||
handleInputChange('contact', '')
|
||||
}}
|
||||
disabled={formState.contact.trim() === ''}
|
||||
>
|
||||
<PlusIcon />
|
||||
</ButtonComponent>
|
||||
</FieldContainer>
|
||||
<h3 className={'col-span-12'}>Hostnames:</h3>
|
||||
<ul className={'col-span-12'}>
|
||||
{formState.hostnames.map((hostname) => (
|
||||
<li key={hostname.hostname} className={'grid grid-cols-12 gap-4 w-full'}>
|
||||
<span className={'col-span-10'}>{hostname.hostname}</span>
|
||||
<ButtonComponent
|
||||
colspan={2}
|
||||
onClick={() => {
|
||||
const updatedHostnames = formState.hostnames.filter(h => h !== hostname)
|
||||
handleInputChange('hostnames', updatedHostnames)
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</ButtonComponent>
|
||||
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<TextBoxComponent
|
||||
colspan={10}
|
||||
label={'New Hostname'}
|
||||
value={formState.hostname}
|
||||
onChange={(e) => {
|
||||
if (formState.hostnames.find(h => h.hostname === e.target.value))
|
||||
return
|
||||
|
||||
handleInputChange('hostname', e.target.value)
|
||||
}}
|
||||
placeholder={'Add hostname'}
|
||||
type={'text'}
|
||||
errorText={errors.hostname}
|
||||
/>
|
||||
<FieldContainer colspan={2}>
|
||||
<ButtonComponent
|
||||
onClick={() => {
|
||||
handleInputChange('hostnames', [...formState.hostnames, formState.hostname])
|
||||
handleInputChange('hostname', '')
|
||||
}}
|
||||
disabled={formState.hostname.trim() === ''}
|
||||
>
|
||||
<PlusIcon />
|
||||
</ButtonComponent>
|
||||
</FieldContainer>
|
||||
</div>
|
||||
</FormContent>
|
||||
<FormFooter
|
||||
|
||||
@ -137,13 +137,14 @@ const Home: FC = () => {
|
||||
: 'Not Upcoming'}
|
||||
</span>
|
||||
</span>
|
||||
<CheckBoxComponent
|
||||
colspan={3}
|
||||
value={hostname.isDisabled}
|
||||
label={'Disabled'}
|
||||
disabled={true}
|
||||
/>
|
||||
|
||||
<span className={'col-span-3'}>
|
||||
<label className={'mr-2'}>Disabled:</label>
|
||||
<input
|
||||
type={'checkbox'}
|
||||
checked={hostname.isDisabled}
|
||||
disabled={true}
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import { PatchOperation } from './PatchOperation'
|
||||
|
||||
export interface PatchAction<T> {
|
||||
op: PatchOperation // Enum for operation type
|
||||
index?: number // Index for the operation (for arrays/lists)
|
||||
value?: T // Value for the operation
|
||||
}
|
||||
@ -1,6 +1,22 @@
|
||||
export enum PatchOperation {
|
||||
Add,
|
||||
Remove,
|
||||
Replace,
|
||||
None
|
||||
|
||||
/// <summary>
|
||||
/// When you need to set or replace a normal field
|
||||
/// </summary>
|
||||
SetField,
|
||||
|
||||
/// <summary>
|
||||
/// When you need to set a normal field to null
|
||||
/// </summary>
|
||||
RemoveField,
|
||||
|
||||
/// <summary>
|
||||
/// When you need to add an item to a collection
|
||||
/// </summary>
|
||||
AddToCollection,
|
||||
|
||||
/// <summary>
|
||||
/// When you need to remove an item from a collection
|
||||
/// </summary>
|
||||
RemoveFromCollection,
|
||||
}
|
||||
14
src/MaksIT.WebUI/src/models/PatchRequestModelBase.ts
Normal file
14
src/MaksIT.WebUI/src/models/PatchRequestModelBase.ts
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
import z, { object, record, string } from 'zod'
|
||||
import { RequestModelBase, RequestModelBaseSchema } from './RequestModelBase'
|
||||
import { PatchOperation } from './PatchOperation'
|
||||
|
||||
export interface PatchRequestModelBase extends RequestModelBase {
|
||||
operations?: { [key: string]: PatchOperation }
|
||||
}
|
||||
|
||||
export const PatchRequestModelBaseSchema = RequestModelBaseSchema.and(
|
||||
object({
|
||||
operations: record(string(), z.enum(PatchOperation)).optional()
|
||||
})
|
||||
)
|
||||
9
src/MaksIT.WebUI/src/models/RequestModelBase.ts
Normal file
9
src/MaksIT.WebUI/src/models/RequestModelBase.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { object, Schema } from 'zod'
|
||||
|
||||
export interface RequestModelBase {
|
||||
[key: string]: unknown; // Add index signature
|
||||
}
|
||||
|
||||
export const RequestModelBaseSchema: Schema<RequestModelBase> = object({
|
||||
// Define the schema for the base request model
|
||||
})
|
||||
@ -1,3 +0,0 @@
|
||||
export interface ResponseModelBase {
|
||||
[key: string]: unknown
|
||||
}
|
||||
@ -1,9 +1,20 @@
|
||||
import { PatchAction } from '@/models/PatchAction'
|
||||
import { PatchHostnameRequest } from './PatchHostnameRequest'
|
||||
import { PatchHostnameRequest, PatchHostnameRequestSchema } from './PatchHostnameRequest'
|
||||
import { PatchRequestModelBase, PatchRequestModelBaseSchema } from '../../../PatchRequestModelBase'
|
||||
import { array, boolean, object, Schema, string } from 'zod'
|
||||
|
||||
export interface PatchAccountRequest {
|
||||
description?: PatchAction<string>
|
||||
isDisabled?: PatchAction<boolean>
|
||||
contacts?: PatchAction<string>[]
|
||||
export interface PatchAccountRequest extends PatchRequestModelBase {
|
||||
description?: string
|
||||
isDisabled?: boolean
|
||||
contacts?: string[]
|
||||
hostnames?: PatchHostnameRequest[]
|
||||
}
|
||||
|
||||
export const PatchAccountRequestSchema: Schema<PatchAccountRequest> = PatchRequestModelBaseSchema.and(
|
||||
object({
|
||||
description: string().optional(),
|
||||
isDisabled: boolean().optional(),
|
||||
contacts: array(string()).optional(),
|
||||
hostnames: array(PatchHostnameRequestSchema).optional()
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
import { PatchAction } from '@/models/PatchAction'
|
||||
import { boolean, object, Schema, string } from 'zod'
|
||||
import { PatchRequestModelBase, PatchRequestModelBaseSchema } from '../../../PatchRequestModelBase'
|
||||
|
||||
export interface PatchHostnameRequest {
|
||||
hostname?: PatchAction<string>
|
||||
isDisabled?: PatchAction<boolean>
|
||||
export interface PatchHostnameRequest extends PatchRequestModelBase {
|
||||
hostname?: string
|
||||
isDisabled?: boolean
|
||||
}
|
||||
|
||||
export const PatchHostnameRequestSchema: Schema<PatchHostnameRequest> = PatchRequestModelBaseSchema.and(
|
||||
object({
|
||||
hostname: string().optional(),
|
||||
isDisabled: boolean().optional()
|
||||
})
|
||||
)
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import z, { array, boolean, object, Schema, string } from 'zod'
|
||||
import { ChallengeType } from '../../../../entities/ChallengeType'
|
||||
import { RequestModelBase, RequestModelBaseSchema } from '../../../RequestModelBase'
|
||||
|
||||
export interface PostAccountRequest {
|
||||
export interface PostAccountRequest extends RequestModelBase {
|
||||
description: string
|
||||
contacts: string[]
|
||||
challengeType: string
|
||||
@ -9,10 +10,12 @@ export interface PostAccountRequest {
|
||||
isStaging: boolean
|
||||
}
|
||||
|
||||
export const PostAccountRequestSchema: Schema<PostAccountRequest> = object({
|
||||
description: string(),
|
||||
contacts: array(string()),
|
||||
hostnames: array(string()),
|
||||
challengeType: z.enum(ChallengeType),
|
||||
isStaging: boolean()
|
||||
})
|
||||
export const PostAccountRequestSchema: Schema<PostAccountRequest> = RequestModelBaseSchema.and(
|
||||
object({
|
||||
description: string(),
|
||||
contacts: array(string()),
|
||||
hostnames: array(string()),
|
||||
challengeType: z.enum(ChallengeType),
|
||||
isStaging: boolean()
|
||||
})
|
||||
)
|
||||
@ -1,7 +1,8 @@
|
||||
import { CacheAccount } from '../../../../entities/CacheAccount'
|
||||
import { ResponseModelBase } from '../../../ResponseModelBase'
|
||||
import { GetHostnameResponse } from './GetHostnameResponse'
|
||||
|
||||
export interface GetAccountResponse {
|
||||
export interface GetAccountResponse extends ResponseModelBase {
|
||||
accountId: string
|
||||
isDisabled: boolean
|
||||
description: string
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export interface GetHostnameResponse {
|
||||
import { ResponseModelBase } from '../../../ResponseModelBase'
|
||||
|
||||
export interface GetHostnameResponse extends ResponseModelBase {
|
||||
hostname: string
|
||||
expires: string
|
||||
isUpcomingExpire: boolean
|
||||
|
||||
Loading…
Reference in New Issue
Block a user