Skip to content

Commit b37f7e4

Browse files
committed
Align dashboard access and dedupe subscription state reads
1 parent ae29041 commit b37f7e4

File tree

5 files changed

+36
-70
lines changed

5 files changed

+36
-70
lines changed

src/app/(dashboard)/home/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export default async function Home({
102102

103103
// Check if user has Pro access (paid, canceling-but-active, or trialing)
104104
const isProPlan = subscription.hasProAccess;
105-
const needsTrial = subscription.status !== 'active' && subscription.status !== 'canceled';
105+
const shouldGateResumeActions = false;
106106

107107
// console.log(subscription);
108108

@@ -132,7 +132,7 @@ export default async function Home({
132132
}
133133

134134
return (
135-
<TrialGateProvider enabled={needsTrial}>
135+
<TrialGateProvider enabled={shouldGateResumeActions}>
136136
<main className="min-h-screen relative sm:pb-12 pb-40">
137137

138138
{/* Welcome Dialog for New Signups */}
@@ -156,7 +156,7 @@ export default async function Home({
156156
{/* Profile Overview */}
157157
<div className="mb-6 space-y-4">
158158
{/* API Key Alert */}
159-
{!isProPlan && <ApiKeyAlert variant={needsTrial ? 'trial' : 'upgrade'} />}
159+
{!isProPlan && <ApiKeyAlert variant="upgrade" />}
160160

161161
{/* Greeting & Edit Button */}
162162
<div className="flex items-center justify-between">

src/app/(dashboard)/settings/page.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,36 @@
33
"use server"
44

55
import { SettingsContent } from '@/components/settings/settings-content'
6-
import { checkSubscriptionPlan } from '@/utils/actions/stripe/actions';
76
import { createClient } from '@/utils/supabase/server'
7+
import { getSubscriptionAccessState } from '@/lib/subscription-access';
88

99

1010
export default async function SettingsPage() {
1111
const supabase = await createClient();
1212
const { data: { user } } = await supabase.auth.getUser();
1313

14+
const { data: subscription } = user
15+
? await supabase
16+
.from('subscriptions')
17+
.select('subscription_plan, subscription_status, current_period_end, trial_end, stripe_subscription_id')
18+
.eq('user_id', user.id)
19+
.maybeSingle()
20+
: { data: null };
1421

15-
// Check if user is on Pro plan
16-
const subscription = await checkSubscriptionPlan();
17-
const isProPlan = subscription.plan === 'pro';
22+
const subscriptionState = getSubscriptionAccessState(subscription);
23+
const isProPlan = subscriptionState.hasProAccess;
24+
const subscriptionStatus = subscription?.subscription_status ?? '';
1825

1926
return (
2027
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-white to-gray-50">
2128
<main className="pt-4 pb-16 px-4 md:px-8 max-w-7xl mx-auto">
2229
<SettingsContent
2330
user={user}
2431
isProPlan={isProPlan}
25-
subscriptionStatus={subscription.status}
32+
subscriptionStatus={subscriptionStatus}
33+
subscriptionSnapshot={subscription}
2634
/>
2735
</main>
2836
</div>
2937
)
30-
}
38+
}

src/components/settings/settings-content.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { User } from "@supabase/supabase-js"
99
import { cn } from "@/lib/utils"
1010
import { Button } from "@/components/ui/button"
1111
import { useEffect, useState } from "react"
12+
import type { SubscriptionSnapshot } from "@/lib/subscription-access"
1213

1314
const sections = [
1415
{ id: "security", title: "Security", description: "Manage your email and password settings", icon: "🔒" },
@@ -22,9 +23,10 @@ interface SettingsContentProps {
2223
user: User | null;
2324
isProPlan: boolean;
2425
subscriptionStatus: string;
26+
subscriptionSnapshot: SubscriptionSnapshot | null;
2527
}
2628

27-
export function SettingsContent({ user, isProPlan, subscriptionStatus }: SettingsContentProps) {
29+
export function SettingsContent({ user, isProPlan, subscriptionStatus, subscriptionSnapshot }: SettingsContentProps) {
2830
const [activeSection, setActiveSection] = useState<string>("security")
2931

3032
useEffect(() => {
@@ -113,7 +115,7 @@ export function SettingsContent({ user, isProPlan, subscriptionStatus }: Setting
113115
<CardDescription>Manage your subscription and billing settings</CardDescription>
114116
</CardHeader>
115117
<CardContent>
116-
<SubscriptionSection />
118+
<SubscriptionSection initialProfile={subscriptionSnapshot} />
117119
</CardContent>
118120
</Card>
119121

@@ -152,4 +154,4 @@ export function SettingsContent({ user, isProPlan, subscriptionStatus }: Setting
152154
</div>
153155
</div>
154156
)
155-
}
157+
}

src/components/settings/subscription-section.tsx

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,23 @@
11
'use client'
22

3-
import { useState, useEffect } from 'react';
3+
import { useState } from 'react';
44
import { useRouter } from 'next/navigation';
55
import { Button } from "@/components/ui/button"
66
import { Badge } from "@/components/ui/badge"
77
import { Sparkles, Star, Clock, Zap, ArrowRight, Crown, Shield, Check, Users, TrendingUp } from "lucide-react"
88
import { cn } from '@/lib/utils';
99
import { createPortalSession } from '@/app/(dashboard)/subscription/stripe-session';
10-
import { getSubscriptionStatus } from '@/utils/actions/stripe/actions';
1110
import { getSubscriptionAccessState, type SubscriptionSnapshot } from '@/lib/subscription-access';
1211

13-
export function SubscriptionSection() {
12+
interface SubscriptionSectionProps {
13+
initialProfile: SubscriptionSnapshot | null;
14+
}
15+
16+
export function SubscriptionSection({ initialProfile }: SubscriptionSectionProps) {
1417
const [isLoading, setIsLoading] = useState(false);
15-
const [profile, setProfile] = useState<SubscriptionSnapshot | null>(null);
16-
const [isLoadingProfile, setIsLoadingProfile] = useState(true);
1718
const router = useRouter();
1819

19-
useEffect(() => {
20-
async function fetchSubscriptionStatus() {
21-
try {
22-
const data = await getSubscriptionStatus();
23-
setProfile(data);
24-
} catch (error) {
25-
console.error('Error fetching subscription status:', error);
26-
} finally {
27-
setIsLoadingProfile(false);
28-
}
29-
}
30-
31-
fetchSubscriptionStatus();
32-
}, []);
33-
34-
const subscriptionAccessState = getSubscriptionAccessState(profile);
20+
const subscriptionAccessState = getSubscriptionAccessState(initialProfile);
3521
const {
3622
hasProAccess,
3723
isCanceling,
@@ -68,20 +54,6 @@ export function SubscriptionSection() {
6854

6955
const endDate = currentPeriodEndLabel;
7056

71-
if (isLoadingProfile) {
72-
return (
73-
<div className="space-y-6 relative min-h-[400px] flex items-center justify-center">
74-
<div className="flex flex-col items-center gap-4">
75-
<div className="h-16 w-16 bg-gradient-to-br from-blue-100 to-purple-100 rounded-2xl animate-pulse" />
76-
<div className="space-y-2 text-center">
77-
<div className="h-6 w-48 bg-gradient-to-r from-slate-200 to-slate-100 rounded-lg animate-pulse" />
78-
<div className="h-4 w-64 bg-gradient-to-r from-slate-100 to-slate-200 rounded-lg animate-pulse" />
79-
</div>
80-
</div>
81-
</div>
82-
);
83-
}
84-
8557
return (
8658
<div className="space-y-8">
8759
{/* Background decoration */}

src/utils/supabase/middleware.ts

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createServerClient } from '@supabase/ssr'
22
import { NextResponse, type NextRequest } from 'next/server'
3+
import { getSubscriptionAccessState } from '@/lib/subscription-access'
34

45
// Routes available on the free plan (auth still required)
56
const SUBSCRIPTION_EXEMPT_ROUTES = [
@@ -19,13 +20,6 @@ function isSubscriptionExemptRoute(pathname: string): boolean {
1920
return SUBSCRIPTION_EXEMPT_ROUTES.some(route => pathname.startsWith(route))
2021
}
2122

22-
function isFutureDate(value: string | null | undefined): boolean {
23-
if (!value) return false
24-
const parsed = Date.parse(value)
25-
if (Number.isNaN(parsed)) return false
26-
return parsed > Date.now()
27-
}
28-
2923
export async function updateSession(request: NextRequest) {
3024
// Debug logging
3125
console.log('🔍 updateSession running on:', request.nextUrl.pathname)
@@ -112,7 +106,7 @@ export async function updateSession(request: NextRequest) {
112106
console.log('🧭 Subscription check for path:', pathname)
113107
const { data: subscription } = await supabase
114108
.from('subscriptions')
115-
.select('stripe_subscription_id, subscription_status, current_period_end, trial_end')
109+
.select('subscription_plan, stripe_subscription_id, subscription_status, current_period_end, trial_end')
116110
.eq('user_id', user.id)
117111
.maybeSingle()
118112

@@ -123,23 +117,13 @@ export async function updateSession(request: NextRequest) {
123117
trial_end: subscription?.trial_end,
124118
})
125119

126-
const status = subscription?.subscription_status
127-
const isTrialing = isFutureDate(subscription?.trial_end)
128-
const isWithinAccessWindow = isFutureDate(subscription?.current_period_end)
129-
130-
// Access rules for protected routes:
131-
// - active subscriptions are allowed
132-
// - canceled subscriptions are allowed only until current_period_end
133-
// - active trials are always allowed
134-
const hasProtectedRouteAccess =
135-
status === 'active' ||
136-
isTrialing ||
137-
(status === 'canceled' && isWithinAccessWindow)
120+
const subscriptionState = getSubscriptionAccessState(subscription)
121+
const hasProtectedRouteAccess = subscriptionState.hasProAccess
138122

139123
console.log('✅ accessCheck:', {
140-
status,
141-
isTrialing,
142-
isWithinAccessWindow,
124+
status: subscription?.subscription_status,
125+
isTrialing: subscriptionState.isTrialing,
126+
isWithinAccessWindow: subscriptionState.isWithinAccessWindow,
143127
hasProtectedRouteAccess,
144128
})
145129

0 commit comments

Comments
 (0)