Skip to content

Commit a41fb47

Browse files
authored
Merge pull request #506 from wasp-lang/franjo/account-page-improvements
2 parents 2713f9e + 537c295 commit a41fb47

File tree

2 files changed

+93
-113
lines changed

2 files changed

+93
-113
lines changed

template/app/src/user/AccountPage.tsx

Lines changed: 92 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "../components/ui/card";
1111
import { Separator } from "../components/ui/separator";
1212
import {
13+
PaymentPlanId,
1314
SubscriptionStatus,
1415
parsePaymentPlanId,
1516
prettyPaymentPlanName,
@@ -29,12 +30,12 @@ export default function AccountPage({ user }: { user: User }) {
2930
{!!user.email && (
3031
<div className="px-6 py-4">
3132
<div className="grid grid-cols-1 sm:grid-cols-3 sm:gap-4">
32-
<dt className="text-muted-foreground text-sm font-medium">
33+
<div className="text-muted-foreground text-sm font-medium">
3334
Email address
34-
</dt>
35-
<dd className="text-foreground mt-1 text-sm sm:col-span-2 sm:mt-0">
35+
</div>
36+
<div className="text-foreground mt-1 text-sm sm:col-span-2 sm:mt-0">
3637
{user.email}
37-
</dd>
38+
</div>
3839
</div>
3940
</div>
4041
)}
@@ -43,41 +44,52 @@ export default function AccountPage({ user }: { user: User }) {
4344
<Separator />
4445
<div className="px-6 py-4">
4546
<div className="grid grid-cols-1 sm:grid-cols-3 sm:gap-4">
46-
<dt className="text-muted-foreground text-sm font-medium">
47+
<div className="text-muted-foreground text-sm font-medium">
4748
Username
48-
</dt>
49-
<dd className="text-foreground mt-1 text-sm sm:col-span-2 sm:mt-0">
49+
</div>
50+
<div className="text-foreground mt-1 text-sm sm:col-span-2 sm:mt-0">
5051
{user.username}
51-
</dd>
52+
</div>
5253
</div>
5354
</div>
5455
</>
5556
)}
5657
<Separator />
5758
<div className="px-6 py-4">
5859
<div className="grid grid-cols-1 sm:grid-cols-3 sm:gap-4">
59-
<dt className="text-muted-foreground text-sm font-medium">
60+
<div className="text-muted-foreground text-sm font-medium">
6061
Your Plan
61-
</dt>
62-
<UserCurrentPaymentPlan
63-
subscriptionStatus={
64-
user.subscriptionStatus as SubscriptionStatus
65-
}
62+
</div>
63+
<UserCurrentSubscriptionPlan
6664
subscriptionPlan={user.subscriptionPlan}
65+
subscriptionStatus={user.subscriptionStatus}
6766
datePaid={user.datePaid}
68-
credits={user.credits}
6967
/>
7068
</div>
7169
</div>
7270
<Separator />
7371
<div className="px-6 py-4">
7472
<div className="grid grid-cols-1 sm:grid-cols-3 sm:gap-4">
75-
<dt className="text-muted-foreground text-sm font-medium">
73+
<div className="text-muted-foreground text-sm font-medium">
74+
Credits
75+
</div>
76+
<div className="text-foreground mt-1 text-sm sm:col-span-1 sm:mt-0">
77+
{user.credits} credits
78+
</div>
79+
<div className="ml-auto mt-4 sm:mt-0">
80+
<BuyMoreButton subscriptionStatus={user.subscriptionStatus} />
81+
</div>
82+
</div>
83+
</div>
84+
<Separator />
85+
<div className="px-6 py-4">
86+
<div className="grid grid-cols-1 sm:grid-cols-3 sm:gap-4">
87+
<div className="text-muted-foreground text-sm font-medium">
7688
About
77-
</dt>
78-
<dd className="text-foreground mt-1 text-sm sm:col-span-2 sm:mt-0">
89+
</div>
90+
<div className="text-foreground mt-1 text-sm sm:col-span-2 sm:mt-0">
7991
I'm a cool customer.
80-
</dd>
92+
</div>
8193
</div>
8294
</div>
8395
</div>
@@ -87,129 +99,97 @@ export default function AccountPage({ user }: { user: User }) {
8799
);
88100
}
89101

90-
type UserCurrentPaymentPlanProps = {
91-
subscriptionPlan: string | null;
92-
subscriptionStatus: SubscriptionStatus | null;
93-
datePaid: Date | null;
94-
credits: number;
95-
};
96-
97-
function UserCurrentPaymentPlan({
102+
function UserCurrentSubscriptionPlan({
98103
subscriptionPlan,
99104
subscriptionStatus,
100105
datePaid,
101-
credits,
102-
}: UserCurrentPaymentPlanProps) {
103-
if (subscriptionStatus && subscriptionPlan && datePaid) {
104-
return (
105-
<>
106-
<dd className="text-foreground mt-1 text-sm sm:col-span-1 sm:mt-0">
107-
{getUserSubscriptionStatusDescription({
108-
subscriptionPlan,
109-
subscriptionStatus,
110-
datePaid,
111-
})}
112-
</dd>
113-
{subscriptionStatus !== SubscriptionStatus.Deleted ? (
114-
<CustomerPortalButton />
115-
) : (
116-
<BuyMoreButton />
117-
)}
118-
</>
106+
}: Pick<User, "subscriptionPlan" | "subscriptionStatus" | "datePaid">) {
107+
let subscriptionPlanMessage = "Free Plan";
108+
if (
109+
subscriptionPlan !== null &&
110+
subscriptionStatus !== null &&
111+
datePaid !== null
112+
) {
113+
subscriptionPlanMessage = formatSubscriptionStatusMessage(
114+
parsePaymentPlanId(subscriptionPlan),
115+
datePaid,
116+
subscriptionStatus as SubscriptionStatus,
119117
);
120118
}
121119

122120
return (
123121
<>
124-
<dd className="text-foreground mt-1 text-sm sm:col-span-1 sm:mt-0">
125-
Credits remaining: {credits}
126-
</dd>
127-
<BuyMoreButton />
122+
<div className="text-foreground mt-1 text-sm sm:col-span-1 sm:mt-0">
123+
{subscriptionPlanMessage}
124+
</div>
125+
<div className="ml-auto mt-4 sm:mt-0">
126+
<CustomerPortalButton />
127+
</div>
128128
</>
129129
);
130130
}
131131

132-
function getUserSubscriptionStatusDescription({
133-
subscriptionPlan,
134-
subscriptionStatus,
135-
datePaid,
136-
}: {
137-
subscriptionPlan: string;
138-
subscriptionStatus: SubscriptionStatus;
139-
datePaid: Date;
140-
}) {
141-
const planName = prettyPaymentPlanName(parsePaymentPlanId(subscriptionPlan));
142-
const endOfBillingPeriod = prettyPrintEndOfBillingPeriod(datePaid);
143-
return prettyPrintStatus(planName, subscriptionStatus, endOfBillingPeriod);
144-
}
145-
146-
function prettyPrintStatus(
147-
planName: string,
132+
function formatSubscriptionStatusMessage(
133+
subscriptionPlan: PaymentPlanId,
134+
datePaid: Date,
148135
subscriptionStatus: SubscriptionStatus,
149-
endOfBillingPeriod: string,
150136
): string {
137+
const paymentPlanName = prettyPaymentPlanName(subscriptionPlan);
151138
const statusToMessage: Record<SubscriptionStatus, string> = {
152-
active: `${planName}`,
153-
past_due: `Payment for your ${planName} plan is past due! Please update your subscription payment information.`,
154-
cancel_at_period_end: `Your ${planName} plan subscription has been canceled, but remains active until the end of the current billing period${endOfBillingPeriod}`,
139+
active: `${paymentPlanName}`,
140+
past_due: `Payment for your ${paymentPlanName} plan is past due! Please update your subscription payment information.`,
141+
cancel_at_period_end: `Your ${paymentPlanName} plan subscription has been canceled, but remains active until the end of the current billing period: ${prettyPrintEndOfBillingPeriod(
142+
datePaid,
143+
)}`,
155144
deleted: `Your previous subscription has been canceled and is no longer active.`,
156145
};
157-
if (Object.keys(statusToMessage).includes(subscriptionStatus)) {
158-
return statusToMessage[subscriptionStatus];
159-
} else {
160-
throw new Error(`Invalid subscriptionStatus: ${subscriptionStatus}`);
146+
147+
if (!statusToMessage[subscriptionStatus]) {
148+
throw new Error(`Invalid subscription status: ${subscriptionStatus}`);
161149
}
150+
151+
return statusToMessage[subscriptionStatus];
162152
}
163153

164154
function prettyPrintEndOfBillingPeriod(date: Date) {
165155
const oneMonthFromNow = new Date(date);
166156
oneMonthFromNow.setMonth(oneMonthFromNow.getMonth() + 1);
167-
return ": " + oneMonthFromNow.toLocaleDateString();
157+
return oneMonthFromNow.toLocaleDateString();
168158
}
169159

170-
function BuyMoreButton() {
160+
function CustomerPortalButton() {
161+
const { data: customerPortalUrl, isLoading: isCustomerPortalUrlLoading } =
162+
useQuery(getCustomerPortalUrl);
163+
164+
if (!customerPortalUrl) {
165+
return null;
166+
}
167+
171168
return (
172-
<div className="ml-4 flex-shrink-0 sm:col-span-1 sm:mt-0">
173-
<WaspRouterLink
174-
to={routes.PricingPageRoute.to}
175-
className="text-primary hover:text-primary/80 text-sm font-medium transition-colors duration-200"
176-
>
177-
Buy More/Upgrade
178-
</WaspRouterLink>
179-
</div>
169+
<a href={customerPortalUrl} target="_blank" rel="noopener noreferrer">
170+
<Button disabled={isCustomerPortalUrlLoading} variant="link">
171+
Manage Payment Details
172+
</Button>
173+
</a>
180174
);
181175
}
182176

183-
function CustomerPortalButton() {
184-
const {
185-
data: customerPortalUrl,
186-
isLoading: isCustomerPortalUrlLoading,
187-
error: customerPortalUrlError,
188-
} = useQuery(getCustomerPortalUrl);
189-
190-
const handleClick = () => {
191-
if (customerPortalUrlError) {
192-
console.error("Error fetching customer portal url");
193-
}
194-
195-
if (customerPortalUrl) {
196-
window.open(customerPortalUrl, "_blank");
197-
} else {
198-
console.error("Customer portal URL is not available");
199-
}
200-
};
177+
function BuyMoreButton({
178+
subscriptionStatus,
179+
}: Pick<User, "subscriptionStatus">) {
180+
if (
181+
subscriptionStatus === SubscriptionStatus.Active ||
182+
subscriptionStatus === SubscriptionStatus.CancelAtPeriodEnd
183+
) {
184+
return null;
185+
}
201186

202187
return (
203-
<div className="ml-4 flex-shrink-0 sm:col-span-1 sm:mt-0">
204-
<Button
205-
onClick={handleClick}
206-
disabled={isCustomerPortalUrlLoading}
207-
variant="outline"
208-
size="sm"
209-
className="text-sm font-medium"
210-
>
211-
Manage Subscription
212-
</Button>
213-
</div>
188+
<WaspRouterLink
189+
to={routes.PricingPageRoute.to}
190+
className="text-primary hover:text-primary/80 text-sm font-medium transition-colors duration-200"
191+
>
192+
<Button variant="link">Buy More Credits</Button>
193+
</WaspRouterLink>
214194
);
215195
}

template/e2e-tests/tests/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export const makeStripePayment = async ({
123123
await page.waitForURL("**/checkout?status=success");
124124
await page.waitForURL("**/account");
125125
if (planId === "credits10") {
126-
await expect(page.getByText("Credits remaining: 13")).toBeVisible();
126+
await expect(page.getByText("13 credits")).toBeVisible();
127127
} else {
128128
await expect(page.getByText(planId)).toBeVisible();
129129
}

0 commit comments

Comments
 (0)