Skip to content

Commit aa51290

Browse files
authored
Connect UI to Database (#4)
1 parent 18ea426 commit aa51290

6 files changed

Lines changed: 151 additions & 109 deletions

File tree

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,24 @@ pnpm run dev
8585

8686
Tracking progress on key features and tasks for the project.
8787

88-
- [ ] 🛢️ Set up the database and define data models
89-
- [ ] 🔗 Sync folder open state with the URL
88+
- [x] 🛢️ Set up the database and define data models
89+
- [x] 🔗 Sync folder open state with the URL
9090
- [ ] 🔐 Implement user authentication
9191
- [ ] 📁 Enable file upload functionality
92+
- [ ] 📊 Add analytics tracking
9293

9394
### 📝 Note from 5-28-2025
9495

9596
Just finished up the database connection, next steps:
9697

9798
- [x] Update schema to show files and folders
9899
- [x] Manually insert examples
99-
- [ ] Render them in the UI
100-
- [ ] Push and make sure it all works
100+
- [x] Render them in the UI
101+
102+
### 📝 Note from 6-4-2025
103+
104+
The database and UI are now connected, some improvements to make:
105+
106+
- [ ] Change folders to link components, remove all client state
107+
- [ ] Clean up the database and data fetching patterns
108+
- [ ] Real homepage

src/app/drive-contents.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use client";
2+
3+
import { ChevronRight, Upload } from "lucide-react";
4+
import { useMemo, useState } from "react";
5+
6+
import { Button } from "~/components/ui/button";
7+
import type { files, folders } from "~/server/db/schema";
8+
import { FileRow, FolderRow } from "./file-row";
9+
10+
export default function DriveContents(props: {
11+
files: (typeof files.$inferSelect)[];
12+
folders: (typeof folders.$inferSelect)[];
13+
}) {
14+
const [currentFolder, setCurrentFolder] = useState(1);
15+
16+
const handleFolderClick = (folderId: number) => {
17+
setCurrentFolder(folderId);
18+
};
19+
20+
const breadcrumbs = useMemo(() => {
21+
const breadcrumbs = [];
22+
let currentId = currentFolder;
23+
24+
while (currentId !== 1) {
25+
const folder = props.folders.find((file) => file.id === currentId);
26+
if (folder) {
27+
breadcrumbs.unshift(folder);
28+
currentId = folder.parent ?? 1;
29+
} else {
30+
break;
31+
}
32+
}
33+
34+
return breadcrumbs;
35+
}, [currentFolder, props.folders]);
36+
37+
const handleUpload = () => {
38+
alert("Upload functionality would be implemented here");
39+
};
40+
41+
return (
42+
<div className="min-h-screen bg-gray-900 p-8 text-gray-100">
43+
<div className="mx-auto max-w-6xl">
44+
<div className="mb-6 flex items-center justify-between">
45+
<div className="flex items-center">
46+
<Button
47+
onClick={() => setCurrentFolder(1)}
48+
variant="ghost"
49+
className="mr-2 text-gray-300 hover:text-white"
50+
>
51+
My Drive
52+
</Button>
53+
{breadcrumbs.map((folder) => (
54+
<div key={folder.id} className="flex items-center">
55+
<ChevronRight className="mx-2 text-gray-500" size={16} />
56+
<Button
57+
onClick={() => handleFolderClick(folder.id)}
58+
variant="ghost"
59+
className="text-gray-300 hover:text-white"
60+
>
61+
{folder.name}
62+
</Button>
63+
</div>
64+
))}
65+
</div>
66+
<Button
67+
onClick={handleUpload}
68+
className="bg-blue-600 text-white hover:bg-blue-700"
69+
>
70+
<Upload className="mr-2" size={20} />
71+
Upload
72+
</Button>
73+
</div>
74+
<div className="rounded-lg bg-gray-800 shadow-xl">
75+
<div className="border-b border-gray-700 px-6 py-4">
76+
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-gray-400">
77+
<div className="col-span-6">Name</div>
78+
<div className="col-span-3">Type</div>
79+
<div className="col-span-3">Size</div>
80+
</div>
81+
</div>
82+
<ul>
83+
{props.folders.map((folder) => (
84+
<FolderRow
85+
key={folder.id}
86+
folder={folder}
87+
onFolderClick={() => handleFolderClick(folder.id)}
88+
/>
89+
))}
90+
{props.files.map((file) => (
91+
<FileRow key={file.id} file={file} />
92+
))}
93+
</ul>
94+
</div>
95+
</div>
96+
</div>
97+
);
98+
}

src/app/f/[folderId]/page.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { eq } from "drizzle-orm";
2+
import { z } from "zod";
3+
4+
import { db } from "~/server/db";
5+
import {
6+
files as fileSchema,
7+
folders as folderSchema,
8+
} from "~/server/db/schema";
9+
10+
import DriveContents from "../../drive-contents";
11+
12+
export default async function GoogleDriveClone(props: {
13+
params: Promise<{ folderId: number }>;
14+
}) {
15+
const params = await props.params;
16+
const { data, success } = z
17+
.object({ folderId: z.coerce.number() })
18+
.safeParse(params);
19+
20+
if (!success) return <div>Invalid Folder ID</div>;
21+
22+
const folderId = data.folderId;
23+
console.log(folderId);
24+
25+
const folders = await db
26+
.select()
27+
.from(folderSchema)
28+
.where(eq(folderSchema.parent, folderId));
29+
const files = await db
30+
.select()
31+
.from(fileSchema)
32+
.where(eq(fileSchema.parent, folderId));
33+
34+
return <DriveContents folders={folders} files={files} />;
35+
}

src/app/file-row.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { FileIcon, Folder as FolderIcon } from "lucide-react";
22

3-
import type { File, Folder } from "~/lib/mock-data";
3+
import type { files, folders } from "~/server/db/schema";
44

5-
export function FileRow(props: { file: File }) {
5+
export function FileRow(props: { file: typeof files.$inferSelect }) {
66
const { file } = props;
77

88
return (
@@ -29,7 +29,7 @@ export function FileRow(props: { file: File }) {
2929
}
3030

3131
export function FolderRow(props: {
32-
folder: Folder;
32+
folder: typeof folders.$inferSelect;
3333
onFolderClick: () => void;
3434
}) {
3535
const { folder, onFolderClick } = props;

src/app/page.tsx

Lines changed: 2 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,3 @@
1-
"use client";
2-
3-
import { ChevronRight, Upload } from "lucide-react";
4-
import { useMemo, useState } from "react";
5-
6-
import { Button } from "~/components/ui/button";
7-
import { mockFiles, mockFolders } from "~/lib/mock-data";
8-
import { FileRow, FolderRow } from "./file-row";
9-
10-
export default function GoogleDriveClone() {
11-
const [currentFolder, setCurrentFolder] = useState("root");
12-
13-
const getCurrentFiles = () => {
14-
return mockFiles.filter((file) => file.parent === currentFolder);
15-
};
16-
17-
const getCurrentFolders = () => {
18-
return mockFolders.filter((folder) => folder.parent === currentFolder);
19-
};
20-
21-
const handleFolderClick = (folderId: string) => {
22-
setCurrentFolder(folderId);
23-
};
24-
25-
const breadcrumbs = useMemo(() => {
26-
const breadcrumbs = [];
27-
let currentId = currentFolder;
28-
29-
while (currentId !== "root") {
30-
const folder = mockFolders.find((file) => file.id === currentId);
31-
if (folder) {
32-
breadcrumbs.unshift(folder);
33-
currentId = folder.parent ?? "root";
34-
} else {
35-
break;
36-
}
37-
}
38-
39-
return breadcrumbs;
40-
}, [currentFolder]);
41-
42-
const handleUpload = () => {
43-
alert("Upload functionality would be implemented here");
44-
};
45-
46-
return (
47-
<div className="min-h-screen bg-gray-900 p-8 text-gray-100">
48-
<div className="mx-auto max-w-6xl">
49-
<div className="mb-6 flex items-center justify-between">
50-
<div className="flex items-center">
51-
<Button
52-
onClick={() => setCurrentFolder("root")}
53-
variant="ghost"
54-
className="mr-2 text-gray-300 hover:text-white"
55-
>
56-
My Drive
57-
</Button>
58-
{breadcrumbs.map((folder) => (
59-
<div key={folder.id} className="flex items-center">
60-
<ChevronRight className="mx-2 text-gray-500" size={16} />
61-
<Button
62-
onClick={() => handleFolderClick(folder.id)}
63-
variant="ghost"
64-
className="text-gray-300 hover:text-white"
65-
>
66-
{folder.name}
67-
</Button>
68-
</div>
69-
))}
70-
</div>
71-
<Button
72-
onClick={handleUpload}
73-
className="bg-blue-600 text-white hover:bg-blue-700"
74-
>
75-
<Upload className="mr-2" size={20} />
76-
Upload
77-
</Button>
78-
</div>
79-
<div className="rounded-lg bg-gray-800 shadow-xl">
80-
<div className="border-b border-gray-700 px-6 py-4">
81-
<div className="grid grid-cols-12 gap-4 text-sm font-medium text-gray-400">
82-
<div className="col-span-6">Name</div>
83-
<div className="col-span-3">Type</div>
84-
<div className="col-span-3">Size</div>
85-
</div>
86-
</div>
87-
<ul>
88-
{getCurrentFolders().map((folder) => (
89-
<FolderRow
90-
key={folder.id}
91-
folder={folder}
92-
onFolderClick={() => handleFolderClick(folder.id)}
93-
/>
94-
))}
95-
{getCurrentFiles().map((file) => (
96-
<FileRow key={file.id} file={file} />
97-
))}
98-
</ul>
99-
</div>
100-
</div>
101-
</div>
102-
);
1+
export default async function HomePage() {
2+
return <h1>Welcome to your Drive</h1>;
1033
}

src/server/db/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const files = createTable(
1515
.primaryKey()
1616
.autoincrement(),
1717
name: text("name").notNull(),
18+
url: text("url").notNull(),
1819
size: int("size").notNull(),
1920
parent: bigint("parent", { mode: "number", unsigned: true }),
2021
},

0 commit comments

Comments
 (0)