Browse Source

handle form submit using js

avoid weird behavior with FormData
pull/1572/head
Bernd Storath 3 months ago
parent
commit
c1c6088c5d
  1. 4
      src/app/components/form/ActionField.vue
  2. 6
      src/app/components/form/Element.vue
  3. 8
      src/app/pages/clients/[id].vue
  4. 2
      src/i18n/locales/en.json
  5. 3
      src/package.json
  6. 12
      src/pnpm-lock.yaml
  7. 2
      src/server/api/client/[clientId]/index.post.ts
  8. 31
      src/server/utils/apiHelper.ts
  9. 29
      src/server/utils/types.ts

4
src/app/components/form/ActionField.vue

@ -2,8 +2,6 @@
<input
:value="label"
:type="type ?? 'button'"
:formmethod="formmethod"
:formaction="formaction"
class="col-span-2 rounded-lg border-2 border-gray-100 py-2 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
</template>
@ -12,7 +10,5 @@
defineProps<{
label: string;
type?: string;
formaction?: string;
formmethod?: string;
}>();
</script>

6
src/app/components/form/Element.vue

@ -1,9 +1,5 @@
<template>
<form :action="action" :method="method">
<form>
<slot />
</form>
</template>
<script lang="ts" setup>
defineProps<{ method: string; action: string }>();
</script>

8
src/app/pages/clients/[id].vue

@ -5,7 +5,7 @@
<PanelHeadTitle :text="data.name" />
</PanelHead>
<PanelBody>
<FormElement :action="submitAction" method="post">
<FormElement @submit.prevent="submit">
<FormGroup>
<FormHeading>
{{ $t('me.sectionGeneral') }}
@ -74,13 +74,15 @@ authStore.update();
const route = useRoute();
const id = route.params.id as string;
const submitAction = computed(() => `/api/client/${id}`);
const { data: _data, refresh } = await useFetch(`/api/client/${id}`, {
method: 'get',
});
const data = toRef(_data.value);
function submit() {
console.log(data.value);
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;

2
src/i18n/locales/en.json

@ -52,6 +52,8 @@
"serverAllowedIPs": "Allowed IPs must be a valid array of strings",
"name": "Name must be a valid string",
"nameMin": "Name must be at least 1 Character",
"mtu": "MTU must be a valid number",
"persistentKeepalive": "Persistent Keepalive must be a valid number",
"file": "File must be a valid string",
"username": "Username must be a valid string",
"usernameMin": "Username must be at least 8 Characters",

3
src/package.json

@ -41,8 +41,7 @@
"timeago.js": "^4.0.2",
"vue": "latest",
"vue3-apexcharts": "^1.8.0",
"zod": "^3.24.1",
"zod-form-data": "^2.0.5"
"zod": "^3.24.1"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.7.3",

12
src/pnpm-lock.yaml

@ -83,9 +83,6 @@ importers:
zod:
specifier: ^3.24.1
version: 3.24.1
zod-form-data:
specifier: ^2.0.5
version: 2.0.5([email protected])
devDependencies:
'@nuxt/eslint-config':
specifier: ^0.7.3
@ -4541,11 +4538,6 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
[email protected]:
resolution: {integrity: sha512-T7dV6lTBCwkd8PyvJVCnjXKpgXomU8gEm/TcvEZY7qNdRhIo9T17HrdlHIK68PzTAYaV2HxR9rgwpTSWv0L+QQ==}
peerDependencies:
zod: '>= 3.11.0'
[email protected]:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
@ -9426,8 +9418,4 @@ snapshots:
compress-commons: 6.0.2
readable-stream: 4.5.2
[email protected]([email protected]):
dependencies:
zod: 3.24.1
[email protected]: {}

2
src/server/api/client/[clientId]/index.post.ts

@ -3,7 +3,7 @@ export default defineEventHandler(async (event) => {
event,
validateZod(clientIdType)
);
const data = await readValidatedFormData(
const data = await readValidatedBody(
event,
validateZod(clientUpdateType, event)
);

31
src/server/utils/apiHelper.ts

@ -1,31 +0,0 @@
import type { H3Event, InferEventInput } from 'h3';
export async function readValidatedFormData<
Event extends H3Event = H3Event,
T = InferEventInput<'body', Event, null>,
>(event: Event, validate: (data: FormData) => T) {
const _form = await readFormData(event);
return validateData(_form, validate);
}
async function validateData<T, K>(data: T, fn: (data: T) => K) {
try {
const res = await fn(data);
if (res === false) {
throw createValidationError();
}
return res;
} catch (error) {
throw createValidationError(error);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createValidationError(validateError?: any) {
throw createError({
status: 400,
statusMessage: 'Validation Error',
message: validateError?.message || 'Validation Error',
data: validateError,
});
}

29
src/server/utils/types.ts

@ -2,7 +2,6 @@ import type { ZodSchema, ZodTypeDef } from 'zod';
import { z, ZodError } from 'zod';
import type { H3Event, EventHandlerRequest } from 'h3';
import { LOCALES } from '#shared/locales';
import { zfd } from 'zod-form-data';
// TODO: make objects strict
@ -155,22 +154,18 @@ const address6 = z
.pipe(safeStringRefine);
/** expects formdata, strict */
export const clientUpdateType = zfd.formData({
name: zfd.text(name),
enabled: zfd.checkbox(),
expiresAt: zfd.text(expireDate.optional()),
address4: zfd.text(address4),
address6: zfd.text(address6),
allowedIPs: zfd.repeatable(
z
.array(zfd.text(address), { message: 'zod.allowedIPs' })
.min(1, { message: 'zod.allowedIPsMin' })
),
serverAllowedIPs: zfd.repeatable(
z.array(zfd.text(address), { message: 'zod.serverAllowedIPs' })
),
mtu: zfd.numeric(),
persistentKeepalive: zfd.numeric(),
export const clientUpdateType = z.strictObject({
name: name,
enabled: z.boolean(),
expiresAt: expireDate,
address4: address4,
address6: address6,
allowedIPs: z
.array(address, { message: 'zod.allowedIPs' })
.min(1, { message: 'zod.allowedIPsMin' }),
serverAllowedIPs: z.array(address, { message: 'zod.serverAllowedIPs' }),
mtu: z.number({ message: 'zod.mtu' }),
persistentKeepalive: z.number({ message: 'zod.persistentKeepalive' }),
});
// from https://github.com/airjp73/rvf/blob/7e7c35d98015ea5ecff5affaf89f78296e84e8b9/packages/zod-form-data/src/helpers.ts#L117

Loading…
Cancel
Save