Files
milsim-site-v4/ui/src/components/application/ApplicationForm.vue

338 lines
12 KiB
Vue

<script setup lang="ts">
import Button from '@/components/ui/button/Button.vue';
import Checkbox from '@/components/ui/checkbox/Checkbox.vue';
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import Input from '@/components/ui/input/Input.vue';
import Textarea from '@/components/ui/textarea/Textarea.vue';
import { toTypedSchema } from '@vee-validate/zod';
import { Form } from 'vee-validate';
import { nextTick, onMounted, ref, watch } from 'vue';
import * as z from 'zod';
import DateInput from '../form/DateInput.vue';
import { ApplicationData } from '@shared/types/application';
import Dialog from '../ui/dialog/Dialog.vue';
import DialogTrigger from '../ui/dialog/DialogTrigger.vue';
import DialogContent from '../ui/dialog/DialogContent.vue';
import DialogHeader from '../ui/dialog/DialogHeader.vue';
import DialogTitle from '../ui/dialog/DialogTitle.vue';
import DialogDescription from '../ui/dialog/DialogDescription.vue';
import { getCoC } from '@/api/application';
import { startBrowserTracingPageLoadSpan } from '@sentry/vue';
const regexA = /^https?:\/\/steamcommunity\.com\/id\/[A-Za-z0-9_]+\/?$/;
const regexB = /^https?:\/\/steamcommunity\.com\/profiles\/\d+\/?$/;
const formSchema = toTypedSchema(z.object({
dob: z.string().refine(v => v, { message: "A date of birth is required." }),
name: z.string().nonempty(),
playtime: z.coerce.number({ invalid_type_error: "Must be a number", }).min(0, "Cannot be less than 0"),
hobbies: z.string().nonempty(),
military: z.boolean(),
communities: z.string().nonempty(),
joinReason: z.string().nonempty(),
milsimAttraction: z.string().nonempty(),
referral: z.string().nonempty(),
steamProfile: z.string().nonempty().refine((val) => regexA.test(val) || regexB.test(val), { message: "Invalid Steam profile URL." }),
timezone: z.string().nonempty(),
canAttendSaturday: z.boolean(),
interests: z.string().nonempty(),
acknowledgeRules: z.literal(true, {
errorMap: () => ({ message: "Required" })
}),
}))
const fallbackInitials = {
military: false,
canAttendSaturday: false,
acknowledgeRules: false,
}
const props = defineProps<{
readOnly: boolean,
data: ApplicationData,
}>()
const emit = defineEmits(['submit']);
const initialValues = ref<Record<string, unknown> | null>(null);
async function onSubmit(val: any) {
emit('submit', val);
}
onMounted(async () => {
if (props.data !== null) {
const parsed = typeof props.data === "string"
? JSON.parse(props.data)
: props.data;
initialValues.value = { ...parsed };
} else {
initialValues.value = { ...fallbackInitials };
}
// CoCbox.value.innerHTML = await getCoC()
CoCString.value = await getCoC();
})
const showCoC = ref(false);
const CoCbox = ref<HTMLElement>();
const CoCString = ref<string>();
async function onDialogToggle(state: boolean) {
showCoC.value = state;
}
function enforceExternalLinks() {
if (!CoCbox.value) return;
const links = CoCbox.value.querySelectorAll("a");
links.forEach(a => {
a.setAttribute("target", "_blank");
a.setAttribute("rel", "noopener noreferrer");
});
}
watch(() => showCoC.value, async () => {
if (showCoC) {
await nextTick(); // wait for v-html to update
enforceExternalLinks();
}
});
</script>
<template>
<Form v-if="initialValues" :validation-schema="formSchema" :initial-values="initialValues" @submit="onSubmit"
class="space-y-6">
<!-- Age -->
<FormField name="dob" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>What is your date of birth?</FormLabel>
<FormControl>
<DateInput :model-value="(value as string) ?? ''" :disabled="readOnly" @update:model-value="handleChange" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Name -->
<FormField name="name" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>What name will you be going by within the community?</FormLabel>
<FormDescription>This name must be consistent across platforms.</FormDescription>
<FormControl>
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Playtime -->
<FormField name="playtime" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>How long have you played Arma 3 for (in hours)?</FormLabel>
<FormControl>
<Input type="number" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Hobbies -->
<FormField name="hobbies" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>What hobbies do you like to participate in outside of gaming?</FormLabel>
<FormControl>
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange"
:disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Military (boolean) -->
<FormField name="military" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Have you ever served in the military?</FormLabel>
<FormControl>
<div class="flex items-center gap-2">
<Checkbox :checked="value ?? false" @update:checked="handleChange" :disabled="readOnly" />
<span>Yes (checked) / No (unchecked)</span>
</div>
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Other communities (freeform) -->
<FormField name="communities" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Are you a part of any other communities? If so, which ones? If none, type "No"</FormLabel>
<FormControl>
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Why join -->
<FormField name="joinReason" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Why do you want to join our community?</FormLabel>
<FormControl>
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange"
:disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Attraction to milsim -->
<FormField name="milsimAttraction" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>What attracts you to the Arma 3 milsim playstyle?</FormLabel>
<FormControl>
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange"
:disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Referral (freeform) -->
<FormField name="referral" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Where did you hear about us? (If another member, who?)</FormLabel>
<FormControl>
<Input placeholder="e.g., Reddit / Member: Alice" :model-value="value" @update:model-value="handleChange"
:disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Steam profile -->
<FormField name="steamProfile" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Steam profile link</FormLabel>
<FormDescription>
Format: <code>https://steamcommunity.com/id/USER/</code> or
<code>https://steamcommunity.com/profiles/STEAMID64/</code>
</FormDescription>
<FormControl>
<Input type="url" placeholder="https://steamcommunity.com/profiles/7656119..." :model-value="value"
@update:model-value="handleChange" :disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Timezone -->
<FormField name="timezone" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>What time zone are you in?</FormLabel>
<FormControl>
<Input placeholder="e.g., AEST, EST, UTC+10" :model-value="value" @update:model-value="handleChange"
:disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Attendance (boolean) -->
<FormField name="canAttendSaturday" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Are you able to attend weekly operations Saturdays @ 7pm CST?</FormLabel>
<FormControl>
<div class="flex items-center gap-2">
<Checkbox :model-value="value ?? false" @update:model-value="handleChange" :disabled="readOnly" />
<span>Yes (checked) / No (unchecked)</span>
</div>
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Interests / Playstyle (freeform) -->
<FormField name="interests" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Which playstyles interest you?</FormLabel>
<FormControl>
<Input placeholder="e.g., Rifleman; Medic; Pilot" :model-value="value" @update:model-value="handleChange"
:disabled="readOnly" />
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<!-- Code of Conduct (boolean, field name kept as-is) -->
<FormField name="acknowledgeRules" v-slot="{ value, handleChange }">
<FormItem>
<FormLabel>Community Code of Conduct</FormLabel>
<FormControl>
<div class="flex items-center gap-2">
<Checkbox :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
<span>By checking this box, you accept the <Button variant="link" class="p-0 h-min"
@click.prevent.stop="showCoC = true">Code of
Conduct</Button>.</span>
</div>
</FormControl>
<div class="h-4">
<FormMessage class="text-destructive" />
</div>
</FormItem>
</FormField>
<div class="pt-2" v-if="!readOnly">
<Button type="submit" :disabled="readOnly">Submit Application</Button>
</div>
<Dialog :open="showCoC" @update:open="onDialogToggle">
<DialogContent class="sm:max-w-fit">
<DialogHeader>
<DialogTitle>Community Code of Conduct</DialogTitle>
<DialogDescription class="w-full max-h-[75vh] overflow-y-auto scrollbar-themed">
<div v-html="CoCString" ref="CoCbox" class="bookstack-container w-full"></div>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
</Form>
</template>