wrapped up most of the application stuff for now (pending database integration)
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink, RouterView } from 'vue-router';
|
import { RouterLink, RouterView } from 'vue-router';
|
||||||
import Separator from './components/ui/separator/Separator.vue';
|
import Separator from './components/ui/separator/Separator.vue';
|
||||||
import Application from './components/application/ApplicationForm.vue';
|
|
||||||
import Button from './components/ui/button/Button.vue';
|
import Button from './components/ui/button/Button.vue';
|
||||||
|
import Application from './pages/Application.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
55
ui/src/api/application.ts
Normal file
55
ui/src/api/application.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
export 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
|
||||||
|
}>
|
||||||
|
|
||||||
|
type ApplicationFull = Partial<{
|
||||||
|
app: ApplicationDto,
|
||||||
|
messages: object[]
|
||||||
|
}>
|
||||||
|
|
||||||
|
const addr = "localhost:3000"
|
||||||
|
|
||||||
|
export async function loadApplication(): Promise<ApplicationFull | null> {
|
||||||
|
const res = await fetch(`http://${addr}/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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postApplication(val: any) {
|
||||||
|
await fetch(`http://${addr}/application`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(val),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postChatMessage(val: any) {
|
||||||
|
|
||||||
|
let output = {
|
||||||
|
message: val,
|
||||||
|
sender: 1,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch(`http://${addr}/application/message`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(output),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -10,11 +10,10 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form'
|
} from '@/components/ui/form'
|
||||||
import Input from '@/components/ui/input/Input.vue';
|
import Input from '@/components/ui/input/Input.vue';
|
||||||
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 { onMounted, ref } from 'vue';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
@@ -36,22 +35,6 @@ const formSchema = toTypedSchema(z.object({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
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
|
|
||||||
}>
|
|
||||||
|
|
||||||
const fallbackInitials = {
|
const fallbackInitials = {
|
||||||
military: false,
|
military: false,
|
||||||
@@ -59,44 +42,23 @@ const fallbackInitials = {
|
|||||||
aknowledgeRules: false,
|
aknowledgeRules: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
readOnly: boolean,
|
||||||
|
data: object,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['submit']);
|
||||||
|
|
||||||
const initialValues = ref<Record<string, unknown> | null>(null);
|
const initialValues = ref<Record<string, unknown> | null>(null);
|
||||||
const readOnly = ref(false);
|
|
||||||
|
|
||||||
async function onSubmit(val: any) {
|
async function onSubmit(val: any) {
|
||||||
if (readOnly.value) return // guard (shouldn't be visible anyway)
|
emit('submit', val);
|
||||||
await fetch('http://localhost:3000/application', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(val),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadApplication(): Promise<ApplicationDto | null> {
|
onMounted(() => {
|
||||||
const res = await fetch('http://localhost:3000/me/application')
|
if (props.data) {
|
||||||
if (res.status === 204) return null
|
initialValues.value = { ...props.data, ...fallbackInitials }
|
||||||
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 {
|
} 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 }
|
initialValues.value = { ...fallbackInitials }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -104,7 +66,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form v-if="initialValues" :validation-schema="formSchema" :initial-values="initialValues" @submit="onSubmit"
|
<Form v-if="initialValues" :validation-schema="formSchema" :initial-values="initialValues" @submit="onSubmit"
|
||||||
class="space-y-6 max-w-3xl mx-auto my-20">
|
class="space-y-6">
|
||||||
<!-- Age -->
|
<!-- Age -->
|
||||||
<FormField name="age" v-slot="{ value, handleChange }">
|
<FormField name="age" v-slot="{ value, handleChange }">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
@@ -144,7 +106,8 @@ onMounted(async () => {
|
|||||||
<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" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange"
|
||||||
|
:disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -180,7 +143,8 @@ onMounted(async () => {
|
|||||||
<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" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange"
|
||||||
|
:disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -191,7 +155,8 @@ onMounted(async () => {
|
|||||||
<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" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
<Textarea rows="4" class="resize-none" :model-value="value" @update:model-value="handleChange"
|
||||||
|
:disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -202,7 +167,8 @@ onMounted(async () => {
|
|||||||
<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" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
<Input placeholder="e.g., Reddit / Member: Alice" :model-value="value" @update:model-value="handleChange"
|
||||||
|
:disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -229,7 +195,8 @@ onMounted(async () => {
|
|||||||
<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" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
<Input placeholder="e.g., AEST, EST, UTC+10" :model-value="value" @update:model-value="handleChange"
|
||||||
|
:disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -254,7 +221,8 @@ onMounted(async () => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Which playstyles interest you?</FormLabel>
|
<FormLabel>Which playstyles interest you?</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="e.g., Rifleman; Medic; Pilot" :model-value="value" @update:model-value="handleChange" :disabled="readOnly" />
|
<Input placeholder="e.g., Rifleman; Medic; Pilot" :model-value="value" @update:model-value="handleChange"
|
||||||
|
:disabled="readOnly" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -275,7 +243,7 @@ onMounted(async () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<div class="pt-2">
|
<div class="pt-2" v-if="!readOnly">
|
||||||
<Button type="submit" :onClick="() => console.log('hi')" :disabled="readOnly">Submit Application</Button>
|
<Button type="submit" :onClick="() => console.log('hi')" :disabled="readOnly">Submit Application</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
39
ui/src/pages/Application.vue
Normal file
39
ui/src/pages/Application.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ApplicationChat from '@/components/application/ApplicationChat.vue';
|
||||||
|
import ApplicationForm from '@/components/application/ApplicationForm.vue';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { loadApplication, postApplication, postChatMessage } from '@/api/application';
|
||||||
|
|
||||||
|
const appData = ref<Record<string, unknown> | null>(null);
|
||||||
|
const chatData = ref<object[]>([])
|
||||||
|
const readOnly = ref<boolean>(false);
|
||||||
|
const newApp = ref<boolean>(true);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const data = await loadApplication()
|
||||||
|
if (data) {
|
||||||
|
appData.value = data.app;
|
||||||
|
chatData.value = data.messages;
|
||||||
|
readOnly.value = true;
|
||||||
|
} else {
|
||||||
|
appData.value = {}
|
||||||
|
newApp.value = false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="max-w-3xl mx-auto my-20">
|
||||||
|
<ApplicationForm v-if="appData" :read-only="readOnly" :data="appData" @submit="(e) => { postApplication(e) }"
|
||||||
|
class="mb-7"></ApplicationForm>
|
||||||
|
<div v-if="newApp">
|
||||||
|
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight mb-4">Discussion</h3>
|
||||||
|
<ApplicationChat :messages="chatData" @post="postChatMessage"></ApplicationChat>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user