You are tasked with building a fully functional, multi-page MVP of a SaaS application called "S3 Explorer" (working title, can be refined). This application will act as a user-friendly, visual interface for managing AWS S3 buckets, abstracting away the complexities of the AWS console and CLI. The goal is to make S3 accessible and manageable for developers, small teams, and businesses who need efficient cloud storage management without deep AWS expertise.
**1. PROJECT OVERVIEW:**
S3 Explorer aims to solve the problem of cumbersome S3 bucket management. Users often find the AWS console unintuitive and the CLI intimidating for everyday tasks. This application provides a clean, visual, and intuitive dashboard to browse, upload, download, organize, and manage files and folders within S3 buckets. The core value proposition is simplified cloud storage management, increased productivity, and reduced learning curve for AWS S3.
**2. TECH STACK:**
- **Framework:** Next.js (App Router)
- **Language:** TypeScript
- **Styling:** Tailwind CSS
- **ORM:** Drizzle ORM (with PostgreSQL via Vercel Postgres or Neon)
- **UI Components:** shadcn/ui (for accessible, reusable components)
- **Authentication:** NextAuth.js (for email/password and potentially OAuth)
- **State Management:** React Context API / Zustand (for global state like auth, bucket list; component-level state as needed)
- **Form Handling:** React Hook Form + Zod (for validation)
- **API Client:** `fetch` API or `axios`
- **Deployment:** Vercel
- **Cloud Storage Interaction:** AWS SDK for JavaScript (v3)
**3. DATABASE SCHEMA (PostgreSQL with Drizzle ORM):**
```typescript
// schema.ts
import { pgTable, uuid, varchar, timestamp, text, boolean, pgEnum } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
// User Authentication
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name'),
email: varchar('email', { length: 255 }).notNull().unique(),
emailVerified: timestamp('emailVerified', { mode: 'date' }),
image: text('image'),
createdAt: timestamp('createdAt').defaultNow(),
updatedAt: timestamp('updatedAt').defaultNow(),
});
export const accounts = pgTable('accounts', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
type: varchar('type', { length: 100 }).$type<"oauth" | "email">().notNull(),
provider: varchar('provider', { length: 100 }).notNull(),
providerAccountId: varchar('providerAccountId', { length: 255 }).notNull(),
refresh_token: text('refresh_token'),
access_token: text('access_token'),
expires_at: pgEnum('expires_at', { mode: 'number' }),
token_type: varchar('token_type', { length: 50 }),
scope: text('scope'),
session_state: text('session_state'),
});
export const sessions = pgTable('sessions', {
id: uuid('id').primaryKey().defaultRandom(),
sessionToken: varchar('sessionToken', { length: 255 }).notNull().unique(),
userId: uuid('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
expires: timestamp('expires', { mode: 'date' }).notNull(),
});
export const verificationTokens = pgTable('verificationTokens', {
id: uuid('id').primaryKey().defaultRandom(),
identifier: text('identifier').notNull(),
token: varchar('token', { length: 255 }).notNull().unique(),
expires: timestamp('expires', { mode: 'date' }).notNull(),
});
// S3 Specific Data
export const s3Connections = pgTable('s3Connections', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
name: varchar('name', { length: 255 }).notNull(), // e.g., 'My Project Bucket'
region: varchar('region', { length: 100 }).notNull(),
bucketName: varchar('bucketName', { length: 255 }).notNull(),
accessKeyId: text('accessKeyId').notNull(), // Store securely, consider encryption
secretAccessKey: text('secretAccessKey').notNull(), // Store securely, consider encryption
createdAt: timestamp('createdAt').defaultNow(),
updatedAt: timestamp('updatedAt').defaultNow(),
});
export const s3Objects = pgTable('s3Objects', {
id: uuid('id').primaryKey().defaultRandom(),
connectionId: uuid('connectionId').notNull().references(() => s3Connections.id, { onDelete: 'cascade' }),
key: text('key').notNull(), // Full path in S3, e.g., 'folder/subfolder/file.txt'
lastModified: timestamp('lastModified', { mode: 'date' }).notNull(),
size: bigint('size', { mode: 'number' }).notNull(), // Size in bytes
etag: varchar('etag', { length: 255 }), // ETag for versioning/integrity
isFolder: boolean('isFolder').default(false), // Denotes if this key represents a folder
createdAt: timestamp('createdAt').defaultNow(), // When it was first registered in our system
updatedAt: timestamp('updatedAt').defaultNow(), // When this object's metadata was last updated
});
// Relations
export const userRelations = relations(users, ({ many }) => ({
s3Connections: many(s3Connections),
}));
export const s3ConnectionRelations = relations(s3Connections, ({ one, many }) => ({
user: one(users, {
fields: [s3Connections.userId],
references: [users.id],
}),
s3Objects: many(s3Objects),
}));
export const s3ObjectRelations = relations(s3Objects, ({ one }) => ({
s3Connection: one(s3Connections, {
fields: [s3Objects.connectionId],
references: [s3Connections.id],
}),
}));
// Ensure Indexes for performance
// CREATE INDEX idx_s3objects_connectionId ON s3Objects(connectionId);
// CREATE INDEX idx_s3objects_key ON s3Objects(key);
```
**4. CORE FEATURES & USER FLOW:**
* **Authentication Flow:**
1. User lands on the homepage.
2. Clicks "Sign Up" or "Login".
3. Presented with email/password form (or OAuth options if implemented).
4. On successful login, redirected to the dashboard.
5. On sign up, email verification might be required (consider for MVP+).
6. "Forgot Password" flow via email.
* **AWS Connection Management:**
1. User navigates to "Connections" or "Settings" page.
2. Clicks "Add New Connection".
3. Fills a form with Connection Name (alias), AWS Region, Bucket Name, Access Key ID, and Secret Access Key.
4. App validates these credentials by making a lightweight S3 API call (e.g., `ListBuckets` or checking bucket existence/permissions).
5. If successful, the connection is saved to the database (`s3Connections` table) associated with the user.
6. User can view, edit, or delete existing connections.
* **S3 Bucket Browsing (File Explorer):**
1. User logs in and is on the Dashboard, which lists their saved S3 connections.
2. User clicks on a specific connection.
3. App fetches the root contents of the specified bucket using `ListObjectsV2` API call from AWS SDK, filtering for top-level objects/prefixes.
4. Contents are displayed in a file-tree/list view, distinguishing between files and folders (folders are identified by keys ending in `/` or by convention).
5. Clicking a folder navigates into it (fetches contents of `folderName/`). Breadcrumbs should update to show the path.
6. Clicking a file might show details or offer download options (MVP focuses on browsing).
7. "Back" button or clicking parent folder in breadcrumbs navigates up.
* **File Upload:**
1. User is browsing a bucket/folder.
2. User clicks an "Upload" button or drags files onto a designated drop zone.
3. Selected files are uploaded using `PutObject` API calls (potentially in parallel or chunked for large files - MVP can start simple).
4. Progress indicators are shown for each upload.
5. On completion, the file explorer view is refreshed to show the new file.
* **File/Folder Download:**
1. User selects one or more files/folders in the explorer view.
2. Clicks a "Download" button.
3. For single files, use `GetObject` and stream the response to the user's browser.
4. For multiple files or folders, create a zip archive server-side (using a library like `archiver`) and stream the zip file for download.
5. Progress indicators for large downloads are desirable.
**5. API & DATA FETCHING (Next.js App Router):**
* **Backend API Routes (app/api/...):**
* `/api/auth/...`: Handled by NextAuth.js.
* `/api/connections`: `POST` to create, `GET` to list user's connections.
* `/api/connections/[id]`: `PUT` to update, `DELETE` to remove, `GET` to fetch a single connection (requires authorization).
* `/api/buckets/[connectionId]/list`: `GET` to list contents of a bucket/folder. Accepts query params like `prefix` (folder path) and `nextToken` (for pagination).
* `/api/buckets/[connectionId]/upload`: `POST` endpoint handling multipart/form-data uploads. This endpoint will stream the upload to S3.
* `/api/buckets/[connectionId]/download`: `POST` (to allow sending bucket/key list in body) or `GET` (if downloading single item). Streams file(s) back to the client, potentially as a zip archive.
* **Data Fetching in Components:**
* Server Components will fetch initial data where possible (e.g., list of connections on dashboard).
* Client Components will use `fetch` or libraries like SWR/React Query for dynamic data (e.g., browsing bucket contents, upload progress).
* All S3 interactions must use the credentials stored securely for the specific `connectionId` and be proxied through the backend API routes for security (never expose AWS keys directly to the frontend).
* When fetching bucket contents, the API should return a structured object including `Contents` (files/folders array), `CommonPrefixes` (folders), and `IsTruncated`/`NextContinuationToken` for pagination.
**6. COMPONENT BREAKDOWN (Next.js App Router Structure):**
* **`app/layout.tsx`:** Root layout with HTML, Head, Body. Includes global styles, theme provider, and possibly auth provider setup.
* **`app/page.tsx`:** Landing Page (Public). Features, CTA, login/signup links.
* **`app/(auth)/login/page.tsx`:** Login form component.
* **`app/(auth)/signup/page.tsx`:** Signup form component.
* **`app/(auth)/forgot-password/page.tsx`:** Password reset form.
* **`app/(app)/layout.tsx`:** Authenticated App Shell. Sidebar navigation, header, main content area.
* **`app/(app)/dashboard/page.tsx`:** (Server Component) Displays user's S3 connections. Links to add new connection and access individual connections.
* **`app/(app)/connections/page.tsx`:** (Client Component) Form for adding/editing S3 connections. Uses `react-hook-form` and Zod validation.
* **`app/(app)/buckets/[connectionId]/page.tsx`:** (Client Component) The core File Explorer. Renders the file tree/list, handles browsing, uploads, downloads. Uses S3 API routes.
* **Components within `buckets/[connectionId]`:**
* `Breadcrumbs.tsx`: Displays current path, allows navigation.
* `FileList.tsx`: Renders files and folders in a table or grid.
* `FileListItem.tsx`: Renders a single file or folder row/item.
* `UploadZone.tsx`: Handles drag-and-drop file uploads.
* `DownloadButton.tsx`: Initiates download process.
* `NewFolderButton.tsx`: Creates a new folder.
* `ObjectDetailsModal.tsx`: (Optional MVP+) Shows metadata for a selected file.
* **`app/(app)/settings/page.tsx`:** User profile settings, potentially account management.
* **`components/ui/...`:** Reusable UI elements from shadcn/ui (Button, Input, Card, Table, Dialog, etc.).
* **`components/layout/Sidebar.tsx`:** Navigation links.
* **`components/layout/Header.tsx`:** App header with user menu/logout.
* **`components/common/LoadingSpinner.tsx`:** Global loading indicator.
* **`components/common/ErrorMessage.tsx`:** Displays error messages.
* **`lib/awsClient.ts`:** Helper functions for interacting with AWS SDK.
* **`lib/db.ts`:** Drizzle ORM database connection setup.
* **`lib/auth.ts`:** NextAuth.js configuration.
* **`hooks/`:** Custom React hooks (e.g., `useS3Browser`, `useFileUpload`).
**7. UI/UX DESIGN & VISUAL IDENTITY:**
* **Design Style:** Modern, Clean, Professional.
* **Color Palette:**
* Primary: `#4F46E5` (Indigo-500)
* Secondary: `#6366F1` (Indigo-600)
* Accent: `#2563EB` (Blue-600)
* Background: `#FFFFFF` (White)
* Dark Background/Elements: `#F3F4F6` (Gray-100)
* Text (Dark): `#1F2937` (Gray-800)
* Text (Light): `#FFFFFF` (White)
* Borders/Dividers: `#E5E7EB` (Gray-200)
* **Typography:** Inter (Sans-serif) - Titles: 700, Body: 400.
* **Layout:** Primarily left-aligned content. Use cards for distinct sections (connections, file details). A persistent sidebar for navigation. Main content area is spacious.
* **Responsiveness:** Mobile-first approach. Sidebar collapses into a hamburger menu on smaller screens. File list adjusts columns or becomes vertically stacked.
* **Interactions:** Subtle hover effects on buttons and list items. Smooth transitions for modal/dropdowns. Clear visual feedback for loading states and errors.
**8. SAMPLE/MOCK DATA:**
* **`s3Connections` Table:**
1. `{ id: 'uuid1', userId: 'user1', name: 'Project Alpha Bucket', region: 'us-east-1', bucketName: 'alpha-project-data', accessKeyId: 'AKIA...', secretAccessKey: '...' }
2. `{ id: 'uuid2', userId: 'user1', name: 'Media Assets', region: 'eu-west-1', bucketName: 'my-company-media', accessKeyId: 'AKIA...', secretAccessKey: '...' }`
* **`s3Objects` Table (for `connectionId: 'uuid1'`):**
1. `{ connectionId: 'uuid1', key: 'assets/', isFolder: true, lastModified: '...', size: 0, etag: null }
2. `{ connectionId: 'uuid1', key: 'src/', isFolder: true, lastModified: '...', size: 0, etag: null }
3. `{ connectionId: 'uuid1', key: 'assets/logo.png', isFolder: false, lastModified: '2023-10-26T10:00:00Z', size: 15032, etag: '