feat: Implement new 'Calmer Internet' light theme, update UI components, and set up initial Prisma database with OpenSSL support.
This commit is contained in:
@@ -24,6 +24,9 @@ RUN npm run build
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Install OpenSSL for Prisma
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# Install OpenSSL for Prisma
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
|
||||
34
prisma/migrations/20260104204941_init/migration.sql
Normal file
34
prisma/migrations/20260104204941_init/migration.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Event" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"startDate" DATE NOT NULL,
|
||||
"endDate" DATE NOT NULL,
|
||||
"shareCode" TEXT NOT NULL,
|
||||
"adminPasswordHash" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Event_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Response" (
|
||||
"id" TEXT NOT NULL,
|
||||
"eventId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"availableDates" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Response_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Event_shareCode_key" ON "Event"("shareCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Response_eventId_idx" ON "Response"("eventId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Response" ADD CONSTRAINT "Response_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -1,5 +1,6 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
|
||||
@@ -153,13 +153,13 @@ export default function AnalyticsPage({ params }: { params: Promise<{ code: stri
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: 'var(--bg-secondary)' }}>
|
||||
{/* Header */}
|
||||
<div className="hero-gradient" style={{ padding: 'var(--space-12) 0' }}>
|
||||
<div className="hero-content container-md text-center">
|
||||
<div className="hero-wrapper" style={{ padding: 'var(--space-12) 0' }}>
|
||||
<div className="container-md text-center">
|
||||
<div className="badge badge-primary mb-4">Analytics Dashboard</div>
|
||||
<h1 style={{ color: 'white', marginBottom: 'var(--space-3)', fontSize: '2.5rem' }}>
|
||||
<h1 className="mb-4" style={{ fontSize: '2.5rem' }}>
|
||||
{analytics.event.title}
|
||||
</h1>
|
||||
<p style={{ color: 'rgba(255,255,255,0.6)', fontSize: '0.875rem' }}>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '0.875rem' }}>
|
||||
Created on {new Date(analytics.event.createdAt).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
@@ -220,7 +220,7 @@ export default function AnalyticsPage({ params }: { params: Promise<{ code: stri
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3,
|
||||
background: `rgba(99, 91, 255, ${level * 0.2})`
|
||||
background: `rgba(224, 108, 80, ${level * 0.2})` // Coral accent
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -170,18 +170,18 @@ export default function EventPage({ params }: { params: Promise<{ code: string }
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: 'var(--bg-secondary)' }}>
|
||||
{/* Header */}
|
||||
<div className="hero-gradient" style={{ padding: 'var(--space-12) 0' }}>
|
||||
<div className="hero-content container-md text-center">
|
||||
<div className="hero-wrapper" style={{ padding: 'var(--space-12) 0' }}>
|
||||
<div className="container-md text-center">
|
||||
<div className="badge badge-primary mb-4">Event Invitation</div>
|
||||
<h1 style={{ color: 'white', marginBottom: 'var(--space-3)', fontSize: '2.5rem' }}>
|
||||
<h1 className="mb-4" style={{ fontSize: '2.5rem' }}>
|
||||
{event.title}
|
||||
</h1>
|
||||
{event.description && (
|
||||
<p style={{ color: 'rgba(255,255,255,0.7)', fontSize: '1.125rem', maxWidth: '600px', margin: '0 auto' }}>
|
||||
<p style={{ fontSize: '1.125rem', maxWidth: '600px', margin: '0 auto' }}>
|
||||
{event.description}
|
||||
</p>
|
||||
)}
|
||||
<div style={{ marginTop: 'var(--space-4)', color: 'rgba(255,255,255,0.5)', fontSize: '0.875rem' }}>
|
||||
<div style={{ marginTop: 'var(--space-4)', color: 'var(--text-tertiary)', fontSize: '0.875rem' }}>
|
||||
{event.responseCount} {event.responseCount === 1 ? 'person has' : 'people have'} responded
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,23 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Newsreader, Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const newsreader = Newsreader({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-serif",
|
||||
display: "swap",
|
||||
style: ["normal", "italic"],
|
||||
});
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Timepickr - Find the perfect time for your event",
|
||||
description: "Create events and let your friends pick their available dates. Find the best time that works for everyone.",
|
||||
title: "Timepickr - Find the perfect time",
|
||||
description: "Create events and let your friends pick their available dates. A calmer way to schedule.",
|
||||
keywords: ["event planning", "scheduling", "availability", "calendar", "group planning"],
|
||||
};
|
||||
|
||||
@@ -13,7 +27,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="en" className={`${newsreader.variable} ${inter.variable}`}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -56,8 +56,8 @@ export default function HomePage() {
|
||||
|
||||
if (result) {
|
||||
return (
|
||||
<div className="hero-gradient" style={{ minHeight: '100vh' }}>
|
||||
<div className="hero-content">
|
||||
<div style={{ minHeight: '100vh' }}>
|
||||
<div className="hero-wrapper">
|
||||
<div className="container-sm" style={{ paddingTop: 'var(--space-20)', paddingBottom: 'var(--space-20)' }}>
|
||||
<div className="card-elevated">
|
||||
<div className="success-icon">
|
||||
@@ -108,22 +108,23 @@ export default function HomePage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hero-gradient" style={{ minHeight: '100vh' }}>
|
||||
<div className="hero-content">
|
||||
<div style={{ minHeight: '100vh' }}>
|
||||
<div className="hero-wrapper">
|
||||
<div className="container-sm" style={{ paddingTop: 'var(--space-20)', paddingBottom: 'var(--space-20)' }}>
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="badge badge-primary mb-4">Free & Simple</div>
|
||||
<h1 style={{ color: 'white', marginBottom: 'var(--space-4)' }}>
|
||||
Find the perfect time
|
||||
<div className="hero-wrapper">
|
||||
<h1 className="hero-title">
|
||||
welcome to a <br />
|
||||
<em>calmer</em> way to plan
|
||||
</h1>
|
||||
<p style={{ fontSize: '1.25rem', color: 'rgba(255,255,255,0.7)', maxWidth: '480px', margin: '0 auto' }}>
|
||||
Create an event, share the link, and let everyone pick their available dates.
|
||||
<p className="hero-subtitle">
|
||||
Beautifully designed, privacy-focused event scheduling. <br />
|
||||
Coordinate with friends without the chaos.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Form Card */}
|
||||
<div className="card-elevated">
|
||||
<div className="card-elevated" style={{ maxWidth: '480px', margin: '0 auto' }}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="title" className="form-label">Event Name *</label>
|
||||
@@ -229,26 +230,26 @@ export default function HomePage() {
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
gap: 'var(--space-6)',
|
||||
marginTop: 'var(--space-12)',
|
||||
color: 'white'
|
||||
color: 'var(--text-secondary)'
|
||||
}}>
|
||||
<div className="text-center">
|
||||
<div style={{ fontSize: '2rem', marginBottom: 'var(--space-2)' }}>📅</div>
|
||||
<h4 style={{ color: 'white', marginBottom: 'var(--space-1)' }}>Easy Scheduling</h4>
|
||||
<p style={{ fontSize: '0.875rem', color: 'rgba(255,255,255,0.6)' }}>
|
||||
<h4 className="mb-1">Easy Scheduling</h4>
|
||||
<p style={{ fontSize: '0.875rem' }}>
|
||||
Visual calendar for picking dates
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div style={{ fontSize: '2rem', marginBottom: 'var(--space-2)' }}>🔗</div>
|
||||
<h4 style={{ color: 'white', marginBottom: 'var(--space-1)' }}>Share Link</h4>
|
||||
<p style={{ fontSize: '0.875rem', color: 'rgba(255,255,255,0.6)' }}>
|
||||
<h4 className="mb-1">Share Link</h4>
|
||||
<p style={{ fontSize: '0.875rem' }}>
|
||||
No accounts required for guests
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div style={{ fontSize: '2rem', marginBottom: 'var(--space-2)' }}>📊</div>
|
||||
<h4 style={{ color: 'white', marginBottom: 'var(--space-1)' }}>Analytics</h4>
|
||||
<p style={{ fontSize: '0.875rem', color: 'rgba(255,255,255,0.6)' }}>
|
||||
<h4 className="mb-1">Analytics</h4>
|
||||
<p style={{ fontSize: '0.875rem' }}>
|
||||
See who's available when
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user