468 lines
16 KiB
Vue
468 lines
16 KiB
Vue
<template>
|
|
<div>
|
|
<!-- No WizardSubnav for co-op setup tool -->
|
|
|
|
<section class="py-8 max-w-4xl mx-auto font-mono">
|
|
<!-- Header -->
|
|
<div class="mb-10 text-center">
|
|
<h1
|
|
class="text-3xl font-black text-black dark:text-white mb-4 leading-tight uppercase tracking-wide border-t-2 border-b-2 border-black dark:border-white py-4"
|
|
>
|
|
Co-op Builder
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- Completed State -->
|
|
<div v-if="isCompleted" class="text-center py-12 relative">
|
|
<!-- Dithered shadow background -->
|
|
<div class="absolute top-2 left-2 w-full h-full dither-shadow"></div>
|
|
|
|
<div
|
|
class="relative bg-white dark:bg-neutral-950 border-2 border-black dark:border-white p-8"
|
|
>
|
|
<div
|
|
class="w-16 h-16 bg-black dark:bg-white border-2 border-black dark:border-white flex items-center justify-center mx-auto mb-4"
|
|
>
|
|
<UIcon name="i-heroicons-check" class="w-8 h-8 text-white dark:text-black" />
|
|
</div>
|
|
<h2
|
|
class="text-2xl font-bold text-black dark:text-white mb-2 uppercase tracking-wide"
|
|
>
|
|
You're all set!
|
|
</h2>
|
|
<p class="text-neutral-600 dark:text-neutral-400 mb-6">
|
|
Your co-op is configured and ready to go.
|
|
</p>
|
|
|
|
<div class="flex justify-center gap-4">
|
|
<button class="export-btn" @click="restartWizard" :disabled="isResetting">
|
|
Start Over
|
|
</button>
|
|
<button class="export-btn primary" @click="navigateTo('/budget')">
|
|
Go to Dashboard
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Vertical Steps Layout -->
|
|
<div v-else class="space-y-4">
|
|
<!-- Step 1: Members -->
|
|
<div class="relative">
|
|
<!-- Dithered shadow for selected state -->
|
|
<div
|
|
v-if="focusedStep === 1"
|
|
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
|
></div>
|
|
|
|
<div
|
|
:class="[
|
|
'relative bg-white dark:bg-neutral-950 border-2 border-black dark:border-white overflow-hidden',
|
|
focusedStep === 1 ? 'item-selected' : '',
|
|
]"
|
|
>
|
|
<div
|
|
class="p-8 cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
@click="setFocusedStep(1)"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-8 h-8 flex items-center justify-center text-sm font-bold border-2"
|
|
:class="
|
|
membersStore.isValid
|
|
? 'bg-black dark:bg-white text-white dark:text-black border-black dark:border-white'
|
|
: 'bg-white dark:bg-neutral-950 text-black dark:text-white border-black dark:border-white'
|
|
"
|
|
>
|
|
<UIcon
|
|
v-if="membersStore.isValid"
|
|
name="i-heroicons-check"
|
|
class="w-4 h-4"
|
|
/>
|
|
<span v-else>1</span>
|
|
</div>
|
|
<div>
|
|
<h3
|
|
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide"
|
|
>
|
|
Add your team
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<UIcon
|
|
name="i-heroicons-chevron-down"
|
|
class="w-6 h-6 text-black dark:text-white transition-transform font-bold"
|
|
:class="{ 'rotate-180': focusedStep === 1 }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="focusedStep === 1"
|
|
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white"
|
|
>
|
|
<WizardMembersStep @save-status="handleSaveStatus" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Wage -->
|
|
<div class="relative">
|
|
<!-- Dithered shadow for selected state -->
|
|
<div
|
|
v-if="focusedStep === 2"
|
|
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
|
></div>
|
|
|
|
<div
|
|
:class="[
|
|
'relative bg-white dark:bg-neutral-950 border-2 border-black dark:border-white overflow-hidden',
|
|
focusedStep === 2 ? 'item-selected' : '',
|
|
]"
|
|
>
|
|
<div
|
|
class="p-8 cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
@click="setFocusedStep(2)"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-8 h-8 flex items-center justify-center text-sm font-bold border-2"
|
|
:class="
|
|
policiesStore.isValid
|
|
? 'bg-black dark:bg-white text-white dark:text-black border-black dark:border-white'
|
|
: 'bg-white dark:bg-neutral-950 text-black dark:text-white border-black dark:border-white'
|
|
"
|
|
>
|
|
<UIcon
|
|
v-if="policiesStore.isValid"
|
|
name="i-heroicons-check"
|
|
class="w-4 h-4"
|
|
/>
|
|
<span v-else>2</span>
|
|
</div>
|
|
<div>
|
|
<h3
|
|
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide"
|
|
>
|
|
Set your wage
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<UIcon
|
|
name="i-heroicons-chevron-down"
|
|
class="w-6 h-6 text-black dark:text-white transition-transform font-bold"
|
|
:class="{ 'rotate-180': focusedStep === 2 }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="focusedStep === 2"
|
|
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white"
|
|
>
|
|
<WizardPoliciesStep @save-status="handleSaveStatus" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Costs -->
|
|
<div class="relative">
|
|
<!-- Dithered shadow for selected state -->
|
|
<div
|
|
v-if="focusedStep === 3"
|
|
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
|
></div>
|
|
|
|
<div
|
|
:class="[
|
|
'relative bg-white dark:bg-neutral-950 border-2 border-black dark:border-white overflow-hidden',
|
|
focusedStep === 3 ? 'item-selected' : '',
|
|
]"
|
|
>
|
|
<div
|
|
class="p-8 cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
@click="setFocusedStep(3)"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-8 h-8 flex items-center justify-center text-sm font-bold border-2 bg-black dark:bg-white text-white dark:text-black border-black dark:border-white"
|
|
>
|
|
<UIcon name="i-heroicons-check" class="w-4 h-4" />
|
|
</div>
|
|
<div>
|
|
<h3
|
|
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide"
|
|
>
|
|
Monthly costs
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<UIcon
|
|
name="i-heroicons-chevron-down"
|
|
class="w-6 h-6 text-black dark:text-white transition-transform font-bold"
|
|
:class="{ 'rotate-180': focusedStep === 3 }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="focusedStep === 3"
|
|
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white"
|
|
>
|
|
<WizardCostsStep @save-status="handleSaveStatus" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 4: Revenue -->
|
|
<div class="relative">
|
|
<!-- Dithered shadow for selected state -->
|
|
<div
|
|
v-if="focusedStep === 4"
|
|
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
|
></div>
|
|
|
|
<div
|
|
:class="[
|
|
'relative bg-white dark:bg-neutral-950 border-2 border-black dark:border-white overflow-hidden',
|
|
focusedStep === 4 ? 'item-selected' : '',
|
|
]"
|
|
>
|
|
<div
|
|
class="p-8 cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
@click="setFocusedStep(4)"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-8 h-8 flex items-center justify-center text-sm font-bold border-2"
|
|
:class="
|
|
streamsStore.hasValidStreams
|
|
? 'bg-black dark:bg-white text-white dark:text-black border-black dark:border-white'
|
|
: 'bg-white dark:bg-neutral-950 text-black dark:text-white border-black dark:border-white'
|
|
"
|
|
>
|
|
<UIcon
|
|
v-if="streamsStore.hasValidStreams"
|
|
name="i-heroicons-check"
|
|
class="w-4 h-4"
|
|
/>
|
|
<span v-else>4</span>
|
|
</div>
|
|
<div>
|
|
<h3
|
|
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide"
|
|
>
|
|
Revenue streams
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<UIcon
|
|
name="i-heroicons-chevron-down"
|
|
class="w-6 h-6 text-black dark:text-white transition-transform font-bold"
|
|
:class="{ 'rotate-180': focusedStep === 4 }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="focusedStep === 4"
|
|
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white"
|
|
>
|
|
<WizardRevenueStep @save-status="handleSaveStatus" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 5: Review -->
|
|
<div class="relative">
|
|
<!-- Dithered shadow for selected state -->
|
|
<div
|
|
v-if="focusedStep === 5"
|
|
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
|
></div>
|
|
|
|
<div
|
|
:class="[
|
|
'relative bg-white dark:bg-neutral-950 border-2 border-black dark:border-white overflow-hidden',
|
|
focusedStep === 5 ? 'item-selected' : '',
|
|
]"
|
|
>
|
|
<div
|
|
class="p-8 cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
@click="setFocusedStep(5)"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-8 h-8 flex items-center justify-center text-sm font-bold border-2"
|
|
:class="
|
|
canComplete
|
|
? 'bg-black dark:bg-white text-white dark:text-black border-black dark:border-white'
|
|
: 'bg-white dark:bg-neutral-950 text-black dark:text-white border-black dark:border-white'
|
|
"
|
|
>
|
|
<UIcon v-if="canComplete" name="i-heroicons-check" class="w-4 h-4" />
|
|
<span v-else>5</span>
|
|
</div>
|
|
<div>
|
|
<h3
|
|
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide"
|
|
>
|
|
Review & finish
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<UIcon
|
|
name="i-heroicons-chevron-down"
|
|
class="w-6 h-6 text-black dark:text-white transition-transform font-bold"
|
|
:class="{ 'rotate-180': focusedStep === 5 }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="focusedStep === 5"
|
|
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white"
|
|
>
|
|
<WizardReviewStep @complete="completeWizard" @reset="resetWizard" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Actions -->
|
|
<div class="flex justify-between items-center pt-8">
|
|
<button class="export-btn" @click="resetWizard" :disabled="isResetting">
|
|
Start Over
|
|
</button>
|
|
|
|
<div class="flex items-center gap-4">
|
|
<!-- Save status -->
|
|
<div
|
|
class="flex items-center gap-2 text-sm font-mono uppercase tracking-wide"
|
|
>
|
|
<UIcon
|
|
v-if="saveStatus === 'saving'"
|
|
name="i-heroicons-arrow-path"
|
|
class="w-4 h-4 animate-spin text-neutral-500 dark:text-neutral-400"
|
|
/>
|
|
<UIcon
|
|
v-if="saveStatus === 'saved'"
|
|
name="i-heroicons-check-circle"
|
|
class="w-4 h-4 text-black dark:text-white"
|
|
/>
|
|
<span
|
|
v-if="saveStatus === 'saving'"
|
|
class="text-neutral-500 dark:text-neutral-400"
|
|
>Saving...</span
|
|
>
|
|
<span v-if="saveStatus === 'saved'" class="text-black dark:text-white"
|
|
>Saved</span
|
|
>
|
|
</div>
|
|
|
|
<button v-if="canComplete" class="export-btn primary" @click="completeWizard">
|
|
Complete Setup
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
// Stores
|
|
const membersStore = useMembersStore();
|
|
const policiesStore = usePoliciesStore();
|
|
const streamsStore = useStreamsStore();
|
|
const budgetStore = useBudgetStore();
|
|
const coopBuilderStore = useCoopBuilderStore();
|
|
|
|
// UI state
|
|
const focusedStep = ref(1);
|
|
const saveStatus = ref("");
|
|
const isResetting = ref(false);
|
|
const isCompleted = ref(false);
|
|
|
|
// Computed validation
|
|
const canComplete = computed(
|
|
() => membersStore.isValid && policiesStore.isValid && streamsStore.hasValidStreams
|
|
);
|
|
|
|
// Save status handler
|
|
function handleSaveStatus(status: "saving" | "saved" | "error") {
|
|
saveStatus.value = status;
|
|
if (status === "saved") {
|
|
// Clear status after delay
|
|
setTimeout(() => {
|
|
if (saveStatus.value === "saved") {
|
|
saveStatus.value = "";
|
|
}
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
// Step management
|
|
function setFocusedStep(step: number) {
|
|
// Toggle if clicking on already focused step
|
|
if (focusedStep.value === step) {
|
|
focusedStep.value = 0; // Close the section
|
|
} else {
|
|
focusedStep.value = step; // Open the section
|
|
}
|
|
}
|
|
|
|
function completeWizard() {
|
|
// Mark setup as complete and show restart button for testing
|
|
isCompleted.value = true;
|
|
}
|
|
|
|
async function resetWizard() {
|
|
isResetting.value = true;
|
|
|
|
// Reset all stores
|
|
membersStore.resetMembers();
|
|
policiesStore.resetPolicies();
|
|
streamsStore.resetStreams();
|
|
budgetStore.resetBudgetOverhead();
|
|
|
|
// Reset coop builder state
|
|
coopBuilderStore.reset();
|
|
saveStatus.value = "";
|
|
|
|
// Small delay for UX
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
isResetting.value = false;
|
|
}
|
|
|
|
async function restartWizard() {
|
|
isResetting.value = true;
|
|
|
|
// Reset completion state
|
|
isCompleted.value = false;
|
|
focusedStep.value = 1;
|
|
|
|
// Reset all stores and coop builder state
|
|
membersStore.resetMembers();
|
|
policiesStore.resetPolicies();
|
|
streamsStore.resetStreams();
|
|
budgetStore.resetBudgetOverhead();
|
|
coopBuilderStore.reset();
|
|
saveStatus.value = "";
|
|
|
|
// Small delay for UX
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
isResetting.value = false;
|
|
}
|
|
|
|
// SEO
|
|
useSeoMeta({
|
|
title: "Co-op Builder - Build Your Financial Foundation",
|
|
description:
|
|
"Build your co-op's financial foundation: set up members, policies, costs, and revenue streams.",
|
|
});
|
|
</script>
|