From c543d3d8daee1ce61af29a703dbbb44801293d66 Mon Sep 17 00:00:00 2001 From: MoRevolution <81017119+MoRevolution@users.noreply.github.com> Date: Tue, 29 Apr 2025 09:31:07 -0500 Subject: [PATCH 1/6] bugfix: force page to load all data before fetch for dashboard --- .github/workflows/lint.yml | 6 ++-- components/google-login.tsx | 57 +++++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d798f85..48dc6bd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,10 +22,12 @@ jobs: uses: pnpm/action-setup@v2 with: version: 8 - run_install: false - name: Install dependencies - run: pnpm install --frozen-lockfile --prod false + run: | + pnpm add -D next@15.2.4 react@^18.3.1 react-dom@^18.3.1 + pnpm add -D eslint + pnpm install --frozen-lockfile=false - name: Run ESLint run: pnpm run lint \ No newline at end of file diff --git a/components/google-login.tsx b/components/google-login.tsx index d085a4b..688cc70 100644 --- a/components/google-login.tsx +++ b/components/google-login.tsx @@ -22,11 +22,13 @@ interface GoogleLoginProps { export function GoogleLogin({ variant }: GoogleLoginProps) { const [isLoading, setIsLoading] = useState(false) + const [dataLoadingStatus, setDataLoadingStatus] = useState(null) const router = useRouter() const { login } = useAuth() const handleGoogleLogin = async () => { setIsLoading(true) + setDataLoadingStatus("Signing in with Google...") try { const provider = new GoogleAuthProvider() @@ -37,6 +39,8 @@ export function GoogleLogin({ variant }: GoogleLoginProps) { const token = credential?.accessToken const user = result.user + setDataLoadingStatus("Creating your account...") + // Create/update user via API const createUserResponse = await fetch('/api/users/create', { method: 'POST', @@ -58,6 +62,7 @@ export function GoogleLogin({ variant }: GoogleLoginProps) { const hasData = await isDataInIndexedDB(WATCH_HISTORY_FILE); if (!hasData) { + setDataLoadingStatus("Fetching your YouTube watch history...") console.log("πŸ“¦ No watch history data found in IndexedDB, fetching..."); try { if (!token) { @@ -77,12 +82,15 @@ export function GoogleLogin({ variant }: GoogleLoginProps) { variant: "destructive" }); setIsLoading(false); + setDataLoadingStatus(null); return; // Don't proceed to dashboard if data fetch fails } } else { console.log("πŸ“¦ Using existing watch history data from IndexedDB"); } + setDataLoadingStatus("Finalizing your data...") + // Complete login process with actual user data login({ uid: user.uid, @@ -99,6 +107,7 @@ export function GoogleLogin({ variant }: GoogleLoginProps) { : "You have been successfully logged in.", }) + // Only redirect after all data is loaded router.push("/dashboard") } catch (error) { console.error("Error during Google login process:", error) @@ -108,6 +117,7 @@ export function GoogleLogin({ variant }: GoogleLoginProps) { }) } finally { setIsLoading(false) + setDataLoadingStatus(null) } } @@ -119,29 +129,34 @@ export function GoogleLogin({ variant }: GoogleLoginProps) { disabled={isLoading} > {isLoading ? ( - + <> + + {dataLoadingStatus || "Loading..."} + ) : ( - - - - - - - + <> + + + + + + + + {variant === "login" ? "Sign in with Google" : "Sign up with Google"} + )} - {variant === "login" ? "Sign in with Google" : "Sign up with Google"} ) } From 250c2790e2ed418a4f5e5d552eda2248c5692c6d Mon Sep 17 00:00:00 2001 From: MoRevolution <81017119+MoRevolution@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:58:48 -0500 Subject: [PATCH 2/6] fix(indexeddb): handle missing object store creation Fix issue where object store wasn't being created properly, causing errors. --- lib/indexeddb.ts | 100 +++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/lib/indexeddb.ts b/lib/indexeddb.ts index 7c3ef39..7240d39 100644 --- a/lib/indexeddb.ts +++ b/lib/indexeddb.ts @@ -1,27 +1,44 @@ -import { openDB } from "idb" +import { openDB, IDBPDatabase } from "idb" import { getVideosMetadata } from "./youtube-metadata" import { DB_NAME, FILES_STORE, WATCH_HISTORY_FILE } from './constants' -export async function isDataInIndexedDB(fileId: string): Promise { +// Current database version - increment this when schema changes +const DB_VERSION = 1 + +/** + * Initializes the database and ensures the object store exists + * @returns A promise that resolves to the database instance + */ +async function initDB(): Promise { try { - const db = await openDB(DB_NAME, 1, { + const db = await openDB(DB_NAME, DB_VERSION, { upgrade(db) { + // Create object store if it doesn't exist if (!db.objectStoreNames.contains(FILES_STORE)) { + console.log(`πŸ”„ Creating object store: ${FILES_STORE}`) db.createObjectStore(FILES_STORE, { keyPath: "fileName" }) } }, }) - // Check if the object store exists + // Verify the store exists if (!db.objectStoreNames.contains(FILES_STORE)) { - console.log("❌ Object store does not exist") - return false + throw new Error(`Failed to create object store: ${FILES_STORE}`) } + return db + } catch (error) { + console.error("❌ Database initialization failed:", error) + throw error + } +} + +export async function isDataInIndexedDB(fileId: string): Promise { + try { + const db = await initDB() const tx = db.transaction(FILES_STORE, "readonly") const store = tx.objectStore(FILES_STORE) const existingFile = await store.get(fileId) - return !!existingFile } catch (error) { console.error("❌ Error checking IndexedDB:", error) @@ -30,33 +47,30 @@ export async function isDataInIndexedDB(fileId: string): Promise { } export async function storeDataInIndexedDB(data: { [key: string]: string }) { + let db: IDBPDatabase | null = null + try { - const db = await openDB(DB_NAME, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains(FILES_STORE)) { - db.createObjectStore(FILES_STORE, { keyPath: "fileName" }) - } - }, - }) - - // Check if the object store exists - if (!db.objectStoreNames.contains(FILES_STORE)) { - console.error("❌ Object store does not exist") - throw new Error("Object store does not exist") - } - + db = await initDB() const tx = db.transaction(FILES_STORE, "readwrite") const store = tx.objectStore(FILES_STORE) - for (const [fileName, content] of Object.entries(data)) { - await store.put({ fileName, content }) - } - + // Store all entries in a single transaction + const putPromises = Object.entries(data).map(([fileName, content]) => + store.put({ fileName, content }) + ) + + await Promise.all(putPromises) await tx.done - console.log("βœ… Data stored in IndexedDB successfully") + + console.log(`βœ… Successfully stored ${Object.keys(data).length} items in IndexedDB`) } catch (error) { console.error("❌ Error storing data in IndexedDB:", error) - throw error // Re-throw to let the caller handle it + throw error + } finally { + // Ensure database connection is closed + if (db) { + db.close() + } } } @@ -169,30 +183,14 @@ export async function processAndStoreWatchHistoryByYear(watchHistoryData: any[]) // Store all year data in a single transaction console.log('πŸ’Ύ Storing processed data...') - const db = await openDB(DB_NAME, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains(FILES_STORE)) { - db.createObjectStore(FILES_STORE, { keyPath: "fileName" }) - } - }, - }) - - const tx = db.transaction(FILES_STORE, "readwrite") - const store = tx.objectStore(FILES_STORE) - - // Prepare all put operations - const putOperations = Object.entries(dataByYear).map(([year, data]) => { - const fileName = `watch-history-${year}` - console.log(`πŸ“ Storing ${data.length} entries for year ${year}...`) - return store.put({ - fileName, - content: JSON.stringify(data) - }) - }) - - // Execute all put operations - await Promise.all(putOperations) - await tx.done + await storeDataInIndexedDB( + Object.fromEntries( + Object.entries(dataByYear).map(([year, data]) => [ + `watch-history-${year}`, + JSON.stringify(data) + ]) + ) + ) console.log('βœ… Watch history processing complete!') } \ No newline at end of file From fe8b496a9830376ef4456e7a28f63fc9290ed69c Mon Sep 17 00:00:00 2001 From: MoRevolution <81017119+MoRevolution@users.noreply.github.com> Date: Thu, 1 May 2025 21:46:25 -0500 Subject: [PATCH 3/6] chore(ui cleanup + updates) - cleaned up some visual inconsistencies - added emphasis to the takout data being super necessary - made the logo (top left) a redirect to the homepage for all pages etc etc etc, bla bla bla --- app/dashboard/creators/page.tsx | 1 - app/dashboard/page.tsx | 6 ++-- app/login/page.tsx | 20 ++++++++--- app/page.tsx | 60 +++++++++++++++++++++---------- app/profile/page.tsx | 6 ++-- app/takeout-instructions/page.tsx | 8 +++-- components/dashboard-header.tsx | 7 ++-- 7 files changed, 74 insertions(+), 34 deletions(-) diff --git a/app/dashboard/creators/page.tsx b/app/dashboard/creators/page.tsx index 39d776d..ee3a9b6 100644 --- a/app/dashboard/creators/page.tsx +++ b/app/dashboard/creators/page.tsx @@ -134,7 +134,6 @@ export default function CreatorsPage() { } catch (error) { console.error("❌ Error in fetchData:", error) - setError('There was an error loading your YouTube data. Showing sample data instead.') setStats(mockCreatorStats as { primaryYear: DashboardStats; comparisonYear?: DashboardStats }) } finally { setIsLoading(false) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index d871212..cff78d9 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -67,7 +67,7 @@ const mockStats = { title: "These new computers are getting creepy… Copilot+ PC first look", channel: "Fireship", count: 12, - videoId: "" + videoId: "hlwcZpEx2IY" }, { title: "iPhone 16/16 Pro Review: Times Have Changed!", @@ -437,8 +437,8 @@ export default function DashboardPage() {
-
- {stats?.primaryYear.mostWatchedVideos?.map((video) => ( +
+ {stats?.primaryYear.mostWatchedVideos?.map((video) => (
- - YouTube Wrapped + + + YouTube Wrapped +
@@ -62,8 +64,18 @@ export default function LoginPage() { -
- Make sure you have already completed the takeout instructions before signing in. +
+
+
+ + + +
+
+

Psst! Before you dive in... 🎬

+

You'll need your YouTube watch history to get your Wrapped! Don't worry, it's super easy - just follow our quick guide to get your data.

+
+
diff --git a/app/page.tsx b/app/page.tsx index 2d94ee4..0107a0d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -17,8 +17,10 @@ export default function HomePage() {
- - YouTube Wrapped + + + YouTube Wrapped +
@@ -100,14 +104,17 @@ export default function HomePage() {
-
-
+
+

Top Categories

-
+
Gaming - 32% +
+ 32% + (+8%) +
@@ -116,7 +123,10 @@ export default function HomePage() {
Tech - 28% +
+ 28% + (+3%) +
@@ -125,7 +135,10 @@ export default function HomePage() {
Music - 18% +
+ 18% + (-2%) +
@@ -134,7 +147,10 @@ export default function HomePage() {
Education - 12% +
+ 12% + (+4%) +
@@ -143,7 +159,10 @@ export default function HomePage() {
Entertainment - 10% +
+ 10% + (-13%) +
@@ -198,19 +217,22 @@ export default function HomePage() {

How It Works

- Some steps to get us going... + Get your personalized YouTube Wrapped in just 3 simple steps

-
+
+
+ Required +
1

Export Your Data

Get your YouTube watch history from Google Takeout. (It's sooo easy, kinda!)

-
diff --git a/app/profile/page.tsx b/app/profile/page.tsx index 0c7ba5c..3bf1a1e 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -94,8 +94,10 @@ export default function ProfilePage() {
- - YouTube Wrapped + + + YouTube Wrapped +
diff --git a/app/takeout-instructions/page.tsx b/app/takeout-instructions/page.tsx index fd7b53c..1458c41 100644 --- a/app/takeout-instructions/page.tsx +++ b/app/takeout-instructions/page.tsx @@ -12,8 +12,10 @@ export default function TakeoutInstructionsPage() {
- - YouTube Wrapped + + + YouTube Wrapped +