mirror of
https://github.com/papra-hq/papra.git
synced 2025-12-17 03:51:45 -06:00
Compare commits
1 Commits
@papra/lec
...
formisch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2f0b83863 |
@@ -31,9 +31,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@corentinth/chisels": "^1.3.1",
|
||||
"@formisch/solid": "^0.2.0",
|
||||
"@kobalte/core": "^0.13.10",
|
||||
"@kobalte/utils": "^0.9.1",
|
||||
"@modular-forms/solid": "^0.25.1",
|
||||
"@pdfslick/solid": "^2.3.0",
|
||||
"@solid-primitives/storage": "^4.3.2",
|
||||
"@solidjs/router": "^0.14.10",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { setValue } from '@modular-forms/solid';
|
||||
import { setInput } from '@formisch/solid';
|
||||
import { A } from '@solidjs/router';
|
||||
import { createSignal, Show } from 'solid-js';
|
||||
import * as v from 'valibot';
|
||||
@@ -79,35 +79,35 @@ export const CreateApiKeyPage: Component = () => {
|
||||
|
||||
<Show when={!getToken()}>
|
||||
<Form>
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="name">{t('api-keys.create.form.name.label')}</TextFieldLabel>
|
||||
<TextField type="text" id="name" placeholder={t('api-keys.create.form.name.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="text" id="name" placeholder={t('api-keys.create.form.name.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="permissions" type="string[]">
|
||||
<Field path={['permissions']}>
|
||||
{field => (
|
||||
<div>
|
||||
<p class="text-sm font-bold">{t('api-keys.create.form.permissions.label')}</p>
|
||||
|
||||
<div class="p-6 pb-8 border rounded-md mt-2">
|
||||
<ApiKeyPermissionsPicker permissions={field.value ?? []} onChange={permissions => setValue(form, 'permissions', permissions)} />
|
||||
<ApiKeyPermissionsPicker permissions={(field.input as string[]) ?? []} onChange={permissions => setInput(form, { path: ['permissions'], input: permissions })} />
|
||||
</div>
|
||||
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</div>
|
||||
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="flex justify-end mt-6">
|
||||
<Button type="submit" isLoading={form.submitting}>
|
||||
<Button type="submit" isLoading={form.isSubmitting}>
|
||||
{t('api-keys.create.form.submit')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ export const EmailLoginForm: Component = () => {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
throw new Error(error.message);
|
||||
}
|
||||
},
|
||||
schema: v.object({
|
||||
@@ -54,32 +54,32 @@ export const EmailLoginForm: Component = () => {
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['email']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="email">{t('auth.login.form.email.label')}</TextFieldLabel>
|
||||
<TextField type="email" id="email" placeholder={t('auth.login.form.email.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="email" id="email" placeholder={t('auth.login.form.email.placeholder')} {...field.props} value={field.input} autoFocus aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="password">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['password']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="password">{t('auth.login.form.password.label')}</TextFieldLabel>
|
||||
|
||||
<TextField type="password" id="password" placeholder={t('auth.login.form.password.placeholder')} {...inputProps} value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="password" id="password" placeholder={t('auth.login.form.password.placeholder')} {...field.props} value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<Field name="rememberMe" type="boolean">
|
||||
{(field, inputProps) => (
|
||||
<Checkbox class="flex items-center gap-2" defaultChecked={field.value}>
|
||||
<CheckboxControl inputProps={inputProps} />
|
||||
<Field path={['rememberMe']}>
|
||||
{field => (
|
||||
<Checkbox class="flex items-center gap-2" defaultChecked={field.input as boolean}>
|
||||
<CheckboxControl inputProps={field.props} />
|
||||
<CheckboxLabel class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
{t('auth.login.form.remember-me.label')}
|
||||
</CheckboxLabel>
|
||||
@@ -94,9 +94,9 @@ export const EmailLoginForm: Component = () => {
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Button type="submit" class="w-full">{t('auth.login.form.submit')}</Button>
|
||||
<Button type="submit" class="w-full" isLoading={form.isSubmitting}>{t('auth.login.form.submit')}</Button>
|
||||
|
||||
<div class="text-red-500 text-sm mt-4">{form.response.message}</div>
|
||||
<div class="text-red-500 text-sm mt-4">{form.errors?.[0]}</div>
|
||||
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export const EmailRegisterForm: Component = () => {
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
if (config.auth.isEmailVerificationRequired) {
|
||||
@@ -63,41 +63,42 @@ export const EmailRegisterForm: Component = () => {
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['email']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="email">{t('auth.register.form.email.label')}</TextFieldLabel>
|
||||
<TextField type="email" id="email" placeholder={t('auth.register.form.email.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="email" id="email" placeholder={t('auth.register.form.email.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="name">{t('auth.register.form.name.label')}</TextFieldLabel>
|
||||
<TextField type="text" id="name" placeholder={t('auth.register.form.name.placeholder')} {...inputProps} value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="text" id="name" placeholder={t('auth.register.form.name.placeholder')} {...field.props} value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="password">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['password']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="password">{t('auth.register.form.password.label')}</TextFieldLabel>
|
||||
|
||||
<TextField type="password" id="password" placeholder={t('auth.register.form.password.placeholder')} {...inputProps} value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="password" id="password" placeholder={t('auth.register.form.password.placeholder')} {...field.props} value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Button type="submit" class="w-full">{t('auth.register.form.submit')}</Button>
|
||||
|
||||
<div class="text-red-500 text-sm mt-4">{form.response.message}</div>
|
||||
<Button type="submit" class="w-full" isLoading={form.isSubmitting}>
|
||||
{t('auth.register.form.submit')}
|
||||
</Button>
|
||||
|
||||
<div class="text-red-500 text-sm mt-4">{form.errors?.[0]}</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,21 +29,21 @@ export const ResetPasswordForm: Component<{ onSubmit: (args: { email: string })
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['email']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="email">{t('auth.request-password-reset.form.email.label')}</TextFieldLabel>
|
||||
<TextField type="email" id="email" placeholder={t('auth.request-password-reset.form.email.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="email" id="email" placeholder={t('auth.request-password-reset.form.email.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Button type="submit" class="w-full">
|
||||
<Button type="submit" class="w-full" isLoading={form.isSubmitting}>
|
||||
{t('auth.request-password-reset.form.submit')}
|
||||
</Button>
|
||||
|
||||
<div class="text-red-500 text-sm mt-2">{form.response.message}</div>
|
||||
<div class="text-red-500 text-sm mt-2">{form.errors?.[0]}</div>
|
||||
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -27,21 +27,21 @@ export const ResetPasswordForm: Component<{ onSubmit: (args: { newPassword: stri
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Field name="newPassword">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['newPassword']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="newPassword">{t('auth.reset-password.form.new-password.label')}</TextFieldLabel>
|
||||
<TextField type="password" id="newPassword" placeholder={t('auth.reset-password.form.new-password.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="password" id="newPassword" placeholder={t('auth.reset-password.form.new-password.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Button type="submit" class="w-full">
|
||||
<Button type="submit" class="w-full" isLoading={form.isSubmitting}>
|
||||
{t('auth.reset-password.form.submit')}
|
||||
</Button>
|
||||
|
||||
<div class="text-red-500 text-sm mt-2">{form.response.message}</div>
|
||||
<div class="text-red-500 text-sm mt-2">{form.errors?.[0]}</div>
|
||||
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Component, ParentComponent } from 'solid-js';
|
||||
import { setValue } from '@modular-forms/solid';
|
||||
import { setInput } from '@formisch/solid';
|
||||
import { useMutation } from '@tanstack/solid-query';
|
||||
import { createContext, createEffect, createSignal, useContext } from 'solid-js';
|
||||
import * as v from 'valibot';
|
||||
@@ -58,7 +58,7 @@ export const RenameDocumentDialog: Component<{
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
setValue(form, 'name', getDocumentNameWithoutExtension({ name: props.documentName }));
|
||||
setInput(form, { path: ['name'], input: getDocumentNameWithoutExtension({ name: props.documentName }) });
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -69,21 +69,21 @@ export const RenameDocumentDialog: Component<{
|
||||
</DialogHeader>
|
||||
|
||||
<Form>
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot>
|
||||
<TextFieldLabel class="sr-only" for="name">{t('documents.rename.form.name.label')}</TextFieldLabel>
|
||||
<TextField {...inputProps} value={field.value} id="name" placeholder={t('documents.rename.form.name.placeholder')} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField {...field.props} value={field.input} id="name" placeholder={t('documents.rename.form.name.placeholder')} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="flex justify-end mt-4 gap-2">
|
||||
<Button type="button" variant="secondary" onClick={() => props.setIsOpen(false)}>
|
||||
<Button type="button" variant="secondary" onClick={() => props.setIsOpen(false)} disabled={form.isSubmitting}>
|
||||
{t('documents.rename.cancel')}
|
||||
</Button>
|
||||
<Button type="submit">{t('documents.rename.form.submit')}</Button>
|
||||
<Button type="submit" isLoading={form.isSubmitting}>{t('documents.rename.form.submit')}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
|
||||
@@ -102,21 +102,21 @@ const AllowedOriginsDialog: Component<{
|
||||
</DialogHeader>
|
||||
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['email']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4 mt-4">
|
||||
<TextFieldLabel for="email">{t('intake-emails.allowed-origins.add.label')}</TextFieldLabel>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<TextField type="email" id="email" placeholder={t('intake-emails.allowed-origins.add.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
<Button type="submit">
|
||||
<TextField type="email" id="email" placeholder={t('intake-emails.allowed-origins.add.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
<Button type="submit" isLoading={form.isSubmitting}>
|
||||
<div class="i-tabler-plus size-4 mr-2" />
|
||||
{t('intake-emails.allowed-origins.add.button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="text-red-500 text-sm mt-4">{form.response.message}</div>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error }</div>}
|
||||
<div class="text-red-500 text-sm mt-4">{form.errors?.[0]}</div>
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -37,23 +37,23 @@ export const CreateOrganizationForm: Component<{
|
||||
return (
|
||||
<div>
|
||||
<Form>
|
||||
<Field name="organizationName">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['organizationName']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-6">
|
||||
<TextFieldLabel for="organizationName">{t('organizations.create.form.name.label')}</TextFieldLabel>
|
||||
<TextField type="text" id="organizationName" placeholder={t('organizations.create.form.name.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="text" id="organizationName" placeholder={t('organizations.create.form.name.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button type="submit" isLoading={form.submitting} class="w-full">
|
||||
<Button type="submit" isLoading={form.isSubmitting} class="w-full">
|
||||
{t('organizations.create.form.submit')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="text-red-500 text-sm mt-4">{form.response.message}</div>
|
||||
<div class="text-red-500 text-sm mt-4">{form.errors?.[0]}</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { setValue } from '@modular-forms/solid';
|
||||
import { setInput } from '@formisch/solid';
|
||||
import { useNavigate, useParams } from '@solidjs/router';
|
||||
import { useMutation } from '@tanstack/solid-query';
|
||||
import { onMount, Show } from 'solid-js';
|
||||
@@ -101,8 +101,8 @@ export const InviteMemberPage: Component = () => {
|
||||
|
||||
<div class="mt-10 max-w-xs mx-auto">
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['email']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="email">
|
||||
{t('organizations.invite-member.form.email.label')}
|
||||
@@ -113,16 +113,16 @@ export const InviteMemberPage: Component = () => {
|
||||
placeholder={t(
|
||||
'organizations.invite-member.form.email.placeholder',
|
||||
)}
|
||||
{...inputProps}
|
||||
{...field.props}
|
||||
/>
|
||||
{field.error && (
|
||||
<div class="text-red-500 text-sm">{field.error}</div>
|
||||
{field.errors && (
|
||||
<div class="text-red-500 text-sm">{field.errors[0]}</div>
|
||||
)}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="role">
|
||||
<Field path={['role']}>
|
||||
{field => (
|
||||
<div>
|
||||
<label for="role" class="text-sm font-medium mb-1 block">
|
||||
@@ -139,9 +139,9 @@ export const InviteMemberPage: Component = () => {
|
||||
{tRole(props.item.rawValue)}
|
||||
</SelectItem>
|
||||
)}
|
||||
value={field.value}
|
||||
value={field.input as InvitableRole}
|
||||
onChange={value =>
|
||||
setValue(form, 'role', value as InvitableRole)}
|
||||
setInput(form, { path: ['role'], input: value as InvitableRole })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue<string>>
|
||||
|
||||
@@ -138,25 +138,25 @@ const UpdateOrganizationNameCard: Component<{ organization: Organization }> = (p
|
||||
|
||||
<Form>
|
||||
<CardContent class="pt-6 ">
|
||||
<Field name="organizationName">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['organizationName']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1">
|
||||
<TextFieldLabel for="organizationName" class="sr-only">
|
||||
{t('organization.settings.name.title')}
|
||||
</TextFieldLabel>
|
||||
<div class="flex gap-2 flex-col sm:flex-row">
|
||||
<TextField type="text" id="organizationName" placeholder={t('organization.settings.name.placeholder')} {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} />
|
||||
<TextField type="text" id="organizationName" placeholder={t('organization.settings.name.placeholder')} {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} />
|
||||
|
||||
<Button type="submit" isLoading={form.submitting} class="flex-shrink-0" disabled={field.value?.trim() === props.organization.name}>
|
||||
<Button type="submit" isLoading={form.isSubmitting} class="flex-shrink-0" disabled={(field.input as string)?.trim() === props.organization.name}>
|
||||
{t('organization.settings.name.update')}
|
||||
</Button>
|
||||
</div>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="text-red-500 text-sm">{form.response.message}</div>
|
||||
<div class="text-red-500 text-sm">{form.errors?.[0]}</div>
|
||||
</CardContent>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { FormErrors, FormProps, PartialValues } from '@modular-forms/solid';
|
||||
import type { FieldArrayProps, FieldProps, FormProps } from '@formisch/solid';
|
||||
import type * as v from 'valibot';
|
||||
import { createForm as createModularForm, FormError, valiForm } from '@modular-forms/solid';
|
||||
import { createForm as createFormishForm, Field, FieldArray, Form } from '@formisch/solid';
|
||||
import { createHook } from '../hooks/hooks';
|
||||
|
||||
// Extracted from the library to avoid type errors
|
||||
type FormishDeepPartial<TValue> = TValue extends readonly unknown[] ? number extends TValue['length'] ? TValue : { [Key in keyof TValue]?: FormishDeepPartial<TValue[Key]> | undefined } : TValue extends Record<PropertyKey, unknown> ? { [Key in keyof TValue]?: FormishDeepPartial<TValue[Key]> | undefined } : TValue | undefined;
|
||||
|
||||
export function createForm<Schema extends v.ObjectSchema<any, any>>({
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
}: {
|
||||
schema: Schema;
|
||||
initialValues?: PartialValues<v.InferInput<Schema>>;
|
||||
initialValues?: FormishDeepPartial<v.InferInput<Schema>>;
|
||||
onSubmit?: (values: v.InferInput<Schema>) => Promise<void>;
|
||||
}) {
|
||||
const submitHook = createHook<v.InferInput<Schema>>();
|
||||
@@ -18,18 +21,18 @@ export function createForm<Schema extends v.ObjectSchema<any, any>>({
|
||||
submitHook.on(onSubmit);
|
||||
}
|
||||
|
||||
const [form, { Form, Field, FieldArray }] = createModularForm<v.InferInput<Schema>>({
|
||||
validate: valiForm(schema),
|
||||
initialValues,
|
||||
const form = createFormishForm({
|
||||
schema,
|
||||
initialInput: initialValues,
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
Form: (props: Omit<FormProps<v.InferInput<Schema>, undefined>, 'of'>) => Form({ ...props, onSubmit: submitHook.trigger }),
|
||||
Field,
|
||||
FieldArray,
|
||||
onSubmit: submitHook.on,
|
||||
Form: (props: Omit<FormProps<Schema>, 'of' | 'onSubmit'>) => Form({ of: form, ...props, onSubmit: async (args) => {
|
||||
await submitHook.trigger(args);
|
||||
} }),
|
||||
Field: (props: Omit<FieldProps<Schema>, 'of'>) => Field({ of: form, ...props }),
|
||||
FieldArray: (props: Omit<FieldArrayProps<Schema>, 'of'>) => FieldArray({ of: form, ...props }),
|
||||
submit: submitHook.trigger,
|
||||
createFormError: ({ message, fields }: { message: string; fields?: FormErrors<v.InferInput<Schema>> }) => new FormError<v.InferInput<Schema>>(message, fields),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import type { TaggingRule, TaggingRuleForCreation } from '../tagging-rules.types';
|
||||
import { insert, remove, setValue } from '@modular-forms/solid';
|
||||
import { insert, remove, setInput } from '@formisch/solid';
|
||||
import { A } from '@solidjs/router';
|
||||
import { For, Show } from 'solid-js';
|
||||
import * as v from 'valibot';
|
||||
@@ -45,24 +45,26 @@ export const TaggingRuleForm: Component<{
|
||||
}
|
||||
}
|
||||
|
||||
props.onSubmit({ taggingRule: { name, conditions, tagIds, description } });
|
||||
props.onSubmit({ taggingRule: { name, conditions, tagIds, description: description ?? '' } });
|
||||
},
|
||||
schema: v.object({
|
||||
name: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('tagging-rules.form.name.min-length')),
|
||||
v.minLength(1, t('tagging-rules.form.name.min-length')),
|
||||
v.maxLength(64, t('tagging-rules.form.name.max-length')),
|
||||
),
|
||||
description: v.pipe(
|
||||
v.string(),
|
||||
v.maxLength(256, t('tagging-rules.form.description.max-length')),
|
||||
description: v.optional(
|
||||
v.pipe(
|
||||
v.string(),
|
||||
v.maxLength(256, t('tagging-rules.form.description.max-length')),
|
||||
),
|
||||
),
|
||||
conditions: v.optional(
|
||||
v.array(v.object({
|
||||
field: v.picklist(Object.values(TAGGING_RULE_FIELDS)),
|
||||
operator: v.picklist(Object.values(TAGGING_RULE_OPERATORS)),
|
||||
value: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('tagging-rules.form.conditions.value.min-length')),
|
||||
v.minLength(1, t('tagging-rules.form.conditions.value.min-length')),
|
||||
),
|
||||
})),
|
||||
@@ -90,33 +92,33 @@ export const TaggingRuleForm: Component<{
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1">
|
||||
<TextFieldLabel for="name">{t('tagging-rules.form.name.label')}</TextFieldLabel>
|
||||
<TextField
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder={t('tagging-rules.form.name.placeholder')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="description">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['description']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mt-6">
|
||||
<TextFieldLabel for="description">{t('tagging-rules.form.description.label')}</TextFieldLabel>
|
||||
<TextArea
|
||||
id="description"
|
||||
placeholder={t('tagging-rules.form.description.placeholder')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
@@ -126,7 +128,7 @@ export const TaggingRuleForm: Component<{
|
||||
<p class="mb-1 font-medium">{t('tagging-rules.form.conditions.label')}</p>
|
||||
<p class="mb-2 text-sm text-muted-foreground">{t('tagging-rules.form.conditions.description')}</p>
|
||||
|
||||
<FieldArray name="conditions">
|
||||
<FieldArray path={['conditions']}>
|
||||
{fieldArray => (
|
||||
<div>
|
||||
<For each={fieldArray.items}>
|
||||
@@ -134,12 +136,12 @@ export const TaggingRuleForm: Component<{
|
||||
<div class="px-4 py-4 mb-1 flex gap-2 items-center bg-card border rounded-md">
|
||||
<div>When</div>
|
||||
|
||||
<Field name={`conditions.${index()}.field`}>
|
||||
<Field path={['conditions', index(), 'field']}>
|
||||
{field => (
|
||||
<Select
|
||||
id="field"
|
||||
defaultValue={field.value}
|
||||
onChange={value => value && setValue(form, `conditions.${index()}.field`, value)}
|
||||
defaultValue={field.input as string}
|
||||
onChange={value => value && setInput(form, { path: ['conditions', index(), 'field'], input: value })}
|
||||
options={Object.values(TAGGING_RULE_FIELDS)}
|
||||
itemComponent={props => (
|
||||
<SelectItem item={props.item}>{getFieldLabel(props.item.rawValue)}</SelectItem>
|
||||
@@ -153,12 +155,12 @@ export const TaggingRuleForm: Component<{
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name={`conditions.${index()}.operator`}>
|
||||
<Field path={['conditions', index(), 'operator']}>
|
||||
{field => (
|
||||
<Select
|
||||
id="operator"
|
||||
defaultValue={field.value}
|
||||
onChange={value => value && setValue(form, `conditions.${index()}.operator`, value)}
|
||||
defaultValue={field.input as string}
|
||||
onChange={value => value && setInput(form, { path: ['conditions', index(), 'operator'], input: value })}
|
||||
options={Object.values(TAGGING_RULE_OPERATORS)}
|
||||
itemComponent={props => (
|
||||
<SelectItem item={props.item}>{getOperatorLabel(props.item.rawValue)}</SelectItem>
|
||||
@@ -172,36 +174,36 @@ export const TaggingRuleForm: Component<{
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name={`conditions.${index()}.value`}>
|
||||
{(field, inputProps) => (
|
||||
<Field path={['conditions', index(), 'value']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 flex-1">
|
||||
<TextField
|
||||
id="value"
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
placeholder={t('tagging-rules.form.conditions.value.placeholder')}
|
||||
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Button variant="outline" size="icon" onClick={() => remove(form, 'conditions', { at: index() })}>
|
||||
<Button variant="outline" size="icon" onClick={() => remove(form, { path: ['conditions'], at: index() })}>
|
||||
<div class="i-tabler-x size-4"></div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
{fieldArray.error && <div class="text-red-500 text-sm">{fieldArray.error}</div>}
|
||||
{fieldArray.errors && <div class="text-red-500 text-sm">{fieldArray.errors[0]}</div>}
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => insert(form, 'conditions', { value: { field: 'name', operator: 'contains', value: '' } })}
|
||||
onClick={() => insert(form, { path: ['conditions'], input: { field: 'name', operator: 'contains', value: '' } })}
|
||||
class="gap-2 mt-2"
|
||||
>
|
||||
<div class="i-tabler-plus size-4"></div>
|
||||
@@ -213,7 +215,7 @@ export const TaggingRuleForm: Component<{
|
||||
<p class="mb-1 font-medium">{t('tagging-rules.form.tags.label')}</p>
|
||||
<p class="mb-2 text-sm text-muted-foreground">{t('tagging-rules.form.tags.description')}</p>
|
||||
|
||||
<Field name="tagIds" type="string[]">
|
||||
<Field path={['tagIds']}>
|
||||
{field => (
|
||||
<>
|
||||
<div class="flex gap-2 sm:items-center sm:flex-row flex-col">
|
||||
@@ -221,8 +223,8 @@ export const TaggingRuleForm: Component<{
|
||||
|
||||
<DocumentTagPicker
|
||||
organizationId={props.organizationId}
|
||||
tagIds={field.value ?? []}
|
||||
onTagsChange={({ tags }) => setValue(form, 'tagIds', tags.map(tag => tag.id))}
|
||||
tagIds={(field.input as string[]) ?? []}
|
||||
onTagsChange={({ tags }) => setInput(form, { path: ['tagIds'], input: tags.map(tag => tag.id) })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -235,7 +237,7 @@ export const TaggingRuleForm: Component<{
|
||||
)}
|
||||
</CreateTagModal>
|
||||
</div>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { DialogTriggerProps } from '@kobalte/core/dialog';
|
||||
import type { Component, JSX } from 'solid-js';
|
||||
import type { Tag as TagType } from '../tags.types';
|
||||
import { safely } from '@corentinth/chisels';
|
||||
import { getValues, setValue } from '@modular-forms/solid';
|
||||
import { getInput, setInput } from '@formisch/solid';
|
||||
import { A, useParams } from '@solidjs/router';
|
||||
import { useQuery } from '@tanstack/solid-query';
|
||||
import { createSignal, For, Show, Suspense } from 'solid-js';
|
||||
@@ -45,7 +45,7 @@ const TagColorPicker: Component<{
|
||||
};
|
||||
|
||||
const TagForm: Component<{
|
||||
onSubmit: (values: { name: string; color: string; description: string }) => Promise<void>;
|
||||
onSubmit: (values: { name: string; color: string; description?: string }) => Promise<void>;
|
||||
initialValues?: { name?: string; color?: string; description?: string | null };
|
||||
submitLabel?: string;
|
||||
}> = (props) => {
|
||||
@@ -54,21 +54,23 @@ const TagForm: Component<{
|
||||
onSubmit: props.onSubmit,
|
||||
schema: v.object({
|
||||
name: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('tags.form.name.required')),
|
||||
v.trim(),
|
||||
v.nonEmpty(t('tags.form.name.required')),
|
||||
v.maxLength(64, t('tags.form.name.max-length')),
|
||||
),
|
||||
color: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('tags.form.color.required')),
|
||||
v.trim(),
|
||||
v.nonEmpty(t('tags.form.color.required')),
|
||||
v.hexColor(t('tags.form.color.invalid')),
|
||||
),
|
||||
description: v.pipe(
|
||||
v.string(),
|
||||
v.trim(),
|
||||
v.maxLength(256, t('tags.form.description.max-length')),
|
||||
description: v.optional(
|
||||
v.pipe(
|
||||
v.string(),
|
||||
v.trim(),
|
||||
v.maxLength(256, t('tags.form.description.max-length')),
|
||||
),
|
||||
'',
|
||||
),
|
||||
}),
|
||||
initialValues: {
|
||||
@@ -77,39 +79,39 @@ const TagForm: Component<{
|
||||
},
|
||||
});
|
||||
|
||||
const getFormValues = () => getValues(form);
|
||||
const getFormValues = () => getInput(form);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="name">{t('tags.form.name.label')}</TextFieldLabel>
|
||||
<TextField type="text" id="name" {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} placeholder={t('tags.form.name.placeholder')} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextField type="text" id="name" {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} placeholder={t('tags.form.name.placeholder')} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="color">
|
||||
<Field path={['color']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="color">{t('tags.form.color.label')}</TextFieldLabel>
|
||||
<TagColorPicker color={field.value ?? ''} onChange={color => setValue(form, 'color', color)} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TagColorPicker color={(field.input as string) ?? ''} onChange={color => setInput(form, { path: ['color'], input: color })} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="description">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['description']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1 mb-4">
|
||||
<TextFieldLabel for="description">
|
||||
{t('tags.form.description.label')}
|
||||
<span class="font-normal ml-1 text-muted-foreground">{t('tags.form.description.optional')}</span>
|
||||
</TextFieldLabel>
|
||||
<TextArea id="description" {...inputProps} autoFocus value={field.value} aria-invalid={Boolean(field.error)} placeholder={t('tags.form.description.placeholder')} />
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
<TextArea id="description" {...field.props} autoFocus value={field.input} aria-invalid={Boolean(field.errors)} placeholder={t('tags.form.description.placeholder')} />
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
@@ -137,7 +139,7 @@ export const CreateTagModal: Component<{
|
||||
const { t } = useI18n();
|
||||
const { getErrorMessage } = useI18nApiErrors({ t });
|
||||
|
||||
const onSubmit = async ({ name, color, description }: { name: string; color: string; description: string }) => {
|
||||
const onSubmit = async ({ name, color, description }: { name: string; color: string; description?: string }) => {
|
||||
const [,error] = await safely(createTag({
|
||||
name,
|
||||
color: color.toLowerCase(),
|
||||
@@ -188,7 +190,7 @@ const UpdateTagModal: Component<{
|
||||
const [getIsModalOpen, setIsModalOpen] = createSignal(false);
|
||||
const { t } = useI18n();
|
||||
|
||||
const onSubmit = async ({ name, color, description }: { name: string; color: string; description: string }) => {
|
||||
const onSubmit = async ({ name, color, description }: { name: string; color: string; description?: string }) => {
|
||||
await updateTag({
|
||||
name,
|
||||
color: color.toLowerCase(),
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function fetchTags({ organizationId }: { organizationId: string })
|
||||
};
|
||||
}
|
||||
|
||||
export async function createTag({ organizationId, name, color, description }: { organizationId: string; name: string; color: string; description: string }) {
|
||||
export async function createTag({ organizationId, name, color, description = '' }: { organizationId: string; name: string; color: string; description?: string }) {
|
||||
const { tag } = await apiClient<{ tag: AsDto<Tag> }>({
|
||||
path: `/api/organizations/${organizationId}/tags`,
|
||||
method: 'POST',
|
||||
@@ -26,7 +26,7 @@ export async function createTag({ organizationId, name, color, description }: {
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateTag({ organizationId, tagId, name, color, description }: { organizationId: string; tagId: string; name: string; color: string; description: string }) {
|
||||
export async function updateTag({ organizationId, tagId, name, color, description = '' }: { organizationId: string; tagId: string; name: string; color: string; description?: string }) {
|
||||
const { tag } = await apiClient<{ tag: AsDto<Tag> }>({
|
||||
path: `/api/organizations/${organizationId}/tags/${tagId}`,
|
||||
method: 'PUT',
|
||||
|
||||
@@ -90,8 +90,8 @@ const UpdateFullNameCard: Component<{ name: string }> = (props) => {
|
||||
|
||||
<Form>
|
||||
<CardContent class="pt-6">
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col gap-1">
|
||||
<TextFieldLabel for="name" class="sr-only">
|
||||
{t('user.settings.name.label')}
|
||||
@@ -101,25 +101,25 @@ const UpdateFullNameCard: Component<{ name: string }> = (props) => {
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder={t('user.settings.name.placeholder')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={form.submitting}
|
||||
isLoading={form.isSubmitting}
|
||||
class="flex-shrink-0"
|
||||
disabled={field.value?.trim() === props.name}
|
||||
disabled={(field.input as string)?.trim() === props.name}
|
||||
>
|
||||
{t('user.settings.name.update')}
|
||||
</Button>
|
||||
</div>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="text-red-500 text-sm">{form.response.message}</div>
|
||||
<div class="text-red-500 text-sm">{form.errors?.[0]}</div>
|
||||
</CardContent>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { setValue } from '@modular-forms/solid';
|
||||
import type { WebhookEvent } from '../webhooks.types';
|
||||
import { setInput } from '@formisch/solid';
|
||||
import { A, useNavigate, useParams } from '@solidjs/router';
|
||||
import * as v from 'valibot';
|
||||
import { useI18n } from '@/modules/i18n/i18n.provider';
|
||||
@@ -39,11 +40,11 @@ export const CreateWebhookPage: Component = () => {
|
||||
},
|
||||
schema: v.object({
|
||||
name: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('webhooks.create.form.name.required')),
|
||||
v.nonEmpty(t('webhooks.create.form.name.required')),
|
||||
),
|
||||
url: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('webhooks.create.form.url.required')),
|
||||
v.nonEmpty(t('webhooks.create.form.url.required')),
|
||||
v.url(t('webhooks.create.form.url.invalid')),
|
||||
),
|
||||
@@ -71,68 +72,68 @@ export const CreateWebhookPage: Component = () => {
|
||||
</div>
|
||||
|
||||
<Form>
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="name">{t('webhooks.create.form.name.label')}</TextFieldLabel>
|
||||
<TextField
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder={t('webhooks.create.form.name.placeholder')}
|
||||
{...inputProps}
|
||||
{...field.props}
|
||||
autoFocus
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="url">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['url']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="url">{t('webhooks.create.form.url.label')}</TextFieldLabel>
|
||||
<TextField
|
||||
type="url"
|
||||
id="url"
|
||||
placeholder={t('webhooks.create.form.url.placeholder')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="secret">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['secret']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="secret">{t('webhooks.create.form.secret.label')}</TextFieldLabel>
|
||||
<TextField
|
||||
type="password"
|
||||
id="secret"
|
||||
placeholder={t('webhooks.create.form.secret.placeholder')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="events" type="string[]">
|
||||
<Field path={['events']}>
|
||||
{field => (
|
||||
<div>
|
||||
<p class="text-sm font-bold">{t('webhooks.create.form.events.label')}</p>
|
||||
|
||||
<div class="p-6 pb-8 border rounded-md mt-2">
|
||||
<WebhookEventsPicker events={field.value ?? []} onChange={events => setValue(form, 'events', events)} />
|
||||
<WebhookEventsPicker events={(field.input as WebhookEvent[]) ?? []} onChange={events => setInput(form, { path: ['events'], input: events })} />
|
||||
</div>
|
||||
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
@@ -141,10 +142,12 @@ export const CreateWebhookPage: Component = () => {
|
||||
<Button type="button" variant="secondary" as={A} href={`/organizations/${params.organizationId}/settings/webhooks`}>
|
||||
{t('webhooks.create.back')}
|
||||
</Button>
|
||||
<Button type="submit" class="ml-2" isLoading={form.submitting}>
|
||||
<Button type="submit" class="ml-2" isLoading={form.isSubmitting}>
|
||||
{t('webhooks.create.form.submit')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="text-red-500 text-sm">{form.errors?.[0]}</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import type { Webhook } from '../webhooks.types';
|
||||
import { setValue } from '@modular-forms/solid';
|
||||
import type { Webhook, WebhookEvent } from '../webhooks.types';
|
||||
import { setInput } from '@formisch/solid';
|
||||
import { A, useNavigate, useParams } from '@solidjs/router';
|
||||
import { useQuery } from '@tanstack/solid-query';
|
||||
import { createSignal, Show, Suspense } from 'solid-js';
|
||||
@@ -53,11 +53,11 @@ export const EditWebhookForm: Component<{ webhook: Webhook }> = (props) => {
|
||||
},
|
||||
schema: v.object({
|
||||
name: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('webhooks.create.form.name.required')),
|
||||
v.nonEmpty(t('webhooks.create.form.name.required')),
|
||||
),
|
||||
url: v.pipe(
|
||||
v.string(),
|
||||
v.string(t('webhooks.create.form.url.required')),
|
||||
v.nonEmpty(t('webhooks.create.form.url.required')),
|
||||
v.url(t('webhooks.create.form.url.invalid')),
|
||||
),
|
||||
@@ -79,44 +79,44 @@ export const EditWebhookForm: Component<{ webhook: Webhook }> = (props) => {
|
||||
return (
|
||||
|
||||
<Form>
|
||||
<Field name="name">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['name']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="name">{t('webhooks.create.form.name.label')}</TextFieldLabel>
|
||||
<TextField
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder={t('webhooks.create.form.name.placeholder')}
|
||||
{...inputProps}
|
||||
{...field.props}
|
||||
autoFocus
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Field name="url">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['url']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mb-6">
|
||||
<TextFieldLabel for="url">{t('webhooks.create.form.url.label')}</TextFieldLabel>
|
||||
<TextField
|
||||
type="url"
|
||||
id="url"
|
||||
placeholder={t('webhooks.create.form.url.placeholder')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
/>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="mb-6">
|
||||
<Field name="secret">
|
||||
{(field, inputProps) => (
|
||||
<Field path={['secret']}>
|
||||
{field => (
|
||||
<TextFieldRoot class="flex flex-col mt-4">
|
||||
<TextFieldLabel for="secret">{t('webhooks.create.form.secret.label')}</TextFieldLabel>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -124,9 +124,9 @@ export const EditWebhookForm: Component<{ webhook: Webhook }> = (props) => {
|
||||
type="password"
|
||||
id="secret"
|
||||
placeholder={rotateSecret() ? t('webhooks.update.form.secret.placeholder') : t('webhooks.update.form.secret.placeholder-redacted')}
|
||||
{...inputProps}
|
||||
value={field.value}
|
||||
aria-invalid={Boolean(field.error)}
|
||||
{...field.props}
|
||||
value={field.input}
|
||||
aria-invalid={Boolean(field.errors)}
|
||||
disabled={!rotateSecret()}
|
||||
/>
|
||||
<Show when={!rotateSecret()}>
|
||||
@@ -135,22 +135,22 @@ export const EditWebhookForm: Component<{ webhook: Webhook }> = (props) => {
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</TextFieldRoot>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field name="events" type="string[]">
|
||||
<Field path={['events']}>
|
||||
{field => (
|
||||
<div>
|
||||
<p class="text-sm font-bold">{t('webhooks.create.form.events.label')}</p>
|
||||
|
||||
<div class="p-6 pb-8 border rounded-md mt-2">
|
||||
<WebhookEventsPicker events={field.value ?? []} onChange={events => setValue(form, 'events', events)} />
|
||||
<WebhookEventsPicker events={(field.input as WebhookEvent[]) ?? []} onChange={events => setInput(form, { path: ['events'], input: events })} />
|
||||
</div>
|
||||
|
||||
{field.error && <div class="text-red-500 text-sm">{field.error}</div>}
|
||||
{field.errors && <div class="text-red-500 text-sm">{field.errors[0]}</div>}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
@@ -159,7 +159,7 @@ export const EditWebhookForm: Component<{ webhook: Webhook }> = (props) => {
|
||||
<Button type="button" variant="secondary" as={A} href={`/organizations/${params.organizationId}/settings/webhooks`}>
|
||||
{t('webhooks.update.cancel')}
|
||||
</Button>
|
||||
<Button type="submit" class="ml-2" isLoading={form.submitting}>
|
||||
<Button type="submit" class="ml-2" isLoading={form.isSubmitting}>
|
||||
{t('webhooks.update.submit')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
59
pnpm-lock.yaml
generated
59
pnpm-lock.yaml
generated
@@ -12,9 +12,6 @@ catalogs:
|
||||
'@types/node':
|
||||
specifier: ^22.15.21
|
||||
version: 22.16.0
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.0.2
|
||||
version: 3.2.4
|
||||
better-auth:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
@@ -24,9 +21,6 @@ catalogs:
|
||||
typescript:
|
||||
specifier: ^5.6.2
|
||||
version: 5.8.3
|
||||
unbuild:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
vitest:
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.4
|
||||
@@ -117,15 +111,15 @@ importers:
|
||||
'@corentinth/chisels':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
'@formisch/solid':
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(solid-js@1.9.7)(typescript@5.8.3)(valibot@1.0.0-beta.10(typescript@5.8.3))
|
||||
'@kobalte/core':
|
||||
specifier: ^0.13.10
|
||||
version: 0.13.10(solid-js@1.9.7)
|
||||
'@kobalte/utils':
|
||||
specifier: ^0.9.1
|
||||
version: 0.9.1(solid-js@1.9.7)
|
||||
'@modular-forms/solid':
|
||||
specifier: ^0.25.1
|
||||
version: 0.25.1(solid-js@1.9.7)(typescript@5.8.3)
|
||||
'@pdfslick/solid':
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0(react@18.3.1)(solid-js@1.9.7)(use-sync-external-store@1.2.2(react@18.3.1))
|
||||
@@ -2233,6 +2227,16 @@ packages:
|
||||
'@floating-ui/utils@0.2.9':
|
||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||
|
||||
'@formisch/solid@0.2.0':
|
||||
resolution: {integrity: sha512-9prUUijdbIO69sYA3noyXagSTD0TInF0A3KFbWQLmG9jzOnVD2P8TBghyUE1eapA/bfxMRT/hBJ3wOys+vHcQQ==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.0
|
||||
typescript: '>=5'
|
||||
valibot: ^1.0.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@hexagon/base64@1.1.28':
|
||||
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||
|
||||
@@ -2517,11 +2521,6 @@ packages:
|
||||
'@mdx-js/mdx@3.1.0':
|
||||
resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==}
|
||||
|
||||
'@modular-forms/solid@0.25.1':
|
||||
resolution: {integrity: sha512-issNZ3xl4tj+1K7KT4dNTQaRq5SmVUXgUPeGTMjtrAzCeTnwM/u6vUxSuTY2bcMw4GzTxreFXLx1xMMhrFkt0A==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.3.1
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.68':
|
||||
resolution: {integrity: sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10159,12 +10158,6 @@ snapshots:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.5.1(eslint@9.30.1(jiti@2.4.2))':
|
||||
dependencies:
|
||||
eslint: 9.30.1(jiti@2.4.2)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.7.0(eslint@9.27.0(jiti@2.4.2))':
|
||||
dependencies:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
@@ -10310,6 +10303,13 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.9': {}
|
||||
|
||||
'@formisch/solid@0.2.0(solid-js@1.9.7)(typescript@5.8.3)(valibot@1.0.0-beta.10(typescript@5.8.3))':
|
||||
dependencies:
|
||||
solid-js: 1.9.7
|
||||
valibot: 1.0.0-beta.10(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@hexagon/base64@1.1.28': {}
|
||||
|
||||
'@hono/node-server@1.14.4(hono@4.8.2)':
|
||||
@@ -10638,13 +10638,6 @@ snapshots:
|
||||
- acorn
|
||||
- supports-color
|
||||
|
||||
'@modular-forms/solid@0.25.1(solid-js@1.9.7)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
solid-js: 1.9.7
|
||||
valibot: 1.0.0-beta.10(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.68':
|
||||
optional: true
|
||||
|
||||
@@ -13499,15 +13492,15 @@ snapshots:
|
||||
|
||||
eslint-plugin-astro@1.3.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.5.1(eslint@9.30.1(jiti@2.4.2))
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@typescript-eslint/types': 8.26.1
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
|
||||
'@jridgewell/sourcemap-codec': 1.5.4
|
||||
'@typescript-eslint/types': 8.35.1
|
||||
astro-eslint-parser: 1.1.0(typescript@5.8.3)
|
||||
eslint: 9.30.1(jiti@2.4.2)
|
||||
eslint-compat-utils: 0.6.4(eslint@9.30.1(jiti@2.4.2))
|
||||
eslint-compat-utils: 0.6.5(eslint@9.30.1(jiti@2.4.2))
|
||||
globals: 15.15.0
|
||||
postcss: 8.5.3
|
||||
postcss-selector-parser: 7.0.0
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
Reference in New Issue
Block a user