refactor: enhance routing and state management in CoopBuilder, add migration checks on startup, and update Tailwind configuration for improved component styling
This commit is contained in:
parent
848386e3dd
commit
4cea1f71fe
55 changed files with 4053 additions and 1486 deletions
|
|
@ -6,8 +6,7 @@
|
|||
<!-- 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"
|
||||
>
|
||||
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>
|
||||
|
|
@ -18,16 +17,15 @@
|
|||
<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"
|
||||
>
|
||||
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" />
|
||||
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"
|
||||
>
|
||||
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">
|
||||
|
|
@ -35,7 +33,10 @@
|
|||
</p>
|
||||
|
||||
<div class="flex justify-center gap-4">
|
||||
<button class="export-btn" @click="restartWizard" :disabled="isResetting">
|
||||
<button
|
||||
class="export-btn"
|
||||
@click="restartWizard"
|
||||
:disabled="isResetting">
|
||||
Start Over
|
||||
</button>
|
||||
<button class="export-btn primary" @click="navigateTo('/budget')">
|
||||
|
|
@ -52,40 +53,34 @@
|
|||
<!-- Dithered shadow for selected state -->
|
||||
<div
|
||||
v-if="focusedStep === 1"
|
||||
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
||||
></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 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)"
|
||||
>
|
||||
@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
|
||||
membersValid
|
||||
? '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"
|
||||
v-if="membersValid"
|
||||
name="i-heroicons-check"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
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"
|
||||
>
|
||||
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide">
|
||||
Add your team
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -93,15 +88,13 @@
|
|||
<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 }"
|
||||
/>
|
||||
: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"
|
||||
>
|
||||
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white">
|
||||
<WizardMembersStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -112,40 +105,34 @@
|
|||
<!-- Dithered shadow for selected state -->
|
||||
<div
|
||||
v-if="focusedStep === 2"
|
||||
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
||||
></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 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)"
|
||||
>
|
||||
@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
|
||||
policiesValid
|
||||
? '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"
|
||||
v-if="policiesValid"
|
||||
name="i-heroicons-check"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
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"
|
||||
>
|
||||
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide">
|
||||
Set your wage
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -153,15 +140,13 @@
|
|||
<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 }"
|
||||
/>
|
||||
: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"
|
||||
>
|
||||
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white">
|
||||
<WizardPoliciesStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -172,30 +157,25 @@
|
|||
<!-- Dithered shadow for selected state -->
|
||||
<div
|
||||
v-if="focusedStep === 3"
|
||||
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
||||
></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 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)"
|
||||
>
|
||||
@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"
|
||||
>
|
||||
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"
|
||||
>
|
||||
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide">
|
||||
Monthly costs
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -203,15 +183,13 @@
|
|||
<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 }"
|
||||
/>
|
||||
: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"
|
||||
>
|
||||
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white">
|
||||
<WizardCostsStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -222,40 +200,34 @@
|
|||
<!-- Dithered shadow for selected state -->
|
||||
<div
|
||||
v-if="focusedStep === 4"
|
||||
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
||||
></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 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)"
|
||||
>
|
||||
@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
|
||||
streamsValid
|
||||
? '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"
|
||||
v-if="streamsValid"
|
||||
name="i-heroicons-check"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
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"
|
||||
>
|
||||
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide">
|
||||
Revenue streams
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -263,15 +235,13 @@
|
|||
<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 }"
|
||||
/>
|
||||
: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"
|
||||
>
|
||||
class="p-8 bg-neutral-50 dark:bg-neutral-900 border-t-2 border-black dark:border-white">
|
||||
<WizardRevenueStep @save-status="handleSaveStatus" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -282,19 +252,16 @@
|
|||
<!-- Dithered shadow for selected state -->
|
||||
<div
|
||||
v-if="focusedStep === 5"
|
||||
class="absolute top-2 left-2 w-full h-full dither-shadow"
|
||||
></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 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)"
|
||||
>
|
||||
@click="setFocusedStep(5)">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
|
|
@ -303,15 +270,16 @@
|
|||
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" />
|
||||
">
|
||||
<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"
|
||||
>
|
||||
class="text-2xl font-black text-black dark:text-white uppercase tracking-wide">
|
||||
Review & finish
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -319,52 +287,57 @@
|
|||
<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 }"
|
||||
/>
|
||||
: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" />
|
||||
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">
|
||||
<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"
|
||||
>
|
||||
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"
|
||||
/>
|
||||
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"
|
||||
/>
|
||||
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"
|
||||
<span
|
||||
v-if="saveStatus === 'saved'"
|
||||
class="text-black dark:text-white"
|
||||
>Saved</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<button v-if="canComplete" class="export-btn primary" @click="completeWizard">
|
||||
<button
|
||||
v-if="canComplete"
|
||||
class="export-btn primary"
|
||||
@click="completeWizard">
|
||||
Complete Setup
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -375,12 +348,8 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Stores
|
||||
const membersStore = useMembersStore();
|
||||
const policiesStore = usePoliciesStore();
|
||||
const streamsStore = useStreamsStore();
|
||||
const budgetStore = useBudgetStore();
|
||||
const coopBuilderStore = useCoopBuilderStore();
|
||||
// Store
|
||||
const coop = useCoopBuilder();
|
||||
|
||||
// UI state
|
||||
const focusedStep = ref(1);
|
||||
|
|
@ -389,9 +358,36 @@ const isResetting = ref(false);
|
|||
const isCompleted = ref(false);
|
||||
|
||||
// Computed validation
|
||||
const canComplete = computed(
|
||||
() => membersStore.isValid && policiesStore.isValid && streamsStore.hasValidStreams
|
||||
);
|
||||
const canComplete = computed(() => {
|
||||
return coop.members.value.length > 0 && coop.streams.value.length > 0;
|
||||
});
|
||||
|
||||
// Local validity flags for step headers
|
||||
const membersValid = computed(() => {
|
||||
// Valid if at least one member with a name and positive hours
|
||||
return coop.members.value.some((m: any) => {
|
||||
const hasName = typeof m.name === "string" && m.name.trim().length > 0;
|
||||
const hours = Number((m as any).hoursPerMonth ?? 0);
|
||||
return hasName && Number.isFinite(hours) && hours > 0;
|
||||
});
|
||||
});
|
||||
|
||||
const policiesValid = computed(() => {
|
||||
// Placeholder policy validity; mark true when wage text or policy set exists
|
||||
// Since policy not persisted yet in this store, consider valid when any member exists
|
||||
return membersValid.value;
|
||||
});
|
||||
|
||||
const streamsValid = computed(() => {
|
||||
// Valid if all streams have name, category, and non-negative monthly
|
||||
return (
|
||||
coop.streams.value.length > 0 &&
|
||||
coop.streams.value.every((s: any) => {
|
||||
const monthly = Number((s as any).monthly ?? 0);
|
||||
return (s.label || "").toString().trim().length > 0 && monthly >= 0;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Save status handler
|
||||
function handleSaveStatus(status: "saving" | "saved" | "error") {
|
||||
|
|
@ -424,14 +420,8 @@ function completeWizard() {
|
|||
async function resetWizard() {
|
||||
isResetting.value = true;
|
||||
|
||||
// Reset all stores
|
||||
membersStore.resetMembers();
|
||||
policiesStore.resetPolicies();
|
||||
streamsStore.resetStreams();
|
||||
budgetStore.resetBudgetOverhead();
|
||||
|
||||
// Reset coop builder state
|
||||
coopBuilderStore.reset();
|
||||
// Reset centralized store
|
||||
coop.reset();
|
||||
saveStatus.value = "";
|
||||
|
||||
// Small delay for UX
|
||||
|
|
@ -446,12 +436,8 @@ async function restartWizard() {
|
|||
isCompleted.value = false;
|
||||
focusedStep.value = 1;
|
||||
|
||||
// Reset all stores and coop builder state
|
||||
membersStore.resetMembers();
|
||||
policiesStore.resetPolicies();
|
||||
streamsStore.resetStreams();
|
||||
budgetStore.resetBudgetOverhead();
|
||||
coopBuilderStore.reset();
|
||||
// Reset centralized store
|
||||
coop.reset();
|
||||
saveStatus.value = "";
|
||||
|
||||
// Small delay for UX
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue