Skip to content

Commit 87c6656

Browse files
Subscriptions, duplicate checker and smart match.
1 parent d6a5390 commit 87c6656

22 files changed

Lines changed: 2022 additions & 21 deletions

.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,14 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
5757
VITE_PUSHER_PORT="${PUSHER_PORT}"
5858
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
5959
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
60+
61+
# Stripe Configuration for Premium Subscriptions
62+
STRIPE_KEY=pk_test_your_stripe_publishable_key_here
63+
STRIPE_SECRET=sk_test_your_stripe_secret_key_here
64+
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
65+
STRIPE_PREMIUM_PRICE_ID=price_premium_monthly
66+
STRIPE_PREMIUM_PRODUCT_ID=prod_premium
67+
68+
# Cashier Configuration
69+
CASHIER_CURRENCY=gbp
70+
CASHIER_CURRENCY_LOCALE=en_GB
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace App\Filament\App\Pages;
4+
5+
use App\Services\SubscriptionService;
6+
use Filament\Actions\Action;
7+
use Filament\Notifications\Notification;
8+
use Filament\Pages\Page;
9+
use Illuminate\Support\Facades\Auth;
10+
11+
class PremiumDashboardPage extends Page
12+
{
13+
protected static ?string $navigationIcon = 'heroicon-o-star';
14+
15+
protected static ?string $navigationLabel = 'Premium Dashboard';
16+
17+
protected static ?string $navigationGroup = 'Account';
18+
19+
protected static ?int $navigationSort = 1;
20+
21+
protected static string $view = 'filament.app.pages.premium-dashboard-page';
22+
23+
protected static ?string $title = 'Premium Dashboard';
24+
25+
public function mount(): void
26+
{
27+
// Redirect if user is not premium
28+
if (!Auth::user()->isPremium()) {
29+
$this->redirect(route('filament.app.pages.subscription'));
30+
}
31+
}
32+
33+
protected function getHeaderActions(): array
34+
{
35+
$user = Auth::user();
36+
$actions = [];
37+
38+
if ($user->subscription('premium')?->cancelled()) {
39+
$actions[] = Action::make('resume')
40+
->label('Resume Subscription')
41+
->icon('heroicon-o-play')
42+
->color('success')
43+
->action('resumeSubscription');
44+
} else {
45+
$actions[] = Action::make('cancel')
46+
->label('Cancel Subscription')
47+
->icon('heroicon-o-x-mark')
48+
->color('danger')
49+
->requiresConfirmation()
50+
->action('cancelSubscription');
51+
}
52+
53+
return $actions;
54+
}
55+
56+
public function cancelSubscription(): void
57+
{
58+
try {
59+
$subscriptionService = app(SubscriptionService::class);
60+
$subscriptionService->cancelPremiumSubscription(Auth::user());
61+
62+
Notification::make()
63+
->title('Subscription Cancelled')
64+
->body('Your subscription has been cancelled. You can continue using premium features until the end of your billing period.')
65+
->warning()
66+
->send();
67+
68+
$this->redirect(route('filament.app.pages.subscription'));
69+
70+
} catch (\Exception $e) {
71+
Notification::make()
72+
->title('Cancellation Error')
73+
->body('There was an error cancelling your subscription. Please try again.')
74+
->danger()
75+
->send();
76+
}
77+
}
78+
79+
public function resumeSubscription(): void
80+
{
81+
try {
82+
$subscriptionService = app(SubscriptionService::class);
83+
$subscriptionService->resumePremiumSubscription(Auth::user());
84+
85+
Notification::make()
86+
->title('Subscription Resumed')
87+
->body('Your premium subscription has been resumed successfully!')
88+
->success()
89+
->send();
90+
91+
} catch (\Exception $e) {
92+
Notification::make()
93+
->title('Resume Error')
94+
->body('There was an error resuming your subscription. Please try again.')
95+
->danger()
96+
->send();
97+
}
98+
}
99+
100+
public function getSubscriptionData(): array
101+
{
102+
$user = Auth::user();
103+
$subscription = $user->subscription('premium');
104+
105+
return [
106+
'is_premium' => $user->isPremium(),
107+
'on_trial' => $user->onPremiumTrial(),
108+
'trial_days_remaining' => $user->trialDaysRemaining(),
109+
'subscription_status' => $subscription?->stripe_status,
110+
'subscription_ends_at' => $subscription?->ends_at,
111+
'premium_started_at' => $user->premium_started_at,
112+
];
113+
}
114+
115+
public function getPremiumFeatures(): array
116+
{
117+
$subscriptionService = app(SubscriptionService::class);
118+
return $subscriptionService->getPremiumFeaturesStatus(Auth::user());
119+
}
120+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace App\Filament\App\Pages;
4+
5+
use App\Services\SubscriptionService;
6+
use Filament\Actions\Action;
7+
use Filament\Forms\Components\TextInput;
8+
use Filament\Forms\Form;
9+
use Filament\Notifications\Notification;
10+
use Filament\Pages\Page;
11+
use Illuminate\Support\Facades\Auth;
12+
13+
class SubscriptionPage extends Page
14+
{
15+
protected static ?string $navigationIcon = 'heroicon-o-star';
16+
17+
protected static ?string $navigationLabel = 'Premium Subscription';
18+
19+
protected static ?string $navigationGroup = 'Account';
20+
21+
protected static ?int $navigationSort = 2;
22+
23+
protected static string $view = 'filament.app.pages.subscription-page';
24+
25+
protected static ?string $title = 'Premium Subscription';
26+
27+
public function mount(): void
28+
{
29+
// Redirect if user is already premium
30+
if (Auth::user()->isPremium()) {
31+
$this->redirect(route('filament.app.pages.premium-dashboard'));
32+
}
33+
}
34+
35+
protected function getHeaderActions(): array
36+
{
37+
return [
38+
Action::make('subscribe')
39+
->label('Start Premium Trial')
40+
->icon('heroicon-o-star')
41+
->color('primary')
42+
->size('lg')
43+
->action('startTrial'),
44+
];
45+
}
46+
47+
public function startTrial(): void
48+
{
49+
try {
50+
$subscriptionService = app(SubscriptionService::class);
51+
$user = Auth::user();
52+
53+
// Create trial subscription
54+
$subscription = $subscriptionService->createPremiumSubscription($user);
55+
56+
Notification::make()
57+
->title('Premium Trial Started!')
58+
->body('Welcome to Premium! Your 7-day trial has begun.')
59+
->success()
60+
->send();
61+
62+
$this->redirect(route('filament.app.pages.premium-dashboard'));
63+
64+
} catch (\Exception $e) {
65+
Notification::make()
66+
->title('Subscription Error')
67+
->body('There was an error starting your trial. Please try again.')
68+
->danger()
69+
->send();
70+
}
71+
}
72+
73+
public function getPricingData(): array
74+
{
75+
$subscriptionService = app(SubscriptionService::class);
76+
return $subscriptionService->getPricingInfo();
77+
}
78+
79+
public function getDnaLimitData(): array
80+
{
81+
$subscriptionService = app(SubscriptionService::class);
82+
return $subscriptionService->checkDnaUploadLimit(Auth::user());
83+
}
84+
}

app/Filament/App/Resources/DnaResource.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ class DnaResource extends Resource
3232

3333
protected static ?int $navigationSort = 1;
3434

35+
public static function canCreate(): bool
36+
{
37+
return auth()->user()->canUploadDna();
38+
}
39+
3540
#[\Override]
3641
public static function form(Schema $form): Schema
3742
{
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace App\Filament\App\Resources;
4+
5+
use App\Filament\App\Resources\DuplicateCheckResource\Pages;
6+
use App\Models\DuplicateCheck;
7+
use App\Services\DuplicateCheckerService;
8+
use Filament\Forms\Form;
9+
use Filament\Resources\Resource;
10+
use Filament\Tables;
11+
use Filament\Tables\Table;
12+
use Filament\Tables\Columns\TextColumn;
13+
use Filament\Tables\Columns\BadgeColumn;
14+
use Filament\Tables\Actions\Action;
15+
use Illuminate\Database\Eloquent\Builder;
16+
use Illuminate\Support\Facades\Auth;
17+
18+
class DuplicateCheckResource extends Resource
19+
{
20+
protected static ?string $model = DuplicateCheck::class;
21+
22+
protected static ?string $navigationIcon = 'heroicon-o-document-duplicate';
23+
24+
protected static ?string $navigationLabel = 'Duplicate Checker';
25+
26+
protected static ?string $navigationGroup = 'Research';
27+
28+
protected static ?int $navigationSort = 3;
29+
30+
public static function canAccess(): bool
31+
{
32+
return Auth::user()->isPremium();
33+
}
34+
35+
public static function form(Form $form): Form
36+
{
37+
return $form->schema([]);
38+
}
39+
40+
public static function table(Table $table): Table
41+
{
42+
return $table
43+
->columns([
44+
TextColumn::make('created_at')
45+
->label('Run Date')
46+
->dateTime()
47+
->sortable(),
48+
BadgeColumn::make('status')
49+
->colors([
50+
'warning' => 'pending',
51+
'success' => 'completed',
52+
'danger' => 'failed',
53+
]),
54+
TextColumn::make('duplicates_found')
55+
->label('Duplicates Found')
56+
->numeric()
57+
->sortable(),
58+
TextColumn::make('completed_at')
59+
->label('Completed')
60+
->dateTime()
61+
->sortable(),
62+
])
63+
->filters([])
64+
->actions([
65+
Action::make('view_results')
66+
->label('View Results')
67+
->icon('heroicon-o-eye')
68+
->url(fn (DuplicateCheck $record): string => route('filament.app.resources.duplicate-checks.view', $record))
69+
->visible(fn (DuplicateCheck $record): bool => $record->isCompleted()),
70+
])
71+
->headerActions([
72+
Action::make('run_check')
73+
->label('Run Duplicate Check')
74+
->icon('heroicon-o-play')
75+
->color('primary')
76+
->action(function () {
77+
$service = app(DuplicateCheckerService::class);
78+
$service->runDuplicateCheck(Auth::user());
79+
80+
return redirect()->back();
81+
})
82+
->requiresConfirmation()
83+
->modalHeading('Run Duplicate Check')
84+
->modalDescription('This will scan your family tree for potential duplicate people. This may take a few minutes.')
85+
->modalSubmitActionLabel('Start Check'),
86+
])
87+
->bulkActions([])
88+
->modifyQueryUsing(fn (Builder $query) => $query->where('user_id', Auth::id()));
89+
}
90+
91+
public static function getPages(): array
92+
{
93+
return [
94+
'index' => Pages\ListDuplicateChecks::route('/'),
95+
'view' => Pages\ViewDuplicateCheck::route('/{record}'),
96+
];
97+
}
98+
}

0 commit comments

Comments
 (0)