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 express = require('express')
|
||||||
|
const cors = require('cors')
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
|
app.use(cors())
|
||||||
|
app.use(express.json())
|
||||||
|
|
||||||
const port = 3000
|
const port = 3000
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.send('Hello World!')
|
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, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening on port ${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",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -122,6 +123,19 @@
|
|||||||
"node": ">=6.6.0"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
@@ -505,6 +519,15 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
"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"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
"express": "^5.1.0"
|
"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 Textarea from '@/components/ui/textarea/Textarea.vue';
|
||||||
import { toTypedSchema } from '@vee-validate/zod';
|
import { toTypedSchema } from '@vee-validate/zod';
|
||||||
import { Form } from 'vee-validate';
|
import { Form } from 'vee-validate';
|
||||||
|
import { onMounted, readonly, ref } from 'vue';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
@@ -35,60 +36,115 @@ const formSchema = toTypedSchema(z.object({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function onSubmit(val) {
|
type ApplicationDto = Partial<{
|
||||||
console.log(val)
|
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>
|
const fallbackInitials = {
|
||||||
|
|
||||||
<template>
|
|
||||||
<Form :validation-schema="formSchema"
|
|
||||||
:initial-values="{
|
|
||||||
military: false,
|
military: false,
|
||||||
canAttendSaturday: false,
|
canAttendSaturday: false,
|
||||||
aknowledgeRules: 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 -->
|
<!-- Age -->
|
||||||
<FormField name="age" v-slot="{ componentField }">
|
<FormField name="age" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>What is your age?</FormLabel>
|
<FormLabel>What is your age?</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input v-bind="componentField" />
|
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
<FormField name="name" v-slot="{ componentField }">
|
<FormField name="name" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>What name will you be going by within the community?</FormLabel>
|
<FormLabel>What name will you be going by within the community?</FormLabel>
|
||||||
<FormDescription>This name must be consistent across platforms.</FormDescription>
|
<FormDescription>This name must be consistent across platforms.</FormDescription>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input v-bind="componentField" />
|
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Playtime -->
|
<!-- Playtime -->
|
||||||
<FormField name="playtime" v-slot="{ componentField }">
|
<FormField name="playtime" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>How long have you played Arma 3 for (in hours)?</FormLabel>
|
<FormLabel>How long have you played Arma 3 for (in hours)?</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="number" v-bind="componentField" />
|
<Input type="number" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Hobbies -->
|
<!-- Hobbies -->
|
||||||
<FormField name="hobbies" v-slot="{ componentField }">
|
<FormField name="hobbies" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>What hobbies do you like to participate in outside of gaming?</FormLabel>
|
<FormLabel>What hobbies do you like to participate in outside of gaming?</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -100,7 +156,7 @@ function onSubmit(val) {
|
|||||||
<FormLabel>Have you ever served in the military?</FormLabel>
|
<FormLabel>Have you ever served in the military?</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div class="flex items-center gap-2">
|
<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>
|
<span>Yes (checked) / No (unchecked)</span>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -109,51 +165,51 @@ function onSubmit(val) {
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Other communities (freeform) -->
|
<!-- Other communities (freeform) -->
|
||||||
<FormField name="communities" v-slot="{ componentField }">
|
<FormField name="communities" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Are you a part of any other communities? If so, which ones? If none, type "No"</FormLabel>
|
<FormLabel>Are you a part of any other communities? If so, which ones? If none, type "No"</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input v-bind="componentField" />
|
<Input :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Why join -->
|
<!-- Why join -->
|
||||||
<FormField name="joinReason" v-slot="{ componentField }">
|
<FormField name="joinReason" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Why do you want to join our community?</FormLabel>
|
<FormLabel>Why do you want to join our community?</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Attraction to milsim -->
|
<!-- Attraction to milsim -->
|
||||||
<FormField name="milsimAttraction" v-slot="{ componentField }">
|
<FormField name="milsimAttraction" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>What attracts you to the Arma 3 milsim playstyle?</FormLabel>
|
<FormLabel>What attracts you to the Arma 3 milsim playstyle?</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Referral (freeform) -->
|
<!-- Referral (freeform) -->
|
||||||
<FormField name="referral" v-slot="{ componentField }">
|
<FormField name="referral" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Where did you hear about us? (If another member, who?)</FormLabel>
|
<FormLabel>Where did you hear about us? (If another member, who?)</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Steam profile -->
|
<!-- Steam profile -->
|
||||||
<FormField name="steamProfile" v-slot="{ componentField }">
|
<FormField name="steamProfile" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Steam profile link</FormLabel>
|
<FormLabel>Steam profile link</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
@@ -161,18 +217,19 @@ function onSubmit(val) {
|
|||||||
<code>https://steamcommunity.com/profiles/STEAMID64/</code>
|
<code>https://steamcommunity.com/profiles/STEAMID64/</code>
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Timezone -->
|
<!-- Timezone -->
|
||||||
<FormField name="timezone" v-slot="{ componentField }">
|
<FormField name="timezone" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>What time zone are you in?</FormLabel>
|
<FormLabel>What time zone are you in?</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -184,7 +241,7 @@ function onSubmit(val) {
|
|||||||
<FormLabel>Are you able to attend weekly operations Saturdays @ 7pm CST?</FormLabel>
|
<FormLabel>Are you able to attend weekly operations Saturdays @ 7pm CST?</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div class="flex items-center gap-2">
|
<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>
|
<span>Yes (checked) / No (unchecked)</span>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -193,11 +250,11 @@ function onSubmit(val) {
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- Interests / Playstyle (freeform) -->
|
<!-- Interests / Playstyle (freeform) -->
|
||||||
<FormField name="interests" v-slot="{ componentField }">
|
<FormField name="interests" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Which playstyles interest you?</FormLabel>
|
<FormLabel>Which playstyles interest you?</FormLabel>
|
||||||
<FormControl>
|
<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>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -209,8 +266,9 @@ function onSubmit(val) {
|
|||||||
<FormLabel>Community Code of Conduct</FormLabel>
|
<FormLabel>Community Code of Conduct</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Checkbox :checked="value" @update:model-value="handleChange" />
|
<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>
|
<span>By checking this box, you accept the <Button variant="link" class="p-0">Code of
|
||||||
|
Conduct</Button>.</span>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -218,7 +276,7 @@ function onSubmit(val) {
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<div class="pt-2">
|
<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>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
Reference in New Issue
Block a user