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.
- package/dist/{src/backend → backend}/api-utils.d.ts +2 -2
- package/dist/backend/api-utils.js +135 -0
- package/dist/backend/auth.js +387 -0
- package/dist/{src/backend → backend}/database.d.ts +1 -1
- package/dist/backend/database.js +91 -0
- package/dist/{src/backend → backend}/model.d.ts +2 -2
- package/dist/backend/model.js +176 -0
- package/dist/{src/backend → backend}/router.d.ts +1 -1
- package/dist/backend/router.js +137 -0
- package/dist/backend/server.js +268 -0
- package/dist/batch.js +22 -0
- package/dist/cli/index.js +1 -0
- package/dist/{src/component.d.ts → component.d.ts} +1 -1
- package/dist/component.js +84 -0
- package/dist/components/Counter.js +2 -0
- package/dist/context.js +20 -0
- package/dist/frontend-hamroun.es.js +1680 -0
- package/dist/frontend-hamroun.es.js.map +1 -0
- package/dist/frontend-hamroun.umd.js +2 -0
- package/dist/frontend-hamroun.umd.js.map +1 -0
- package/dist/hooks.js +164 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +52 -355
- package/dist/jsx-runtime/index.d.ts +9 -0
- package/dist/jsx-runtime/index.js +16 -0
- package/dist/jsx-runtime/jsx-dev-runtime.js +1 -0
- package/dist/jsx-runtime/jsx-runtime.js +91 -0
- package/dist/{src/jsx-runtime.d.ts → jsx-runtime.d.ts} +1 -1
- package/dist/jsx-runtime.js +192 -0
- package/dist/renderer.js +51 -0
- package/dist/{src/server-renderer.d.ts → server-renderer.d.ts} +3 -0
- package/dist/server-renderer.js +102 -0
- package/dist/vdom.js +27 -0
- package/package.json +38 -52
- package/scripts/generate.js +134 -0
- package/src/backend/api-utils.ts +178 -0
- package/src/backend/auth.ts +543 -0
- package/src/backend/database.ts +104 -0
- package/src/backend/model.ts +196 -0
- package/src/backend/router.ts +176 -0
- package/src/backend/server.ts +330 -0
- package/src/backend/types.ts +257 -0
- package/src/batch.ts +24 -0
- package/src/cli/index.js +22 -40
- package/src/component.ts +98 -0
- package/src/components/Counter.tsx +4 -0
- package/src/context.ts +32 -0
- package/src/hooks.ts +211 -0
- package/src/index.ts +113 -0
- package/src/jsx-runtime/index.ts +24 -0
- package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
- package/src/jsx-runtime/jsx-runtime.ts +99 -0
- package/src/jsx-runtime.ts +226 -0
- package/src/renderer.ts +55 -0
- package/src/server-renderer.ts +114 -0
- package/src/types/bcrypt.d.ts +30 -0
- package/src/types/jsonwebtoken.d.ts +55 -0
- package/src/types.d.ts +26 -0
- package/src/types.ts +21 -0
- package/src/vdom.ts +34 -0
- package/templates/basic-app/package.json +17 -15
- package/templates/basic-app/postcss.config.js +1 -0
- package/templates/basic-app/src/App.tsx +65 -0
- package/templates/basic-app/src/api.ts +58 -0
- package/templates/basic-app/src/components/Counter.tsx +26 -0
- package/templates/basic-app/src/components/Header.tsx +9 -0
- package/templates/basic-app/src/components/TodoList.tsx +90 -0
- package/templates/basic-app/src/main.ts +20 -0
- package/templates/basic-app/src/server.ts +99 -0
- package/templates/basic-app/tailwind.config.js +23 -2
- package/bin/cli.js +0 -371
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -139269
- package/dist/index.mjs.map +0 -1
- package/dist/src/index.d.ts +0 -16
- package/dist/test/setupTests.d.ts +0 -4
- /package/dist/{src/backend → backend}/auth.d.ts +0 -0
- /package/dist/{src/backend → backend}/server.d.ts +0 -0
- /package/dist/{src/backend → backend}/types.d.ts +0 -0
- /package/dist/{test/backend.test.d.ts → backend/types.js} +0 -0
- /package/dist/{src/batch.d.ts → batch.d.ts} +0 -0
- /package/dist/{src/cli → cli}/index.d.ts +0 -0
- /package/dist/{src/components → components}/Counter.d.ts +0 -0
- /package/dist/{src/context.d.ts → context.d.ts} +0 -0
- /package/dist/{src/hooks.d.ts → hooks.d.ts} +0 -0
- /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-dev-runtime.d.ts +0 -0
- /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-runtime.d.ts +0 -0
- /package/dist/{src/renderer.d.ts → renderer.d.ts} +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{test/mockTest.d.ts → types.js} +0 -0
- /package/dist/{src/vdom.d.ts → vdom.d.ts} +0 -0
- /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": "
|
2
|
+
"name": "front-package-app",
|
3
3
|
"private": true,
|
4
|
-
"version": "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
|
-
"
|
11
|
-
"
|
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
|
-
"
|
15
|
+
"front-package": "latest"
|
15
16
|
},
|
16
17
|
"devDependencies": {
|
17
|
-
"
|
18
|
-
"
|
19
|
-
"@
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"
|
23
|
-
"
|
24
|
-
"tailwindcss": "^3.3.
|
25
|
-
"
|
26
|
-
"
|
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
|
}
|
@@ -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,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
|
}
|