Skip to content

Commit 13fb063

Browse files
authored
feature/single course pricing (#558)
* feature(update account with purchases) * fix(unlock lesson for single stripe purchase)
1 parent c6347e5 commit 13fb063

19 files changed

Lines changed: 196 additions & 56 deletions

File tree

‎apps/codingcatdev/src/lib/client/firebase.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from 'firebase/analytics';
3030

3131
import { env } from '$env/dynamic/public';
32+
import { getStorage, type FirebaseStorage } from 'firebase/storage';
3233

3334
export const firebaseConfig = {
3435
apiKey: env.PUBLIC_FB_API_KEY,
@@ -45,6 +46,7 @@ export let auth: Auth;
4546
export let firestore: Firestore;
4647
export let functions: Functions;
4748
export let analytics: Analytics;
49+
export let storage: FirebaseStorage;
4850

4951
if (
5052
!app &&
@@ -65,6 +67,7 @@ if (
6567
firestore = initializeFirestore(app, { ignoreUndefinedProperties: true });
6668
functions = getFunctions(app);
6769
analytics = getAnalytics(app);
70+
storage = getStorage(app);
6871
} else {
6972
if (
7073
browser &&

‎apps/codingcatdev/src/lib/server/firebase.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,34 @@ export const getStripeProducts = async () => {
102102
}
103103
return products;
104104
};
105+
106+
export const didUserPurchase = async (slug: string, uid?: string) => {
107+
if (!uid) return false;
108+
109+
// User
110+
const auth = getAuth(app);
111+
const user = await auth.getUser(uid);
112+
113+
const db = getFirestore();
114+
115+
if (!db) return false;
116+
117+
const snapshot = await db
118+
.collection(`stripe-customers/${user.uid}/payments`)
119+
.where('canceled_at', '==', null)
120+
.get();
121+
122+
if (snapshot.empty) return false;
123+
124+
for (const doc of snapshot.docs) {
125+
const payment = doc.data();
126+
for (const item of payment?.items) {
127+
const productSnapshot = await db.doc(`stripe-products/${item?.price?.product}`).get();
128+
if (!productSnapshot) return false;
129+
130+
const product = productSnapshot.data();
131+
if (product?.metadata?.slug === slug) return true;
132+
}
133+
}
134+
return false;
135+
};

‎apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { error, redirect } from '@sveltejs/kit';
22
import { allowLocal, filterContent, getContentTypePath } from '$lib/server/content';
33
import { ContentType, type Course, type Author, type Sponsor } from '$lib/types';
4-
import { getShowDrafts } from '$lib/server/firebase';
4+
import { didUserPurchase, getShowDrafts } from '$lib/server/firebase';
55

66
//export const prerender = false;
77

@@ -28,7 +28,7 @@ export const load = async ({ url, parent }) => {
2828
contentItems: [md],
2929
userPreview: showDrafts
3030
});
31-
const course = contentItems?.at(0);
31+
let course = contentItems?.at(0);
3232
if (!course) throw error(404);
3333

3434
// Content is good, fetch surrounding items
@@ -48,7 +48,24 @@ export const load = async ({ url, parent }) => {
4848
}
4949
}
5050

51-
const content = course.lesson?.find((l) => l.slug === lessonSlug);
51+
/*
52+
* Single Course Purchase check to see if lesson should be unlocked
53+
* stripe-products/productId/metadata.slug will have course
54+
* this shoudl be updated in the stripe product and it will push to firestore
55+
*/
56+
57+
const singlePurchase = await didUserPurchase(course.slug, data?.user?.uid);
58+
59+
course = {
60+
...course,
61+
lesson: course.lesson?.map((l) => {
62+
return { ...l, locked: l.locked ? !singlePurchase : l.locked };
63+
})
64+
};
65+
66+
// Mark any of the current lessons as locked or not
67+
let content = course.lesson?.find((l) => l.slug === lessonSlug);
68+
5269
if (
5370
lessonSlug &&
5471
content?.locked &&
@@ -62,7 +79,8 @@ export const load = async ({ url, parent }) => {
6279
content,
6380
course,
6481
authors,
65-
sponsors
82+
sponsors,
83+
singlePurchase
6684
};
6785
} catch (e) {
6886
console.error(e);

‎apps/codingcatdev/src/routes/(content-single)/course/LessonCards.svelte‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import Image from '$lib/components/content/Image.svelte';
3+
import { ContentType } from '$lib/types';
34
45
import type { LayoutData } from './$types';
56
import ProCourseMark from './ProCourseMark.svelte';
@@ -39,7 +40,12 @@
3940
<div class="p-4">
4041
<header class="relative py-6 text-2xl font-bold capitalize card-header">
4142
<div class="absolute top-0 right-0">
42-
<ProCourseMark locked={l?.locked} lesson={l} {data} />
43+
<ProCourseMark
44+
locked={l?.locked}
45+
lesson={l}
46+
{data}
47+
contentType={ContentType.lesson}
48+
/>
4349
</div>
4450
<div>
4551
{l.title}

‎apps/codingcatdev/src/routes/(content-single)/course/LessonList.svelte‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { afterNavigate } from '$app/navigation';
33
import { storeCurrentUrl } from '$lib/stores/stores';
4+
import { ContentType } from '$lib/types';
45
import type { LayoutData } from './$types';
56
import ProCourseMark from './ProCourseMark.svelte';
67
export let data: LayoutData;
@@ -48,7 +49,12 @@
4849
{l.title}
4950
</a>
5051
<div class="flex gap-1 absolute right-1 top-1">
51-
<ProCourseMark locked={l?.locked} lesson={l} {data} />
52+
<ProCourseMark
53+
locked={l?.locked}
54+
lesson={l}
55+
{data}
56+
contentType={ContentType.lesson}
57+
/>
5258
</div>
5359
</li>
5460
{/each}

‎apps/codingcatdev/src/routes/(content-single)/course/ProCourseMark.svelte‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<script lang="ts">
22
import Locked from './Locked.svelte';
33
import type { LayoutData } from './$types';
4-
import type { Content, Course, Lesson, Saved } from '$lib/types';
5-
import { ContentType } from '$lib/types';
4+
import { ContentType, type Content, type Course, type Lesson, type Saved } from '$lib/types';
65
import ProCourseCompleted from './ProCourseCompleted.svelte';
76
import ProCourseBookmarked from './ProCourseBookmarked.svelte';
87
import ProSaved from './ProSaved.svelte';
@@ -18,6 +17,7 @@
1817
};
1918
export let lesson: Lesson | undefined = undefined;
2019
export let locked: boolean | undefined = undefined;
20+
export let contentType: ContentType | undefined = undefined;
2121
2222
const user = userStore(auth);
2323
</script>
@@ -47,11 +47,10 @@
4747
savedRef="/bookmarked"
4848
savedIconSource={Bookmark}
4949
/>
50-
</div>{/if}
51-
{:else if data.content?.type === ContentType.lesson}
50+
</div>
51+
{/if}
52+
{:else if contentType === ContentType.lesson}
5253
<div class="flex w-12 gap-1">
5354
<Locked {locked} />
5455
</div>
55-
{:else}
56-
<div />
5756
{/if}

‎apps/codingcatdev/src/routes/(content-single)/course/sveltekit-firebase/+page.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ authors:
55
cloudinary_convert: false
66
cover: https://media.codingcat.dev/image/upload/v1702159174/main-codingcatdev-photo/courses/sveltekit-firebase/SvelteFirebaseCourse.png
77
excerpt: 'Learn how to use SvelteKit and Firebase to build high-performance, scalable web applications.'
8-
published: draft
8+
published: published
99
slug: sveltekit-firebase
10-
start: Jan 1, 2024
10+
start: Dec 15, 2023
1111
title: 'Learn SvelteKit and Firebase: The Ultimate Guide'
1212
youtube:
1313
---

‎apps/codingcatdev/src/routes/(content-single)/course/sveltekit-firebase/lesson/1_1-welcome/+page.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ cloudinary_convert: false
66
cover: https://media.codingcat.dev/image/upload/v1702154357/main-codingcatdev-photo/courses/sveltekit-firebase/sveltekit-firebase-welcome/sveltekit-firebase-welcome.png
77
excerpt: Thank you for trusting me to teach you this course!
88
locked: false
9-
published: draft
9+
published: published
1010
slug: welcome
11-
start: April 17, 2023
11+
start: Dec 15, 2023
1212
section: Welcome and Resources
1313
title: Welcome
1414
weight: 1

‎apps/codingcatdev/src/routes/(content-single)/course/sveltekit-firebase/lesson/1_2-resources/+page.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ cloudinary_convert: false
66
cover: https://media.codingcat.dev/video/upload/v1702220205/main-codingcatdev-photo/courses/sveltekit-firebase/sveltekit-firebase-resources/sveltekit-firebase-resources.mp4
77
excerpt: Resources and links for the course.
88
locked: false
9-
published: draft
9+
published: published
1010
slug: welcome
11-
start: April 17, 2023
11+
start: Dec 15, 2023
1212
title: Resources
1313
weight: 2
1414
videoCloudinary: https://media.codingcat.dev/video/upload/v1702162150/main-codingcatdev-photo/courses/sveltekit-firebase/sveltekit-firebase-resources/sveltekit-firebase-resources.mp4

‎apps/codingcatdev/src/routes/(content-single)/course/sveltekit-firebase/lesson/2_1-intro-to-svelte/+page.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ cloudinary_convert: false
66
cover: https://media.codingcat.dev/image/upload/v1702235179/main-codingcatdev-photo/courses/sveltekit-firebase/svelte-firebase-intro-to-svelte/sveltekit-firebase-intro-svelte.png
77
excerpt: What is Svelte?
88
locked: false
9-
published: draft
9+
published: published
1010
slug: intro-to-svelte
11-
start: April 17, 2023
11+
start: Dec 15, 2023
1212
section: Intro / Project Setup
1313
title: Intro to Svelte
14-
weight: 3
14+
weight: 2.1
1515
videoCloudinary:
1616
youtube: https://youtu.be/gjsJFZsiXOU
1717
---

0 commit comments

Comments
 (0)