feat(pages): add contact page
This commit is contained in:
@@ -14,7 +14,17 @@ vi.mock('#app', () => ({
|
||||
}));
|
||||
|
||||
// Mock $fetch globally
|
||||
global.$fetch = vi.fn();
|
||||
declare global {
|
||||
var $fetch: ReturnType<typeof vi.fn>;
|
||||
}
|
||||
// global.$fetch = vi.fn();
|
||||
vi.mock('#app', () => ({
|
||||
useRuntimeConfig: vi.fn(() => ({
|
||||
public: {
|
||||
apiBase: 'http://localhost:3100/api'
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('useApi', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { ApiError } from '~/types/api/error';
|
||||
import type { HttpMethod } from '~/types/http-method';
|
||||
import { QueryResult } from '~/types/query-result';
|
||||
|
||||
export type UseApiResponse<T, B = unknown> = QueryResult<T, B>;
|
||||
|
||||
export interface UseApi {
|
||||
get: <T>(path: string, opts?: FetchOptions, immediate?: boolean) => UseApiResponse<T>;
|
||||
del: <T>(path: string, opts?: FetchOptions, immediate?: boolean) => UseApiResponse<T>;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { ContactRequest, ContactResponse } from '~/types/api/contact';
|
||||
import type { MetaResponse } from '~/types/api/meta';
|
||||
import type { UseApiResponse } from './useApi';
|
||||
|
||||
export const useBackend = () => {
|
||||
const api = useApi();
|
||||
|
||||
const getMeta = (): UseApiResponse<MetaResponse> => api.get<MetaResponse>('/meta');
|
||||
const postContact = (): UseApiResponse<ContactResponse, ContactRequest> =>
|
||||
api.post<ContactResponse, ContactRequest>('/contact', false);
|
||||
api.post<ContactResponse, ContactRequest>('/contact', undefined, true);
|
||||
|
||||
return { getMeta, postContact };
|
||||
};
|
||||
|
||||
123
app/pages/contact.vue
Normal file
123
app/pages/contact.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<NuxtLayout name="default">
|
||||
<UPage>
|
||||
<h1 class="text-4xl text-highlighted font-bold mb-8">
|
||||
{{ $t('pages.contact.name') }}
|
||||
</h1>
|
||||
<UPageCard class="bg-background-100">
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="submitContactForm">
|
||||
<div class="flex flex-row w-full gap-5">
|
||||
<UFormField
|
||||
:label="$t('pages.contact.form.labels.name')"
|
||||
name="name"
|
||||
class="w-full"
|
||||
:ui="{ label: 'text-text text-lg text-bold' }"
|
||||
>
|
||||
<UInput
|
||||
v-model="state.name"
|
||||
autofocus
|
||||
:ui="{
|
||||
root: 'relative inline-flex items-center w-full',
|
||||
base: 'placeholder:text-300',
|
||||
}"
|
||||
required
|
||||
:placeholder="$t('pages.contact.form.placeholders.name')"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
:label="$t('pages.contact.form.labels.email')"
|
||||
name="email"
|
||||
class="w-full"
|
||||
:ui="{ label: 'text-text text-lg text-bold' }"
|
||||
>
|
||||
<UInput
|
||||
v-model="state.email"
|
||||
type="email"
|
||||
:ui="{
|
||||
root: 'relative inline-flex items-center w-full',
|
||||
base: 'placeholder:text-300',
|
||||
}"
|
||||
required
|
||||
:placeholder="$t('pages.contact.form.placeholders.email')"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
<UFormField
|
||||
class="w-full"
|
||||
name="website"
|
||||
:label="$t('pages.contact.form.honeypot')"
|
||||
:ui="{ label: 'text-text text-lg text-bold' }"
|
||||
>
|
||||
<UInput
|
||||
:ui="{ root: 'relative inline-flex items-center w-full', base: 'placeholder:text-300' }"
|
||||
:placeholder="$t('pages.contact.form.placeholders.honeypot')"
|
||||
/>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
v-model="state.website"
|
||||
:label="$t('pages.contact.form.labels.message')"
|
||||
name="message"
|
||||
:ui="{ label: 'text-text text-lg text-bold' }"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="state.message"
|
||||
:ui="{
|
||||
root: 'relative inline-flex items-center w-full',
|
||||
base: 'placeholder:text-300',
|
||||
}"
|
||||
:placeholder="$t('pages.contact.form.placeholders.message')"
|
||||
/>
|
||||
</UFormField>
|
||||
<UButton
|
||||
icon="mdi:send-outline"
|
||||
color="primary"
|
||||
type="submit"
|
||||
class="w-full text-center text-lg justify-center-safe"
|
||||
size="lg"
|
||||
>
|
||||
{{ $t('pages.contact.form.sendButton') }}
|
||||
</UButton>
|
||||
</UForm>
|
||||
</UPageCard>
|
||||
</UPage>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormSubmitEvent } from '@nuxt/ui';
|
||||
import { z } from 'zod';
|
||||
|
||||
const { postContact } = useBackend();
|
||||
|
||||
const schema = z.object({
|
||||
email: z.email($t('pages.contact.form.validation.invalidEmail')),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, $t('pages.contact.form.validation.shortName'))
|
||||
.max(100, $t('pages.contact.form.validation.longName')),
|
||||
message: z
|
||||
.string()
|
||||
.min(10, $t('pages.contact.form.validation.shortMessage'))
|
||||
.max(5000, $t('pages.contact.form.validation.longMessage')),
|
||||
website: z.string().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.output<typeof schema>;
|
||||
|
||||
const state = reactive<Partial<Schema>>({
|
||||
name: undefined,
|
||||
email: undefined,
|
||||
message: undefined,
|
||||
website: undefined,
|
||||
});
|
||||
|
||||
const { data: contactResponse, error: contactError, loading, run: sendRequest } = postContact();
|
||||
|
||||
const submitContactForm = async (event: FormSubmitEvent<Schema>) => await sendRequest!(event.data);
|
||||
|
||||
watchEffect(() => {
|
||||
if (loading.value) console.log('loading');
|
||||
if (contactResponse.value) console.log('Response:', contactResponse.value.message);
|
||||
if (contactError.value) console.error('Error', contactError.value);
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user