frontend-hamroun 1.2.2 → 1.2.3

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.
@@ -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,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -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,144 @@
1
+ import { render, useState, useEffect, useMemo, useRef, useErrorBoundary, createContext, useContext } from 'frontend-hamroun';
2
+ import './main.css';
3
+
4
+ // Create a theme context
5
+ const ThemeContext = createContext<'light' | 'dark'>('light');
6
+
7
+ // Create a component that will throw an error when clicked
8
+ function BuggyComponent() {
9
+ const [shouldError, setShouldError] = useState(false);
10
+
11
+ if (shouldError) {
12
+ throw new Error("Test error from BuggyComponent");
13
+ }
14
+
15
+ return (
16
+ <button
17
+ onClick={() => setShouldError(true)}
18
+ className="bg-red-600 text-white px-4 py-2 rounded mt-4 hover:bg-red-700"
19
+ >
20
+ Trigger Error
21
+ </button>
22
+ );
23
+ }
24
+
25
+ function App() {
26
+ const [count, setCount] = useState(0);
27
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
28
+ const renderCount = useRef(0);
29
+ const [error, resetError] = useErrorBoundary();
30
+
31
+ // Demonstrate useEffect
32
+ useEffect(() => {
33
+ document.title = `Count: ${count}`;
34
+ renderCount.current += 1;
35
+
36
+ return () => {
37
+ console.log('Cleanup effect');
38
+ };
39
+ }, [count]);
40
+
41
+ // Demonstrate useMemo
42
+ const doubled = useMemo(() => count * 2, [count]);
43
+
44
+ if (error) {
45
+ return (
46
+ <div className="p-8 max-w-md mx-auto bg-red-50 rounded-lg shadow-lg">
47
+ <h1 className="text-2xl font-bold text-red-600 mb-4">Something went wrong!</h1>
48
+ <button
49
+ onClick={resetError}
50
+ className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
51
+ >
52
+ Try again
53
+ </button>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ return (
59
+ <ThemeContext.Provider value={theme}>
60
+ <div className={`p-8 max-w-4xl mx-auto rounded-lg shadow-lg transition-colors ${
61
+ theme === 'dark'
62
+ ? 'bg-gray-800 text-white'
63
+ : 'bg-white text-gray-900'
64
+ }`}>
65
+ <h1 className="text-3xl font-bold mb-6">Welcome to Frontend Hamroun</h1>
66
+
67
+ <div className="mb-6">
68
+ <button
69
+ onClick={() => setCount(count - 1)}
70
+ className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
71
+ >-</button>
72
+ <span className="mx-4 text-xl">{count}</span>
73
+ <button
74
+ onClick={() => setCount(count + 1)}
75
+ className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
76
+ >+</button>
77
+ </div>
78
+
79
+ <p className="mb-2 text-lg">Doubled value: <span className="font-semibold">{doubled}</span></p>
80
+ <p className="mb-4 text-lg">Render count: <span className="font-semibold">{renderCount.current}</span></p>
81
+
82
+ <button
83
+ onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
84
+ className={`px-4 py-2 rounded mb-8 ${
85
+ theme === 'dark'
86
+ ? 'bg-yellow-400 text-gray-900 hover:bg-yellow-300'
87
+ : 'bg-gray-800 text-white hover:bg-gray-700'
88
+ }`}
89
+ >
90
+ Toggle Theme ({theme})
91
+ </button>
92
+
93
+ <div className="mt-8 border-t pt-6">
94
+ <h2 className="text-2xl font-bold mb-4">Test Error Boundary</h2>
95
+ <BuggyComponent />
96
+ </div>
97
+
98
+ <div className="mt-8 border-t pt-6">
99
+ <h2 className="text-2xl font-bold mb-4">Test Context</h2>
100
+ <ContextConsumer />
101
+ </div>
102
+ </div>
103
+ </ThemeContext.Provider>
104
+ );
105
+ }
106
+
107
+ // Component to test context
108
+ function ContextConsumer() {
109
+ // Get the full context object for debugging
110
+ const themeContext = useContext(ThemeContext);
111
+ console.log('Theme context object:', themeContext);
112
+
113
+ // Access the current theme from the parent component instead
114
+ // This is a workaround until context is properly implemented
115
+ return (
116
+ <div className={`mt-4 p-4 rounded-md border ${
117
+ themeContext === 'dark' ? 'border-gray-600' : 'border-gray-300'
118
+ }`}>
119
+ <p className="mb-2"><strong>Note:</strong> Context API needs a fix in the framework.</p>
120
+ <p className="mb-1">Context object type: {typeof themeContext}</p>
121
+ <p className="mb-4">Context object keys: {Object.keys(themeContext as object).join(', ') || 'none'}</p>
122
+
123
+ <button
124
+ onClick={() => {
125
+ // Since we can't get the value directly from context,
126
+ // Let's add a button to demonstrate we can still use the Provider
127
+ const newTheme = document.body.style.backgroundColor === 'rgb(51, 51, 51)'
128
+ ? 'light' : 'dark';
129
+
130
+ console.log('Manually changing theme to:', newTheme);
131
+ // This won't work yet, but demonstrates the intent
132
+ if (ThemeContext) {
133
+ console.log('Provider exists, would set value to:', newTheme);
134
+ }
135
+ }}
136
+ className="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded"
137
+ >
138
+ Check Context Implementation
139
+ </button>
140
+ </div>
141
+ );
142
+ }
143
+
144
+ render(<App />, document.getElementById('root')!);
@@ -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
+ });
@@ -0,0 +1,32 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
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
+ },
27
+ },
28
+ plugins: [
29
+ require('@tailwindcss/forms'),
30
+ require('@tailwindcss/typography'),
31
+ ],
32
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "jsx": "preserve",
7
+ "moduleResolution": "bundler",
8
+ "esModuleInterop": true,
9
+ "strict": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ esbuild: {
5
+ jsxFactory: 'jsx',
6
+ jsxFragment: 'Fragment'
7
+ },
8
+ build: {
9
+ outDir: 'dist',
10
+ emptyOutDir: true,
11
+ rollupOptions: {
12
+ // Mark testing dependencies as external so they won't be included in the bundle
13
+ external: [
14
+ 'mock-aws-s3',
15
+ 'aws-sdk',
16
+ 'nock',
17
+ 'jest',
18
+ 'jest-mock',
19
+ '@testing-library/react',
20
+ '@testing-library/jest-dom'
21
+ ]
22
+ }
23
+ },
24
+ server: {
25
+ port: 3000,
26
+ open: true
27
+ }
28
+ });
@@ -6,6 +6,8 @@ export default defineConfig({
6
6
  jsxFragment: 'Fragment'
7
7
  },
8
8
  build: {
9
+ outDir: 'dist',
10
+ emptyOutDir: true,
9
11
  rollupOptions: {
10
12
  // Mark testing dependencies as external so they won't be included in the bundle
11
13
  external: [
@@ -18,5 +20,9 @@ export default defineConfig({
18
20
  '@testing-library/jest-dom'
19
21
  ]
20
22
  }
23
+ },
24
+ server: {
25
+ port: 3000,
26
+ open: true
21
27
  }
22
28
  });
package/bin/cli.js DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const path = require('path');
4
- const fs = require('fs');
5
-
6
- try {
7
- // Try to load the ESM version
8
- import('../dist/cli/index.js')
9
- .catch(err => {
10
- console.error('Failed to load CLI:', err);
11
- process.exit(1);
12
- });
13
- } catch (error) {
14
- console.error('Error loading CLI:', error);
15
- process.exit(1);
16
- }