diff --git a/apps/codingcatdev/package.json b/apps/codingcatdev/package.json index 46e5e57a0..116ac07d9 100644 --- a/apps/codingcatdev/package.json +++ b/apps/codingcatdev/package.json @@ -61,6 +61,7 @@ "firebase": "^10.4.0", "gsap": "^3.12.2", "prism-svelte": "^0.5.0", - "prism-themes": "^1.9.0" + "prism-themes": "^1.9.0", + "sveltefire": "^0.4.2" } } \ No newline at end of file diff --git a/apps/codingcatdev/src/lib/client/firebase.ts b/apps/codingcatdev/src/lib/client/firebase.ts index 99739a31f..6a143137d 100644 --- a/apps/codingcatdev/src/lib/client/firebase.ts +++ b/apps/codingcatdev/src/lib/client/firebase.ts @@ -1,17 +1,25 @@ import { browser } from '$app/environment'; -import { initializeApp, getApps, FirebaseError } from 'firebase/app'; +import { initializeApp, getApps } from 'firebase/app'; import { getAuth, - setPersistence, - browserSessionPersistence, signInWithEmailAndPassword, signInWithPopup, type AuthProvider, type Auth, createUserWithEmailAndPassword } from 'firebase/auth'; -import { getFirestore, collection, doc, addDoc, onSnapshot, Firestore } from 'firebase/firestore'; +import { + getFirestore, + collection, + doc, + addDoc, + onSnapshot, + Firestore, + setDoc, + type DocumentData, + initializeFirestore +} from 'firebase/firestore'; import { httpsCallable, getFunctions, type Functions } from 'firebase/functions'; import { getAnalytics, @@ -32,11 +40,11 @@ export const firebaseConfig = { measurementId: env.PUBLIC_FB_MEASUREMENT_ID }; -let app = getApps().at(0); -let auth: Auth; -let db: Firestore; -let functions: Functions; -let analytics: Analytics; +export let app = getApps().at(0); +export let auth: Auth; +export let firestore: Firestore; +export let functions: Functions; +export let analytics: Analytics; if ( !app && @@ -50,15 +58,25 @@ if ( firebaseConfig.measurementId ) { app = initializeApp(firebaseConfig); - auth = getAuth(app); + // As httpOnly cookies are to be used, do not persist any state client side. - setPersistence(auth, browserSessionPersistence); - db = getFirestore(app); + // setPersistence(auth, browserSessionPersistence); + firestore = initializeFirestore(app, { ignoreUndefinedProperties: true }); functions = getFunctions(app); analytics = getAnalytics(app); } else { - console.debug('Skipping Firebase Initialization, check firebaseconfig.'); + if ( + browser && + (!firebaseConfig.apiKey || + !firebaseConfig.authDomain || + !firebaseConfig.projectId || + !firebaseConfig.storageBucket || + !firebaseConfig.messagingSenderId || + !firebaseConfig.appId || + !firebaseConfig.measurementId) + ) + console.debug('Skipping Firebase Initialization, check firebaseconfig.'); } /* AUTH */ @@ -100,10 +118,13 @@ export const ccdSignInWithPopUp = async (provider: AuthProvider) => { }; /* DB */ +export const updateUser = async (docRef: string, data: DocumentData) => { + return setDoc(doc(firestore, docRef), data, { merge: true }); +}; /* STRIPE */ export const addSubscription = async (price: string, uid: string) => { - const userDoc = doc(collection(db, 'stripe-customers'), uid); + const userDoc = doc(collection(firestore, 'stripe-customers'), uid); return await addDoc(collection(userDoc, 'checkout_sessions'), { price, success_url: window.location.href, diff --git a/apps/codingcatdev/src/lib/server/firebase.ts b/apps/codingcatdev/src/lib/server/firebase.ts index 21e7e9168..4a7e807ed 100644 --- a/apps/codingcatdev/src/lib/server/firebase.ts +++ b/apps/codingcatdev/src/lib/server/firebase.ts @@ -5,6 +5,7 @@ import { getFirestore } from 'firebase-admin/firestore'; import { env as publicEnv } from '$env/dynamic/public'; import { env as privateEnv } from '$env/dynamic/private'; +import type { UserDoc } from '$lib/types'; export let app = getApps().at(0); @@ -101,33 +102,3 @@ export const getStripeProducts = async () => { } return products; }; - -export interface UserSettings { - settings: { - showDrafts?: boolean; - }; -} - -export const getUser = async (uid?: string) => { - if (!uid) return undefined; - - // Check if user is Pro and wants drafts - const auth = getAuth(app); - const user = await auth.getUser(uid); - - const db = getFirestore(); - const doc = await db.collection('users').doc(user.uid).get(); - return doc.data() as UserSettings; -}; - -export const updateUser = async (uid?: string, userSettings?: UserSettings) => { - if (!uid) return undefined; - if (!userSettings) return; - - // Check if user is Pro and wants drafts - const auth = getAuth(app); - const user = await auth.getUser(uid); - - const db = getFirestore(); - await db.collection('users').doc(user.uid).set(userSettings, { merge: true }); -}; diff --git a/apps/codingcatdev/src/lib/types/index.ts b/apps/codingcatdev/src/lib/types/index.ts index 024c7dcfd..f6e5fbc80 100644 --- a/apps/codingcatdev/src/lib/types/index.ts +++ b/apps/codingcatdev/src/lib/types/index.ts @@ -75,11 +75,11 @@ export enum ContentType { page = 'page', podcast = 'podcast', post = 'post', - sponsor = 'sponsor', + sponsor = 'sponsor' } export enum PodcastType { - codingcatdev = 'codingcatdev', + codingcatdev = 'codingcatdev' } export interface Socials { @@ -151,3 +151,25 @@ export interface DirectoryStub { } export type Stub = FileStub | DirectoryStub; +export interface UserDoc { + pro?: Pro; +} +export interface Pro { + settings?: { + showDrafts?: boolean; + }; + completed?: PathDate[]; + bookmarked?: PathDate[]; +} + +export interface PathDate { + path: string; + date: number; +} + +export interface Saved extends Content, Lesson { + savedId: string; + savedUpdated: Date; + savedComplete: boolean; + lesson?: Saved[]; +} diff --git a/apps/codingcatdev/src/routes/(content-list)/ContentCards.svelte b/apps/codingcatdev/src/routes/(content-list)/ContentCards.svelte index 98bfd42fe..3bcdd68c3 100644 --- a/apps/codingcatdev/src/routes/(content-list)/ContentCards.svelte +++ b/apps/codingcatdev/src/routes/(content-list)/ContentCards.svelte @@ -2,7 +2,14 @@ import Image from '$lib/components/content/Image.svelte'; import { ContentPublished, type Content, type Course } from '$lib/types'; import { ContentType } from '$lib/types'; - export let data: { contentType: ContentType; content: Content[] & Course[]; next?: any }; + import type { LayoutData } from '../$types'; + import ProCourseMark from '../(content-single)/course/ProCourseMark.svelte'; + export let data: { + contentType: ContentType; + content: Content[] & Course[]; + next?: any; + user: LayoutData['user']; + }; let next = data.next; const contentType = data.contentType; @@ -18,7 +25,8 @@ data = { contentType, content: [...data.content, ...d.content], - next + next, + user: data?.user }; next = d.next; }; @@ -58,13 +66,30 @@
{#if contentType === ContentType.course} - {#if content?.lesson?.filter((l) => l.locked).length} - Pro - {:else} - Free - {/if} +
+ {#if content?.lesson?.filter((l) => l.locked).length} + Pro + {:else} + Free + {/if} + +
+ {:else} +
+ +
{/if}

{content.title} @@ -88,9 +113,3 @@

{/if} - - diff --git a/apps/codingcatdev/src/routes/(content-list)/authors/AuthorCards.svelte b/apps/codingcatdev/src/routes/(content-list)/authors/AuthorCards.svelte index 0f6023574..3d9dea429 100644 --- a/apps/codingcatdev/src/routes/(content-list)/authors/AuthorCards.svelte +++ b/apps/codingcatdev/src/routes/(content-list)/authors/AuthorCards.svelte @@ -70,9 +70,3 @@ {/if} - - diff --git a/apps/codingcatdev/src/routes/(content-list)/guests/GuestCards.svelte b/apps/codingcatdev/src/routes/(content-list)/guests/GuestCards.svelte index 37b78bb6e..f02ad54c9 100644 --- a/apps/codingcatdev/src/routes/(content-list)/guests/GuestCards.svelte +++ b/apps/codingcatdev/src/routes/(content-list)/guests/GuestCards.svelte @@ -71,9 +71,3 @@ {/if} - - diff --git a/apps/codingcatdev/src/routes/(content-list)/sponsors/SponsorCards.svelte b/apps/codingcatdev/src/routes/(content-list)/sponsors/SponsorCards.svelte index f0bde5c75..4e32da3cd 100644 --- a/apps/codingcatdev/src/routes/(content-list)/sponsors/SponsorCards.svelte +++ b/apps/codingcatdev/src/routes/(content-list)/sponsors/SponsorCards.svelte @@ -63,9 +63,3 @@ {/if} - - diff --git a/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts b/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts index f174c1f5f..f814bde5d 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts +++ b/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts @@ -3,7 +3,7 @@ import { allowLocal, filterContent, getContentTypePath } from '$lib/server/conte import { ContentType, type Course, type Author, type Sponsor } from '$lib/types'; import { getShowDrafts } from '$lib/server/firebase'; -export const prerender = false; +//export const prerender = false; export const load = async ({ url, parent }) => { const data = await parent(); diff --git a/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte b/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte index 454a1d940..191e93e8e 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte @@ -61,14 +61,14 @@ {/key} {#if data?.course?.lesson && data?.course?.lesson.length > 0 && data?.course?.slug} - + {/if} {#if data?.course?.lesson && data?.course?.lesson.length > 0 && data?.course?.slug} - + {/if} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte b/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte index 99148eee8..2f0b591f0 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte @@ -1,14 +1,12 @@ {#if data?.course} @@ -25,6 +23,16 @@ {:else if data?.course?.cover} {data.course.title} {/if} +
+ {#if data?.course?.lesson?.filter((l) => l.locked).length} + Pro + {:else} + Free + {/if} + +
{#if data?.authors}
{#each data?.authors as author (author.slug)} @@ -44,15 +52,6 @@ {/each}
{/if} -
- {#if data?.course?.lesson?.filter((l) => l.locked).length} - Pro - {:else} - Free - {/if} -

{data.course.title}

{#if data?.sponsors?.length} @@ -90,12 +89,7 @@
- {#if data?.course?.lesson} -
-

Lessons

- -
- {/if} + {:else} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte b/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte index c883b6b0d..a9ff2451d 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte @@ -1,15 +1,12 @@ {#if data?.content} @@ -31,6 +28,16 @@ {:else if data?.content?.cover} {data.content.title} {/if} +
+ {#if data?.course?.lesson?.filter((l) => l.locked).length} + Pro + {:else} + Free + {/if} + +
{#if data?.authors}
{#each data?.authors as author (author.slug)} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/LessonCards.svelte b/apps/codingcatdev/src/routes/(content-single)/course/LessonCards.svelte index f2458ec86..a4ed63d66 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/LessonCards.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/LessonCards.svelte @@ -1,59 +1,57 @@ -
- {#each lesson as l} - {#if l?.section} -
- - {l.section} - -
-
- {/if} - -
+ + + {/each} +
+ +{/if} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/LessonList.svelte b/apps/codingcatdev/src/routes/(content-single)/course/LessonList.svelte index 15b3894ed..5f743af66 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/LessonList.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/LessonList.svelte @@ -1,52 +1,40 @@ -
- - +
+{/if} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/Locked.svelte b/apps/codingcatdev/src/routes/(content-single)/course/Locked.svelte new file mode 100644 index 000000000..2cc816359 --- /dev/null +++ b/apps/codingcatdev/src/routes/(content-single)/course/Locked.svelte @@ -0,0 +1,13 @@ + + +{#if locked} + +{:else} + +{/if} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/ProCourseBookmarked.svelte b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseBookmarked.svelte new file mode 100644 index 000000000..f56dd06f9 --- /dev/null +++ b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseBookmarked.svelte @@ -0,0 +1,13 @@ + + + diff --git a/apps/codingcatdev/src/routes/(content-single)/course/ProCourseCompleted.svelte b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseCompleted.svelte new file mode 100644 index 000000000..22829e762 --- /dev/null +++ b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseCompleted.svelte @@ -0,0 +1,13 @@ + + + diff --git a/apps/codingcatdev/src/routes/(content-single)/course/ProCourseMark.svelte b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseMark.svelte new file mode 100644 index 000000000..8a28f27be --- /dev/null +++ b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseMark.svelte @@ -0,0 +1,57 @@ + + +{#if data?.user?.stripeRole && $user?.uid} + {#if data?.course !== undefined} +
+ + +
+ {:else if data?.content} +
+ +
{/if} +{:else if data.content?.type === ContentType.lesson} +
+ +
+{:else} +
+{/if} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/ProCourseSaved.svelte b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseSaved.svelte new file mode 100644 index 000000000..82389eaf4 --- /dev/null +++ b/apps/codingcatdev/src/routes/(content-single)/course/ProCourseSaved.svelte @@ -0,0 +1,122 @@ + + + +{#if lesson} + {#if $saved?.lesson?.filter((l) => l.savedId === lessonId && l.savedComplete === true).length} + + {:else} + + {/if} +{:else} + + {#if $saved?.savedComplete} + + {:else} + + {/if} +{/if} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/ProSaved.svelte b/apps/codingcatdev/src/routes/(content-single)/course/ProSaved.svelte new file mode 100644 index 000000000..f412ceafd --- /dev/null +++ b/apps/codingcatdev/src/routes/(content-single)/course/ProSaved.svelte @@ -0,0 +1,52 @@ + + + +{#if $saved?.savedComplete} + +{:else} + +{/if} diff --git a/apps/codingcatdev/src/routes/(protected)/+layout.server.ts b/apps/codingcatdev/src/routes/(protected)/+layout.server.ts index f02cd3957..72f5548d6 100644 --- a/apps/codingcatdev/src/routes/(protected)/+layout.server.ts +++ b/apps/codingcatdev/src/routes/(protected)/+layout.server.ts @@ -2,7 +2,7 @@ import { allowLocal } from '$lib/server/content'; import { getStripeProducts } from '$lib/server/firebase'; import { redirect } from '@sveltejs/kit'; -export const prerender = false; +//export const prerender = false; export const load = async ({ url, parent }) => { const data = await parent(); diff --git a/apps/codingcatdev/src/routes/(protected)/account/+page.server.ts b/apps/codingcatdev/src/routes/(protected)/account/+page.server.ts index e0bb4a6b5..96402780b 100644 --- a/apps/codingcatdev/src/routes/(protected)/account/+page.server.ts +++ b/apps/codingcatdev/src/routes/(protected)/account/+page.server.ts @@ -1,30 +1,8 @@ -import { - ccdValidateSessionCookie, - getUser, - updateUser, - type UserSettings -} from '$lib/server/firebase'; -import { fail } from '@sveltejs/kit'; +//export const prerender = false; -export const prerender = false; - -export const load = async ({ url, parent }) => { +export const load = async ({ parent }) => { const data = await parent(); return { - ...(await getUser(data?.user?.uid)) + data }; }; - -export const actions = { - default: async ({ request, cookies }) => { - const data = await request.formData(); - const ccdsession = cookies.get('session'); - if (!ccdsession) { - return fail(401, { user: 'missing' }); - } - const user = await ccdValidateSessionCookie(ccdsession); - const settings = Object.fromEntries(data.entries()); - - await updateUser(user?.uid, { settings }); - } -}; diff --git a/apps/codingcatdev/src/routes/(protected)/account/+page.svelte b/apps/codingcatdev/src/routes/(protected)/account/+page.svelte index 4c3ffe989..d661c2911 100644 --- a/apps/codingcatdev/src/routes/(protected)/account/+page.svelte +++ b/apps/codingcatdev/src/routes/(protected)/account/+page.svelte @@ -5,6 +5,10 @@ import AccountCard from './AccountCard.svelte'; import MembershipCard from './MembershipCard.svelte'; import UserSettings from './UserSettings.svelte'; + import { auth } from '$lib/client/firebase'; + import { userStore } from 'sveltefire'; + + const user = userStore(auth); @@ -17,7 +21,9 @@
- + {#if $user?.uid} + + {/if}
diff --git a/apps/codingcatdev/src/routes/(protected)/account/UserSettings.svelte b/apps/codingcatdev/src/routes/(protected)/account/UserSettings.svelte index ccd14230e..4c246e767 100644 --- a/apps/codingcatdev/src/routes/(protected)/account/UserSettings.svelte +++ b/apps/codingcatdev/src/routes/(protected)/account/UserSettings.svelte @@ -1,7 +1,32 @@
@@ -12,21 +37,19 @@
-
+
updateShowDrafts(e)} >
Show Drafts
Pro Feature
-
- -
- +
diff --git a/apps/codingcatdev/src/routes/(protected)/dashboard/+page.svelte b/apps/codingcatdev/src/routes/(protected)/dashboard/+page.svelte index 4effd20d9..34b1526c1 100644 --- a/apps/codingcatdev/src/routes/(protected)/dashboard/+page.svelte +++ b/apps/codingcatdev/src/routes/(protected)/dashboard/+page.svelte @@ -1,12 +1,19 @@
@@ -22,33 +29,13 @@ {/if}
+ + + {#if $user?.uid} + + + {/if} -
-

✨ New and Featured

-
- -
-
-
-

📅 Coming Soon

-
- {#if !data?.user?.stripeRole} -
-
You must be a Pro member to preview upcoming courses.
- -
- {:else if data?.showDrafts} - - {:else} -
-
- You have chosen to not show drafts, if you would like to start seeing them again go - to your Account. -
-
- {/if} -
-
diff --git a/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardBookmarks.svelte b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardBookmarks.svelte new file mode 100644 index 000000000..c6119df48 --- /dev/null +++ b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardBookmarks.svelte @@ -0,0 +1,82 @@ + + +
+
+
+

Bookmarks

+
+ {#if !data?.user?.stripeRole} +
+
You must be a Pro member to view bookmarks.
+ +
+ {:else if $user?.uid} + + {/if} +
diff --git a/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardComingSoon.svelte b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardComingSoon.svelte new file mode 100644 index 000000000..08d458ca2 --- /dev/null +++ b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardComingSoon.svelte @@ -0,0 +1,31 @@ + + +
+
+
+

Coming Soon

+
+ {#if !data?.user?.stripeRole} +
+
You must be a Pro member to preview upcoming courses.
+ +
+ {:else if data?.showDrafts} + + {:else} +
+
+ You have chosen to not show drafts, if you would like to start seeing them again go to your Account. +
+
+ {/if} +
diff --git a/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardCompleted.svelte b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardCompleted.svelte new file mode 100644 index 000000000..43288b3c6 --- /dev/null +++ b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardCompleted.svelte @@ -0,0 +1,82 @@ + + +
+
+
+

Completed

+
+ {#if !data?.user?.stripeRole} +
+
You must be a Pro member to view completed lessons.
+ +
+ {:else if $user?.uid} + + {/if} +
diff --git a/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardNewFeatured.svelte b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardNewFeatured.svelte new file mode 100644 index 000000000..3e518d7c6 --- /dev/null +++ b/apps/codingcatdev/src/routes/(protected)/dashboard/DashboardNewFeatured.svelte @@ -0,0 +1,15 @@ + + +
+
+
+

New and Featured

+
+ +
diff --git a/apps/codingcatdev/src/routes/+layout.server.ts b/apps/codingcatdev/src/routes/+layout.server.ts index 43d9106f5..3a05f6105 100644 --- a/apps/codingcatdev/src/routes/+layout.server.ts +++ b/apps/codingcatdev/src/routes/+layout.server.ts @@ -1,8 +1,10 @@ -import { listContent, getContentTypeDirectory } from '$lib/server/content'; +import { listContent, getContentTypeDirectory, allowLocal } from '$lib/server/content'; import { ccdValidateSessionCookie, validateStripeRole } from '$lib/server/firebase'; import { type Content, ContentType } from '$lib/types'; import type { Cookies } from '@sveltejs/kit'; -export const prerender = false; +import { preview } from '$lib/server/content'; + +//export const prerender = false; export const load = async ({ cookies }: { cookies: Cookies }) => { try { // Get latest podcast @@ -16,7 +18,8 @@ export const load = async ({ cookies }: { cookies: Cookies }) => { const ccdsession = cookies.get('session'); if (!ccdsession) { return { - podcasts + podcasts, + preview }; } const user = await ccdValidateSessionCookie(ccdsession); @@ -29,12 +32,13 @@ export const load = async ({ cookies }: { cookies: Cookies }) => { ...user, stripeRole }, - podcasts + podcasts, + preview }; } catch (error) { cookies.set('session', '', { expires: new Date(0) }); console.error(error); - return {}; + return { preview }; } }; diff --git a/apps/codingcatdev/src/routes/+layout.svelte b/apps/codingcatdev/src/routes/+layout.svelte index 3523d6704..faeb206fe 100644 --- a/apps/codingcatdev/src/routes/+layout.svelte +++ b/apps/codingcatdev/src/routes/+layout.svelte @@ -1,6 +1,5 @@