added support for saving and loading applications (no session/database yet)
This commit is contained in:
24
api/index.js
24
api/index.js
@@ -1,11 +1,35 @@
|
||||
const express = require('express')
|
||||
const cors = require('cors')
|
||||
const app = express()
|
||||
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
const port = 3000
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello World!')
|
||||
})
|
||||
|
||||
var application;
|
||||
|
||||
app.post('/application', (req, res) => {
|
||||
data = req.body
|
||||
application = data;
|
||||
console.log(data);
|
||||
res.send('Application received')
|
||||
})
|
||||
|
||||
app.get('/me/application', (req, res) => {
|
||||
if (application) {
|
||||
res.send(application);
|
||||
}
|
||||
else {
|
||||
res.status(204).send();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening on port ${port}`)
|
||||
})
|
||||
23
api/package-lock.json
generated
23
api/package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0"
|
||||
}
|
||||
},
|
||||
@@ -122,6 +123,19 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
@@ -505,6 +519,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
584
ui/package-lock.json
generated
584
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ import Label from '@/components/ui/label/Label.vue';
|
||||
import Textarea from '@/components/ui/textarea/Textarea.vue';
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { Form } from 'vee-validate';
|
||||
import { onMounted, readonly, ref } from 'vue';
|
||||
import * as z from 'zod';
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
@@ -35,60 +36,115 @@ const formSchema = toTypedSchema(z.object({
|
||||
}),
|
||||
}))
|
||||
|
||||
function onSubmit(val) {
|
||||
console.log(val)
|
||||
}
|
||||
type ApplicationDto = Partial<{
|
||||
age: number | string
|
||||
name: string
|
||||
playtime: number | string
|
||||
hobbies: string
|
||||
military: boolean
|
||||
communities: string
|
||||
joinReason: string
|
||||
milsimAttraction: string
|
||||
referral: string
|
||||
steamProfile: string
|
||||
timezone: string
|
||||
canAttendSaturday: boolean
|
||||
interests: string
|
||||
aknowledgeRules: boolean
|
||||
}>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :validation-schema="formSchema"
|
||||
:initial-values="{
|
||||
const fallbackInitials = {
|
||||
military: false,
|
||||
canAttendSaturday: false,
|
||||
aknowledgeRules: false,
|
||||
}"
|
||||
@submit="onSubmit" class="space-y-6 max-w-3xl mx-auto my-20">
|
||||
}
|
||||
|
||||
const initialValues = ref<Record<string, unknown> | null>(null);
|
||||
const readOnly = ref(false);
|
||||
|
||||
async function onSubmit(val: any) {
|
||||
if (readOnly.value) return // guard (shouldn't be visible anyway)
|
||||
await fetch('http://localhost:3000/application', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(val),
|
||||
})
|
||||
}
|
||||
|
||||
async function loadApplication(): Promise<ApplicationDto | null> {
|
||||
const res = await fetch('http://localhost:3000/me/application')
|
||||
if (res.status === 204) return null
|
||||
if (!res.ok) throw new Error('Failed to load application')
|
||||
const json = await res.json()
|
||||
// Accept either the object at root or under `application`
|
||||
return (json?.application ?? json) as ApplicationDto
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await loadApplication()
|
||||
if (data) {
|
||||
// Optional: coerce/normalize incoming payload to field names/types if needed
|
||||
initialValues.value = {
|
||||
...fallbackInitials, // ensure booleans exist even if API omits them
|
||||
...data,
|
||||
}
|
||||
readOnly.value = true;
|
||||
} else {
|
||||
// 204 or empty → use your preset three fields only
|
||||
initialValues.value = { ...fallbackInitials }
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// On error, also fall back
|
||||
initialValues.value = { ...fallbackInitials }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form v-if="initialValues" :validation-schema="formSchema" :initial-values="initialValues" @submit="onSubmit"
|
||||
class="space-y-6 max-w-3xl mx-auto my-20">
|
||||
<!-- Age -->
|
||||
<FormField name="age" v-slot="{ componentField }">
|
||||
<FormField name="age" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>What is your age?</FormLabel>
|
||||
<FormControl>
|
||||
<Input v-bind="componentField" />
|
||||
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Name -->
|
||||
<FormField name="name" v-slot="{ componentField }">
|
||||
<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 v-bind="componentField" />
|
||||
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Playtime -->
|
||||
<FormField name="playtime" v-slot="{ componentField }">
|
||||
<FormField name="playtime" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>How long have you played Arma 3 for (in hours)?</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" v-bind="componentField" />
|
||||
<Input type="number" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Hobbies -->
|
||||
<FormField name="hobbies" v-slot="{ componentField }">
|
||||
<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" v-bind="componentField" />
|
||||
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -100,7 +156,7 @@ function onSubmit(val) {
|
||||
<FormLabel>Have you ever served in the military?</FormLabel>
|
||||
<FormControl>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox :checked="value ?? false" @update:checked="handleChange" />
|
||||
<Checkbox :checked="value ?? false" @update:checked="handleChange" :disabled="readOnly" />
|
||||
<span>Yes (checked) / No (unchecked)</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
@@ -109,51 +165,51 @@ function onSubmit(val) {
|
||||
</FormField>
|
||||
|
||||
<!-- Other communities (freeform) -->
|
||||
<FormField name="communities" v-slot="{ componentField }">
|
||||
<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 v-bind="componentField" />
|
||||
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Why join -->
|
||||
<FormField name="joinReason" v-slot="{ componentField }">
|
||||
<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" v-bind="componentField" />
|
||||
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Attraction to milsim -->
|
||||
<FormField name="milsimAttraction" v-slot="{ componentField }">
|
||||
<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" v-bind="componentField" />
|
||||
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Referral (freeform) -->
|
||||
<FormField name="referral" v-slot="{ componentField }">
|
||||
<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" v-bind="componentField" />
|
||||
<Input placeholder="e.g., Reddit / Member: Alice" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Steam profile -->
|
||||
<FormField name="steamProfile" v-slot="{ componentField }">
|
||||
<FormField name="steamProfile" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>Steam profile link</FormLabel>
|
||||
<FormDescription>
|
||||
@@ -161,18 +217,19 @@ function onSubmit(val) {
|
||||
<code>https://steamcommunity.com/profiles/STEAMID64/</code>
|
||||
</FormDescription>
|
||||
<FormControl>
|
||||
<Input type="url" placeholder="https://steamcommunity.com/profiles/7656119..." v-bind="componentField" />
|
||||
<Input type="url" placeholder="https://steamcommunity.com/profiles/7656119..." :model-value="value"
|
||||
@update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Timezone -->
|
||||
<FormField name="timezone" v-slot="{ componentField }">
|
||||
<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" v-bind="componentField" />
|
||||
<Input placeholder="e.g., AEST, EST, UTC+10" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -184,7 +241,7 @@ function onSubmit(val) {
|
||||
<FormLabel>Are you able to attend weekly operations Saturdays @ 7pm CST?</FormLabel>
|
||||
<FormControl>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox :checked="value ?? false" @update:checked="handleChange" />
|
||||
<Checkbox :model-value="value ?? false" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
<span>Yes (checked) / No (unchecked)</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
@@ -193,11 +250,11 @@ function onSubmit(val) {
|
||||
</FormField>
|
||||
|
||||
<!-- Interests / Playstyle (freeform) -->
|
||||
<FormField name="interests" v-slot="{ componentField }">
|
||||
<FormField name="interests" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>Which playstyles interest you?</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g., Rifleman; Medic; Pilot" v-bind="componentField" />
|
||||
<Input placeholder="e.g., Rifleman; Medic; Pilot" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -209,8 +266,9 @@ function onSubmit(val) {
|
||||
<FormLabel>Community Code of Conduct</FormLabel>
|
||||
<FormControl>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox :checked="value" @update:model-value="handleChange" />
|
||||
<span>By checking this box, you accept the <Button variant="link" class="p-0">Code of Conduct</Button>.</span>
|
||||
<Checkbox :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||
<span>By checking this box, you accept the <Button variant="link" class="p-0">Code of
|
||||
Conduct</Button>.</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -218,7 +276,7 @@ function onSubmit(val) {
|
||||
</FormField>
|
||||
|
||||
<div class="pt-2">
|
||||
<Button type="submit" :onClick="() => console.log('hi')">Submit Application</Button>
|
||||
<Button type="submit" :onClick="() => console.log('hi')" :disabled="readOnly">Submit Application</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
Reference in New Issue
Block a user