feat: Implement new 'Calmer Internet' light theme, update UI components, and set up initial Prisma database with OpenSSL support.

This commit is contained in:
2026-01-04 21:44:00 +00:00
parent 6f5e7b74b7
commit c1a7aa614d
10 changed files with 389 additions and 551 deletions

View File

@@ -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

View File

@@ -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

View 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;

View 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"

View File

@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {

View File

@@ -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
}}
/>
))}

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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&apos;s available when
</p>
</div>