Skip to content

Commit e67047f

Browse files
author
Alex Patterson
committed
adjust settings pages
1 parent 8973703 commit e67047f

13 files changed

Lines changed: 493 additions & 359 deletions

File tree

‎app/(main)/(user)/dashboard/bookmarks/bookmarks.tsx‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import CoverImage from "@/components/cover-image";
2+
import CloudinaryImage from "@/components/cloudinary-image";
33
import { Badge } from "@/components/ui/badge";
44
import {
55
Card,
@@ -20,11 +20,16 @@ export default function Bookmarks() {
2020
key={bookmark._id}
2121
className="overflow-hidden shadow-md transition-all hover:scale-[1.02] hover:shadow-lg relative flex flex-col"
2222
>
23-
<CardHeader className="p-0">
24-
<Link href={bookmark._cc_pathname} className="block mb-5 group">
25-
<CoverImage image={bookmark.coverImage} priority={false} />
26-
</Link>
27-
</CardHeader>
23+
{bookmark?.coverImage?.public_id && (
24+
<CardHeader className="p-0">
25+
<Link href={bookmark._cc_pathname} className="block mb-5 group">
26+
<CloudinaryImage
27+
src={bookmark?.coverImage?.public_id}
28+
alt={bookmark?.coverImage?.context?.custom?.alt || ""}
29+
/>
30+
</Link>
31+
</CardHeader>
32+
)}
2833
<CardContent className="flex-grow">
2934
<h3 className="mb-3 text-3xl leading-snug text-balance">
3035
<Link href={bookmark._cc_pathname} className="hover:underline">

‎app/(main)/(user)/dashboard/layout.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Link from "next/link";
22
import { FaVrCardboard } from "react-icons/fa6";
33
import UserNav from "../user-nav";
4+
import UserNavMenu from "../user-nav-menu";
45

56
export default function SettingsLayout({
67
children,
@@ -31,6 +32,9 @@ export default function SettingsLayout({
3132
<FaVrCardboard className="h-6 w-6" />
3233
<span className="sr-only">Dashboard</span>
3334
</Link>
35+
<div className="flex lg:hidden">
36+
<UserNavMenu paths={paths} />
37+
</div>
3438
</header>
3539
<main className="flex flex-1 flex-col gap-4 p-4 md:gap-8 md:p-6">
3640
{children}

‎app/(main)/(user)/settings/account/page.tsx‎

Lines changed: 6 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,14 @@
1-
"use client";
2-
import { Badge } from "@/components/ui/badge";
3-
import { Button } from "@/components/ui/button";
41
import {
52
Card,
63
CardHeader,
74
CardTitle,
85
CardDescription,
96
CardContent,
107
} from "@/components/ui/card";
11-
import { Label } from "@/components/ui/label";
12-
import { ScrollArea } from "@/components/ui/scroll-area";
13-
import { Separator } from "@/components/ui/separator";
14-
import { useFirestoreUser } from "@/lib/firebase.hooks";
15-
import { Subscription } from "@/lib/stripe.types";
16-
import {
17-
collection,
18-
doc,
19-
getFirestore,
20-
onSnapshot,
21-
query,
22-
} from "firebase/firestore";
23-
import { getFunctions, httpsCallable } from "firebase/functions";
24-
import { useEffect, useState } from "react";
25-
26-
export default function Component() {
27-
const { currentUser } = useFirestoreUser();
28-
const [stripeSubscriptions, setStripeSubscriptions] = useState<
29-
Subscription[]
30-
>([]);
31-
const [showStripePortal, setShowStripePortal] = useState(false);
32-
33-
useEffect(() => {
34-
if (!currentUser?.uid) return;
35-
const unsub = onSnapshot(
36-
query(
37-
collection(
38-
doc(collection(getFirestore(), "stripe-customers"), currentUser?.uid),
39-
"subscriptions"
40-
)
41-
),
42-
(querySnapshot) => {
43-
const subscriptions: Subscription[] = [];
44-
querySnapshot.forEach((doc) => {
45-
subscriptions.push({ id: doc.id, ...doc.data() } as Subscription);
46-
});
47-
setStripeSubscriptions(subscriptions);
48-
}
49-
);
50-
return () => unsub();
51-
}, [currentUser]);
52-
53-
const onShowStripePortal = async (e: { preventDefault: () => void }) => {
54-
e.preventDefault();
55-
setShowStripePortal(true);
8+
import Subscriptions from "./subscriptions";
9+
import { Suspense } from "react";
5610

57-
const functionRef = httpsCallable(
58-
getFunctions(),
59-
"ext-firestore-stripe-payments-createPortalLink"
60-
);
61-
const { data } = (await functionRef({
62-
returnUrl: window.location.href,
63-
})) as { data: { url: string } };
64-
window.location.assign(data.url);
65-
};
11+
export default function AccountPage() {
6612

6713
return (
6814
<div className="grid gap-6">
@@ -71,98 +17,9 @@ export default function Component() {
7117
<CardTitle>Account</CardTitle>
7218
<CardDescription>Manage your account settings.</CardDescription>
7319
</CardHeader>
74-
<CardContent className="flex flex-col gap-4">
75-
<section>
76-
<h2 className="mb-4">Active Subscriptions</h2>
77-
<ScrollArea className="h-32 rounded-md border">
78-
<div className="p-4">
79-
{stripeSubscriptions
80-
?.filter((subscription) => subscription.status === "active")
81-
.map((sub) => (
82-
<>
83-
<div key={sub.id} className="text-sm flex">
84-
{sub.items.map((item) => (
85-
<>
86-
<div className="flex items-center gap-2">
87-
<p>{item.price.product.name}</p>
88-
<Badge>active</Badge>
89-
90-
<p className="flex gap-2">
91-
{new Date(
92-
sub.current_period_start.seconds * 1000
93-
).toLocaleString()}
94-
{" to "}
95-
{new Date(
96-
sub.current_period_end.seconds * 1000
97-
).toLocaleString()}
98-
</p>
99-
{sub?.canceled_at && (
100-
<Badge variant="secondary">
101-
Canceled{" "}
102-
{new Date(
103-
sub.canceled_at.seconds * 1000
104-
).toLocaleString()}
105-
</Badge>
106-
)}
107-
</div>
108-
</>
109-
))}
110-
</div>
111-
<Separator className="my-2" />
112-
</>
113-
))}
114-
</div>
115-
</ScrollArea>
116-
</section>
117-
<section>
118-
<h2 className="mb-4">Canceled Subscriptions</h2>
119-
<ScrollArea className="h-32 rounded-md border">
120-
<div className="p-4">
121-
{stripeSubscriptions
122-
?.filter((subscription) => subscription.status === "canceled")
123-
.map((sub) => (
124-
<>
125-
<div key={sub.id} className="text-sm flex">
126-
{sub.items.map((item) => (
127-
<>
128-
<div className="flex items-center gap-2">
129-
<p>{item.price.product.name}</p>
130-
<Badge variant="destructive">canceled</Badge>
131-
132-
<p className="flex gap-2">
133-
{new Date(
134-
sub.current_period_start.seconds * 1000
135-
).toLocaleString()}
136-
<span> to </span>
137-
{new Date(
138-
sub.current_period_end.seconds * 1000
139-
).toLocaleString()}
140-
{sub?.canceled_at &&
141-
new Date(
142-
sub.canceled_at.seconds * 1000
143-
).toLocaleString()}
144-
</p>
145-
</div>
146-
</>
147-
))}
148-
</div>
149-
<Separator className="my-2" />
150-
</>
151-
))}
152-
</div>
153-
</ScrollArea>
154-
</section>
155-
<section>
156-
<h2 className="mb-4">Stripe Portal</h2>
157-
158-
<Button
159-
className="hover:cursor-pointer"
160-
onClick={onShowStripePortal}
161-
>
162-
{showStripePortal ? "Redirecting..." : "Open Stripe Portal"}
163-
</Button>
164-
</section>
165-
</CardContent>
20+
<Suspense fallback={<CardContent>Loading...</CardContent>}>
21+
<Subscriptions />
22+
</Suspense>
16623
</Card>
16724

16825
{/* <Card>
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"use client";
2+
import { Badge } from "@/components/ui/badge";
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
Card,
6+
CardHeader,
7+
CardTitle,
8+
CardDescription,
9+
CardContent,
10+
} from "@/components/ui/card";
11+
import { Label } from "@/components/ui/label";
12+
import { ScrollArea } from "@/components/ui/scroll-area";
13+
import { Separator } from "@/components/ui/separator";
14+
import { useFirestoreUser } from "@/lib/firebase.hooks";
15+
import { Subscription } from "@/lib/stripe.types";
16+
import {
17+
collection,
18+
doc,
19+
getFirestore,
20+
onSnapshot,
21+
query,
22+
} from "firebase/firestore";
23+
import { getFunctions, httpsCallable } from "firebase/functions";
24+
import { useEffect, useState } from "react";
25+
26+
export default function Subscriptions() {
27+
const { currentUser } = useFirestoreUser();
28+
const [stripeSubscriptions, setStripeSubscriptions] = useState<
29+
Subscription[]
30+
>([]);
31+
const [showStripePortal, setShowStripePortal] = useState(false);
32+
const [loading, setLoading] = useState(false);
33+
34+
35+
useEffect(() => {
36+
if (!currentUser?.uid) return;
37+
setLoading(true)
38+
const unsub = onSnapshot(
39+
query(
40+
collection(
41+
doc(collection(getFirestore(), "stripe-customers"), currentUser?.uid),
42+
"subscriptions"
43+
)
44+
),
45+
(querySnapshot) => {
46+
const subscriptions: Subscription[] = [];
47+
querySnapshot.forEach((doc) => {
48+
subscriptions.push({ id: doc.id, ...doc.data() } as Subscription);
49+
});
50+
setStripeSubscriptions(subscriptions);
51+
setLoading(false)
52+
}
53+
);
54+
return () => unsub();
55+
}, [currentUser]);
56+
57+
const onShowStripePortal = async (e: { preventDefault: () => void }) => {
58+
e.preventDefault();
59+
setShowStripePortal(true);
60+
61+
const functionRef = httpsCallable(
62+
getFunctions(),
63+
"ext-firestore-stripe-payments-createPortalLink"
64+
);
65+
const { data } = (await functionRef({
66+
returnUrl: window.location.href,
67+
})) as { data: { url: string } };
68+
window.location.assign(data.url);
69+
};
70+
71+
72+
return (
73+
<CardContent className="flex flex-col gap-4">
74+
<section>
75+
<h2 className="mb-4">Active Subscriptions</h2>
76+
<ScrollArea className="h-32 rounded-md border">
77+
<div className="p-4">
78+
{loading ? <>Loading...</> : stripeSubscriptions
79+
?.filter((subscription) => subscription.status === "active")
80+
.map((sub) => (
81+
<div key={sub.id}>
82+
<div className="text-sm flex">
83+
{sub.items.map((item) => (
84+
<div className="flex items-center gap-2" key={item.id}>
85+
<p>{item.price.product.name}</p>
86+
<Badge>active</Badge>
87+
88+
<p className="flex gap-2">
89+
{new Date(
90+
sub.current_period_start.seconds * 1000
91+
).toLocaleString()}
92+
{" to "}
93+
{new Date(
94+
sub.current_period_end.seconds * 1000
95+
).toLocaleString()}
96+
</p>
97+
{sub?.canceled_at && (
98+
<Badge variant="secondary">
99+
Canceled{" "}
100+
{new Date(
101+
sub.canceled_at.seconds * 1000
102+
).toLocaleString()}
103+
</Badge>
104+
)}
105+
</div>
106+
))}
107+
</div>
108+
<Separator className="my-2" />
109+
</div>
110+
))}
111+
</div>
112+
</ScrollArea>
113+
</section>
114+
<section>
115+
<h2 className="mb-4">Canceled Subscriptions</h2>
116+
<ScrollArea className="h-32 rounded-md border">
117+
<div className="p-4">
118+
{loading ? <>Loading...</> : stripeSubscriptions
119+
?.filter((subscription) => subscription.status === "canceled")
120+
.map((sub) => (
121+
<div key={sub.id}>
122+
<div className="text-sm flex">
123+
{sub.items.map((item) => (
124+
<div className="flex items-center gap-2" key={item.id}>
125+
<p>{item.price.product.name}</p>
126+
<Badge variant="destructive">canceled</Badge>
127+
128+
<p className="flex gap-2">
129+
{new Date(
130+
sub.current_period_start.seconds * 1000
131+
).toLocaleString()}
132+
<span> to </span>
133+
{new Date(
134+
sub.current_period_end.seconds * 1000
135+
).toLocaleString()}
136+
{sub?.canceled_at &&
137+
new Date(
138+
sub.canceled_at.seconds * 1000
139+
).toLocaleString()}
140+
</p>
141+
</div>
142+
))}
143+
</div>
144+
<Separator className="my-2" />
145+
</div>
146+
))}
147+
</div>
148+
</ScrollArea>
149+
</section>
150+
<section>
151+
<h2 className="mb-4">Stripe Portal</h2>
152+
153+
<Button
154+
className="hover:cursor-pointer"
155+
onClick={onShowStripePortal}
156+
>
157+
{showStripePortal ? "Redirecting..." : "Open Stripe Portal"}
158+
</Button>
159+
</section>
160+
</CardContent>
161+
);
162+
}

0 commit comments

Comments
 (0)