chore: update application configuration and UI components for improved styling and functionality

This commit is contained in:
Jennie Robinson Faber 2025-08-16 08:13:35 +01:00
parent 0af6b17792
commit 37ab8d7bab
54 changed files with 23293 additions and 1666 deletions

View file

@ -1,89 +1,268 @@
<template>
<section class="py-8 space-y-6">
<div class="flex items-center justify-between">
<h2 class="text-2xl font-semibold">Setup Wizard</h2>
<div class="flex items-center gap-3">
<UBadge color="primary" variant="subtle"
>Step {{ currentStep }} of 5</UBadge
>
<section class="py-8 max-w-4xl mx-auto">
<!-- Header -->
<div class="mb-10">
<h1 class="text-5xl font-black text-black mb-4 leading-tight">
Set up your co-op
</h1>
<p class="text-xl text-neutral-700 font-medium">
Get your worker-owned co-op configured in a few simple steps. Jump to
any step or work through them in order.
</p>
</div>
<!-- Completed State -->
<div v-if="isCompleted" class="text-center py-12">
<div
class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<UIcon name="i-heroicons-check" class="w-8 h-8 text-green-600" />
</div>
<h2 class="text-2xl font-bold text-black mb-2">You're all set!</h2>
<p class="text-neutral-600 mb-6">
Your co-op is configured and ready to go.
</p>
<div class="flex justify-center gap-4">
<UButton
size="sm"
variant="ghost"
@click="resetWizard"
variant="outline"
color="gray"
@click="restartWizard"
:disabled="isResetting">
Reset Wizard
Start Over
</UButton>
<UButton
@click="navigateTo('/scenarios')"
size="lg"
variant="solid"
color="black">
Go to Dashboard
</UButton>
</div>
</div>
<UCard>
<div class="space-y-6">
<!-- Step 1: Members -->
<div v-if="currentStep === 1">
<!-- Vertical Steps Layout -->
<div v-else class="space-y-4">
<!-- Step 1: Members -->
<div
class="bg-white border-4 border-black rounded-xl overflow-hidden shadow-lg">
<div
class="p-8 cursor-pointer hover:bg-yellow-50 transition-colors"
:class="{ 'bg-yellow-100': focusedStep === 1 }"
@click="setFocusedStep(1)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
:class="
membersStore.isValid
? 'bg-green-100 text-green-700'
: 'bg-white text-black border-2 border-black'
">
<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">Add your team</h3>
</div>
</div>
<UIcon
name="i-heroicons-chevron-down"
class="w-6 h-6 text-black transition-transform font-bold"
:class="{ 'rotate-180': focusedStep === 1 }" />
</div>
</div>
<div v-if="focusedStep === 1" class="p-8 bg-yellow-25">
<WizardMembersStep @save-status="handleSaveStatus" />
</div>
</div>
<!-- Step 2: Wage & Policies -->
<div v-if="currentStep === 2">
<!-- Step 2: Wage -->
<div
class="bg-white border-4 border-black rounded-xl overflow-hidden shadow-lg">
<div
class="p-8 cursor-pointer hover:bg-green-50 transition-colors"
:class="{ 'bg-green-100': focusedStep === 2 }"
@click="setFocusedStep(2)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
:class="
policiesStore.isValid
? 'bg-green-100 text-green-700'
: 'bg-white text-black border-2 border-black'
">
<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">Set your wage</h3>
</div>
</div>
<UIcon
name="i-heroicons-chevron-down"
class="w-6 h-6 text-black transition-transform font-bold"
:class="{ 'rotate-180': focusedStep === 2 }" />
</div>
</div>
<div v-if="focusedStep === 2" class="p-8 bg-green-25">
<WizardPoliciesStep @save-status="handleSaveStatus" />
</div>
</div>
<!-- Step 3: Costs -->
<div v-if="currentStep === 3">
<!-- Step 3: Costs -->
<div
class="bg-white border-4 border-black rounded-xl overflow-hidden shadow-lg">
<div
class="p-8 cursor-pointer hover:bg-blue-50 transition-colors"
:class="{ 'bg-blue-100': focusedStep === 3 }"
@click="setFocusedStep(3)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-full bg-green-100 text-green-700 flex items-center justify-center text-sm font-bold">
<UIcon name="i-heroicons-check" class="w-4 h-4" />
</div>
<div>
<h3 class="text-2xl font-black text-black">Monthly costs</h3>
</div>
</div>
<UIcon
name="i-heroicons-chevron-down"
class="w-6 h-6 text-black transition-transform font-bold"
:class="{ 'rotate-180': focusedStep === 3 }" />
</div>
</div>
<div v-if="focusedStep === 3" class="p-8 bg-blue-25">
<WizardCostsStep @save-status="handleSaveStatus" />
</div>
</div>
<!-- Step 4: Revenue -->
<div v-if="currentStep === 4">
<!-- Step 4: Revenue -->
<div
class="bg-white border-4 border-black rounded-xl overflow-hidden shadow-lg">
<div
class="p-8 cursor-pointer hover:bg-purple-50 transition-colors"
:class="{ 'bg-purple-100': focusedStep === 4 }"
@click="setFocusedStep(4)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
:class="
streamsStore.hasValidStreams
? 'bg-green-100 text-green-700'
: 'bg-white text-black border-2 border-black'
">
<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">Revenue streams</h3>
</div>
</div>
<UIcon
name="i-heroicons-chevron-down"
class="w-6 h-6 text-black transition-transform font-bold"
:class="{ 'rotate-180': focusedStep === 4 }" />
</div>
</div>
<div v-if="focusedStep === 4" class="p-8 bg-purple-25">
<WizardRevenueStep @save-status="handleSaveStatus" />
</div>
</div>
<!-- Step 5: Review -->
<div v-if="currentStep === 5">
<WizardReviewStep @complete="completeWizard" @reset="resetWizard" />
<!-- Step 5: Review -->
<div
class="bg-white border-4 border-black rounded-xl overflow-hidden shadow-lg">
<div
class="p-8 cursor-pointer hover:bg-orange-50 transition-colors"
:class="{ 'bg-orange-100': focusedStep === 5 }"
@click="setFocusedStep(5)">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
:class="
canComplete
? 'bg-green-100 text-green-700'
: 'bg-white text-black border-2 border-black'
">
<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">Review & finish</h3>
</div>
</div>
<UIcon
name="i-heroicons-chevron-down"
class="w-6 h-6 text-black transition-transform font-bold"
:class="{ 'rotate-180': focusedStep === 5 }" />
</div>
</div>
<!-- Navigation -->
<div class="flex justify-between items-center pt-6 border-t">
<UButton v-if="currentStep > 1" variant="ghost" @click="previousStep">
Previous
</UButton>
<div v-else></div>
<div v-if="focusedStep === 5" class="p-8 bg-orange-25">
<WizardReviewStep @complete="completeWizard" @reset="resetWizard" />
</div>
</div>
<!-- Save status indicator -->
<div class="flex items-center gap-2">
<!-- Progress Actions -->
<div class="flex justify-between items-center pt-8">
<UButton
variant="outline"
color="red"
@click="resetWizard"
:disabled="isResetting">
Start Over
</UButton>
<div class="flex items-center gap-4">
<!-- Save status -->
<div class="flex items-center gap-2 text-sm">
<UIcon
v-if="saveStatus === 'saving'"
name="i-heroicons-arrow-path"
class="w-4 h-4 animate-spin text-gray-500" />
class="w-4 h-4 animate-spin text-neutral-500" />
<UIcon
v-if="saveStatus === 'saved'"
name="i-heroicons-check-circle"
class="w-4 h-4 text-green-500" />
<span v-if="saveStatus === 'saving'" class="text-xs text-gray-500"
<span v-if="saveStatus === 'saving'" class="text-neutral-500"
>Saving...</span
>
<span v-if="saveStatus === 'saved'" class="text-xs text-green-600"
<span v-if="saveStatus === 'saved'" class="text-green-600"
>Saved</span
>
</div>
<UButton
v-if="currentStep < 5"
@click="nextStep"
:disabled="!isHydrated || !canProceed">
Next
v-if="canComplete"
@click="completeWizard"
size="lg"
variant="solid"
color="black">
Complete Setup
</UButton>
</div>
<!-- Step validation messages -->
<div
v-if="!canProceed && currentStep < 5"
class="text-sm text-red-600 mt-2">
{{ validationMessage }}
</div>
</div>
</UCard>
</div>
</section>
</template>
@ -93,35 +272,20 @@ const membersStore = useMembersStore();
const policiesStore = usePoliciesStore();
const streamsStore = useStreamsStore();
const budgetStore = useBudgetStore();
// Wizard state (persisted)
const wizardStore = useWizardStore();
const currentStep = computed({
get: () => wizardStore.currentStep,
set: (val: number) => wizardStore.setStep(val),
});
// UI state
const focusedStep = ref(1);
const saveStatus = ref("");
const isResetting = ref(false);
const isHydrated = ref(false);
onMounted(() => {
isHydrated.value = true;
});
const isCompleted = ref(false);
// Debug: log step and validation state
watch(
() => ({
step: currentStep.value,
membersValid: membersStore.isValid,
policiesValid: policiesStore.isValid,
streamsValid: streamsStore.hasValidStreams,
members: membersStore.members,
memberValidation: membersStore.validationDetails,
}),
(state) => {
// eslint-disable-next-line no-console
console.debug("Wizard state:", JSON.parse(JSON.stringify(state)));
},
{ deep: true }
// Computed validation
const canComplete = computed(
() =>
membersStore.isValid &&
policiesStore.isValid &&
streamsStore.hasValidStreams
);
// Save status handler
@ -137,54 +301,19 @@ function handleSaveStatus(status: "saving" | "saved" | "error") {
}
}
// Step validation
const canProceed = computed(() => {
switch (currentStep.value) {
case 1:
return membersStore.isValid;
case 2:
return policiesStore.isValid;
case 3:
return true; // Costs are optional
case 4:
return streamsStore.hasValidStreams;
default:
return true;
}
});
const validationMessage = computed(() => {
switch (currentStep.value) {
case 1:
if (membersStore.members.length === 0)
return "Add at least one member to continue";
return "Complete all required member fields";
case 2:
if (policiesStore.equalHourlyWage <= 0)
return "Enter an hourly wage greater than 0";
return "Complete all required policy fields";
case 4:
return "Add at least one valid revenue stream";
default:
return "";
}
});
function nextStep() {
if (currentStep.value < 5 && canProceed.value) {
currentStep.value++;
}
}
function previousStep() {
if (currentStep.value > 1) {
currentStep.value--;
// 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 redirect
navigateTo("/scenarios");
// Mark setup as complete and show restart button for testing
isCompleted.value = true;
}
async function resetWizard() {
@ -205,6 +334,26 @@ async function resetWizard() {
isResetting.value = false;
}
async function restartWizard() {
isResetting.value = true;
// Reset completion state
isCompleted.value = false;
focusedStep.value = 1;
// Reset all stores and wizard state
membersStore.resetMembers();
policiesStore.resetPolicies();
streamsStore.resetStreams();
budgetStore.resetBudgetOverhead();
wizardStore.reset();
saveStatus.value = "";
// Small delay for UX
await new Promise((resolve) => setTimeout(resolve, 300));
isResetting.value = false;
}
// SEO
useSeoMeta({
title: "Setup Wizard - Configure Your Co-op",