frontend-hamroun 1.1.90 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/{src/backend → backend}/api-utils.d.ts +2 -2
  2. package/dist/backend/api-utils.js +135 -0
  3. package/dist/backend/auth.js +387 -0
  4. package/dist/{src/backend → backend}/database.d.ts +1 -1
  5. package/dist/backend/database.js +91 -0
  6. package/dist/{src/backend → backend}/model.d.ts +2 -2
  7. package/dist/backend/model.js +176 -0
  8. package/dist/{src/backend → backend}/router.d.ts +1 -1
  9. package/dist/backend/router.js +137 -0
  10. package/dist/backend/server.js +268 -0
  11. package/dist/batch.js +22 -0
  12. package/dist/cli/index.js +1 -0
  13. package/dist/{src/component.d.ts → component.d.ts} +1 -1
  14. package/dist/component.js +84 -0
  15. package/dist/components/Counter.js +2 -0
  16. package/dist/context.js +20 -0
  17. package/dist/frontend-hamroun.es.js +1680 -0
  18. package/dist/frontend-hamroun.es.js.map +1 -0
  19. package/dist/frontend-hamroun.umd.js +2 -0
  20. package/dist/frontend-hamroun.umd.js.map +1 -0
  21. package/dist/hooks.js +164 -0
  22. package/dist/index.d.ts +46 -0
  23. package/dist/index.js +52 -355
  24. package/dist/jsx-runtime/index.d.ts +9 -0
  25. package/dist/jsx-runtime/index.js +16 -0
  26. package/dist/jsx-runtime/jsx-dev-runtime.js +1 -0
  27. package/dist/jsx-runtime/jsx-runtime.js +91 -0
  28. package/dist/{src/jsx-runtime.d.ts → jsx-runtime.d.ts} +1 -1
  29. package/dist/jsx-runtime.js +192 -0
  30. package/dist/renderer.js +51 -0
  31. package/dist/{src/server-renderer.d.ts → server-renderer.d.ts} +3 -0
  32. package/dist/server-renderer.js +102 -0
  33. package/dist/vdom.js +27 -0
  34. package/package.json +38 -52
  35. package/scripts/generate.js +134 -0
  36. package/src/backend/api-utils.ts +178 -0
  37. package/src/backend/auth.ts +543 -0
  38. package/src/backend/database.ts +104 -0
  39. package/src/backend/model.ts +196 -0
  40. package/src/backend/router.ts +176 -0
  41. package/src/backend/server.ts +330 -0
  42. package/src/backend/types.ts +257 -0
  43. package/src/batch.ts +24 -0
  44. package/src/cli/index.js +22 -40
  45. package/src/component.ts +98 -0
  46. package/src/components/Counter.tsx +4 -0
  47. package/src/context.ts +32 -0
  48. package/src/hooks.ts +211 -0
  49. package/src/index.ts +113 -0
  50. package/src/jsx-runtime/index.ts +24 -0
  51. package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
  52. package/src/jsx-runtime/jsx-runtime.ts +99 -0
  53. package/src/jsx-runtime.ts +226 -0
  54. package/src/renderer.ts +55 -0
  55. package/src/server-renderer.ts +114 -0
  56. package/src/types/bcrypt.d.ts +30 -0
  57. package/src/types/jsonwebtoken.d.ts +55 -0
  58. package/src/types.d.ts +26 -0
  59. package/src/types.ts +21 -0
  60. package/src/vdom.ts +34 -0
  61. package/templates/basic-app/package.json +17 -15
  62. package/templates/basic-app/postcss.config.js +1 -0
  63. package/templates/basic-app/src/App.tsx +65 -0
  64. package/templates/basic-app/src/api.ts +58 -0
  65. package/templates/basic-app/src/components/Counter.tsx +26 -0
  66. package/templates/basic-app/src/components/Header.tsx +9 -0
  67. package/templates/basic-app/src/components/TodoList.tsx +90 -0
  68. package/templates/basic-app/src/main.ts +20 -0
  69. package/templates/basic-app/src/server.ts +99 -0
  70. package/templates/basic-app/tailwind.config.js +23 -2
  71. package/bin/cli.js +0 -371
  72. package/dist/index.js.map +0 -1
  73. package/dist/index.mjs +0 -139269
  74. package/dist/index.mjs.map +0 -1
  75. package/dist/src/index.d.ts +0 -16
  76. package/dist/test/setupTests.d.ts +0 -4
  77. /package/dist/{src/backend → backend}/auth.d.ts +0 -0
  78. /package/dist/{src/backend → backend}/server.d.ts +0 -0
  79. /package/dist/{src/backend → backend}/types.d.ts +0 -0
  80. /package/dist/{test/backend.test.d.ts → backend/types.js} +0 -0
  81. /package/dist/{src/batch.d.ts → batch.d.ts} +0 -0
  82. /package/dist/{src/cli → cli}/index.d.ts +0 -0
  83. /package/dist/{src/components → components}/Counter.d.ts +0 -0
  84. /package/dist/{src/context.d.ts → context.d.ts} +0 -0
  85. /package/dist/{src/hooks.d.ts → hooks.d.ts} +0 -0
  86. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-dev-runtime.d.ts +0 -0
  87. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-runtime.d.ts +0 -0
  88. /package/dist/{src/renderer.d.ts → renderer.d.ts} +0 -0
  89. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  90. /package/dist/{test/mockTest.d.ts → types.js} +0 -0
  91. /package/dist/{src/vdom.d.ts → vdom.d.ts} +0 -0
  92. /package/{dist/test/mongooseSetup.d.ts → src/cli/index.ts} +0 -0
@@ -0,0 +1,55 @@
1
+ declare module 'jsonwebtoken' {
2
+ export interface JwtPayload {
3
+ [key: string]: any;
4
+ iat?: number;
5
+ exp?: number;
6
+ aud?: string | string[];
7
+ sub?: string;
8
+ iss?: string;
9
+ }
10
+
11
+ export interface VerifyOptions {
12
+ algorithms?: string[];
13
+ audience?: string | string[];
14
+ complete?: boolean;
15
+ issuer?: string | string[];
16
+ jwtid?: string;
17
+ ignoreExpiration?: boolean;
18
+ ignoreNotBefore?: boolean;
19
+ subject?: string;
20
+ clockTolerance?: number;
21
+ maxAge?: string | number;
22
+ clockTimestamp?: number;
23
+ }
24
+
25
+ export interface SignOptions {
26
+ algorithm?: string;
27
+ keyid?: string;
28
+ expiresIn?: string | number;
29
+ notBefore?: string | number;
30
+ audience?: string | string[];
31
+ subject?: string;
32
+ issuer?: string;
33
+ jwtid?: string;
34
+ noTimestamp?: boolean;
35
+ header?: object;
36
+ encoding?: string;
37
+ }
38
+
39
+ export function sign(
40
+ payload: string | Buffer | object,
41
+ secretOrPrivateKey: string | Buffer,
42
+ options?: SignOptions
43
+ ): string;
44
+
45
+ export function verify(
46
+ token: string,
47
+ secretOrPublicKey: string | Buffer,
48
+ options?: VerifyOptions
49
+ ): JwtPayload | string;
50
+
51
+ export function decode(
52
+ token: string,
53
+ options?: { complete?: boolean; json?: boolean }
54
+ ): null | JwtPayload | { [key: string]: any };
55
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ declare namespace JSX {
2
+ interface IntrinsicElements {
3
+ }
4
+
5
+ interface Element extends Node {}
6
+
7
+ interface ElementAttributesProperty {
8
+ props: {};
9
+ }
10
+
11
+ interface ElementChildrenAttribute {
12
+ children: {};
13
+ }
14
+ }
15
+
16
+ interface Window {
17
+ jsx: any;
18
+ jsxs: any;
19
+ Fragment: any;
20
+ __renderStats?: {
21
+ elementsCreated: number;
22
+ textNodesCreated: number;
23
+ eventsAttached: number;
24
+ renderTime: number;
25
+ };
26
+ }
package/src/types.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type { Context } from './context';
2
+
3
+ export interface VNode {
4
+ type: string | Function;
5
+ props: Record<string, any>;
6
+ key?: string | number;
7
+ }
8
+
9
+ // Global JSX namespace
10
+ declare global {
11
+ namespace JSX {
12
+ interface Element {
13
+ type: string | Function;
14
+ props: Record<string, any>;
15
+ key?: string | number;
16
+ }
17
+ interface IntrinsicElements {
18
+ [elemName: string]: any;
19
+ }
20
+ }
21
+ }
package/src/vdom.ts ADDED
@@ -0,0 +1,34 @@
1
+ interface VNode {
2
+ type: string | Function;
3
+ props: Record<string, any>;
4
+ key?: string | number;
5
+ }
6
+
7
+ function arePropsEqual(oldProps: any, newProps: any): boolean {
8
+ // Handle null/undefined props
9
+ oldProps = oldProps || {};
10
+ newProps = newProps || {};
11
+
12
+ const oldKeys = Object.keys(oldProps).filter(k => k !== 'children');
13
+ const newKeys = Object.keys(newProps).filter(k => k !== 'children');
14
+
15
+ if (oldKeys.length !== newKeys.length) return false;
16
+ return oldKeys.every(key => oldProps[key] === newProps[key]);
17
+ }
18
+
19
+ export function diff(oldNode: VNode | any, newNode: VNode | any): boolean {
20
+ if (oldNode == null || newNode == null) return oldNode !== newNode;
21
+ if (typeof oldNode !== typeof newNode) return true;
22
+ if (typeof newNode === 'string' || typeof newNode === 'number')
23
+ return oldNode !== newNode;
24
+
25
+ // Handle primitive values that are not objects
26
+ if (!oldNode.type || !newNode.type) return oldNode !== newNode;
27
+
28
+ if (newNode.type !== oldNode.type) return true;
29
+ return !arePropsEqual(oldNode.props, newNode.props);
30
+ }
31
+
32
+ export function shouldComponentUpdate(oldProps: any, newProps: any): boolean {
33
+ return !arePropsEqual(oldProps, newProps);
34
+ }
@@ -1,28 +1,30 @@
1
1
  {
2
- "name": "my-app",
2
+ "name": "front-package-app",
3
3
  "private": true,
4
- "version": "0.0.0",
4
+ "version": "0.1.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
8
8
  "build": "tsc && vite build",
9
9
  "preview": "vite preview",
10
- "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11
- "lint:fix": "eslint src --ext ts,tsx --fix"
10
+ "start": "node dist/server.js",
11
+ "dev:full": "concurrently \"npm run dev\" \"npm run dev:server\"",
12
+ "dev:server": "tsx watch src/server.ts"
12
13
  },
13
14
  "dependencies": {
14
- "frontend-hamroun": "latest"
15
+ "front-package": "latest"
15
16
  },
16
17
  "devDependencies": {
17
- "typescript": "^5.0.0",
18
- "vite": "^4.4.9",
19
- "@typescript-eslint/eslint-plugin": "^6.0.0",
20
- "@typescript-eslint/parser": "^6.0.0",
21
- "eslint": "^8.45.0",
22
- "eslint-plugin-react-hooks": "^4.6.0",
23
- "eslint-plugin-react-refresh": "^0.4.3",
24
- "tailwindcss": "^3.3.3",
25
- "postcss": "^8.4.27",
26
- "autoprefixer": "^10.4.14"
18
+ "@tailwindcss/forms": "^0.5.7",
19
+ "@tailwindcss/typography": "^0.5.10",
20
+ "@types/node": "^20.10.0",
21
+ "autoprefixer": "^10.4.16",
22
+ "concurrently": "^8.2.2",
23
+ "cssnano": "^6.0.1",
24
+ "postcss": "^8.4.31",
25
+ "tailwindcss": "^3.3.5",
26
+ "tsx": "^4.6.0",
27
+ "typescript": "^5.3.2",
28
+ "vite": "^5.0.0"
27
29
  }
28
30
  }
@@ -2,5 +2,6 @@ export default {
2
2
  plugins: {
3
3
  tailwindcss: {},
4
4
  autoprefixer: {},
5
+ cssnano: process.env.NODE_ENV === 'production' ? { preset: 'default' } : false
5
6
  },
6
7
  }
@@ -0,0 +1,65 @@
1
+ import { useState, useEffect } from 'frontend-hamroun';
2
+ import { Header } from './components/Header';
3
+ import { Counter } from './components/Counter';
4
+ import { ApiClient } from './api.js';
5
+ import { TodoList } from './components/TodoList.js';
6
+
7
+ interface AppProps {
8
+ api: ApiClient;
9
+ }
10
+
11
+ export function App({ api }: AppProps) {
12
+ const [todos, setTodos] = useState([]);
13
+ const [isLoading, setIsLoading] = useState(true);
14
+ const [error, setError] = useState<string | null>(null);
15
+
16
+ useEffect(() => {
17
+ // Fetch data when component mounts
18
+ async function fetchData() {
19
+ try {
20
+ setIsLoading(true);
21
+ const data = await api.getTodos();
22
+ setTodos(data);
23
+ setError(null);
24
+ } catch (err) {
25
+ setError('Failed to fetch todos');
26
+ console.error(err);
27
+ } finally {
28
+ setIsLoading(false);
29
+ }
30
+ }
31
+
32
+ fetchData();
33
+ }, [api]);
34
+
35
+ return (
36
+ <div className="min-h-screen bg-gray-50">
37
+ <Header title="My Front Package App" />
38
+
39
+ <main className="container mx-auto px-4 py-8">
40
+ <h1 className="text-3xl font-bold text-center mb-8">
41
+ Welcome to your Front Package app!
42
+ </h1>
43
+
44
+ <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl mb-8">
45
+ <div className="p-8">
46
+ <Counter />
47
+ </div>
48
+ </div>
49
+
50
+ <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
51
+ <div className="p-8">
52
+ <h2 className="text-xl font-bold mb-4">Todo List</h2>
53
+ {isLoading ? (
54
+ <div className="text-center py-4">Loading...</div>
55
+ ) : error ? (
56
+ <div className="text-red-500 py-4">{error}</div>
57
+ ) : (
58
+ <TodoList todos={todos} api={api} setTodos={setTodos} />
59
+ )}
60
+ </div>
61
+ </div>
62
+ </main>
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,58 @@
1
+ export interface Todo {
2
+ id: string;
3
+ title: string;
4
+ completed: boolean;
5
+ }
6
+
7
+ export interface ApiClientOptions {
8
+ baseUrl: string;
9
+ }
10
+
11
+ export interface ApiClient {
12
+ getTodos: () => Promise<Todo[]>;
13
+ getTodo: (id: string) => Promise<Todo>;
14
+ addTodo: (todo: Omit<Todo, 'id'>) => Promise<Todo>;
15
+ updateTodo: (id: string, updates: Partial<Omit<Todo, 'id'>>) => Promise<Todo>;
16
+ deleteTodo: (id: string) => Promise<void>;
17
+ }
18
+
19
+ export function createApiClient(options: ApiClientOptions): ApiClient {
20
+ const { baseUrl } = options;
21
+
22
+ async function fetchJson<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
23
+ const response = await fetch(`${baseUrl}${endpoint}`, {
24
+ ...options,
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ ...options.headers,
28
+ },
29
+ });
30
+
31
+ if (!response.ok) {
32
+ const error = await response.text();
33
+ throw new Error(error || `API request failed with status ${response.status}`);
34
+ }
35
+
36
+ return response.json();
37
+ }
38
+
39
+ return {
40
+ getTodos: () => fetchJson<Todo[]>('/todos'),
41
+
42
+ getTodo: (id: string) => fetchJson<Todo>(`/todos/${id}`),
43
+
44
+ addTodo: (todo) => fetchJson<Todo>('/todos', {
45
+ method: 'POST',
46
+ body: JSON.stringify(todo),
47
+ }),
48
+
49
+ updateTodo: (id: string, updates) => fetchJson<Todo>(`/todos/${id}`, {
50
+ method: 'PATCH',
51
+ body: JSON.stringify(updates),
52
+ }),
53
+
54
+ deleteTodo: (id: string) => fetchJson<void>(`/todos/${id}`, {
55
+ method: 'DELETE',
56
+ }),
57
+ };
58
+ }
@@ -0,0 +1,26 @@
1
+ import { useState } from 'front-package';
2
+
3
+ export function Counter() {
4
+ const [count, setCount] = useState(0);
5
+
6
+ return (
7
+ <div className="text-center">
8
+ <h2 className="text-xl font-bold mb-4">Counter Example</h2>
9
+ <p className="mb-4">Count: <span className="font-bold">{count}</span></p>
10
+ <div className="flex justify-center gap-2">
11
+ <button
12
+ onClick={() => setCount(count - 1)}
13
+ className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition"
14
+ >
15
+ Decrement
16
+ </button>
17
+ <button
18
+ onClick={() => setCount(count + 1)}
19
+ className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
20
+ >
21
+ Increment
22
+ </button>
23
+ </div>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,9 @@
1
+ export function Header({ title }: { title: string }) {
2
+ return (
3
+ <header className="bg-primary-600 text-white shadow-lg">
4
+ <div className="container mx-auto px-4 py-6">
5
+ <h1 className="text-2xl font-bold">{title}</h1>
6
+ </div>
7
+ </header>
8
+ );
9
+ }
@@ -0,0 +1,90 @@
1
+ import { useState } from 'front-package';
2
+ import { ApiClient, Todo } from '../api';
3
+
4
+ interface TodoListProps {
5
+ todos: Todo[];
6
+ api: ApiClient;
7
+ setTodos: (todos: Todo[]) => void;
8
+ }
9
+
10
+ export function TodoList({ todos, api, setTodos }: TodoListProps) {
11
+ const [newTodoText, setNewTodoText] = useState('');
12
+ const [isSubmitting, setIsSubmitting] = useState(false);
13
+
14
+ async function handleAddTodo(e: Event) {
15
+ e.preventDefault();
16
+ if (!newTodoText.trim()) return;
17
+
18
+ try {
19
+ setIsSubmitting(true);
20
+ const newTodo = await api.addTodo({
21
+ title: newTodoText,
22
+ completed: false
23
+ });
24
+
25
+ setTodos([...todos, newTodo]);
26
+ setNewTodoText('');
27
+ } catch (err) {
28
+ console.error('Failed to add todo:', err);
29
+ } finally {
30
+ setIsSubmitting(false);
31
+ }
32
+ }
33
+
34
+ async function handleToggleTodo(id: string, completed: boolean) {
35
+ try {
36
+ await api.updateTodo(id, { completed });
37
+ setTodos(
38
+ todos.map(todo =>
39
+ todo.id === id ? { ...todo, completed } : todo
40
+ )
41
+ );
42
+ } catch (err) {
43
+ console.error('Failed to update todo:', err);
44
+ }
45
+ }
46
+
47
+ return (
48
+ <div>
49
+ <form onSubmit={handleAddTodo} className="mb-4">
50
+ <div className="flex gap-2">
51
+ <input
52
+ type="text"
53
+ value={newTodoText}
54
+ onChange={(e) => setNewTodoText(e.target.value)}
55
+ placeholder="Add a new todo..."
56
+ className="flex-1 border rounded px-3 py-2"
57
+ disabled={isSubmitting}
58
+ />
59
+ <button
60
+ type="submit"
61
+ className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition"
62
+ disabled={isSubmitting || !newTodoText.trim()}
63
+ >
64
+ {isSubmitting ? 'Adding...' : 'Add'}
65
+ </button>
66
+ </div>
67
+ </form>
68
+
69
+ <ul className="space-y-2">
70
+ {todos.length === 0 ? (
71
+ <li className="text-center py-2 text-gray-500">No todos yet</li>
72
+ ) : (
73
+ todos.map(todo => (
74
+ <li key={todo.id} className="flex items-center gap-2 p-2 border-b">
75
+ <input
76
+ type="checkbox"
77
+ checked={todo.completed}
78
+ onChange={(e) => handleToggleTodo(todo.id, e.target.checked)}
79
+ className="h-5 w-5 text-blue-600"
80
+ />
81
+ <span className={todo.completed ? 'line-through text-gray-500' : ''}>
82
+ {todo.title}
83
+ </span>
84
+ </li>
85
+ ))
86
+ )}
87
+ </ul>
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,20 @@
1
+ import './main.css';
2
+ import { render } from 'frontend-hamroun';
3
+ import { App } from './App';
4
+ import { createApiClient } from './api';
5
+
6
+ // Initialize API client
7
+ const api = createApiClient({
8
+ baseUrl: import.meta.env.VITE_API_URL || '/api'
9
+ });
10
+
11
+ // Render the app into the DOM
12
+ document.addEventListener('DOMContentLoaded', () => {
13
+ const rootElement = document.getElementById('app');
14
+ if (rootElement) {
15
+ render(<App api={api} />, rootElement);
16
+ console.log('App rendered successfully');
17
+ } else {
18
+ console.error('Root element #app not found');
19
+ }
20
+ });
@@ -0,0 +1,99 @@
1
+ import { createServer, Router, Database, ApiRoute } from 'front-package';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import fs from 'fs';
5
+
6
+ // Setup paths for server
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const DIST_PATH = process.env.NODE_ENV === 'production'
10
+ ? join(__dirname, '..')
11
+ : join(__dirname, '../dist');
12
+
13
+ // Initialize database
14
+ const db = new Database({
15
+ filePath: join(__dirname, '../data/db.json'),
16
+ defaultData: {
17
+ todos: [
18
+ { id: '1', title: 'Learn Front Package', completed: false },
19
+ { id: '2', title: 'Build an app', completed: false },
20
+ { id: '3', title: 'Deploy to production', completed: false }
21
+ ]
22
+ }
23
+ });
24
+
25
+ // Create API routes
26
+ const apiRouter = new Router();
27
+
28
+ // GET /api/todos
29
+ apiRouter.get('/todos', async (req, res) => {
30
+ const todos = await db.getAll('todos');
31
+ res.json(todos);
32
+ });
33
+
34
+ // GET /api/todos/:id
35
+ apiRouter.get('/todos/:id', async (req, res) => {
36
+ const todo = await db.getById('todos', req.params.id);
37
+ if (!todo) {
38
+ return res.status(404).json({ error: 'Todo not found' });
39
+ }
40
+ res.json(todo);
41
+ });
42
+
43
+ // POST /api/todos
44
+ apiRouter.post('/todos', async (req, res) => {
45
+ const { title, completed = false } = req.body;
46
+
47
+ if (!title) {
48
+ return res.status(400).json({ error: 'Title is required' });
49
+ }
50
+
51
+ const newTodo = {
52
+ id: Date.now().toString(),
53
+ title,
54
+ completed
55
+ };
56
+
57
+ await db.insert('todos', newTodo);
58
+ res.status(201).json(newTodo);
59
+ });
60
+
61
+ // PATCH /api/todos/:id
62
+ apiRouter.patch('/todos/:id', async (req, res) => {
63
+ const todo = await db.getById('todos', req.params.id);
64
+ if (!todo) {
65
+ return res.status(404).json({ error: 'Todo not found' });
66
+ }
67
+
68
+ const updatedTodo = { ...todo, ...req.body };
69
+ await db.update('todos', req.params.id, updatedTodo);
70
+ res.json(updatedTodo);
71
+ });
72
+
73
+ // DELETE /api/todos/:id
74
+ apiRouter.delete('/todos/:id', async (req, res) => {
75
+ const todo = await db.getById('todos', req.params.id);
76
+ if (!todo) {
77
+ return res.status(404).json({ error: 'Todo not found' });
78
+ }
79
+
80
+ await db.delete('todos', req.params.id);
81
+ res.status(204).end();
82
+ });
83
+
84
+ // Create server
85
+ const server = createServer({
86
+ port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
87
+ staticDir: DIST_PATH,
88
+ routes: [
89
+ new ApiRoute('/api', apiRouter)
90
+ ]
91
+ });
92
+
93
+ // Start server
94
+ server.start().then(() => {
95
+ console.log(`Server running at http://localhost:${server.port}`);
96
+ }).catch(err => {
97
+ console.error('Failed to start server:', err);
98
+ process.exit(1);
99
+ });
@@ -5,7 +5,28 @@ export default {
5
5
  "./src/**/*.{js,ts,jsx,tsx}",
6
6
  ],
7
7
  theme: {
8
- extend: {},
8
+ extend: {
9
+ colors: {
10
+ primary: {
11
+ 50: '#f0f9ff',
12
+ 100: '#e0f2fe',
13
+ 200: '#bae6fd',
14
+ 300: '#7dd3fc',
15
+ 400: '#38bdf8',
16
+ 500: '#0ea5e9',
17
+ 600: '#0284c7',
18
+ 700: '#0369a1',
19
+ 800: '#075985',
20
+ 900: '#0c4a6e',
21
+ },
22
+ },
23
+ fontFamily: {
24
+ sans: ['Inter var', 'sans-serif'],
25
+ },
26
+ },
9
27
  },
10
- plugins: [],
28
+ plugins: [
29
+ require('@tailwindcss/forms'),
30
+ require('@tailwindcss/typography'),
31
+ ],
11
32
  }