diff --git a/.gitignore b/.gitignore index c7cf1fa..b184fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ yarn-error.log /.nova /.vscode /.zed +/.tmp/ diff --git a/app/Livewire/Public/Fuel/Map.php b/app/Livewire/Public/Fuel/Map.php deleted file mode 100644 index 768a7ee..0000000 --- a/app/Livewire/Public/Fuel/Map.php +++ /dev/null @@ -1,21 +0,0 @@ -dispatch('map-update', results: $results, meta: $meta, radius: $radius); - } - - public function render(): View - { - return view('livewire.public.fuel.map'); - } -} diff --git a/app/Livewire/Public/Fuel/Recommendation.php b/app/Livewire/Public/Fuel/Recommendation.php deleted file mode 100644 index 7140e95..0000000 --- a/app/Livewire/Public/Fuel/Recommendation.php +++ /dev/null @@ -1,23 +0,0 @@ -prediction = $prediction; - } - - public function render(): View - { - return view('livewire.public.fuel.recommendation'); - } -} diff --git a/app/Livewire/Public/Fuel/Search.php b/app/Livewire/Public/Fuel/Search.php deleted file mode 100644 index 372e6f2..0000000 --- a/app/Livewire/Public/Fuel/Search.php +++ /dev/null @@ -1,118 +0,0 @@ -hasSearched) { - $this->findStations(); - } - } - - public function updatedRadius(): void - { - if ($this->hasSearched) { - $this->findStations(); - } - } - - public function updatedSort(): void - { - if ($this->hasSearched) { - $this->findStations(); - } - } - - public function findStations(): void - { - $this->validate(); - - $this->apiError = null; - $this->hasSearched = false; - - $radiusKm = round($this->radius * 1.60934, 2); - - try { - $response = Http::timeout(10) - ->withHeaders(['X-Api-Key' => config('app.api_secret_key')]) - ->get(url('/api/stations'), [ - 'postcode' => $this->search, - 'fuel_type' => $this->fuelType, - 'radius' => $radiusKm, - 'sort' => $this->sort, - ]); - } catch (ConnectionException) { - $this->apiError = 'Unable to fetch stations. Please try again.'; - - return; - } - - if ($response->status() === 422) { - $errors = $response->json('errors', []); - $this->apiError = collect($errors)->flatten()->first() - ?? $response->json('message', 'Validation error.'); - - return; - } - - if (! $response->successful()) { - $this->apiError = 'Unable to fetch stations. Please try again.'; - - return; - } - - $results = $response->json('data', []); - $meta = $response->json('meta', []); - $this->hasSearched = true; - - $prediction = null; - - try { - $predictionResponse = Http::timeout(10) - ->withHeaders(['X-Api-Key' => config('app.api_secret_key')]) - ->get(url('/api/prediction')); - - if ($predictionResponse->successful()) { - $prediction = $predictionResponse->json(); - } - } catch (ConnectionException) { - // Prediction failure is silent — stations are more important - } - - $this->dispatch('stations-found', - results: $results, - meta: $meta, - prediction: $prediction, - radius: $this->radius, - ); - } - - public function render(): View - { - return view('livewire.public.fuel.search'); - } -} diff --git a/app/Livewire/Public/Fuel/StationList.php b/app/Livewire/Public/Fuel/StationList.php deleted file mode 100644 index d8166d1..0000000 --- a/app/Livewire/Public/Fuel/StationList.php +++ /dev/null @@ -1,33 +0,0 @@ -results = $results; - $this->meta = $meta; - $this->radius = $radius; - $this->hasSearched = true; - } - - public function render(): View - { - return view('livewire.public.fuel.station-list'); - } -} diff --git a/app/Livewire/Public/FuelFinder.php b/app/Livewire/Public/FuelFinder.php deleted file mode 100644 index 94cb9e0..0000000 --- a/app/Livewire/Public/FuelFinder.php +++ /dev/null @@ -1,16 +0,0 @@ -validate([ + 'password' => $this->currentPasswordRules(), + ]); + + tap(Auth::user(), $logout(...))->delete(); + + $this->redirect('/', navigate: true); + } +} diff --git a/app/Livewire/Settings/Profile.php b/app/Livewire/Settings/Profile.php new file mode 100644 index 0000000..db7bf31 --- /dev/null +++ b/app/Livewire/Settings/Profile.php @@ -0,0 +1,81 @@ +name = Auth::user()->name; + $this->email = Auth::user()->email; + } + + /** + * Update the profile information for the currently authenticated user. + */ + public function updateProfileInformation(): void + { + $user = Auth::user(); + + $validated = $this->validate($this->profileRules($user->id)); + + $user->fill($validated); + + if ($user->isDirty('email')) { + $user->email_verified_at = null; + } + + $user->save(); + + Flux::toast(variant: 'success', text: __('Profile updated.')); + } + + /** + * Send an email verification notification to the current user. + */ + public function resendVerificationNotification(): void + { + $user = Auth::user(); + + if ($user->hasVerifiedEmail()) { + $this->redirectIntended(default: route('dashboard', absolute: false)); + + return; + } + + $user->sendEmailVerificationNotification(); + + Flux::toast(text: __('A new verification link has been sent to your email address.')); + } + + #[Computed] + public function hasUnverifiedEmail(): bool + { + return Auth::user() instanceof MustVerifyEmail && ! Auth::user()->hasVerifiedEmail(); + } + + #[Computed] + public function showDeleteUser(): bool + { + return ! Auth::user() instanceof MustVerifyEmail + || (Auth::user() instanceof MustVerifyEmail && Auth::user()->hasVerifiedEmail()); + } +} diff --git a/app/Livewire/Settings/Security.php b/app/Livewire/Settings/Security.php new file mode 100644 index 0000000..3edf160 --- /dev/null +++ b/app/Livewire/Settings/Security.php @@ -0,0 +1,225 @@ +canManageTwoFactor = Features::canManageTwoFactorAuthentication(); + + if ($this->canManageTwoFactor) { + if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) { + $disableTwoFactorAuthentication(auth()->user()); + } + + $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); + $this->requiresConfirmation = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'); + } + } + + /** + * Update the password for the currently authenticated user. + */ + public function updatePassword(): void + { + try { + $validated = $this->validate([ + 'current_password' => $this->currentPasswordRules(), + 'password' => $this->passwordRules(), + ]); + } catch (ValidationException $e) { + $this->reset('current_password', 'password', 'password_confirmation'); + + throw $e; + } + + Auth::user()->update([ + 'password' => $validated['password'], + ]); + + $this->reset('current_password', 'password', 'password_confirmation'); + + Flux::toast(variant: 'success', text: __('Password updated.')); + } + + /** + * Enable two-factor authentication for the user. + */ + public function enable(EnableTwoFactorAuthentication $enableTwoFactorAuthentication): void + { + $enableTwoFactorAuthentication(auth()->user()); + + if (! $this->requiresConfirmation) { + $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); + } + + $this->loadSetupData(); + + $this->showModal = true; + } + + /** + * Load the two-factor authentication setup data for the user. + */ + private function loadSetupData(): void + { + $user = auth()->user(); + + try { + $this->qrCodeSvg = $user?->twoFactorQrCodeSvg(); + $this->manualSetupKey = decrypt($user->two_factor_secret); + } catch (Exception) { + $this->addError('setupData', 'Failed to fetch setup data.'); + + $this->reset('qrCodeSvg', 'manualSetupKey'); + } + } + + /** + * Show the two-factor verification step if necessary. + */ + public function showVerificationIfNecessary(): void + { + if ($this->requiresConfirmation) { + $this->showVerificationStep = true; + + $this->resetErrorBag(); + + return; + } + + $this->closeModal(); + } + + /** + * Close the two-factor authentication modal. + */ + public function closeModal(): void + { + $this->reset( + 'code', + 'manualSetupKey', + 'qrCodeSvg', + 'showModal', + 'showVerificationStep', + ); + + $this->resetErrorBag(); + + if (! $this->requiresConfirmation) { + $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); + } + } + + /** + * Confirm two-factor authentication for the user. + */ + public function confirmTwoFactor(ConfirmTwoFactorAuthentication $confirmTwoFactorAuthentication): void + { + $this->validate(); + + $confirmTwoFactorAuthentication(auth()->user(), $this->code); + + $this->closeModal(); + + $this->twoFactorEnabled = true; + } + + /** + * Reset two-factor verification state. + */ + public function resetVerification(): void + { + $this->reset('code', 'showVerificationStep'); + + $this->resetErrorBag(); + } + + /** + * Disable two-factor authentication for the user. + */ + public function disable(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void + { + $disableTwoFactorAuthentication(auth()->user()); + + $this->twoFactorEnabled = false; + } + + /** + * Get the current modal configuration state. + */ + public function getModalConfigProperty(): array + { + if ($this->twoFactorEnabled) { + return [ + 'title' => __('Two-factor authentication enabled'), + 'description' => __('Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.'), + 'buttonText' => __('Close'), + ]; + } + + if ($this->showVerificationStep) { + return [ + 'title' => __('Verify authentication code'), + 'description' => __('Enter the 6-digit code from your authenticator app.'), + 'buttonText' => __('Continue'), + ]; + } + + return [ + 'title' => __('Enable two-factor authentication'), + 'description' => __('To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.'), + 'buttonText' => __('Continue'), + ]; + } +} diff --git a/app/Livewire/Settings/TwoFactor/RecoveryCodes.php b/app/Livewire/Settings/TwoFactor/RecoveryCodes.php new file mode 100644 index 0000000..ae0acba --- /dev/null +++ b/app/Livewire/Settings/TwoFactor/RecoveryCodes.php @@ -0,0 +1,50 @@ +loadRecoveryCodes(); + } + + /** + * Load the recovery codes for the user. + */ + private function loadRecoveryCodes(): void + { + $user = auth()->user(); + + if ($user->hasEnabledTwoFactorAuthentication() && $user->two_factor_recovery_codes) { + try { + $this->recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true); + } catch (Exception) { + $this->addError('recoveryCodes', 'Failed to load recovery codes'); + + $this->recoveryCodes = []; + } + } + } + + /** + * Generate new recovery codes for the user. + */ + public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generateNewRecoveryCodes): void + { + $generateNewRecoveryCodes(auth()->user()); + + $this->loadRecoveryCodes(); + } +} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 6ef37bd..44e57aa 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -45,13 +45,13 @@ class FortifyServiceProvider extends ServiceProvider */ private function configureViews(): void { - Fortify::loginView(fn () => view('pages::auth.login')); - Fortify::verifyEmailView(fn () => view('pages::auth.verify-email')); - Fortify::twoFactorChallengeView(fn () => view('pages::auth.two-factor-challenge')); - Fortify::confirmPasswordView(fn () => view('pages::auth.confirm-password')); - Fortify::registerView(fn () => view('pages::auth.register')); - Fortify::resetPasswordView(fn () => view('pages::auth.reset-password')); - Fortify::requestPasswordResetLinkView(fn () => view('pages::auth.forgot-password')); + Fortify::loginView(fn () => view('livewire.auth.login')); + Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email')); + Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge')); + Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password')); + Fortify::registerView(fn () => view('livewire.auth.register')); + Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password')); + Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password')); } /** diff --git a/package-lock.json b/package-lock.json index a5a4987..a45d6ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,9 +103,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", "license": "MIT", "optional": true, "dependencies": { @@ -121,18 +121,18 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", "cpu": [ "arm64" ], @@ -146,9 +146,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -162,9 +162,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -178,9 +178,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ "x64" ], @@ -194,9 +194,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ "arm" ], @@ -210,9 +210,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ "arm64" ], @@ -229,9 +229,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], @@ -248,9 +248,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ "ppc64" ], @@ -267,9 +267,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ "s390x" ], @@ -286,9 +286,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", "cpu": [ "x64" ], @@ -305,9 +305,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", "cpu": [ "x64" ], @@ -324,9 +324,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ "arm64" ], @@ -340,25 +340,27 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", "cpu": [ "arm64" ], @@ -372,9 +374,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -388,9 +390,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", "license": "MIT" }, "node_modules/@rollup/rollup-linux-x64-gnu": { @@ -749,9 +751,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz", - "integrity": "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==", + "version": "2.10.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -794,9 +796,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001784", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", - "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", "funding": [ { "type": "opencollective", @@ -907,9 +909,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.331", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", - "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "version": "1.5.334", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", + "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -1377,9 +1379,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "funding": [ { "type": "opencollective", @@ -1420,13 +1422,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" @@ -1435,21 +1437,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, "node_modules/rxjs": { @@ -1543,13 +1545,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -1604,15 +1606,15 @@ } }, "node_modules/vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", + "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "bin": { @@ -1630,7 +1632,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", diff --git a/resources/css/app.css b/resources/css/app.css index 8b8bbdc..ff130f6 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,4 +1,3 @@ -@import 'leaflet/dist/leaflet.css'; @import 'tailwindcss'; @import '../../vendor/livewire/flux/dist/flux.css'; @@ -9,7 +8,6 @@ @custom-variant dark (&:where(.dark, .dark *)); -/* Remap Flux's zinc scale to FuelAlert's warm brown neutrals */ @theme { --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; @@ -55,18 +53,19 @@ --font-display: 'Manrope', ui-sans-serif, system-ui, sans-serif; } -@layer base { - h1, h2, h3, h4 { - font-family: var(--font-display); - letter-spacing: -0.02em; +@layer theme { + .dark { + --color-accent: var(--color-white); + --color-accent-content: var(--color-white); + --color-accent-foreground: var(--color-neutral-800); } } @layer utilities { .hero-gradient { background: - radial-gradient(circle at top right, color-mix(in oklch, var(--color-primary) 8%, transparent), transparent 50%), - radial-gradient(circle at bottom left, color-mix(in oklch, var(--color-primary) 5%, transparent), transparent 40%); + radial-gradient(circle at top right, color-mix(in oklch, var(--color-primary) 8%, transparent), transparent 50%), + radial-gradient(circle at bottom left, color-mix(in oklch, var(--color-primary) 5%, transparent), transparent 40%); } .glass-card { diff --git a/resources/views/components/action-message.blade.php b/resources/views/components/action-message.blade.php deleted file mode 100644 index d313ee6..0000000 --- a/resources/views/components/action-message.blade.php +++ /dev/null @@ -1,14 +0,0 @@ -@props([ - 'on', -]) - -
merge(['class' => 'text-sm']) }} -> - {{ $slot->isEmpty() ? __('Saved.') : $slot }} -
diff --git a/resources/views/pages/settings/layout.blade.php b/resources/views/components/settings/layout.blade.php similarity index 100% rename from resources/views/pages/settings/layout.blade.php rename to resources/views/components/settings/layout.blade.php diff --git a/resources/views/homepage.blade.php b/resources/views/homepage.blade.php deleted file mode 100644 index 7b8f24b..0000000 --- a/resources/views/homepage.blade.php +++ /dev/null @@ -1,620 +0,0 @@ - - - - - - FuelAlert | Stop Overpaying for Fuel - - - - - - - @vite(['resources/css/app.css', 'resources/js/app.js']) - - - - - {{-- Mobile App Layout (hidden on desktop) --}} -
- - {{-- Mobile Header --}} -
-
-
- -
- FuelAlert -
- -
- - {{-- Mobile Scrollable Main --}} -
- - {{-- Search & Filters --}} -
-
- - -
-
- - - -
-
- - {{-- Recommendation Card --}} -
-
-
-
-

Recommendation

-

Fill up now

-
-
-
- - - - - 80% -
- Confidence -
-
-

- Local prices are at a 30-day low. Regional wholesale trends indicate a 3p/litre increase starting Monday. Securing fuel today is highly advised. -

-
-
- - {{-- Map Section --}} -
- {{-- Simulated map grid --}} -
-
- - {{-- Map Markers --}} -
-
142.9p
- -
-
-
145.7p
- -
-
-
148.9p
- -
- - {{-- Legend --}} -
-
- - Current -
-
- - Recent -
-
- - Stale -
-
-
- - {{-- Nearby Stations --}} -
-
-

Stations Nearby

- 26 Results -
-
-
-

Tesco Superstore

-
142.9p
-
-
-

Sainsbury's Fuel

-
143.1p
-
-
-

BP Connect

-
145.7p
-
-
-

Shell V-Power

-
148.9p
-
-
-

Esso Express

-
151.2p
-
-
-
- - {{-- 14-Day Forecast (Pro) --}} -
-
-
-
-

14-Day Forecast

-
- - Pro -
-
-
- - - -
- -
-
-
-
-
- -
- - {{-- Mobile Tab Bar --}} - - -
{{-- end mobile layout --}} - - {{-- Desktop Layout (hidden on mobile) --}} - {{-- end desktop layout --}} - - - diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 466b298..b7f50df 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -1,5 +1,5 @@ - + {{ $slot }} - \ No newline at end of file + diff --git a/resources/views/layouts/app/header.blade.php b/resources/views/layouts/app/header.blade.php index e1f84d9..989b8e4 100644 --- a/resources/views/layouts/app/header.blade.php +++ b/resources/views/layouts/app/header.blade.php @@ -73,6 +73,12 @@ {{ $slot }} + @persist('toast') + + + + @endpersist + @fluxScripts diff --git a/resources/views/layouts/app/sidebar.blade.php b/resources/views/layouts/app/sidebar.blade.php index 3d04ed1..6db2170 100644 --- a/resources/views/layouts/app/sidebar.blade.php +++ b/resources/views/layouts/app/sidebar.blade.php @@ -90,6 +90,12 @@ {{ $slot }} + @persist('toast') + + + + @endpersist + @fluxScripts diff --git a/resources/views/layouts/auth/card.blade.php b/resources/views/layouts/auth/card.blade.php index db94716..8558cc7 100644 --- a/resources/views/layouts/auth/card.blade.php +++ b/resources/views/layouts/auth/card.blade.php @@ -21,6 +21,12 @@ + @persist('toast') + + + + @endpersist + @fluxScripts diff --git a/resources/views/layouts/auth/simple.blade.php b/resources/views/layouts/auth/simple.blade.php index 6e0d909..bd8bb9f 100644 --- a/resources/views/layouts/auth/simple.blade.php +++ b/resources/views/layouts/auth/simple.blade.php @@ -17,6 +17,12 @@ + @persist('toast') + + + + @endpersist + @fluxScripts diff --git a/resources/views/layouts/auth/split.blade.php b/resources/views/layouts/auth/split.blade.php index 4e9788b..c827a27 100644 --- a/resources/views/layouts/auth/split.blade.php +++ b/resources/views/layouts/auth/split.blade.php @@ -38,6 +38,12 @@ + @persist('toast') + + + + @endpersist + @fluxScripts diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php deleted file mode 100644 index ab52d83..0000000 --- a/resources/views/layouts/guest.blade.php +++ /dev/null @@ -1,9 +0,0 @@ - - - - @include('partials.head') - - - {{ $slot }} - - diff --git a/resources/views/layouts/shell.blade.php b/resources/views/layouts/shell.blade.php deleted file mode 100644 index 0942320..0000000 --- a/resources/views/layouts/shell.blade.php +++ /dev/null @@ -1,10 +0,0 @@ - - - - @include('partials.head', ['title' => $title ?? null]) - - - {{ $slot }} - @fluxScripts - - diff --git a/resources/views/pages/auth/confirm-password.blade.php b/resources/views/livewire/auth/confirm-password.blade.php similarity index 100% rename from resources/views/pages/auth/confirm-password.blade.php rename to resources/views/livewire/auth/confirm-password.blade.php diff --git a/resources/views/pages/auth/forgot-password.blade.php b/resources/views/livewire/auth/forgot-password.blade.php similarity index 100% rename from resources/views/pages/auth/forgot-password.blade.php rename to resources/views/livewire/auth/forgot-password.blade.php diff --git a/resources/views/pages/auth/login.blade.php b/resources/views/livewire/auth/login.blade.php similarity index 100% rename from resources/views/pages/auth/login.blade.php rename to resources/views/livewire/auth/login.blade.php diff --git a/resources/views/pages/auth/register.blade.php b/resources/views/livewire/auth/register.blade.php similarity index 100% rename from resources/views/pages/auth/register.blade.php rename to resources/views/livewire/auth/register.blade.php diff --git a/resources/views/pages/auth/reset-password.blade.php b/resources/views/livewire/auth/reset-password.blade.php similarity index 100% rename from resources/views/pages/auth/reset-password.blade.php rename to resources/views/livewire/auth/reset-password.blade.php diff --git a/resources/views/pages/auth/two-factor-challenge.blade.php b/resources/views/livewire/auth/two-factor-challenge.blade.php similarity index 100% rename from resources/views/pages/auth/two-factor-challenge.blade.php rename to resources/views/livewire/auth/two-factor-challenge.blade.php diff --git a/resources/views/pages/auth/verify-email.blade.php b/resources/views/livewire/auth/verify-email.blade.php similarity index 100% rename from resources/views/pages/auth/verify-email.blade.php rename to resources/views/livewire/auth/verify-email.blade.php diff --git a/resources/views/pages/settings/⚡appearance.blade.php b/resources/views/livewire/settings/appearance.blade.php similarity index 63% rename from resources/views/pages/settings/⚡appearance.blade.php rename to resources/views/livewire/settings/appearance.blade.php index 1fb2ac2..86c7ec7 100644 --- a/resources/views/pages/settings/⚡appearance.blade.php +++ b/resources/views/livewire/settings/appearance.blade.php @@ -1,22 +1,13 @@ - -
@include('partials.settings-heading') {{ __('Appearance settings') }} - + {{ __('Light') }} {{ __('Dark') }} {{ __('System') }} - +
diff --git a/resources/views/livewire/settings/delete-user-form.blade.php b/resources/views/livewire/settings/delete-user-form.blade.php new file mode 100644 index 0000000..a641407 --- /dev/null +++ b/resources/views/livewire/settings/delete-user-form.blade.php @@ -0,0 +1,34 @@ +
+
+ {{ __('Delete account') }} + {{ __('Delete your account and all of its resources') }} +
+ + + + {{ __('Delete account') }} + + + + +
+
+ {{ __('Are you sure you want to delete your account?') }} + + + {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} + +
+ + + +
+ + {{ __('Cancel') }} + + + {{ __('Delete account') }} +
+ +
+
diff --git a/resources/views/livewire/settings/profile.blade.php b/resources/views/livewire/settings/profile.blade.php new file mode 100644 index 0000000..6e9da81 --- /dev/null +++ b/resources/views/livewire/settings/profile.blade.php @@ -0,0 +1,36 @@ +
+ @include('partials.settings-heading') + + {{ __('Profile settings') }} + + +
+ + +
+ + + @if ($this->hasUnverifiedEmail) +
+ + {{ __('Your email address is unverified.') }} + + + {{ __('Click here to re-send the verification email.') }} + + + +
+ @endif +
+ +
+ {{ __('Save') }} +
+ + + @if ($this->showDeleteUser) + + @endif +
+
diff --git a/resources/views/livewire/settings/security.blade.php b/resources/views/livewire/settings/security.blade.php new file mode 100644 index 0000000..7beea38 --- /dev/null +++ b/resources/views/livewire/settings/security.blade.php @@ -0,0 +1,233 @@ +
+ @include('partials.settings-heading') + + {{ __('Security settings') }} + + +
+ + + + +
+ {{ __('Save') }} +
+ + + @if ($canManageTwoFactor) +
+ {{ __('Two-factor authentication') }} + {{ __('Manage your two-factor authentication settings') }} + +
+ @if ($twoFactorEnabled) +
+ + {{ __('You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.') }} + + +
+ + {{ __('Disable 2FA') }} + +
+ + +
+ @else +
+ + {{ __('When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.') }} + + + + {{ __('Enable 2FA') }} + +
+ @endif +
+
+ + +
+
+
+
+
+ @for ($i = 1; $i <= 5; $i++) +
+ @endfor +
+ +
+ @for ($i = 1; $i <= 5; $i++) +
+ @endfor +
+ + +
+
+ +
+ {{ $this->modalConfig['title'] }} + {{ $this->modalConfig['description'] }} +
+
+ + @if ($showVerificationStep) +
+
+ +
+ +
+ + {{ __('Back') }} + + + + {{ __('Confirm') }} + +
+
+ @else + @error('setupData') + + @enderror + +
+
+ @empty($qrCodeSvg) +
+ +
+ @else +
+
+ {!! $qrCodeSvg !!} +
+
+ @endempty +
+
+ +
+ + {{ $this->modalConfig['buttonText'] }} + +
+ +
+
+
+ + {{ __('or, enter the code manually') }} + +
+ +
+
+ @empty($manualSetupKey) +
+ +
+ @else + + + + @endempty +
+
+
+ @endif +
+
+ @endif +
+
diff --git a/resources/views/pages/settings/two-factor/⚡recovery-codes.blade.php b/resources/views/livewire/settings/two-factor/recovery-codes.blade.php similarity index 74% rename from resources/views/pages/settings/two-factor/⚡recovery-codes.blade.php rename to resources/views/livewire/settings/two-factor/recovery-codes.blade.php index 21aa4dc..9ed2674 100644 --- a/resources/views/pages/settings/two-factor/⚡recovery-codes.blade.php +++ b/resources/views/livewire/settings/two-factor/recovery-codes.blade.php @@ -1,50 +1,3 @@ -loadRecoveryCodes(); - } - - /** - * Generate new recovery codes for the user. - */ - public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generateNewRecoveryCodes): void - { - $generateNewRecoveryCodes(auth()->user()); - - $this->loadRecoveryCodes(); - } - - /** - * Load the recovery codes for the user. - */ - private function loadRecoveryCodes(): void - { - $user = auth()->user(); - - if ($user->hasEnabledTwoFactorAuthentication() && $user->two_factor_recovery_codes) { - try { - $this->recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true); - } catch (Exception) { - $this->addError('recoveryCodes', 'Failed to load recovery codes'); - - $this->recoveryCodes = []; - } - } - } -}; ?> -
- -
-
- {{ __('Delete account') }} - {{ __('Delete your account and all of its resources') }} -
- - - - {{ __('Delete account') }} - - - - -
diff --git a/resources/views/pages/settings/⚡delete-user-modal.blade.php b/resources/views/pages/settings/⚡delete-user-modal.blade.php deleted file mode 100644 index 36075fe..0000000 --- a/resources/views/pages/settings/⚡delete-user-modal.blade.php +++ /dev/null @@ -1,50 +0,0 @@ -validate([ - 'password' => $this->currentPasswordRules(), - ]); - - tap(Auth::user(), $logout(...))->delete(); - - $this->redirect('/', navigate: true); - } -}; ?> - - -
-
- {{ __('Are you sure you want to delete your account?') }} - - - {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} - -
- - - -
- - {{ __('Cancel') }} - - - - {{ __('Delete account') }} - -
- -
diff --git a/resources/views/pages/settings/⚡profile.blade.php b/resources/views/pages/settings/⚡profile.blade.php deleted file mode 100644 index 3adedfb..0000000 --- a/resources/views/pages/settings/⚡profile.blade.php +++ /dev/null @@ -1,126 +0,0 @@ -name = Auth::user()->name; - $this->email = Auth::user()->email; - } - - /** - * Update the profile information for the currently authenticated user. - */ - public function updateProfileInformation(): void - { - $user = Auth::user(); - - $validated = $this->validate($this->profileRules($user->id)); - - $user->fill($validated); - - if ($user->isDirty('email')) { - $user->email_verified_at = null; - } - - $user->save(); - - $this->dispatch('profile-updated', name: $user->name); - } - - /** - * Send an email verification notification to the current user. - */ - public function resendVerificationNotification(): void - { - $user = Auth::user(); - - if ($user->hasVerifiedEmail()) { - $this->redirectIntended(default: route('dashboard', absolute: false)); - - return; - } - - $user->sendEmailVerificationNotification(); - - Session::flash('status', 'verification-link-sent'); - } - - #[Computed] - public function hasUnverifiedEmail(): bool - { - return Auth::user() instanceof MustVerifyEmail && ! Auth::user()->hasVerifiedEmail(); - } - - #[Computed] - public function showDeleteUser(): bool - { - return ! Auth::user() instanceof MustVerifyEmail - || (Auth::user() instanceof MustVerifyEmail && Auth::user()->hasVerifiedEmail()); - } -}; ?> - -
- @include('partials.settings-heading') - - {{ __('Profile settings') }} - - -
- - -
- - - @if ($this->hasUnverifiedEmail) -
- - {{ __('Your email address is unverified.') }} - - - {{ __('Click here to re-send the verification email.') }} - - - - @if (session('status') === 'verification-link-sent') - - {{ __('A new verification link has been sent to your email address.') }} - - @endif -
- @endif -
- -
-
- - {{ __('Save') }} - -
- - - {{ __('Saved.') }} - -
- - - @if ($this->showDeleteUser) - - @endif -
-
diff --git a/resources/views/pages/settings/⚡security.blade.php b/resources/views/pages/settings/⚡security.blade.php deleted file mode 100644 index ffd3390..0000000 --- a/resources/views/pages/settings/⚡security.blade.php +++ /dev/null @@ -1,178 +0,0 @@ -canManageTwoFactor = Features::canManageTwoFactorAuthentication(); - - if ($this->canManageTwoFactor) { - if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) { - $disableTwoFactorAuthentication(auth()->user()); - } - - $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); - $this->requiresConfirmation = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'); - } - } - - /** - * Update the password for the currently authenticated user. - */ - public function updatePassword(): void - { - try { - $validated = $this->validate([ - 'current_password' => $this->currentPasswordRules(), - 'password' => $this->passwordRules(), - ]); - } catch (ValidationException $e) { - $this->reset('current_password', 'password', 'password_confirmation'); - - throw $e; - } - - Auth::user()->update([ - 'password' => $validated['password'], - ]); - - $this->reset('current_password', 'password', 'password_confirmation'); - - $this->dispatch('password-updated'); - } - - /** - * Handle the two-factor authentication enabled event. - */ - #[On('two-factor-enabled')] - public function onTwoFactorEnabled(): void - { - $this->twoFactorEnabled = true; - } - - /** - * Disable two-factor authentication for the user. - */ - public function disable(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void - { - $disableTwoFactorAuthentication(auth()->user()); - - $this->twoFactorEnabled = false; - } -}; ?> - -
- @include('partials.settings-heading') - - {{ __('Security settings') }} - - -
- - - - -
-
- - {{ __('Save') }} - -
- - - {{ __('Saved.') }} - -
- - - @if ($canManageTwoFactor) -
- {{ __('Two-factor authentication') }} - {{ __('Manage your two-factor authentication settings') }} - -
- @if ($twoFactorEnabled) -
- - {{ __('You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.') }} - - -
- - {{ __('Disable 2FA') }} - -
- - -
- @else -
- - {{ __('When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.') }} - - - - - {{ __('Enable 2FA') }} - - - - -
- @endif -
-
- @endif -
-
diff --git a/resources/views/pages/settings/⚡two-factor-setup-modal.blade.php b/resources/views/pages/settings/⚡two-factor-setup-modal.blade.php deleted file mode 100644 index 9708e96..0000000 --- a/resources/views/pages/settings/⚡two-factor-setup-modal.blade.php +++ /dev/null @@ -1,304 +0,0 @@ -requiresConfirmation = $requiresConfirmation; - } - - #[On('start-two-factor-setup')] - public function startTwoFactorSetup(): void - { - $enableTwoFactorAuthentication = app(EnableTwoFactorAuthentication::class); - $enableTwoFactorAuthentication(auth()->user()); - - $this->loadSetupData(); - } - - /** - * Load the two-factor authentication setup data for the user. - */ - private function loadSetupData(): void - { - $user = auth()->user()?->fresh(); - - try { - if (! $user || ! $user->two_factor_secret) { - throw new Exception('Two-factor setup secret is not available.'); - } - - $this->qrCodeSvg = $user->twoFactorQrCodeSvg(); - $this->manualSetupKey = decrypt($user->two_factor_secret); - } catch (Exception) { - $this->addError('setupData', 'Failed to fetch setup data.'); - - $this->reset('qrCodeSvg', 'manualSetupKey'); - } - } - - /** - * Show the two-factor verification step if necessary. - */ - public function showVerificationIfNecessary(): void - { - if ($this->requiresConfirmation) { - $this->showVerificationStep = true; - - $this->resetErrorBag(); - - return; - } - - $this->closeModal(); - $this->dispatch('two-factor-enabled'); - } - - /** - * Confirm two-factor authentication for the user. - */ - public function confirmTwoFactor(ConfirmTwoFactorAuthentication $confirmTwoFactorAuthentication): void - { - $this->validate(); - - $confirmTwoFactorAuthentication(auth()->user(), $this->code); - - $this->setupComplete = true; - - $this->closeModal(); - - $this->dispatch('two-factor-enabled'); - } - - /** - * Reset two-factor verification state. - */ - public function resetVerification(): void - { - $this->reset('code', 'showVerificationStep'); - - $this->resetErrorBag(); - } - - /** - * Close the two-factor authentication modal. - */ - public function closeModal(): void - { - $this->reset( - 'code', - 'manualSetupKey', - 'qrCodeSvg', - 'showVerificationStep', - 'setupComplete', - ); - - $this->resetErrorBag(); - } - - /** - * Get the current modal configuration state. - */ - public function getModalConfigProperty(): array - { - if ($this->setupComplete) { - return [ - 'title' => __('Two-factor authentication enabled'), - 'description' => __('Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.'), - 'buttonText' => __('Close'), - ]; - } - - if ($this->showVerificationStep) { - return [ - 'title' => __('Verify authentication code'), - 'description' => __('Enter the 6-digit code from your authenticator app.'), - 'buttonText' => __('Continue'), - ]; - } - - return [ - 'title' => __('Enable two-factor authentication'), - 'description' => __('To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.'), - 'buttonText' => __('Continue'), - ]; - } -}; ?> - - -
-
-
-
-
- @for ($i = 1; $i <= 5; $i++) -
- @endfor -
- -
- @for ($i = 1; $i <= 5; $i++) -
- @endfor -
- - -
-
- -
- {{ $this->modalConfig['title'] }} - {{ $this->modalConfig['description'] }} -
-
- - @if ($showVerificationStep) -
-
- -
- -
- - {{ __('Back') }} - - - - {{ __('Confirm') }} - -
-
- @else - @error('setupData') - - @enderror - -
-
- @empty($qrCodeSvg) -
- -
- @else -
-
- {!! $qrCodeSvg !!} -
-
- @endempty -
-
- -
- - {{ $this->modalConfig['buttonText'] }} - -
- -
-
-
- - {{ __('or, enter the code manually') }} - -
- -
-
- @empty($manualSetupKey) -
- -
- @else - - - - @endempty -
-
-
- @endif -
-
diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php index f153994..52703a4 100644 --- a/resources/views/partials/head.blade.php +++ b/resources/views/partials/head.blade.php @@ -10,7 +10,7 @@ - + @vite(['resources/css/app.css', 'resources/js/app.js']) @fluxAppearance diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100644 index 0000000..25a0178 --- /dev/null +++ b/resources/views/welcome.blade.php @@ -0,0 +1,204 @@ + + + + + + + {{ __('Welcome') }} - {{ config('app.name', 'Laravel') }} + + + + + + + + + + + + + +
+ @if (Route::has('login')) + + @endif +
+
+
+
+

Let's get started

+

Laravel has an incredibly rich ecosystem.
We suggest starting with the following.

+ + +
+
+ {{-- Laravel Logo --}} + + + + + + + + + + + {{-- 13 --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + @if (Route::has('login')) + + @endif + + diff --git a/routes/settings.php b/routes/settings.php index e4f2f91..d533e34 100644 --- a/routes/settings.php +++ b/routes/settings.php @@ -1,25 +1,28 @@ group(function () { - Route::redirect('settings', 'settings/profile'); + Route::redirect('settings', 'settings/profile'); - Route::livewire('settings/profile', 'pages::settings.profile')->name('profile.edit'); + Route::livewire('settings/profile', Profile::class)->name('profile.edit'); }); Route::middleware(['auth', 'verified'])->group(function () { - Route::livewire('settings/appearance', 'pages::settings.appearance')->name('appearance.edit'); + Route::livewire('settings/appearance', Appearance::class)->name('appearance.edit'); - Route::livewire('settings/security', 'pages::settings.security') - ->middleware( - when( - Features::canManageTwoFactorAuthentication() - && Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'), - ['password.confirm'], - [], - ), - ) - ->name('security.edit'); + Route::livewire('settings/security', Security::class) + ->middleware( + when( + Features::canManageTwoFactorAuthentication() + && Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'), + ['password.confirm'], + [], + ), + ) + ->name('security.edit'); }); diff --git a/routes/web.php b/routes/web.php index 693348a..4f99afd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,12 +3,13 @@ use App\Livewire\Public\FuelFinder; use Illuminate\Support\Facades\Route; -Route::view('/', 'homepage')->name('home'); -Route::get('/fuel-finder', FuelFinder::class)->name('fuel-finder'); +//Route::get('/fuel-finder', FuelFinder::class)->name('fuel-finder'); + +Route::view('/', 'welcome')->name('home'); Route::middleware(['auth', 'verified'])->group(function () { - Route::view('dashboard', 'dashboard')->name('dashboard'); + Route::view('dashboard', 'dashboard')->name('dashboard'); }); -require __DIR__.'/settings.php'; +require __DIR__.'/settings.php'; \ No newline at end of file