From 0aac755262d113232f11a7327c841e6ebef1bcb1 Mon Sep 17 00:00:00 2001 From: Fabian ternis Date: Sat, 14 Mar 2026 21:22:16 +0100 Subject: [PATCH 1/2] stuff ... --- .../Settings/PasswordController.php | 32 +++++ .../Settings/ProfileController.php | 57 ++++++++ resources/css/app.css | 42 +++++- resources/views/.DS_Store | Bin 6148 -> 0 bytes .../views/components/app-layout.blade.php | 116 +++++++++------ .../views/components/auth-layout.blade.php | 27 ++-- .../components/settings-layout.blade.php | 28 ++++ .../views/components/theme-toggle.blade.php | 59 ++++++++ resources/views/pages/dashboard.blade.php | 2 +- resources/views/settings/layout.blade.php | 31 ++++ resources/views/settings/password.blade.php | 45 ++++++ resources/views/settings/profile.blade.php | 134 ++++++++++++++++++ resources/views/welcome.blade.php | 101 +++++++++---- routes/settings.php | 30 ++-- routes/web.php | 4 +- 15 files changed, 597 insertions(+), 111 deletions(-) create mode 100644 app/Http/Controllers/Settings/PasswordController.php create mode 100644 app/Http/Controllers/Settings/ProfileController.php delete mode 100644 resources/views/.DS_Store create mode 100644 resources/views/components/settings-layout.blade.php create mode 100644 resources/views/components/theme-toggle.blade.php create mode 100644 resources/views/settings/layout.blade.php create mode 100644 resources/views/settings/password.blade.php create mode 100644 resources/views/settings/profile.blade.php diff --git a/app/Http/Controllers/Settings/PasswordController.php b/app/Http/Controllers/Settings/PasswordController.php new file mode 100644 index 0000000..b04ada5 --- /dev/null +++ b/app/Http/Controllers/Settings/PasswordController.php @@ -0,0 +1,32 @@ +validateWithBag('updatePassword', [ + 'current_password' => ['required', 'current_password'], + 'password' => ['required', Password::defaults(), 'confirmed'], + ]); + + $request->user()->update([ + 'password' => Hash::make($validated['password']), + ]); + + return back()->with('status', 'password-updated'); + } +} diff --git a/app/Http/Controllers/Settings/ProfileController.php b/app/Http/Controllers/Settings/ProfileController.php new file mode 100644 index 0000000..e31202c --- /dev/null +++ b/app/Http/Controllers/Settings/ProfileController.php @@ -0,0 +1,57 @@ + $request->user(), + 'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail, + 'status' => session('status'), + ]); + } + + public function update(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', + 'unique:users,email,' . $request->user()->id], + ]); + + $request->user()->fill($data); + + if ($request->user()->isDirty('email')) { + $request->user()->email_verified_at = null; + } + + $request->user()->save(); + + return back()->with('status', 'profile-updated'); + } + + public function destroy(Request $request): RedirectResponse + { + $request->validateWithBag('userDeletion', [ + 'password' => ['required', 'current_password'], + ]); + + $user = $request->user(); + Auth::logout(); + $user->delete(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return redirect('/'); + } +} diff --git a/resources/css/app.css b/resources/css/app.css index 5b11777..9e3cae7 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -11,6 +11,8 @@ @theme { --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + + /* ── Zinc scale ───────────────────────────────────────────────────── */ --color-zinc-50: #fafafa; --color-zinc-100: #f5f5f5; --color-zinc-200: #e5e5e5; @@ -22,16 +24,31 @@ --color-zinc-800: #262626; --color-zinc-900: #171717; --color-zinc-950: #0a0a0a; - --color-accent: var(--color-neutral-800); - --color-accent-content: var(--color-neutral-800); + + /* ── Brand color (indigo-based, customize here) ───────────────────── */ + --color-brand-50: #eef2ff; + --color-brand-100: #e0e7ff; + --color-brand-200: #c7d2fe; + --color-brand-300: #a5b4fc; + --color-brand-400: #818cf8; + --color-brand-500: #6366f1; + --color-brand-600: #4f46e5; + --color-brand-700: #4338ca; + --color-brand-800: #3730a3; + --color-brand-900: #312e81; + --color-brand-950: #1e1b4b; + + /* ── Flux accent (maps to brand) ─────────────────────────────────── */ + --color-accent: var(--color-brand-500); + --color-accent-content: var(--color-brand-500); --color-accent-foreground: var(--color-white); } @layer theme { .dark { - --color-accent: var(--color-white); - --color-accent-content: var(--color-white); - --color-accent-foreground: var(--color-neutral-800); + --color-accent: var(--color-brand-400); + --color-accent-content: var(--color-brand-400); + --color-accent-foreground: var(--color-zinc-950); } } @@ -43,8 +60,19 @@ ::file-selector-button { border-color: var(--color-gray-200, currentColor); } + + /* Smooth theme transition */ + html { + color-scheme: light dark; + } + + html.transitioning, + html.transitioning * { + transition: background-color 0.2s ease, color 0.15s ease, border-color 0.15s ease !important; + } } +/* ── Flux field/label overrides ─────────────────────────────────────────── */ [data-flux-field]:not(ui-radio, ui-checkbox) { @apply grid gap-2; } @@ -59,7 +87,7 @@ select:focus[data-flux-control] { @apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground; } -/* ── CodeMirror 6 ─────────────────────────────────────────────────────────── */ +/* ── CodeMirror 6 ───────────────────────────────────────────────────────── */ .cm-editor { height: 100%; font-size: 0.85rem; @@ -67,5 +95,5 @@ select:focus[data-flux-control] { .cm-scroller { overflow: auto; - font-family: ui-monospace, 'Cascadia Code', 'JetBrains Mono', monospace; + font-family: ui-monospace, 'Cascadia Code', 'JetBrains Mono', Menlo, monospace; } diff --git a/resources/views/.DS_Store b/resources/views/.DS_Store deleted file mode 100644 index 0a1117dc4b9a86bc9d64046a9d30d686ae3b1dd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%Sr=55UkccEO^P$K2&PB)j&(~cC70#ZNSn7B#o~6xTcpE= zM2%8F3Y;r&nah>e|6BS(|9?)>N(xAUf2DwRo4d`LPpaBFdz{zWM!%qyGH=5y{3g;QeC84o&9KLf6dObYz90^bdj8mj;R diff --git a/resources/views/components/app-layout.blade.php b/resources/views/components/app-layout.blade.php index eb49845..32e8641 100644 --- a/resources/views/components/app-layout.blade.php +++ b/resources/views/components/app-layout.blade.php @@ -7,64 +7,90 @@ {{ $title }} + {{-- Dark mode bootstrap: runs before CSS paint to prevent flash --}} + @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles @stack('head') - + - - + - @if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
- @endif +@if (session('success')) +
+ {{ session('success') }} + +
+@endif + +@if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+@endif -
{{ $slot }}
+
{{ $slot }}
- @livewireScripts - @stack('scripts') +@livewireScripts +@stack('scripts') diff --git a/resources/views/components/auth-layout.blade.php b/resources/views/components/auth-layout.blade.php index 6bc5859..78e8ac8 100644 --- a/resources/views/components/auth-layout.blade.php +++ b/resources/views/components/auth-layout.blade.php @@ -7,23 +7,34 @@ {{ $title }} — {{ config('app.name') }} + @vite(['resources/css/app.css', 'resources/js/app.js']) - + -
-
- - - {{ config('app.name') }} - +
+ -
+
{{ $slot }}
+ @livewireScripts diff --git a/resources/views/components/settings-layout.blade.php b/resources/views/components/settings-layout.blade.php new file mode 100644 index 0000000..ab3ef7e --- /dev/null +++ b/resources/views/components/settings-layout.blade.php @@ -0,0 +1,28 @@ +@props(['title' => 'Settings']) + +
+

Settings

+ +
+ + +
{{ $slot }}
+
+
+
diff --git a/resources/views/components/theme-toggle.blade.php b/resources/views/components/theme-toggle.blade.php new file mode 100644 index 0000000..a1030df --- /dev/null +++ b/resources/views/components/theme-toggle.blade.php @@ -0,0 +1,59 @@ +{{-- + Theme Toggle Button + Uses Alpine + localStorage. + Place anywhere inside a layout that already has Alpine running. +--}} +
+ {{-- Toggle button --}} + +
diff --git a/resources/views/pages/dashboard.blade.php b/resources/views/pages/dashboard.blade.php index 118a1f0..ed1b506 100644 --- a/resources/views/pages/dashboard.blade.php +++ b/resources/views/pages/dashboard.blade.php @@ -26,7 +26,7 @@
{{ $page->name }} - {{ $page->slug }} diff --git a/resources/views/settings/layout.blade.php b/resources/views/settings/layout.blade.php new file mode 100644 index 0000000..d6e2bfc --- /dev/null +++ b/resources/views/settings/layout.blade.php @@ -0,0 +1,31 @@ + +
+

Settings

+ +
+ {{-- Sidebar nav --}} + + + {{-- Content --}} +
+ {{ $slot }} +
+
+
+
diff --git a/resources/views/settings/password.blade.php b/resources/views/settings/password.blade.php new file mode 100644 index 0000000..2cf1059 --- /dev/null +++ b/resources/views/settings/password.blade.php @@ -0,0 +1,45 @@ + + + @if (session('status') === 'password-updated') +
+ Password updated successfully. +
+ @endif + +
+

Change Password

+ +
+ @csrf @method('PUT') + + @foreach ([ + ['current_password', 'Current Password', 'current-password'], + ['password', 'New Password', 'new-password'], + ['password_confirmation', 'Confirm New Password', 'new-password'], + ] as [$name, $label, $autocomplete]) +
+ + + @error($name, 'updatePassword') +

{{ $message }}

+ @enderror +
+ @endforeach + + +
+
+ +
diff --git a/resources/views/settings/profile.blade.php b/resources/views/settings/profile.blade.php new file mode 100644 index 0000000..0be26cb --- /dev/null +++ b/resources/views/settings/profile.blade.php @@ -0,0 +1,134 @@ + + + @if (session('status') === 'profile-updated') +
+ Profile updated successfully. +
+ @endif + + {{-- Profile form --}} +
+

Profile Information

+ +
+ @csrf @method('PATCH') + +
+ + + @error('name') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('email') +

{{ $message }}

+ @enderror + + @if ($mustVerifyEmail && ! $user->hasVerifiedEmail()) +
+ Your email is unverified. + + @csrf + + +
+ @endif +
+ +
+ +
+ +
+ + {{-- Appearance --}} +
+

Appearance

+

Choose your preferred colour scheme.

+ +
+ @foreach ([ + ['light', 'Light', 'sun'], + ['dark', 'Dark', 'moon'], + ['system', 'System', 'computer-desktop'], + ] as [$val, $label, $icon]) + + @endforeach +
+
+ + {{-- Danger zone --}} +
+

Delete Account

+

+ Once your account is deleted, all pages and files will be permanently removed. +

+ +
+ @csrf @method('DELETE') + + +
+ @error('userDeletion.password') +

{{ $message }}

+ @enderror +
+ +
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index d96bf6c..0623089 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -1,49 +1,96 @@ - + - {{ config('app.name') }} — Host your static pages instantly + {{ config('app.name') }} — Host static pages instantly + @vite(['resources/css/app.css']) - + -
+ {{-- Nav --}} + + + {{-- Hero --}} +
+
+ + Now in beta +
-

- {{ config('app.name') }} + Host static HTML pages instantly

-

- Create and host static HTML pages instantly.
- Each page gets its own subdomain and path URL. +

+ Create and manage static mini-sites with a built-in code editor. + Every page gets its own subdomain and path URL — live in seconds.

- -
- @foreach ([ - ['⚡', 'Instant Deploy', 'Edit files in-browser — changes go live immediately.'], - ['🌐', 'Dual URLs', 'Subdomain + path-based access. Choose your style.'], - ['🗂', 'File Manager', 'Upload images, videos, code. Manage everything visually.'], - ] as [$icon, $title, $desc]) -
-
{{ $icon }}
- {{ $title }} -

{{ $desc }}

-
- @endforeach + {{-- Feature cards --}} +
+ @foreach ([ + ['⚡', 'Instant Deploy', 'Edit in the browser — changes go live immediately.'], + ['🌐', 'Dual URLs', 'Subdomain + path-based access. Both always work.'], + ['🗂', 'Full File Manager', 'Upload images, video, code. Manage everything visually.'], + ] as [$icon, $title, $desc]) +
+
{{ $icon }}
+

+ {{ $title }} +

+

{{ $desc }}

- + @endforeach
+ @livewireScripts diff --git a/routes/settings.php b/routes/settings.php index 618957b..762c8a0 100644 --- a/routes/settings.php +++ b/routes/settings.php @@ -1,28 +1,16 @@ group(function () { - Route::redirect('settings', 'settings/profile'); +Route::middleware(['auth', 'verified'])->prefix('settings')->name('settings.')->group(function () { + Route::redirect('/', '/settings/profile')->name('index'); - Route::livewire('settings/profile', Profile::class)->name('profile.edit'); -}); - -Route::middleware(['auth', 'verified'])->group(function () { - Route::livewire('settings/appearance', Appearance::class)->name('appearance.edit'); + Route::get('/profile', [ProfileController::class, 'edit'])->name('profile'); + Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); + Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); - Route::livewire('settings/security', Security::class) - ->middleware( - when( - Features::canManageTwoFactorAuthentication() - && Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'), - ['password.confirm'], - [], - ), - ) - ->name('security.edit'); + Route::get('/password', [PasswordController::class, 'edit'])->name('password'); + Route::put('/password', [PasswordController::class, 'update'])->name('password.update'); }); diff --git a/routes/web.php b/routes/web.php index c32b862..1e34978 100644 --- a/routes/web.php +++ b/routes/web.php @@ -57,6 +57,8 @@ ->name('page.subdomain'); }); +require __DIR__ . '/auth.php'; +require __DIR__ . '/settings.php'; /* |-------------------------------------------------------------------------- | Path-based Page Serving (catch-all – keep last) @@ -66,5 +68,3 @@ ->where('slug', '[a-z0-9\-_]+') ->where('path', '.*') ->name('page.path'); - -require __DIR__ . '/auth.php'; From fa2c50d9bf763f6723deb6ea783e8516e66b13c3 Mon Sep 17 00:00:00 2001 From: Fabian ternis Date: Sat, 14 Mar 2026 21:30:54 +0100 Subject: [PATCH 2/2] fix: FileManager "dirty" stuff --- app/Livewire/FileManager.php | 1 + resources/js/file-manager.js | 18 ++++++++- .../views/livewire/file-manager.blade.php | 37 +++++++++++-------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/app/Livewire/FileManager.php b/app/Livewire/FileManager.php index b8d70d1..ff06109 100644 --- a/app/Livewire/FileManager.php +++ b/app/Livewire/FileManager.php @@ -167,6 +167,7 @@ public function saveFile(string $content): void $this->fileContent = $content; $this->isDirty = false; + $this->dispatch('file-saved'); // Alpine resets dirty indicator $this->showFlash('Saved ✓'); } diff --git a/resources/js/file-manager.js b/resources/js/file-manager.js index 6ad36a4..8482604 100644 --- a/resources/js/file-manager.js +++ b/resources/js/file-manager.js @@ -104,7 +104,9 @@ function createEditor(content, lang) { EditorView.updateListener.of(update => { if (update.selectionSet) publishCursor(update.state); if (!update.docChanged) return; - getWire()?.set('isDirty', true); + // Dispatch to Alpine only — never call $wire.set() here. + // $wire.set() triggers a full Livewire re-render which destroys #cm-host. + window.dispatchEvent(new CustomEvent('cm-dirty')); clearTimeout(saveDebounce); saveDebounce = setTimeout(triggerSave, 1200); }), @@ -135,6 +137,9 @@ document.addEventListener('alpine:init', () => { // Drag-and-drop state dragOver: false, + // Dirty state — tracked purely in Alpine, never via $wire.set() + dirty: false, + // ── Modal helpers ──────────────────────────────────────────────── openModal(name) { this.modal = name; }, closeModal() { this.modal = null; }, @@ -168,12 +173,21 @@ document.addEventListener('alpine:init', () => { // ── Init ───────────────────────────────────────────────────────── init() { - // CodeMirror events + // CodeMirror cursor position window.addEventListener('cm-cursor', e => { this.cursor = e.detail; }); + + // Editor content changed — mark dirty in Alpine only (never via $wire.set) + window.addEventListener('cm-dirty', () => { this.dirty = true; }); + + // File opened — reset dirty and create editor window.addEventListener('load-editor', e => { + this.dirty = false; createEditor(e.detail.content, e.detail.language); }); + // Livewire saved successfully — reset dirty + window.addEventListener('file-saved', () => { this.dirty = false; }); + // Livewire → Alpine modal control window.addEventListener('open-modal', e => this.openModal(e.detail?.name)); window.addEventListener('close-modal', () => this.closeModal()); diff --git a/resources/views/livewire/file-manager.blade.php b/resources/views/livewire/file-manager.blade.php index df45709..49b4ec9 100644 --- a/resources/views/livewire/file-manager.blade.php +++ b/resources/views/livewire/file-manager.blade.php @@ -43,12 +43,11 @@ class="flex items-center gap-1 text-xs text-zinc-500 hover:text-zinc-100 @endif
- @if ($isDirty) - - - Unsaved - - @endif + + + Unsaved +