Set up project configuration and base UI components

Initializes the project with necessary configurations, including Replit settings and a .gitignore file. It also introduces foundational UI components for the application, such as buttons, dialogs, and layout elements, likely for the Pomodoro timer application.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 59a5ae27-3c71-459b-b42f-fe14121bf9c3
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3007b6f6-d03b-45e1-9ed1-7ce8de18ea24/59a5ae27-3c71-459b-b42f-fe14121bf9c3/Uupe4F4
This commit is contained in:
ingecarlosgutie
2025-08-31 20:11:52 +00:00
parent 64aa1e69a0
commit 80595e7002
89 changed files with 17597 additions and 0 deletions

71
server/index.ts Normal file
View File

@@ -0,0 +1,71 @@
import express, { type Request, Response, NextFunction } from "express";
import { registerRoutes } from "./routes";
import { setupVite, serveStatic, log } from "./vite";
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use((req, res, next) => {
const start = Date.now();
const path = req.path;
let capturedJsonResponse: Record<string, any> | undefined = undefined;
const originalResJson = res.json;
res.json = function (bodyJson, ...args) {
capturedJsonResponse = bodyJson;
return originalResJson.apply(res, [bodyJson, ...args]);
};
res.on("finish", () => {
const duration = Date.now() - start;
if (path.startsWith("/api")) {
let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
if (capturedJsonResponse) {
logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
}
if (logLine.length > 80) {
logLine = logLine.slice(0, 79) + "…";
}
log(logLine);
}
});
next();
});
(async () => {
const server = await registerRoutes(app);
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
const status = err.status || err.statusCode || 500;
const message = err.message || "Internal Server Error";
res.status(status).json({ message });
throw err;
});
// importantly only setup vite in development and after
// setting up all the other routes so the catch-all route
// doesn't interfere with the other routes
if (app.get("env") === "development") {
await setupVite(app, server);
} else {
serveStatic(app);
}
// ALWAYS serve the app on the port specified in the environment variable PORT
// Other ports are firewalled. Default to 5000 if not specified.
// this serves both the API and the client.
// It is the only port that is not firewalled.
const port = parseInt(process.env.PORT || '5000', 10);
server.listen({
port,
host: "0.0.0.0",
reusePort: true,
}, () => {
log(`serving on port ${port}`);
});
})();

171
server/routes.ts Normal file
View File

@@ -0,0 +1,171 @@
import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { insertSessionSchema } from "@shared/schema";
import { z } from "zod";
export async function registerRoutes(app: Express): Promise<Server> {
// Create session
app.post("/api/sessions", async (req, res) => {
try {
const sessionData = insertSessionSchema.parse(req.body);
const session = await storage.createSession(sessionData);
// Update device activity
await storage.updateDeviceProfileActivity(sessionData.deviceId);
res.status(201).json({ ok: true, id: session.id });
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ message: "Invalid session data", errors: error.errors });
}
res.status(500).json({ message: "Failed to create session" });
}
});
// Get suggestions for device
app.get("/api/suggestions", async (req, res) => {
try {
const deviceId = req.query.deviceId as string;
if (!deviceId) {
return res.status(400).json({ message: "Device ID is required" });
}
// Get recent sessions (last 7 days)
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const recentSessions = await storage.getSessionsInDateRange(deviceId, sevenDaysAgo, new Date());
const suggestions = generateSuggestions(recentSessions);
res.json({ suggestions });
} catch (error) {
res.status(500).json({ message: "Failed to get suggestions" });
}
});
// Get stats summary
app.get("/api/stats/summary", async (req, res) => {
try {
const deviceId = req.query.deviceId as string;
if (!deviceId) {
return res.status(400).json({ message: "Device ID is required" });
}
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const last7dSessions = await storage.getSessionsInDateRange(deviceId, sevenDaysAgo, new Date());
const last30dSessions = await storage.getSessionsInDateRange(deviceId, thirtyDaysAgo, new Date());
const last7d = calculateStats(last7dSessions.filter(s => s.type === 'focus'));
const last30d = calculateStats(last30dSessions.filter(s => s.type === 'focus'));
res.json({ last7d, last30d });
} catch (error) {
res.status(500).json({ message: "Failed to get stats" });
}
});
const httpServer = createServer(app);
return httpServer;
}
function generateSuggestions(sessions: any[]) {
const focusSessions = sessions.filter(s => s.type === 'focus');
if (focusSessions.length === 0) {
return [
{ minutes: 25, reason: "Classic Pomodoro technique - great for starting out." },
{ minutes: 15, reason: "Short sprints help build focus habits." },
{ minutes: 40, reason: "Extended sessions for deep work." }
];
}
const completedSessions = focusSessions.filter(s => s.completed);
const completionRate = completedSessions.length / focusSessions.length;
// Analyze successful durations
const successfulDurations = completedSessions.map(s => s.intendedMinutes);
const avgSuccessfulDuration = successfulDurations.reduce((a, b) => a + b, 0) / successfulDurations.length;
// Analyze time of day patterns
const now = new Date();
const currentHour = now.getHours();
const timeOfDaySuccess = getTimeOfDaySuccess(completedSessions, currentHour);
const suggestions = [];
// Primary suggestion based on completion rate
if (completionRate >= 0.8) {
suggestions.push({
minutes: Math.round(avgSuccessfulDuration),
reason: `Your 7-day completion rate is ${Math.round(completionRate * 100)}% at ${Math.round(avgSuccessfulDuration)}m.`
});
} else {
suggestions.push({
minutes: 25,
reason: "Classic 25min sessions have proven effective for most users."
});
}
// Time-based suggestion
if (timeOfDaySuccess.avgDuration > 0) {
suggestions.push({
minutes: Math.round(timeOfDaySuccess.avgDuration),
reason: `${getTimeOfDayLabel(currentHour)}: best focus avg ${Math.round(timeOfDaySuccess.avgDuration - 3)}${Math.round(timeOfDaySuccess.avgDuration + 3)}m.`
});
}
// Interruption-based suggestion
const avgInterruptions = focusSessions.reduce((sum, s) => sum + s.interruptions, 0) / focusSessions.length;
if (avgInterruptions > 1) {
suggestions.push({
minutes: 15,
reason: "High interruption periods benefit from short sprints."
});
} else {
suggestions.push({
minutes: 40,
reason: "Low interruption environment - try longer deep work sessions."
});
}
return suggestions.slice(0, 3);
}
function calculateStats(focusSessions: any[]) {
const completed = focusSessions.filter(s => s.completed);
const completionRate = focusSessions.length > 0 ? completed.length / focusSessions.length : 0;
const avgFocusMin = completed.length > 0
? completed.reduce((sum, s) => sum + s.intendedMinutes, 0) / completed.length
: 0;
return {
sessions: focusSessions.length,
completionRate: Math.round(completionRate * 100) / 100,
avgFocusMin: Math.round(avgFocusMin)
};
}
function getTimeOfDaySuccess(sessions: any[], currentHour: number) {
const timeRangeSessions = sessions.filter(s => {
const sessionHour = new Date(s.startedAt).getHours();
return Math.abs(sessionHour - currentHour) <= 2;
});
if (timeRangeSessions.length === 0) {
return { avgDuration: 0 };
}
const avgDuration = timeRangeSessions.reduce((sum, s) => sum + s.intendedMinutes, 0) / timeRangeSessions.length;
return { avgDuration };
}
function getTimeOfDayLabel(hour: number) {
if (hour >= 6 && hour < 12) return "Mornings";
if (hour >= 12 && hour < 17) return "Afternoons";
if (hour >= 17 && hour < 21) return "Evenings";
return "Late hours";
}

76
server/storage.ts Normal file
View File

@@ -0,0 +1,76 @@
import { type Session, type InsertSession, type DeviceProfile, type InsertDeviceProfile } from "@shared/schema";
import { randomUUID } from "crypto";
export interface IStorage {
// Sessions
createSession(session: InsertSession): Promise<Session>;
getSessionsByDeviceId(deviceId: string, limit?: number): Promise<Session[]>;
getSessionsInDateRange(deviceId: string, startDate: Date, endDate: Date): Promise<Session[]>;
// Device Profiles
createDeviceProfile(profile: InsertDeviceProfile): Promise<DeviceProfile>;
getDeviceProfile(deviceId: string): Promise<DeviceProfile | undefined>;
updateDeviceProfileActivity(deviceId: string): Promise<void>;
}
export class MemStorage implements IStorage {
private sessions: Map<string, Session> = new Map();
private deviceProfiles: Map<string, DeviceProfile> = new Map();
async createSession(insertSession: InsertSession): Promise<Session> {
const id = randomUUID();
const session: Session = {
...insertSession,
id,
};
this.sessions.set(id, session);
return session;
}
async getSessionsByDeviceId(deviceId: string, limit = 50): Promise<Session[]> {
const sessions = Array.from(this.sessions.values())
.filter(session => session.deviceId === deviceId)
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())
.slice(0, limit);
return sessions;
}
async getSessionsInDateRange(deviceId: string, startDate: Date, endDate: Date): Promise<Session[]> {
const sessions = Array.from(this.sessions.values())
.filter(session => {
const sessionDate = new Date(session.startedAt);
return session.deviceId === deviceId &&
sessionDate >= startDate &&
sessionDate <= endDate;
})
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
return sessions;
}
async createDeviceProfile(insertProfile: InsertDeviceProfile): Promise<DeviceProfile> {
const id = randomUUID();
const now = new Date();
const profile: DeviceProfile = {
...insertProfile,
id,
createdAt: now,
lastActiveAt: now,
};
this.deviceProfiles.set(insertProfile.deviceId, profile);
return profile;
}
async getDeviceProfile(deviceId: string): Promise<DeviceProfile | undefined> {
return this.deviceProfiles.get(deviceId);
}
async updateDeviceProfileActivity(deviceId: string): Promise<void> {
const profile = this.deviceProfiles.get(deviceId);
if (profile) {
profile.lastActiveAt = new Date();
this.deviceProfiles.set(deviceId, profile);
}
}
}
export const storage = new MemStorage();

85
server/vite.ts Normal file
View File

@@ -0,0 +1,85 @@
import express, { type Express } from "express";
import fs from "fs";
import path from "path";
import { createServer as createViteServer, createLogger } from "vite";
import { type Server } from "http";
import viteConfig from "../vite.config";
import { nanoid } from "nanoid";
const viteLogger = createLogger();
export function log(message: string, source = "express") {
const formattedTime = new Date().toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
second: "2-digit",
hour12: true,
});
console.log(`${formattedTime} [${source}] ${message}`);
}
export async function setupVite(app: Express, server: Server) {
const serverOptions = {
middlewareMode: true,
hmr: { server },
allowedHosts: true as const,
};
const vite = await createViteServer({
...viteConfig,
configFile: false,
customLogger: {
...viteLogger,
error: (msg, options) => {
viteLogger.error(msg, options);
process.exit(1);
},
},
server: serverOptions,
appType: "custom",
});
app.use(vite.middlewares);
app.use("*", async (req, res, next) => {
const url = req.originalUrl;
try {
const clientTemplate = path.resolve(
import.meta.dirname,
"..",
"client",
"index.html",
);
// always reload the index.html file from disk incase it changes
let template = await fs.promises.readFile(clientTemplate, "utf-8");
template = template.replace(
`src="/src/main.tsx"`,
`src="/src/main.tsx?v=${nanoid()}"`,
);
const page = await vite.transformIndexHtml(url, template);
res.status(200).set({ "Content-Type": "text/html" }).end(page);
} catch (e) {
vite.ssrFixStacktrace(e as Error);
next(e);
}
});
}
export function serveStatic(app: Express) {
const distPath = path.resolve(import.meta.dirname, "public");
if (!fs.existsSync(distPath)) {
throw new Error(
`Could not find the build directory: ${distPath}, make sure to build the client first`,
);
}
app.use(express.static(distPath));
// fall through to index.html if the file doesn't exist
app.use("*", (_req, res) => {
res.sendFile(path.resolve(distPath, "index.html"));
});
}