Skip to content

Commit c2f77f7

Browse files
simonhampclaude
andcommitted
Add account settings page with name, password, and delete account
- Create Settings Livewire component with update name, change password, and delete account functionality - Move Settings link from sidebar bottom nav into user profile dropdown - Fix sidebar logo to use favicon.svg - Add PHPUnit tests covering all settings flows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 418d8f3 commit c2f77f7

43 files changed

Lines changed: 3882 additions & 33 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace App\Livewire\Customer;
4+
5+
use App\Enums\Subscription;
6+
use Livewire\Attributes\Computed;
7+
use Livewire\Attributes\Layout;
8+
use Livewire\Attributes\Title;
9+
use Livewire\Component;
10+
11+
#[Layout('components.layouts.dashboard')]
12+
#[Title('Dashboard')]
13+
class Dashboard extends Component
14+
{
15+
#[Computed]
16+
public function licenseCount(): int
17+
{
18+
return auth()->user()->licenses()->count();
19+
}
20+
21+
#[Computed]
22+
public function isEapCustomer(): bool
23+
{
24+
return auth()->user()->isEapCustomer();
25+
}
26+
27+
#[Computed]
28+
public function activeSubscription()
29+
{
30+
return auth()->user()->subscription();
31+
}
32+
33+
#[Computed]
34+
public function subscriptionName(): ?string
35+
{
36+
$subscription = $this->activeSubscription;
37+
38+
if (! $subscription) {
39+
return null;
40+
}
41+
42+
if ($subscription->stripe_price) {
43+
try {
44+
return Subscription::fromStripePriceId($subscription->stripe_price)->name();
45+
} catch (\RuntimeException) {
46+
return ucfirst($subscription->type);
47+
}
48+
}
49+
50+
return ucfirst($subscription->type);
51+
}
52+
53+
#[Computed]
54+
public function pluginLicenseCount(): int
55+
{
56+
return auth()->user()->pluginLicenses()->count();
57+
}
58+
59+
#[Computed]
60+
public function renewalLicenseKey(): ?string
61+
{
62+
if ($this->activeSubscription) {
63+
return null;
64+
}
65+
66+
$highestTierLicense = auth()->user()->licenses()
67+
->whereIn('policy_name', ['max', 'pro', 'mini'])
68+
->orderByRaw("CASE policy_name WHEN 'max' THEN 1 WHEN 'pro' THEN 2 WHEN 'mini' THEN 3 END")
69+
->first();
70+
71+
return $highestTierLicense?->key;
72+
}
73+
74+
#[Computed]
75+
public function connectedAccountsCount(): int
76+
{
77+
$user = auth()->user();
78+
79+
return ($user->hasGitHubToken() ? 1 : 0) + ($user->hasDiscordConnected() ? 1 : 0);
80+
}
81+
82+
#[Computed]
83+
public function connectedAccountsDescription(): string
84+
{
85+
$user = auth()->user();
86+
$hasGitHub = $user->hasGitHubToken();
87+
$hasDiscord = $user->hasDiscordConnected();
88+
89+
return match (true) {
90+
$hasGitHub && $hasDiscord => 'GitHub & Discord',
91+
$hasGitHub => 'GitHub connected',
92+
$hasDiscord => 'Discord connected',
93+
default => 'No accounts connected',
94+
};
95+
}
96+
97+
#[Computed]
98+
public function totalPurchases(): int
99+
{
100+
$user = auth()->user();
101+
102+
return $this->licenseCount
103+
+ $this->pluginLicenseCount
104+
+ $user->productLicenses()->count();
105+
}
106+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace App\Livewire\Customer\Developer;
4+
5+
use App\Services\StripeConnectService;
6+
use Illuminate\Database\Eloquent\Collection;
7+
use Livewire\Attributes\Computed;
8+
use Livewire\Attributes\Layout;
9+
use Livewire\Attributes\Title;
10+
use Livewire\Component;
11+
12+
#[Layout('components.layouts.dashboard')]
13+
#[Title('Developer Dashboard')]
14+
class Dashboard extends Component
15+
{
16+
public function mount(StripeConnectService $stripeConnectService): void
17+
{
18+
$developerAccount = auth()->user()->developerAccount;
19+
20+
if (! $developerAccount || ! $developerAccount->hasCompletedOnboarding()) {
21+
$this->redirect(route('customer.developer.onboarding'), navigate: true);
22+
23+
return;
24+
}
25+
26+
$stripeConnectService->refreshAccountStatus($developerAccount);
27+
}
28+
29+
#[Computed]
30+
public function developerAccount()
31+
{
32+
return auth()->user()->developerAccount;
33+
}
34+
35+
#[Computed]
36+
public function plugins(): Collection
37+
{
38+
return auth()->user()->plugins()->withCount('licenses')->get();
39+
}
40+
41+
#[Computed]
42+
public function payouts(): Collection
43+
{
44+
return $this->developerAccount
45+
->payouts()
46+
->with('pluginLicense.plugin')
47+
->latest()
48+
->limit(10)
49+
->get();
50+
}
51+
52+
#[Computed]
53+
public function totalEarnings(): int
54+
{
55+
return $this->developerAccount
56+
->payouts()
57+
->where('status', 'transferred')
58+
->sum('developer_amount');
59+
}
60+
61+
#[Computed]
62+
public function pendingEarnings(): int
63+
{
64+
return $this->developerAccount
65+
->payouts()
66+
->where('status', 'pending')
67+
->sum('developer_amount');
68+
}
69+
70+
public function render()
71+
{
72+
return view('livewire.customer.developer.dashboard');
73+
}
74+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace App\Livewire\Customer\Developer;
4+
5+
use Livewire\Attributes\Computed;
6+
use Livewire\Attributes\Layout;
7+
use Livewire\Attributes\Title;
8+
use Livewire\Component;
9+
10+
#[Layout('components.layouts.dashboard')]
11+
#[Title('Developer Onboarding')]
12+
class Onboarding extends Component
13+
{
14+
public function mount(): void
15+
{
16+
$developerAccount = auth()->user()->developerAccount;
17+
18+
if ($developerAccount && $developerAccount->hasCompletedOnboarding()) {
19+
$this->redirect(route('customer.developer.dashboard'), navigate: true);
20+
session()->flash('message', 'Your developer account is already set up.');
21+
}
22+
}
23+
24+
#[Computed]
25+
public function developerAccount()
26+
{
27+
return auth()->user()->developerAccount;
28+
}
29+
30+
#[Computed]
31+
public function hasExistingAccount(): bool
32+
{
33+
return $this->developerAccount !== null;
34+
}
35+
36+
public function render()
37+
{
38+
return view('livewire.customer.developer.onboarding');
39+
}
40+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Livewire\Customer;
4+
5+
use Livewire\Attributes\Layout;
6+
use Livewire\Attributes\Title;
7+
use Livewire\Component;
8+
9+
#[Layout('components.layouts.dashboard')]
10+
#[Title('Integrations')]
11+
class Integrations extends Component
12+
{
13+
//
14+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace App\Livewire\Customer\Licenses;
4+
5+
use App\Models\SubLicense;
6+
use Illuminate\Database\Eloquent\Collection;
7+
use Livewire\Attributes\Computed;
8+
use Livewire\Attributes\Layout;
9+
use Livewire\Attributes\Title;
10+
use Livewire\Component;
11+
12+
#[Layout('components.layouts.dashboard')]
13+
#[Title('Your Licenses')]
14+
class Index extends Component
15+
{
16+
#[Computed]
17+
public function licenses(): Collection
18+
{
19+
return auth()->user()->licenses()->orderBy('created_at', 'desc')->get();
20+
}
21+
22+
#[Computed]
23+
public function assignedSubLicenses(): Collection
24+
{
25+
$user = auth()->user();
26+
27+
return SubLicense::query()
28+
->with('parentLicense')
29+
->where('assigned_email', $user->email)
30+
->whereHas('parentLicense', function ($query) use ($user): void {
31+
$query->where('user_id', '!=', $user->id);
32+
})->latest()
33+
->get();
34+
}
35+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace App\Livewire\Customer\Licenses;
4+
5+
use App\Models\License;
6+
use Livewire\Attributes\Layout;
7+
use Livewire\Attributes\Title;
8+
use Livewire\Attributes\Validate;
9+
use Livewire\Component;
10+
11+
#[Layout('components.layouts.dashboard')]
12+
#[Title('License Details')]
13+
class Show extends Component
14+
{
15+
public License $license;
16+
17+
public bool $showEditNameModal = false;
18+
19+
#[Validate('nullable|string|max:255')]
20+
public ?string $licenseName = null;
21+
22+
public function mount(string $licenseKey): void
23+
{
24+
$this->license = auth()->user()->licenses()
25+
->with('subLicenses')
26+
->where('key', $licenseKey)
27+
->firstOrFail();
28+
29+
$this->licenseName = $this->license->name;
30+
}
31+
32+
public function updateLicenseName(): void
33+
{
34+
$this->validate();
35+
36+
$this->license->update([
37+
'name' => $this->licenseName ?: null,
38+
]);
39+
40+
$this->license->refresh();
41+
$this->showEditNameModal = false;
42+
43+
session()->flash('success', 'License name updated successfully!');
44+
}
45+
46+
public function render()
47+
{
48+
return view('livewire.customer.licenses.show');
49+
}
50+
}

0 commit comments

Comments
 (0)