diff --git a/app/Http/Controllers/TrackerController.php b/app/Http/Controllers/TrackerController.php
index 512e6b4..4631686 100644
--- a/app/Http/Controllers/TrackerController.php
+++ b/app/Http/Controllers/TrackerController.php
@@ -26,8 +26,8 @@ public function show(): JsonResponse
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
- 'label' => 'required|string|max:255',
- 'unit' => 'required|string|max:50',
+ 'label' => 'sometimes|string|max:255',
+ 'unit' => 'sometimes|string|max:50',
'price_tracking_enabled' => 'boolean',
'symbol' => 'nullable|string|max:10',
'full_name' => 'nullable|string|max:255',
@@ -46,8 +46,8 @@ public function store(Request $request): JsonResponse
}
$tracker = $user->tracker()->create([
- 'label' => $validated['label'],
- 'unit' => $validated['unit'],
+ 'label' => $validated['label'] ?? 'Counter',
+ 'unit' => $validated['unit'] ?? 'units',
'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? false,
'asset_id' => $assetId,
]);
diff --git a/resources/js/components/Display/LedDisplay.tsx b/resources/js/components/Display/LedDisplay.tsx
index 4ca24a8..a535b35 100644
--- a/resources/js/components/Display/LedDisplay.tsx
+++ b/resources/js/components/Display/LedDisplay.tsx
@@ -3,7 +3,6 @@ import { useEffect, useState } from 'react';
interface LedDisplayProps {
value: number;
- unit?: string;
className?: string;
animate?: boolean;
onClick?: () => void;
@@ -11,7 +10,6 @@ interface LedDisplayProps {
export default function LedDisplay({
value,
- unit,
className,
onClick
}: LedDisplayProps) {
@@ -23,14 +21,7 @@ export default function LedDisplay({
return;
}, [value]);
- // Format number with zero-padding for consistent width
- const formatValue = (value: number) => {
- // Always pad to 5 digits for consistent display width
- const integerPart = Math.floor(value);
- return integerPart.toString().padStart(5, '0');
- };
-
- const formattedValue = formatValue(displayValue);
+ const formattedValue = Math.floor(displayValue).toString();
return (
- {unit && (
-
- {unit}
-
- )}
);
}
diff --git a/resources/js/components/Onboarding/OnboardingFlow.tsx b/resources/js/components/Onboarding/OnboardingFlow.tsx
index 669ef72..a379c7f 100644
--- a/resources/js/components/Onboarding/OnboardingFlow.tsx
+++ b/resources/js/components/Onboarding/OnboardingFlow.tsx
@@ -1,175 +1,102 @@
-import { useState, useEffect, useCallback } from 'react';
-import AddEntryForm from '@/components/Transactions/AddEntryForm';
-import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm';
-import CreateTrackerStep from '@/components/Onboarding/CreateTrackerStep';
-
-interface OnboardingStep {
- id: string;
- title: string;
- description: string;
- completed: boolean;
- required: boolean;
-}
-
-const STEPS: OnboardingStep[] = [
- { id: 'entries', title: 'STARTING AMOUNT', description: 'Enter your starting amount', completed: false, required: true },
- { id: 'milestones', title: 'SET MILESTONES', description: 'Define your goals', completed: false, required: true },
-];
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import InputError from '@/components/InputError';
+import { todayISO } from '@/lib/utils';
+import { LoaderCircle } from 'lucide-react';
+import { FormEventHandler, useState } from 'react';
interface OnboardingFlowProps {
onComplete?: () => void;
}
+function csrfToken(): string {
+ return (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '';
+}
+
export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) {
- const [trackerCreated, setTrackerCreated] = useState(false);
- const [currentStep, setCurrentStep] = useState(0);
- const [steps, setSteps] = useState([]);
+ const [startingValue, setStartingValue] = useState('0');
+ const [processing, setProcessing] = useState(false);
+ const [error, setError] = useState();
- // On mount: check if a tracker already exists and skip step 1 if so
- useEffect(() => {
- fetch('/tracker')
- .then(r => r.ok ? r.json() : null)
- .then(data => {
- if (data?.tracker) {
- setTrackerCreated(true);
- }
- })
- .catch(() => {});
- }, []);
+ const submit: FormEventHandler = async (e) => {
+ e.preventDefault();
+ setProcessing(true);
+ setError(undefined);
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-TOKEN': csrfToken(),
+ Accept: 'application/json',
+ };
- const checkOnboardingStatus = useCallback(async (currentSteps: OnboardingStep[]) => {
try {
- const [entriesData, milestonesData] = await Promise.all([
- fetch('/entries/summary').then(r => r.json()),
- fetch('/milestones').then(r => r.json()),
- ]);
+ const trackerResponse = await fetch(route('tracker.store'), {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({}),
+ });
- const hasEntries = entriesData.total_quantity > 0;
- const hasMilestones = milestonesData.length > 0;
-
- const freshSteps = currentSteps.map(step => ({
- ...step,
- completed:
- (step.id === 'entries' && hasEntries) ||
- (step.id === 'milestones' && hasMilestones),
- }));
-
- setSteps(freshSteps);
-
- const firstIncompleteRequired = freshSteps.findIndex(s => s.required && !s.completed);
-
- if (firstIncompleteRequired !== -1) {
- setCurrentStep(firstIncompleteRequired);
- } else if (onComplete) {
- onComplete();
+ if (!trackerResponse.ok && trackerResponse.status !== 409) {
+ setError('Could not create the counter. Please try again.');
+ return;
}
- } catch (error) {
- console.error('Failed to check onboarding status:', error);
- }
- }, [onComplete]);
- useEffect(() => {
- if (!trackerCreated) return;
+ const quantity = Number(startingValue);
- setSteps(STEPS);
- setCurrentStep(0);
- checkOnboardingStatus(STEPS);
- }, [trackerCreated, checkOnboardingStatus]);
+ if (quantity > 0) {
+ const entryResponse = await fetch(route('entries.store'), {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ date: todayISO(), quantity }),
+ });
- const handleTrackerCreated = () => {
- setTrackerCreated(true);
- };
+ if (!entryResponse.ok) {
+ setError('Could not save the starting value. Please try again.');
+ return;
+ }
+ }
- const handleStepComplete = async () => {
- const updatedSteps = steps.map((step, index) =>
- index === currentStep ? { ...step, completed: true } : step
- );
- setSteps(updatedSteps);
- await checkOnboardingStatus(updatedSteps);
- };
-
- const handleStepSelect = (stepIndex: number) => {
- setCurrentStep(stepIndex);
- };
-
- const renderStepContent = () => {
- const step = steps[currentStep];
- if (!step) return null;
-
- switch (step.id) {
- case 'entries':
- return ;
- case 'milestones':
- return ;
- default:
- return null;
+ onComplete?.();
+ } catch {
+ setError('Something went wrong. Please try again.');
+ } finally {
+ setProcessing(false);
}
};
return (
-
+
-
-
- [SYSTEM] ONBOARDING SEQUENCE
-
-
- {!trackerCreated ? 'Set up your tracker' : 'Configure your tracker'}
-
-
+
diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx
index d79db8d..b380a5d 100644
--- a/resources/js/pages/dashboard.tsx
+++ b/resources/js/pages/dashboard.tsx
@@ -19,36 +19,37 @@ export default function Dashboard() {
const [needsOnboarding, setNeedsOnboarding] = useState(false);
const [tracker, setTracker] = useState
(null);
+ const loadData = useCallback(async () => {
+ const [entriesResponse, milestonesResponse, trackerResponse] = await Promise.all([
+ fetch('/entries/summary'),
+ fetch('/milestones'),
+ fetch('/tracker'),
+ ]);
+
+ if (entriesResponse.ok) {
+ const entries = await entriesResponse.json();
+ setTotalShares(entries.total_quantity);
+ }
+
+ if (milestonesResponse.ok) {
+ setMilestones(await milestonesResponse.json());
+ }
+
+ let trackerExists = false;
+
+ if (trackerResponse.ok) {
+ const { exists, tracker: trackerData } = await trackerResponse.json();
+ setTracker(trackerData ?? null);
+ trackerExists = Boolean(exists);
+ }
+
+ setNeedsOnboarding(!trackerExists);
+ }, []);
+
useEffect(() => {
const fetchData = async () => {
try {
- const [entriesResponse, milestonesResponse, trackerResponse] = await Promise.all([
- fetch('/entries/summary'),
- fetch('/milestones'),
- fetch('/tracker'),
- ]);
-
- let totalQuantity = 0;
- let milestonesCount = 0;
-
- if (entriesResponse.ok) {
- const entries = await entriesResponse.json();
- setTotalShares(entries.total_quantity);
- totalQuantity = entries.total_quantity;
- }
-
- if (milestonesResponse.ok) {
- const milestonesData = await milestonesResponse.json();
- setMilestones(milestonesData);
- milestonesCount = milestonesData.length;
- }
-
- if (trackerResponse.ok) {
- const { tracker: trackerData } = await trackerResponse.json();
- setTracker(trackerData ?? null);
- }
-
- setNeedsOnboarding(totalQuantity === 0 || milestonesCount === 0);
+ await loadData();
} catch (error) {
console.error('Failed to fetch data:', error);
} finally {
@@ -57,7 +58,7 @@ export default function Dashboard() {
};
fetchData();
- }, []);
+ }, [loadData]);
const handlePurchaseSuccess = async () => {
try {
@@ -88,35 +89,7 @@ export default function Dashboard() {
setSelectedMilestoneIndex(index);
};
- const handleOnboardingComplete = useCallback(async () => {
- const [entriesResponse, milestonesResponse, trackerResponse] = await Promise.all([
- fetch('/entries/summary'),
- fetch('/milestones'),
- fetch('/tracker'),
- ]);
-
- let totalQuantity = 0;
- let milestonesCount = 0;
-
- if (entriesResponse.ok) {
- const entries = await entriesResponse.json();
- setTotalShares(entries.total_quantity);
- totalQuantity = entries.total_quantity;
- }
-
- if (milestonesResponse.ok) {
- const milestonesData = await milestonesResponse.json();
- setMilestones(milestonesData);
- milestonesCount = milestonesData.length;
- }
-
- if (trackerResponse.ok) {
- const { tracker: trackerData } = await trackerResponse.json();
- setTracker(trackerData ?? null);
- }
-
- setNeedsOnboarding(totalQuantity === 0 || milestonesCount === 0);
- }, []);
+ const handleOnboardingComplete = loadData;
if (loading) {
return (
@@ -158,7 +131,6 @@ export default function Dashboard() {