diff --git a/app/Http/Controllers/AssetController.php b/app/Http/Controllers/AssetController.php index 16adc1b..245f7ec 100644 --- a/app/Http/Controllers/AssetController.php +++ b/app/Http/Controllers/AssetController.php @@ -24,6 +24,7 @@ public function current(): JsonResponse return response()->json([ 'asset' => $asset, + 'price_tracking_enabled' => $user?->price_tracking_enabled ?? false, ]); } diff --git a/app/Http/Controllers/Pricing/PricingController.php b/app/Http/Controllers/Pricing/PricingController.php index 2a484be..6af8651 100644 --- a/app/Http/Controllers/Pricing/PricingController.php +++ b/app/Http/Controllers/Pricing/PricingController.php @@ -36,7 +36,11 @@ public function update(Request $request) return back()->withErrors(['asset' => 'Please set an asset first.']); } - $assetPrice = AssetPrice::updatePrice($user->asset_id, $validated['date'], $validated['price']); + AssetPrice::updatePrice($user->asset_id, $validated['date'], $validated['price']); + + if (!$user->price_tracking_enabled) { + $user->update(['price_tracking_enabled' => true]); + } return back()->with('success', 'Asset price updated successfully!'); } diff --git a/app/Models/User.php b/app/Models/User.php index e8d3f00..eb0dc63 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -26,6 +26,7 @@ class User extends Authenticatable 'email', 'password', 'asset_id', + 'price_tracking_enabled', ]; /** @@ -43,6 +44,7 @@ protected function casts(): array return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'price_tracking_enabled' => 'boolean', ]; } diff --git a/database/migrations/0001_01_01_000001_create_users_table.php b/database/migrations/0001_01_01_000001_create_users_table.php index 9cb9538..4f7f9b6 100644 --- a/database/migrations/0001_01_01_000001_create_users_table.php +++ b/database/migrations/0001_01_01_000001_create_users_table.php @@ -18,6 +18,7 @@ public function up(): void $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->foreignId('asset_id')->nullable()->constrained()->onDelete('set null'); + $table->boolean('price_tracking_enabled')->default(false); $table->rememberToken(); $table->timestamps(); diff --git a/docker/dev/podman/Dockerfile b/docker/dev/podman/Dockerfile index a8ba300..f22cb15 100644 --- a/docker/dev/podman/Dockerfile +++ b/docker/dev/podman/Dockerfile @@ -22,22 +22,6 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /var/www/html -# Copy composer files and install PHP dependencies -COPY composer.json ./ -RUN composer install --no-dev --optimize-autoloader --no-scripts - -# Copy package.json and install Node dependencies -COPY package*.json ./ -RUN npm ci - -# Copy application code -COPY . . - -# Set permissions -RUN chown -R www-data:www-data /var/www/html \ - && chmod -R 755 /var/www/html/storage \ - && chmod -R 755 /var/www/html/bootstrap/cache - # Copy and set up container start script COPY docker/dev/podman/container-start.sh /usr/local/bin/container-start.sh RUN chmod +x /usr/local/bin/container-start.sh diff --git a/docker/dev/podman/container-start.sh b/docker/dev/podman/container-start.sh index 4cfc0f6..d508480 100644 --- a/docker/dev/podman/container-start.sh +++ b/docker/dev/podman/container-start.sh @@ -15,6 +15,10 @@ if ! grep -q "APP_KEY=base64:" /var/www/html/.env; then sed -i "s/APP_KEY=/APP_KEY=$NEW_KEY/" /var/www/html/.env fi +# Install dependencies if needed +[ ! -f vendor/autoload.php ] && composer install --no-interaction +[ ! -d node_modules/.bin ] && npm install + # Run migrations php artisan migrate --force diff --git a/docker/dev/podman/docker-compose.yml b/docker/dev/podman/docker-compose.yml index e874056..9d61b6f 100644 --- a/docker/dev/podman/docker-compose.yml +++ b/docker/dev/podman/docker-compose.yml @@ -21,8 +21,8 @@ services: - VITE_PORT=5173 volumes: - ../../../:/var/www/html:Z - - /var/www/html/node_modules - - /var/www/html/vendor + - app_node_modules:/var/www/html/node_modules + - app_vendor:/var/www/html/vendor ports: - "8000:8000" - "5173:5173" @@ -69,4 +69,6 @@ networks: volumes: db_data: - driver: local \ No newline at end of file + driver: local + app_node_modules: + app_vendor: \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b8cf021..69890d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "site", + "name": "html", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/js/components/Display/StatsBox.tsx b/resources/js/components/Display/StatsBox.tsx index 29da41e..364174a 100644 --- a/resources/js/components/Display/StatsBox.tsx +++ b/resources/js/components/Display/StatsBox.tsx @@ -27,6 +27,7 @@ interface StatsBoxProps { onAddMilestone?: () => void; onUpdatePrice?: () => void; assetSymbol?: string; + priceTrackingEnabled?: boolean; } export default function StatsBox({ @@ -38,7 +39,8 @@ export default function StatsBox({ onAddPurchase, onAddMilestone, onUpdatePrice, - assetSymbol + assetSymbol, + priceTrackingEnabled = false, }: StatsBoxProps) { const [isDropdownOpen, setIsDropdownOpen] = useState(false); @@ -78,7 +80,7 @@ export default function StatsBox({ Stats
- {stats.currentPrice && ( + {priceTrackingEnabled && stats.currentPrice && (
{assetSymbol ?? 'PRICE'}: {formatCurrencyDetailed(stats.currentPrice)}
@@ -119,7 +121,7 @@ export default function StatsBox({ ADD MILESTONE )} - {onUpdatePrice && ( + {priceTrackingEnabled && onUpdatePrice && ( + )} +
+ ); +} + interface OnboardingStep { id: string; title: string; @@ -43,9 +81,9 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) { { id: 'price', title: 'CURRENT PRICE', - description: 'Set current asset price', + description: 'Set current asset price (optional)', completed: false, - required: true, + required: false, }, ]); @@ -76,26 +114,23 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) { const priceData = await priceResponse.json(); const hasPrice = !!priceData.current_price; - setSteps(prev => prev.map(step => ({ + const freshSteps = steps.map(step => ({ ...step, - completed: + completed: (step.id === 'asset' && hasAsset) || (step.id === 'purchases' && hasPurchases) || (step.id === 'milestones' && hasMilestones) || (step.id === 'price' && hasPrice) - }))); + })); - // Find first incomplete required step - const firstIncompleteStep = steps.findIndex(step => - step.required && !step.completed - ); - - if (firstIncompleteStep !== -1) { - setCurrentStep(firstIncompleteStep); + setSteps(freshSteps); + + const firstIncompleteRequired = freshSteps.findIndex(s => s.required && !s.completed); + + if (firstIncompleteRequired !== -1) { + setCurrentStep(firstIncompleteRequired); } else { - // All required steps complete, check if we should call onComplete - const allRequiredComplete = steps.filter(s => s.required).every(s => s.completed); - if (allRequiredComplete && onComplete) { + if (onComplete) { onComplete(); } } @@ -110,23 +145,8 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) { index === currentStep ? { ...step, completed: true } : step )); - // Refresh onboarding status + // Refresh onboarding status — handles setCurrentStep and onComplete await checkOnboardingStatus(); - - // Move to next incomplete step or complete onboarding - const nextIncompleteStep = steps.findIndex((step, index) => - index > currentStep && step.required && !step.completed - ); - - if (nextIncompleteStep !== -1) { - setCurrentStep(nextIncompleteStep); - } else { - // All required steps complete - const allRequiredComplete = steps.filter(s => s.required).every(s => s.completed); - if (allRequiredComplete && onComplete) { - onComplete(); - } - } }; const handleStepSelect = (stepIndex: number) => { @@ -157,8 +177,9 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) { ); case 'price': return ( - ); default: @@ -177,7 +198,7 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) { [SYSTEM] ONBOARDING SEQUENCE

- Initialize your asset tracking system + Set up your tracker

diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 6463a17..71acacf 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -42,6 +42,7 @@ export default function Dashboard() { const [loading, setLoading] = useState(true); const [needsOnboarding, setNeedsOnboarding] = useState(false); const [currentAsset, setCurrentAsset] = useState(null); + const [priceTrackingEnabled, setPriceTrackingEnabled] = useState(false); // Fetch purchase summary, current price, milestones, and check onboarding useEffect(() => { @@ -72,6 +73,7 @@ export default function Dashboard() { if (assetResponse.ok) { const assetData = await assetResponse.json(); setCurrentAsset(assetData.asset); + setPriceTrackingEnabled(assetData.price_tracking_enabled ?? false); } // Check if onboarding is needed after all data is loaded @@ -238,6 +240,7 @@ export default function Dashboard() { if (assetResponse.ok) { const assetData = await assetResponse.json(); setCurrentAsset(assetData.asset); + setPriceTrackingEnabled(assetData.price_tracking_enabled ?? false); } }; @@ -287,6 +290,7 @@ export default function Dashboard() { onAddMilestone={() => setActiveForm('milestone')} onUpdatePrice={() => setActiveForm('price')} assetSymbol={currentAsset?.symbol} + priceTrackingEnabled={priceTrackingEnabled} /> diff --git a/shell.nix b/shell.nix index 2c52855..da6d35a 100644 --- a/shell.nix +++ b/shell.nix @@ -60,9 +60,9 @@ pkgs.mkShell { } dev-rebuild() { - echo "Rebuilding services (down -v + up)..." + echo "Rebuilding services (down -v + up --build)..." podman-compose -f $COMPOSE_FILE down -v - PODMAN_USERNS=keep-id podman-compose -f $COMPOSE_FILE up -d "$@" + PODMAN_USERNS=keep-id podman-compose -f $COMPOSE_FILE up -d --build "$@" echo "" podman-compose -f $COMPOSE_FILE ps echo "" diff --git a/vite.config.ts b/vite.config.ts index 290d90e..9bef215 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,18 @@ import { resolve } from 'node:path'; import { defineConfig } from 'vite'; export default defineConfig({ + server: { + host: '0.0.0.0', + port: 5173, + hmr: { + host: 'localhost', + clientPort: 5173, + }, + watch: { + usePolling: true, + ignored: ['**/storage/framework/views/**'], + }, + }, plugins: [ laravel({ input: ['resources/css/app.css', 'resources/js/app.tsx'],