Merge remote-tracking branch 'Origin/main' into promotions

This commit is contained in:
2025-12-17 23:45:58 -05:00
34 changed files with 385 additions and 128 deletions

View File

@@ -49,15 +49,8 @@ const dialogRef = ref<any>(null)
// NEW: handle day/time slot clicks to start creating an event
function onDateClick(arg: { dateStr: string }) {
if (!userStore.isLoggedIn) return;
if (userStore.state !== 'member') return;
dialogRef.value?.openDialog(arg.dateStr);
// For now, just open the panel with a draft payload.
// activeEvent.value = {
// id: '__draft__',
// title: 'New event',
// start: arg.dateStr,
// extendedProps: { draft: true }
// }
// panelOpen.value = true
}
const calendarOptions = ref({
@@ -203,7 +196,7 @@ onMounted(() => {
@click="goToday">
Today
</button>
<button v-if="userStore.isLoggedIn"
<button v-if="userStore.isLoggedIn && userStore.state === 'member'"
class="cursor-pointer ml-1 inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:opacity-90"
@click="onCreateEvent">
<Plus class="h-4 w-4" />

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { getWelcomeMessage } from '@/api/docs';
import { Button } from '@/components/ui/button'
import { useUserStore } from '@/stores/user'
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router'
const router = useRouter()
@@ -8,23 +10,126 @@ const router = useRouter()
const user = useUserStore();
function goToApplication() {
router.push('/apply') // change to your form route
router.push('/join') // change to your form route
}
onMounted(async () => {
if (user.state == 'member') {
let policy = await getWelcomeMessage() as any;
welcomeRef.value.innerHTML = policy;
}
})
const welcomeRef = ref<HTMLElement>(null);
</script>
<template>
<div>
<div v-if="user.state == 'guest'" class="flex flex-col items-center justify-center">
<h1 class="text-4xl font-bold mb-4">Welcome to the 17th</h1>
<p class="text-neutral-400 mb-8 max-w-md">
To join our unit, please fill out an application to continue.
</p>
<Button @click="goToApplication" class="px-6 py-3 text-lg">
Begin Application
</Button>
<div v-if="user.state == 'member'" class="mt-10">
<div ref="welcomeRef" class="bookstack-container">
<!-- bookstack -->
</div>
</div>
<div v-else>
HOMEPAGEEEEEEEEEEEEEEEEEEE
<div v-else class="text-foreground px-6 py-12 selection:bg-primary/10">
<div class="max-w-5xl mx-auto space-y-8">
<header class="space-y-4">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 class="text-4xl font-semibold tracking-tight">17th Ranger Battalion</h1>
</div>
</div>
<div class="h-px bg-border w-full"></div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div class="lg:col-span-7 space-y-6">
<div class="space-y-2">
<h2 class="text-sm font-medium uppercase tracking-wider text-primary">Unit Philosophy</h2>
<p class="text-lg leading-relaxed font-normal">
The 17th RBN emphasizes high-skill gameplay through real-world tactics, stripped of
traditional military formalities. We prioritize effective coordination over enforced
etiquette.
</p>
</div>
<p class="text-muted-foreground leading-relaxed">
Our "Real Life First" mindset ensures participation remains a hobby, not a second job. With
a consistent roster of 4050 members for Saturday operations, we focus on effective
coordination and mission success without the requirement of "Yes sir, no sir" protocols.
</p>
<div class="flex flex-wrap items-center gap-6 pt-4">
<Button size="lg" @click="goToApplication" class="font-medium">
Begin Application
</Button>
<div class="flex flex-col">
<span class="text-xs uppercase tracking-tighter text-muted-foreground">Age
Requirement</span>
<span class="text-sm font-medium">18+</span>
</div>
</div>
</div>
<div class="lg:col-span-5 space-y-8">
<section class="space-y-3">
<h3 class="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
Operational Schedule</h3>
<div class="rounded-lg border bg-card p-4 shadow-sm">
<div class="flex justify-between items-center">
<span class="text-sm font-medium">Main Operation</span>
<span class="text-sm font-mono text-primary">Sat 19:00 CST</span>
</div>
</div>
</section>
<section class="space-y-4">
<h3 class="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
Force Structure
</h3>
<ul class="space-y-3">
<li class="flex items-start gap-4">
<span class="h-1.5 w-1.5 rounded-full bg-primary mt-2 shrink-0"></span>
<div class="space-y-1">
<p class="text-sm font-medium leading-none">Alpha Company</p>
<p class="text-[13px] text-muted-foreground leading-relaxed">
Rifleman, Medic, CLS, Anti-Tank, RTO, Leadership
</p>
</div>
</li>
<li class="flex items-start gap-4">
<span class="h-1.5 w-1.5 rounded-full bg-primary mt-2 shrink-0"></span>
<div class="space-y-1">
<p class="text-sm font-medium leading-none">Echo Company</p>
<p class="text-[13px] text-muted-foreground leading-relaxed">
Logistics, CAS Pilot, Armor, Artillery, JTAC, Forward Observer
</p>
</div>
</li>
</ul>
<p class="text-[12px] text-muted-foreground/70 border-t pt-2 border-border">
Roles are fluid; specialization or weekly rotation is supported.
</p>
</section>
</div>
</div>
<section class="space-y-4">
<div class="relative rounded-xl border bg-card shadow-sm overflow-hidden">
<div class="aspect-video">
<iframe class="w-full h-full"
src="https://www.youtube.com/embed/61L397HwmrU?si=oY9qf6vFv6hXo6Fk&controls=1&mute=1&start=102&end=152"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen>
</iframe>
</div>
</div>
</section>
</div>
</div>
</div>
</template>

View File

@@ -34,7 +34,8 @@ import { Plus, X } from 'lucide-vue-next';
import Separator from '@/components/ui/separator/Separator.vue';
import Input from '@/components/ui/input/Input.vue';
import Label from '@/components/ui/label/Label.vue';
import { getMembers, Member } from '@/api/member';
import { getMembers } from '@/api/member';
import { Member } from '@shared/types/member';
const roles = ref<Role[]>([])
const activeRole = ref<Role | null>(null)
@@ -116,16 +117,18 @@ async function handleCreateGroup() {
}
}
function handleAddMember() {
async function handleAddMember() {
//guard
if (memberToAdd.value == null)
return;
addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
await addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
roles.value = await getRoles();
}
function handleRemoveMember(memberId: number) {
async function handleRemoveMember(memberId: number) {
removeMemberFromRole(memberId, activeRole.value.id);
roles.value = await getRoles();
}
async function handleDeleteRole() {
@@ -193,7 +196,7 @@ onMounted(async () => {
</ul>
</div>
<DialogFooter>
<Button @click="handleDeleteRole">Delete Group</Button>
<!-- <Button @click="handleDeleteRole">Delete Group</Button> -->
</DialogFooter>
</DialogContent>
</Dialog>
@@ -232,7 +235,7 @@ onMounted(async () => {
<div class="max-w-5xl mx-auto">
<div class="flex items-center justify-between my-4">
<p>Groups</p>
<Button @click="showCreateGroupDialog = true">+ Add New Group</Button>
<!-- <Button @click="showCreateGroupDialog = true">+ Add New Group</Button> -->
</div>
<div class="grid grid-cols-3 gap-5">
<Card v-for="value in roles" :key="value.id" @click="activeRole = value; showDialog = true"

View File

@@ -11,6 +11,7 @@ import { useMemberDirectory } from "@/stores/memberDirectory";
import { useUserStore } from "@/stores/user";
const saving = ref(false);
const saveSuccess = ref(false);
const loading = ref(true);
const showLoading = ref(false);
const form = ref<memberSettings>();
@@ -20,13 +21,14 @@ const userStore = useUserStore()
function saveSettings() {
saving.value = true;
saveSuccess.value = false;
setTimeout(async () => {
// Replace with your API save call
setMemberSettings(form.value);
saving.value = false;
console.log(userStore.user.id)
memberDictionary.invalidateMember(userStore.user.id)
memberDictionary.invalidateMember(userStore.user.member.member_id)
saveSuccess.value = true;
}, 800);
}
@@ -75,7 +77,8 @@ onMounted(async () => {
</CardContent>
</Transition>
<CardFooter class="flex justify-end">
<CardFooter class="flex justify-end gap-5 items-center">
<p v-if="saveSuccess" class="text-green-500">Profile saved</p>
<Button @click="saveSettings" :disabled="saving">
{{ saving ? "Saving..." : "Save Changes" }}
</Button>