diff --git a/README.md b/README.md index bf383cb..d68667b 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,16 @@ To apply the same base UI in a project, run the following command: npx shadcn@latest add "https://v0.dev/chat/b/b_fFQhsfElqQi" ``` +#### 🏠 Homepage + +The homepage layout was also crafted using v0. The generation prompt for it was: + +```text +A minimal homepage for a Google Drive clone named T4S Drive. It should be just a +marketing page with a "get started" button. A gradient would be nice, please use +black and dark neutral grays. +``` + ### 🧰 Learn More about the T3 Stack To explore more about the [T3 Stack](https://create.t3.gg/), refer to the @@ -108,7 +118,7 @@ The database and UI are now connected, some improvements to make: - [x] Change folders to link components, remove all client state - [x] Clean up the database and data fetching patterns -- [ ] Real homepage +- [x] Real homepage ### 📝 Note from 7-4-2025 @@ -124,7 +134,13 @@ can be approved: ## 🎯 Fun Follow Ups -### Folder deletion - -Make sure to fetch all of the folders that have it as a parent, and their -children too. +- [ ] **Folder creation**
Make a server action that takes a name and + parentId, and creates a folder with that name and parentId (don't forget + to set the ownerId). +- [ ] **Folder deletion**
Make sure to fetch all of the folders that have + it as a parent, and their children too. +- [ ] **Access Control**
Check if user is owner before showing the folder + page. +- [ ] **Make a "file view" page** +- [ ] **Access control** +- [ ] **Toasts notifications** diff --git a/src/app/(home)/drive/page.tsx b/src/app/(home)/drive/page.tsx new file mode 100644 index 0000000..98b4e3f --- /dev/null +++ b/src/app/(home)/drive/page.tsx @@ -0,0 +1,35 @@ +import { auth } from "@clerk/nextjs/server"; +import { redirect } from "next/navigation"; + +import { Button } from "~/components/ui/button"; +import * as mutations from "~/server/db/mutations"; +import * as queries from "~/server/db/queries"; + +export default async function DrivePage() { + const session = await auth(); + if (!session.userId) return redirect("/sign-in"); + + const rootFolder = await queries.getRootFolderForUser(session.userId); + + if (!rootFolder) { + return ( +
{ + "use server"; + const rootFolderId = await mutations.onboardUser(session.userId); + return redirect(`/f/${rootFolderId}`); + }} + > + +
+ ); + } + + return redirect(`/f/${rootFolder.id}`); +} diff --git a/src/app/(home)/layout.tsx b/src/app/(home)/layout.tsx new file mode 100644 index 0000000..58e3313 --- /dev/null +++ b/src/app/(home)/layout.tsx @@ -0,0 +1,129 @@ +import { Cloud, Shield, Users, Zap } from "lucide-react"; +import Link from "next/link"; + +export default function HomePage({ children }: { children: React.ReactNode }) { + return ( +
+ {/* Navigation */} + + + {/* Hero Section */} +
+
+

+ Your files, +
+ + everywhere + +

+ +

+ Store, sync, and share your files with T4S Drive. Access your + documents from any device, anywhere in the world. +

+ + {children} + +

+ Free 15GB storage • No credit card required +

+
+
+ + {/* Features Section */} +
+
+
+
+ +
+

Secure Storage

+

+ Your files are encrypted and protected with enterprise-grade + security. +

+
+ +
+
+ +
+

Lightning Fast

+

+ Upload and download files at blazing speeds with our global CDN. +

+
+ +
+
+ +
+

Easy Sharing

+

+ Share files and folders with anyone, with granular permission + controls. +

+
+
+
+ + {/* Footer */} + +
+ ); +} diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 74ab28a..88456b6 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,3 +1,25 @@ -export default async function HomePage() { - return

Welcome to your Drive

; +import { auth } from "@clerk/nextjs/server"; +import { redirect } from "next/navigation"; + +import { Button } from "~/components/ui/button"; + +export default function HomePage() { + return ( +
{ + "use server"; + const session = await auth(); + if (!session.userId) return redirect("/sign-in"); + redirect("/drive"); + }} + > + +
+ ); } diff --git a/src/app/(home)/sign-in/page.tsx b/src/app/(home)/sign-in/page.tsx new file mode 100644 index 0000000..ae558ad --- /dev/null +++ b/src/app/(home)/sign-in/page.tsx @@ -0,0 +1,15 @@ +import { SignInButton } from "@clerk/nextjs"; + +import { Button } from "~/components/ui/button"; + +export default function HomePage() { + return ( + + ); +} diff --git a/src/server/db/mutations.ts b/src/server/db/mutations.ts index bfedb51..6d05caf 100644 --- a/src/server/db/mutations.ts +++ b/src/server/db/mutations.ts @@ -1,5 +1,26 @@ import { db } from "~/server/db"; -import { type File, files_table as filesSchema } from "~/server/db/schema"; +import { + type File, + folders_table as folderSchema, + files_table as filesSchema, +} from "~/server/db/schema"; + +export async function onboardUser(userId: string) { + const [rootFolder] = await db + .insert(folderSchema) + .values({ name: "root", parent: null, ownerId: userId }) + .$returningId(); + + const rootFolderId = rootFolder!.id; + + await db.insert(folderSchema).values([ + { name: "Trash", parent: rootFolderId, ownerId: userId }, + { name: "Shared", parent: rootFolderId, ownerId: userId }, + { name: "Documents", parent: rootFolderId, ownerId: userId }, + ]); + + return rootFolderId; +} export function createFile( file: Pick, diff --git a/src/server/db/queries.ts b/src/server/db/queries.ts index c8aed30..9bb5e4f 100644 --- a/src/server/db/queries.ts +++ b/src/server/db/queries.ts @@ -1,6 +1,6 @@ import "server-only"; // Ensure this file is only run on the server -import { eq } from "drizzle-orm"; +import { and, eq, isNull } from "drizzle-orm"; import { db } from "~/server/db"; import { @@ -27,6 +27,14 @@ export async function getAllParentsForFolder(folderId: number) { return parents; } +export async function getRootFolderForUser(ownerId: string) { + const folders = await db + .select() + .from(folderSchema) + .where(and(eq(folderSchema.ownerId, ownerId), isNull(folderSchema.parent))); + return folders[0]; +} + export function getAllFolders(folderId: number) { return db .select()