Skip to content

Commit 657f565

Browse files
authored
Merge pull request #17 from robertguss/robertguss/rob-91-e2e-tests-complete-payment-flows
feat: ROB-91 E2E payment flow tests with data-cy attributes
2 parents 4dd9b10 + ba027c5 commit 657f565

File tree

19 files changed

+2606
-16
lines changed

19 files changed

+2606
-16
lines changed

app/dashboard/account/page.tsx

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { useQuery, useAction } from "convex/react";
5+
import { api } from "@/convex/_generated/api";
6+
import { Button } from "@/components/ui/button";
7+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
8+
import { Loader2, CreditCard, Calendar, AlertCircle } from "lucide-react";
9+
import { format } from "date-fns";
10+
11+
export default function AccountPage() {
12+
const router = useRouter();
13+
const subscription = useQuery(api.subscriptions.getUserSubscription);
14+
const pageUsage = useQuery(api.subscriptions.getUserPageUsage);
15+
const createPortalSession = useAction(api.stripe.createPortalSession);
16+
17+
const handleManageSubscription = async () => {
18+
try {
19+
const { url } = await createPortalSession();
20+
window.location.href = url;
21+
} catch (error) {
22+
console.error("Error creating portal session:", error);
23+
}
24+
};
25+
26+
if (!subscription || !pageUsage) {
27+
return (
28+
<div className="flex items-center justify-center min-h-screen">
29+
<Loader2 className="h-8 w-8 animate-spin" />
30+
</div>
31+
);
32+
}
33+
34+
const isFreePlan = !subscription.stripeSubscriptionId;
35+
const nextBillingDate = subscription.currentPeriodEnd
36+
? new Date(subscription.currentPeriodEnd)
37+
: null;
38+
39+
return (
40+
<div className="container mx-auto py-10 max-w-4xl">
41+
<h1 className="text-3xl font-bold mb-8">Account Settings</h1>
42+
43+
<div className="space-y-6">
44+
{/* Subscription Status */}
45+
<Card>
46+
<CardHeader>
47+
<CardTitle>Subscription</CardTitle>
48+
<CardDescription>Manage your subscription and billing</CardDescription>
49+
</CardHeader>
50+
<CardContent className="space-y-4">
51+
<div className="flex justify-between items-center">
52+
<div>
53+
<p className="text-sm text-muted-foreground">Current Plan</p>
54+
<p className="text-xl font-semibold" data-cy="subscription-plan">
55+
{subscription.plan?.name || "Free Plan"}
56+
</p>
57+
</div>
58+
<div className="text-right">
59+
<p className="text-sm text-muted-foreground" data-cy="subscription-status">
60+
{subscription.status === "active" ? "Active" :
61+
subscription.status === "past_due" ? "Past due" :
62+
subscription.status === "canceled" ? "Canceled" :
63+
subscription.status === "incomplete" ? "Incomplete" : "Active"}
64+
</p>
65+
{nextBillingDate && !subscription.cancelAtPeriodEnd && (
66+
<p className="text-sm" data-cy="next-billing-date">
67+
Next billing: {format(nextBillingDate, "PPP")}
68+
</p>
69+
)}
70+
{subscription.cancelAtPeriodEnd && (
71+
<p className="text-sm text-warning" data-cy="cancellation-date">
72+
Cancels on: {format(nextBillingDate!, "PPP")}
73+
</p>
74+
)}
75+
</div>
76+
</div>
77+
78+
{/* Credit Usage */}
79+
<div className="border-t pt-4">
80+
<div className="flex justify-between items-center">
81+
<div>
82+
<p className="text-sm text-muted-foreground">Page Credits</p>
83+
<p className="text-lg">
84+
<span data-cy="credits-used">{pageUsage.used}</span> /
85+
<span data-cy="credits-limit">{pageUsage.limit}</span> used
86+
</p>
87+
</div>
88+
<div>
89+
<p className="text-2xl font-bold" data-cy="credits-remaining">
90+
{pageUsage.remaining} remaining
91+
</p>
92+
</div>
93+
</div>
94+
</div>
95+
96+
{/* Actions */}
97+
<div className="flex gap-2 pt-4">
98+
{isFreePlan ? (
99+
<Button
100+
onClick={() => router.push("/dashboard/upgrade")}
101+
className="flex-1"
102+
data-cy="upgrade-subscription"
103+
>
104+
<CreditCard className="mr-2 h-4 w-4" />
105+
Upgrade Subscription
106+
</Button>
107+
) : (
108+
<Button
109+
onClick={handleManageSubscription}
110+
className="flex-1"
111+
variant="outline"
112+
data-cy="manage-subscription"
113+
>
114+
<CreditCard className="mr-2 h-4 w-4" />
115+
Manage Subscription
116+
</Button>
117+
)}
118+
</div>
119+
120+
{subscription.status === "past_due" && (
121+
<div className="flex items-center gap-2 p-3 bg-red-50 text-red-700 rounded-md" data-cy="payment-failed-banner">
122+
<AlertCircle className="h-4 w-4" />
123+
<p className="text-sm">
124+
Your payment failed. Please update your payment method.
125+
</p>
126+
<Button
127+
size="sm"
128+
variant="outline"
129+
onClick={handleManageSubscription}
130+
className="ml-auto"
131+
data-cy="update-payment-method"
132+
>
133+
Update Payment
134+
</Button>
135+
</div>
136+
)}
137+
138+
{subscription.cancelAtPeriodEnd && (
139+
<div className="flex items-center gap-2 p-3 bg-amber-50 text-amber-700 rounded-md" data-cy="subscription-ending-notice">
140+
<AlertCircle className="h-4 w-4" />
141+
<p className="text-sm">
142+
Your subscription is scheduled to cancel at the end of the billing period.
143+
</p>
144+
<Button
145+
size="sm"
146+
variant="outline"
147+
onClick={handleManageSubscription}
148+
className="ml-auto"
149+
data-cy="reactivate-before-cancellation"
150+
>
151+
Reactivate
152+
</Button>
153+
</div>
154+
)}
155+
</CardContent>
156+
</Card>
157+
158+
{/* Payment History */}
159+
<Card>
160+
<CardHeader>
161+
<CardTitle>Billing History</CardTitle>
162+
<CardDescription>View your payment history and invoices</CardDescription>
163+
</CardHeader>
164+
<CardContent>
165+
{isFreePlan ? (
166+
<p className="text-sm text-muted-foreground">
167+
No billing history available for free accounts.
168+
</p>
169+
) : (
170+
<Button
171+
onClick={handleManageSubscription}
172+
variant="outline"
173+
data-cy="payment-history"
174+
>
175+
<Calendar className="mr-2 h-4 w-4" />
176+
View Payment History
177+
</Button>
178+
)}
179+
</CardContent>
180+
</Card>
181+
182+
{/* Credit History */}
183+
<Card>
184+
<CardHeader>
185+
<CardTitle>Credit Usage History</CardTitle>
186+
<CardDescription>Track your monthly credit usage</CardDescription>
187+
</CardHeader>
188+
<CardContent>
189+
<Button
190+
onClick={() => router.push("/dashboard/credits")}
191+
variant="outline"
192+
data-cy="credit-history"
193+
>
194+
View Credit History
195+
</Button>
196+
</CardContent>
197+
</Card>
198+
</div>
199+
</div>
200+
);
201+
}

app/dashboard/components/FileUploadArea.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function FileUploadArea({ onFileSelect, pageUsage = null }: FileUploadAreaProps)
7373
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
7474
/>
7575
</svg>
76-
<p className="text-lg font-semibold text-red-600 mb-2">
76+
<p className="text-lg font-semibold text-red-600 mb-2" data-cy="no-pages-remaining">
7777
No pages remaining
7878
</p>
7979
<p className="text-sm text-gray-600 mb-4">
@@ -82,6 +82,7 @@ function FileUploadArea({ onFileSelect, pageUsage = null }: FileUploadAreaProps)
8282
<a
8383
href="/dashboard/upgrade"
8484
className="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
85+
data-cy="upgrade-plan-button"
8586
>
8687
Upgrade Plan
8788
</a>

app/dashboard/components/UploadScreen.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ function UploadScreen({ onFileUpload, onExampleSelect, error, clearError }: Uplo
125125
<div
126126
className="relative mb-6 rounded border-l-4 border-red-500 bg-red-100 px-4 py-3 text-red-700 shadow"
127127
role="alert"
128+
data-cy="error-message"
128129
>
129130
<div className="flex">
130131
<div className="py-1">
@@ -138,7 +139,7 @@ function UploadScreen({ onFileUpload, onExampleSelect, error, clearError }: Uplo
138139
</div>
139140
<div>
140141
<p className="font-bold">Error</p>
141-
<p className="text-sm">
142+
<p className="text-sm" data-cy="error-text">
142143
{typeof error === "string" ? error : JSON.stringify(error)}
143144
</p>
144145
</div>

app/dashboard/components/credit-balance.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function CreditBalance({ isDemo = false }: CreditBalanceProps) {
1515
// Show demo mode badge if in demo
1616
if (isDemo) {
1717
return (
18-
<Badge variant="secondary" className="font-semibold">
18+
<Badge variant="secondary" className="font-semibold" data-cy="demo-mode-badge">
1919
<div className="flex items-center gap-2">
2020
<div className="h-2 w-2 animate-pulse rounded-full bg-primary" />
2121
Demo Mode
@@ -44,12 +44,13 @@ export function CreditBalance({ isDemo = false }: CreditBalanceProps) {
4444
<Badge
4545
variant={isEmpty ? 'destructive' : isLow ? 'warning' : 'default'}
4646
className={isEmpty ? 'animate-pulse' : ''}
47+
data-cy="page-credits"
4748
>
4849
{remaining} pages
4950
</Badge>
5051
</div>
51-
<Progress value={percentageUsed} className="h-2" />
52-
<p className="text-xs text-muted-foreground">
52+
<Progress value={percentageUsed} className="h-2" data-cy="credit-usage-progress" />
53+
<p className="text-xs text-muted-foreground" data-cy="credits-used">
5354
{used} of {limit} pages used
5455
</p>
5556
</div>

app/dashboard/upgrade/page.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function Plan({
6464
isLoading = false,
6565
cta = "Get started",
6666
onSelect,
67+
planId,
6768
}: {
6869
name: string;
6970
price: string;
@@ -74,15 +75,17 @@ function Plan({
7475
isLoading?: boolean;
7576
cta?: string;
7677
onSelect?: () => void;
78+
planId: string;
7779
}) {
7880
return (
7981
<section
8082
className={clsx(
8183
"flex flex-col rounded-3xl px-6 sm:px-8",
8284
featured ? "order-first bg-blue-600 py-8 lg:order-none" : "lg:py-8",
8385
)}
86+
data-cy={`${planId}-plan-card`}
8487
>
85-
<h3 className="mt-5 font-display text-lg text-white">{name}</h3>
88+
<h3 className="mt-5 font-display text-lg text-white" data-cy="plan-name">{name}</h3>
8689
<p
8790
className={clsx(
8891
"mt-2 text-base",
@@ -91,7 +94,7 @@ function Plan({
9194
>
9295
{description}
9396
</p>
94-
<p className="order-first font-display text-5xl font-light tracking-tight text-white">
97+
<p className="order-first font-display text-5xl font-light tracking-tight text-white" data-cy="plan-price">
9598
{price}
9699
</p>
97100
<ul
@@ -102,9 +105,9 @@ function Plan({
102105
)}
103106
>
104107
{features.map((feature) => (
105-
<li key={feature} className="flex">
108+
<li key={feature} className="flex" data-cy="plan-feature">
106109
<CheckIcon className={featured ? "text-white" : "text-slate-400"} />
107-
<span className="ml-4">{feature}</span>
110+
<span className="ml-4" data-cy="plan-credits">{feature}</span>
108111
</li>
109112
))}
110113
</ul>
@@ -118,6 +121,7 @@ function Plan({
118121
"w-full opacity-50 cursor-not-allowed"
119122
)}
120123
disabled
124+
data-cy="current-plan-badge"
121125
>
122126
Current Plan
123127
</button>
@@ -133,6 +137,7 @@ function Plan({
133137
"w-full disabled:opacity-50 disabled:cursor-not-allowed"
134138
)}
135139
aria-label={`Get started with the ${name} plan for ${price}`}
140+
data-cy={isCurrentPlan ? undefined : `select-${planId}-plan`}
136141
>
137142
{isLoading ? (
138143
<span className="flex items-center justify-center">
@@ -168,7 +173,7 @@ export default function UpgradePage() {
168173

169174
if (!plans) {
170175
return (
171-
<div className="flex items-center justify-center min-h-screen bg-slate-900">
176+
<div className="flex items-center justify-center min-h-screen bg-slate-900" data-cy="loading-state">
172177
<Loader2 className="h-8 w-8 animate-spin text-white" />
173178
</div>
174179
);
@@ -246,6 +251,7 @@ export default function UpgradePage() {
246251
return (
247252
<Plan
248253
key={plan.id}
254+
planId={plan.id}
249255
name={plan.name}
250256
price={plan.price}
251257
description={plan.description}

components/app-sidebar.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
8383
<SidebarGroupLabel>Subscription</SidebarGroupLabel>
8484
<SidebarGroupContent>
8585
<div className="px-3 py-2">
86-
<p className="text-sm font-medium">
86+
<p className="text-sm font-medium" data-cy="current-plan">
8787
{subscription?.plan?.name || "Free Plan"}
8888
</p>
89-
<p className="text-xs text-muted-foreground">
89+
<p className="text-xs text-muted-foreground" data-cy="plan-features">
9090
{subscription?.plan?.features?.[0] || "10 pages per month"}
9191
</p>
9292
{!subscription && (
@@ -95,6 +95,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
9595
size="sm"
9696
className="mt-2 w-full"
9797
asChild
98+
data-cy="upgrade-button"
9899
>
99100
<Link href="/dashboard/upgrade">
100101
<CreditCard className="mr-2 h-3 w-3" />
@@ -108,6 +109,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
108109
size="sm"
109110
className="mt-2 w-full"
110111
onClick={handleManageSubscription}
112+
data-cy="manage-subscription"
111113
>
112114
<CreditCard className="mr-2 h-3 w-3" />
113115
Manage Subscription

0 commit comments

Comments
 (0)