diff --git a/app/Livewire/Pages/Profile/PersonalInfo.php b/app/Livewire/Pages/Profile/PersonalInfo.php new file mode 100644 index 0000000..4be3670 --- /dev/null +++ b/app/Livewire/Pages/Profile/PersonalInfo.php @@ -0,0 +1,39 @@ +name = auth()->user()->name; + $this->email = auth()->user()->email; + } + + public function save(): void + { + $this->validate([ + 'name' => 'required|string|max:100', + 'email' => 'required|email|max:200|unique:users,email,' . auth()->id(), + ]); + + auth()->user()->update([ + 'name' => $this->name, + 'email' => $this->email, + ]); + + session()->flash('status', 'Profile updated.'); + } + + public function render(): View + { + return view('livewire.pages.profile.personal-info') + ->layout('layouts.nimuli-app', ['title' => 'Profile']); + } +} diff --git a/app/Livewire/Pages/Profile/Preferences.php b/app/Livewire/Pages/Profile/Preferences.php new file mode 100644 index 0000000..1c0cfdb --- /dev/null +++ b/app/Livewire/Pages/Profile/Preferences.php @@ -0,0 +1,39 @@ +locale = auth()->user()->locale ?? 'de'; + $this->theme = auth()->user()->theme ?? 'dark'; + } + + public function save(): void + { + $this->validate([ + 'locale' => 'required|in:de,en', + 'theme' => 'required|in:dark,light,system', + ]); + + auth()->user()->update([ + 'locale' => $this->locale, + 'theme' => $this->theme, + ]); + + session()->flash('status', 'Preferences saved.'); + } + + public function render(): View + { + return view('livewire.pages.profile.preferences') + ->layout('layouts.nimuli-app', ['title' => 'Preferences']); + } +} diff --git a/app/Livewire/Pages/Profile/Security.php b/app/Livewire/Pages/Profile/Security.php new file mode 100644 index 0000000..1ebfa26 --- /dev/null +++ b/app/Livewire/Pages/Profile/Security.php @@ -0,0 +1,37 @@ +validate([ + 'currentPassword' => 'required', + 'newPassword' => 'required|min:8|confirmed', + ]); + + if (!Hash::check($this->currentPassword, auth()->user()->password)) { + $this->addError('currentPassword', 'Current password is incorrect.'); + return; + } + + auth()->user()->update(['password' => Hash::make($this->newPassword)]); + $this->reset(['currentPassword', 'newPassword', 'newPasswordConfirmation']); + session()->flash('status', 'Password changed successfully.'); + } + + public function render(): View + { + return view('livewire.pages.profile.security') + ->layout('layouts.nimuli-app', ['title' => 'Security']); + } +} diff --git a/app/Livewire/Pages/Settings/ApiTokens.php b/app/Livewire/Pages/Settings/ApiTokens.php new file mode 100644 index 0000000..bf8c287 --- /dev/null +++ b/app/Livewire/Pages/Settings/ApiTokens.php @@ -0,0 +1,40 @@ +validate(['tokenName' => 'required|string|max:100']); + + $token = auth()->user()->createToken($this->tokenName); + $this->newToken = $token->plainTextToken; + $this->tokenName = ''; + } + + public function revokeToken(int $id): void + { + auth()->user()->tokens()->where('id', $id)->delete(); + } + + public function dismissNewToken(): void + { + $this->newToken = null; + } + + public function render(): View + { + $tokens = auth()->user()->tokens()->latest()->get(); + + return view('livewire.pages.settings.api-tokens', compact('tokens')) + ->layout('layouts.nimuli-app', ['title' => 'API Tokens']); + } +} diff --git a/app/Livewire/Pages/Settings/Index.php b/app/Livewire/Pages/Settings/Index.php index d0b300f..b517699 100644 --- a/app/Livewire/Pages/Settings/Index.php +++ b/app/Livewire/Pages/Settings/Index.php @@ -7,6 +7,31 @@ use Livewire\Component; class Index extends Component { + public string $workspaceName = ''; + public string $workspaceSlug = ''; + + public function mount(): void + { + $ws = app('current_workspace'); + $this->workspaceName = $ws->name; + $this->workspaceSlug = $ws->slug; + } + + public function saveWorkspace(): void + { + $this->validate([ + 'workspaceName' => 'required|string|max:100', + 'workspaceSlug' => 'nullable|string|max:50|alpha_dash', + ]); + + app('current_workspace')->update([ + 'name' => $this->workspaceName, + 'slug' => $this->workspaceSlug ?: app('current_workspace')->slug, + ]); + + session()->flash('status', 'Workspace settings saved.'); + } + public function render(): View { return view('livewire.pages.settings.index') diff --git a/app/Livewire/Pages/Settings/Webhooks.php b/app/Livewire/Pages/Settings/Webhooks.php new file mode 100644 index 0000000..23ba279 --- /dev/null +++ b/app/Livewire/Pages/Settings/Webhooks.php @@ -0,0 +1,15 @@ +layout('layouts.nimuli-app', ['title' => 'Webhooks']); + } +} diff --git a/app/Livewire/Pages/Team/Members.php b/app/Livewire/Pages/Team/Members.php new file mode 100644 index 0000000..a34fa50 --- /dev/null +++ b/app/Livewire/Pages/Team/Members.php @@ -0,0 +1,22 @@ +where('workspace_id', $workspace->id) + ->get(); + + return view('livewire.pages.team.members', compact('members')) + ->layout('layouts.nimuli-app', ['title' => 'Team']); + } +} diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index a0b516d..7cb42a3 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -61,6 +61,13 @@ 'match' => 'w.billing.*', 'icon' => 'billing', ], + [ + 'label' => 'Team', + 'route' => 'w.team.index', + 'params' => [$wsUlid], + 'match' => 'w.team.*', + 'icon' => 'team', + ], ] : [ [ 'label' => 'Dashboard', @@ -193,6 +200,10 @@ + @elseif($item['icon'] === 'team') + @endif {{ $item['label'] }} diff --git a/resources/views/components/topbar.blade.php b/resources/views/components/topbar.blade.php index 5779d5d..e54233e 100644 --- a/resources/views/components/topbar.blade.php +++ b/resources/views/components/topbar.blade.php @@ -99,7 +99,7 @@
diff --git a/resources/views/livewire/pages/profile/preferences.blade.php b/resources/views/livewire/pages/profile/preferences.blade.php new file mode 100644 index 0000000..055edc3 --- /dev/null +++ b/resources/views/livewire/pages/profile/preferences.blade.php @@ -0,0 +1,46 @@ +{{ $message }}
@enderror +{{ $message }}
@enderror +Token created — copy it now, it won't be shown again
+{{ $newToken }}
+ {{ $message }}
@enderror +Manage your workspace settings
+Once you delete a workspace, there is no going back.
-Receive HTTP POST notifications for workspace events
+Add a webhook URL to receive real-time events.
+Manage workspace members and permissions
+