As an AI assistant, your task is to generate a fully functional, complete Next.js MVP application based on the following comprehensive instructions. This is not a landing page or a simple SPA; it must be a multi-page application with database authentication, CRUD operations, and a robust UI.
**1. PROJECT OVERVIEW:**
The application, named 'Text Metrics Pro', is a cloud-based SaaS platform designed to help developers and designers precisely measure, preview, and manage text layout and rendering in user interfaces. It solves the problem of accurately predicting how text will appear across different fonts, sizes, languages, and content variations, especially in complex and dynamic UI scenarios. The core value proposition is to eliminate wasted development time and UI inconsistencies by providing pixel-perfect text rendering predictions, enabling developers to achieve visual fidelity with confidence.
**2. TECH STACK:**
- **Framework:** Next.js (App Router)
- **Language:** TypeScript
- **Styling:** Tailwind CSS
- **ORM:** Drizzle ORM (PostgreSQL or SQLite for simplicity in MVP)
- **UI Library:** shadcn/ui (for accessible, re-usable components)
- **Authentication:** NextAuth.js (for easy integration with providers like Google, GitHub)
- **State Management:** React Context API and Zustand (for global state if needed)
- **Form Handling:** React Hook Form + Zod (for validation)
- **Database:** PostgreSQL (preferred for production) or SQLite (for MVP simplicity, using Drizzle's SQLite adapter).
- **Deployment:** Vercel (recommended)
- **Other Libraries:** `lucide-react` (for icons), potentially a charting library if advanced analytics are added later (e.g., `chart.js`).
**3. DATABASE SCHEMA:**
We will use Drizzle ORM to define the schema. For an MVP, we'll focus on users and text measurement projects.
```typescript
// schema.ts (using Drizzle ORM syntax)
import { pgTable, serial, text, timestamp, integer, boolean, pgEnum } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
// User Table
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name'),
email: text('email').notNull().unique(),
emailVerified: timestamp('emailVerified', { mode: 'date' }),
image: text('image'),
createdAt: timestamp('createdAt', { mode: 'date' }).defaultNow(),
updatedAt: timestamp('updatedAt', { mode: 'date' }).defaultNow(),
});
export const accounts = pgTable('accounts', {
id: serial('id').primaryKey(),
userId: integer('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
type: text('type').$type<string>().notNull(),
provider: text('provider').notNull(),
providerAccountId: text('providerAccountId').notNull(),
refresh_token: text('refresh_token'),
access_token: text('access_token'),
expires_at: integer('expires_at'),
token_type: text('token_type'),
scope: text('scope'),
id_token: text('id_token'),
session_state: text('session_state'),
});
export const sessions = pgTable('sessions', {
sessionToken: text('sessionToken').primaryKey(),
userId: integer('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
expires: timestamp('expires', { mode: 'date' }).notNull(),
});
export const verificationTokens = pgTable('verificationTokens', {
identifier: text('identifier').notNull(),
token: text('token').notNull(),
expires: timestamp('expires', { mode: 'date' }).notNull(),
}).primaryKey(identifier, token);
// Font Enum (for supported font types)
export const fontTypes = pgEnum('fontTypes', ['woff', 'woff2', 'ttf', 'otf']);
// Font Table
export const fonts = pgTable('fonts', {
id: serial('id').primaryKey(),
userId: integer('userId').references(() => users.id, { onDelete: 'set null' }), // Allow anonymous or user-uploaded fonts
name: text('name').notNull(),
type: fontTypes('type').notNull(),
data: text('data').notNull(), // Base64 encoded font data or URL
isPublic: boolean('isPublic').default(false),
createdAt: timestamp('createdAt', { mode: 'date' }).defaultNow(),
});
// Text Measurement Project Table
export const projects = pgTable('projects', {
id: serial('id').primaryKey(),
userId: integer('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
description: text('description'),
createdAt: timestamp('createdAt', { mode: 'date' }).defaultNow(),
updatedAt: timestamp('updatedAt', { mode: 'date' }).defaultNow(),
});
// Text Configuration Table (for each project)
export const textConfigs = pgTable('textConfigs', {
id: serial('id').primaryKey(),
projectId: integer('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
name: text('name').notNull(), // e.g., 'Product Title', 'User Review'
text: text('text').notNull(),
fontId: integer('fontId').references(() => fonts.id, { onDelete: 'set null' }), // Use default if null
fontSizePx: integer('fontSizePx').notNull(),
fontWeight: integer('fontWeight').default(400),
lineHeightRatio: integer('lineHeightRatio').default(1.5), // e.g., 1.5 for 150%
maxWidthPx: integer('maxWidthPx'), // Max width in pixels, null means no constraint
maxLines: integer('maxLines'), // Max lines, null means no constraint
language: text('language').default('en'), // ISO 639-1 code
createdAt: timestamp('createdAt', { mode: 'date' }).defaultNow(),
updatedAt: timestamp('updatedAt', { mode: 'date' }).defaultNow(),
});
// Relations (optional for MVP but good practice)
export const userRelations = relations(users, ({ many }) => ({
projects: many(projects),
fonts: many(fonts),
}));
export const projectRelations = relations(projects, ({ one, many }) => ({
user: one(users, {
fields: [projects.userId],
references: [users.id],
}),
textConfigs: many(textConfigs),
}));
export const textConfigRelations = relations(textConfigs, ({ one }) => ({
project: one(projects, {
fields: [textConfigs.projectId],
references: [projects.id],
}),
font: one(fonts, {
fields: [textConfigs.fontId],
references: [fonts.id],
}),
}));
export const fontRelations = relations(fonts, ({ one, many }) => ({
user: one(users, {
fields: [fonts.userId],
references: [users.id],
}),
textConfigs: many(textConfigs), // If fonts are linked to configs directly
}));
// Export tables for Drizzle setup
export const schema = {
users, accounts, sessions, verificationTokens, fonts, projects, textConfigs
};