create-elit 3.2.7 → 3.2.8

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,23 @@
1
+ # ELIT_PROJECT_NAME
2
+
3
+ A new Elit project created with create-elit.
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Visit http://localhost:3003 to view your app.
13
+
14
+ ## Available Scripts
15
+
16
+ - `npm run dev` - Start development server with HMR
17
+ - `npm run build` - Build for production
18
+ - `npm run preview` - Preview production build
19
+
20
+ ## Learn More
21
+
22
+ - [Elit Documentation](https://d-osc.github.io/elit)
23
+ - [GitHub Repository](https://github.com/d-osc/elit)
@@ -0,0 +1,59 @@
1
+ import { server } from './src/server';
2
+ import { client } from './src/client';
3
+
4
+ export default {
5
+ dev: {
6
+ port: 3003,
7
+ host: 'localhost',
8
+ open: true,
9
+ logging: true,
10
+ clients: [{
11
+ root: '.',
12
+ basePath: '',
13
+ ssr: () => client,
14
+ api: server
15
+ }]
16
+ },
17
+ build: [{
18
+ entry: './src/main.ts',
19
+ outDir: './dist',
20
+ outFile: 'main.js',
21
+ format: 'esm',
22
+ minify: true,
23
+ sourcemap: true,
24
+ target: 'es2020',
25
+ copy: [
26
+ {
27
+ from: './public/index.html', to: './index.html',
28
+ transform: (content: string, config: { basePath: string; projectName: string; }) => {
29
+ // Replace script src
30
+ let html = content.replace('src="../src/main.ts"', 'src="main.js"');
31
+
32
+ // Replace project name placeholder
33
+ html = html.replace(/ELIT_PROJECT_NAME/g, config.projectName);
34
+
35
+ // Inject base tag if basePath is configured
36
+ if (config.basePath) {
37
+ const baseTag = `<base href="${config.basePath}/">`;
38
+ html = html.replace(
39
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
40
+ `<meta name="viewport" content="width=device-width, initial-scale=1.0">\n ${baseTag}`
41
+ );
42
+ }
43
+
44
+ return html;
45
+ }
46
+ },
47
+ { from: './public/favicon.svg', to: './favicon.svg' }
48
+ ]
49
+ }],
50
+ preview: {
51
+ port: 3000,
52
+ host: 'localhost',
53
+ open: false,
54
+ logging: true,
55
+ root: './dist',
56
+ basePath: '',
57
+ index: './index.html'
58
+ }
59
+ };
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "ELIT_PROJECT_NAME",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "elit dev",
7
+ "build": "elit build",
8
+ "preview": "elit preview"
9
+ },
10
+ "dependencies": {
11
+ "@types/node": "^25.0.9",
12
+ "elit": "^ELIT_VERSION"
13
+ }
14
+ }
@@ -0,0 +1,22 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <defs>
3
+ <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#6366f1"/>
5
+ <stop offset="100%" stop-color="#8b5cf6"/>
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- Clean background -->
10
+ <rect width="100" height="100" rx="20" fill="url(#grad)"/>
11
+
12
+ <!-- Simple E shape - 3 horizontal bars -->
13
+ <rect x="28" y="25" width="44" height="8" rx="4" fill="white"/>
14
+ <rect x="28" y="46" width="32" height="8" rx="4" fill="white"/>
15
+ <rect x="28" y="67" width="44" height="8" rx="4" fill="white"/>
16
+
17
+ <!-- Vertical connector -->
18
+ <rect x="28" y="25" width="8" height="50" rx="4" fill="white"/>
19
+
20
+ <!-- Single accent dot -->
21
+ <circle cx="72" cy="50" r="6" fill="white" opacity="0.5"/>
22
+ </svg>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ELIT_PROJECT_NAME</title>
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
8
+ <meta name="description" content="Built with Elit - Full-stack TypeScript framework">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="../src/main.ts"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,15 @@
1
+ import { div, html, head, body, title, link, script, meta } from 'elit/el';
2
+
3
+ export const client = html(
4
+ head(
5
+ title('ELIT_PROJECT_NAME - Elit App'),
6
+ link({ rel: 'icon', type: 'image/svg+xml', href: 'public/favicon.svg' }),
7
+ meta({ charset: 'UTF-8' }),
8
+ meta({ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }),
9
+ meta({ name: 'description', content: 'Elit - Full-stack TypeScript framework with dev server, HMR, routing, SSR, and REST API.' })
10
+ ),
11
+ body(
12
+ div({ id: 'app' }),
13
+ script({ type: 'module', src: '/src/main.js' })
14
+ )
15
+ );
@@ -0,0 +1,20 @@
1
+ import { footer, div, p, a } from 'elit/el';
2
+
3
+ export function Footer() {
4
+ return footer({ className: 'footer' },
5
+ div({ className: 'footer-content' },
6
+ div({ className: 'footer-section' },
7
+ p({ className: 'footer-title' }, 'My Elit App'),
8
+ p({ className: 'footer-text' }, 'Built with Elit Framework')
9
+ ),
10
+ div({ className: 'footer-section' },
11
+ a({ href: 'https://github.com', target: '_blank', className: 'footer-link' }, 'GitHub'),
12
+ a({ href: '#', className: 'footer-link' }, 'Documentation'),
13
+ a({ href: '#', className: 'footer-link' }, 'Support')
14
+ ),
15
+ div({ className: 'footer-section' },
16
+ p({ className: 'footer-copyright' }, '© 2026 My Elit App. All rights reserved.')
17
+ )
18
+ )
19
+ );
20
+ }
@@ -0,0 +1,70 @@
1
+ import { header, nav, div, a, h1, button, span } from 'elit/el';
2
+ import { createState, reactive } from 'elit/state';
3
+ import type { Router } from 'elit';
4
+
5
+ export function Header(router: Router) {
6
+ // Check if user is logged in (has token in localStorage)
7
+ const isLoggedIn = createState(!!localStorage.getItem('token'));
8
+ const user = createState(() => {
9
+ const userStr = localStorage.getItem('user');
10
+ return userStr ? JSON.parse(userStr) : null;
11
+ });
12
+
13
+ // Listen for storage changes to update header when login/logout happens
14
+ const handleStorageChange = (e: StorageEvent) => {
15
+ if (e.key === 'token' || e.key === 'user') {
16
+ isLoggedIn.value = !!localStorage.getItem('token');
17
+ const userStr = localStorage.getItem('user');
18
+ user.value = userStr ? JSON.parse(userStr) : null;
19
+ }
20
+ };
21
+
22
+ // Also listen for custom storage events (same-tab updates)
23
+ const handleCustomStorageChange = () => {
24
+ isLoggedIn.value = !!localStorage.getItem('token');
25
+ const userStr = localStorage.getItem('user');
26
+ user.value = userStr ? JSON.parse(userStr) : null;
27
+ };
28
+
29
+ window.addEventListener('storage', handleStorageChange);
30
+ window.addEventListener('elit:storage', handleCustomStorageChange);
31
+
32
+ const handleLogout = () => {
33
+ localStorage.removeItem('token');
34
+ localStorage.removeItem('user');
35
+ isLoggedIn.value = false;
36
+ router.push('/');
37
+ };
38
+
39
+ return header({ className: 'header' },
40
+ nav({ className: 'nav' },
41
+ div({ className: 'nav-brand' },
42
+ a({ href: '#/', className: 'brand-link' },
43
+ h1({ className: 'brand-title' }, 'ELIT_PROJECT_NAME')
44
+ )
45
+ ),
46
+
47
+ reactive(isLoggedIn, (loggedIn) => {
48
+ if (loggedIn) {
49
+ return div({ className: 'nav-menu' },
50
+ a({ href: '#/chat/list', className: 'nav-link' }, 'Messages'),
51
+ a({ href: '#/profile', className: 'nav-link' }, 'Profile'),
52
+ reactive(user, (u) => u ? span({ className: 'nav-user' }, `Welcome, ${u.name}`) : null),
53
+ button({
54
+ className: 'btn btn-secondary btn-sm',
55
+ onclick: handleLogout
56
+ }, 'Logout')
57
+ );
58
+ }
59
+
60
+ return div({ className: 'nav-menu' },
61
+ a({ href: '#/login', className: 'nav-link' }, 'Login'),
62
+ button({
63
+ className: 'btn btn-primary btn-sm',
64
+ onclick: () => router.push('/register')
65
+ }, 'Sign Up')
66
+ );
67
+ })
68
+ )
69
+ );
70
+ }
@@ -0,0 +1,2 @@
1
+ export { Header } from './Header';
2
+ export { Footer } from './Footer';
@@ -0,0 +1,22 @@
1
+ import { div, main } from 'elit/el';
2
+ import { reactive } from 'elit/state';
3
+ import { dom } from 'elit/dom';
4
+ import { injectStyles } from './styles';
5
+ import { router, RouterView } from './router';
6
+ import { Header } from './components/Header';
7
+ import { Footer } from './components/Footer';
8
+
9
+ injectStyles()
10
+ // Create reactive state (shared between SSR and client)
11
+ // Main App
12
+ const App = () =>
13
+ div(
14
+ Header(router),
15
+ main(
16
+ reactive(router.currentRoute, () => RouterView())
17
+ ),
18
+ Footer()
19
+ );
20
+
21
+ // Render
22
+ dom.render('#app', App());
@@ -0,0 +1,144 @@
1
+ import { div, h2, h3, p, button, span, input } from 'elit/el';
2
+ import { createState, reactive } from 'elit/state';
3
+ import type { Router } from 'elit';
4
+
5
+ interface User {
6
+ id: string;
7
+ name: string;
8
+ email: string;
9
+ bio: string;
10
+ avatar: string;
11
+ }
12
+
13
+ export function ChatListPage(router: Router) {
14
+ // Check if user is logged in
15
+ const token = localStorage.getItem('token');
16
+ const user = localStorage.getItem('user');
17
+
18
+ if (!token || !user) {
19
+ router.push('/login');
20
+ return div({ className: 'chat-page' }, p('Redirecting to login...'));
21
+ }
22
+
23
+ const userData = JSON.parse(user);
24
+
25
+ const users = createState<User[]>([]);
26
+ const searchQuery = createState('');
27
+ const isLoading = createState(false);
28
+ const error = createState('');
29
+
30
+ // Load users
31
+ const loadUsers = async () => {
32
+ isLoading.value = true;
33
+ try {
34
+ const response = await fetch('/api/users', {
35
+ method: 'GET',
36
+ headers: {
37
+ 'Authorization': `Bearer ${token}`
38
+ }
39
+ });
40
+
41
+ if (response.ok) {
42
+ const data = await response.json();
43
+ console.log('Current user ID:', userData.id);
44
+ console.log('All users:', data.users);
45
+
46
+ // Filter out current user
47
+ const allUsers = data.users || [];
48
+ users.value = allUsers.filter((u: User) => u.id !== userData.id);
49
+
50
+ console.log('Filtered users:', users.value);
51
+ } else {
52
+ error.value = 'Failed to load users';
53
+ }
54
+ } catch (err) {
55
+ console.error('Error loading users:', err);
56
+ error.value = 'Network error. Please try again.';
57
+ } finally {
58
+ isLoading.value = false;
59
+ }
60
+ };
61
+
62
+ // Load users on mount
63
+ loadUsers();
64
+
65
+ // Open chat with specific user
66
+ const openChat = (userId: string) => {
67
+ router.push(`/chat/dm/${userId}`);
68
+ };
69
+
70
+ return div({ className: 'chat-list-page' },
71
+ div({ className: 'chat-container' },
72
+ // Header
73
+ div({ className: 'chat-header' },
74
+ div({ className: 'chat-header-info' },
75
+ h2({ className: 'chat-title' }, 'Messages'),
76
+ p({ className: 'chat-subtitle' }, 'Select a user to start chatting')
77
+ ),
78
+ button({
79
+ className: 'btn btn-secondary btn-sm',
80
+ onclick: () => router.push('/profile')
81
+ }, 'Back to Profile')
82
+ ),
83
+
84
+ // Search
85
+ div({ className: 'chat-search' },
86
+ input({
87
+ type: 'text',
88
+ className: 'chat-input',
89
+ placeholder: 'Search users...',
90
+ oninput: (e: Event) => {
91
+ searchQuery.value = (e.target as HTMLInputElement).value;
92
+ }
93
+ })
94
+ ),
95
+
96
+ // Error Display
97
+ reactive(error, (err) => err ? div({ className: 'auth-error' }, err) : null),
98
+
99
+ // Users List
100
+ div({ className: 'chat-users-list' },
101
+ reactive(isLoading, (loading) => {
102
+ if (loading) {
103
+ return div({ className: 'chat-loading' }, p('Loading users...'));
104
+ }
105
+ return reactive(users, (userList) => {
106
+ const filteredUsers = searchQuery.value
107
+ ? userList.filter(u =>
108
+ u.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
109
+ u.email.toLowerCase().includes(searchQuery.value.toLowerCase())
110
+ )
111
+ : userList;
112
+
113
+ if (filteredUsers.length === 0) {
114
+ return div({ className: 'chat-empty' },
115
+ p({ className: 'chat-empty-text' }, searchQuery.value ? 'No users found' : 'No other users yet')
116
+ );
117
+ }
118
+
119
+ return div({ className: 'chat-users-grid' },
120
+ ...filteredUsers.map(u =>
121
+ div({
122
+ className: 'chat-user-card',
123
+ onclick: () => openChat(u.id)
124
+ },
125
+ div({ className: 'chat-user-avatar' },
126
+ span({ className: 'chat-avatar-text' }, u.name.charAt(0).toUpperCase())
127
+ ),
128
+ div({ className: 'chat-user-info' },
129
+ h3({ className: 'chat-user-name' }, u.name),
130
+ p({ className: 'chat-user-email' }, u.email),
131
+ u.bio ? p({ className: 'chat-user-bio' }, u.bio) : null
132
+ ),
133
+ button({
134
+ className: 'btn btn-primary btn-sm chat-chat-button'
135
+ }, 'Chat')
136
+ )
137
+ )
138
+ );
139
+ });
140
+ })
141
+ )
142
+ )
143
+ );
144
+ }
@@ -0,0 +1,186 @@
1
+ import { div, h2, p, input, button, span, form } from 'elit/el';
2
+ import { createState, reactive, createSharedState } from 'elit/state';
3
+ import type { Router } from 'elit';
4
+
5
+ interface ChatMessage {
6
+ id: string;
7
+ roomId: string;
8
+ userId: string;
9
+ userName: string;
10
+ text: string;
11
+ timestamp: number;
12
+ }
13
+
14
+ export function ChatPage(router: Router) {
15
+ // Check if user is logged in
16
+ const token = localStorage.getItem('token');
17
+ const user = localStorage.getItem('user');
18
+
19
+ if (!token || !user) {
20
+ router.push('/login');
21
+ return div({ className: 'chat-page' }, p('Redirecting to login...'));
22
+ }
23
+
24
+ const userData = JSON.parse(user);
25
+
26
+ // Create shared state for chat messages with WebSocket sync
27
+ const chatMessages = createSharedState<ChatMessage[]>('chat:general', [], `ws://${location.host}`);
28
+ const newMessage = createState('');
29
+ const error = createState('');
30
+ const isLoading = createState(false);
31
+
32
+ // Load initial messages from API
33
+ const loadMessages = async () => {
34
+ try {
35
+ const response = await fetch('/api/chat/messages?roomId=general', {
36
+ method: 'GET',
37
+ headers: {
38
+ 'Authorization': `Bearer ${token}`
39
+ }
40
+ });
41
+
42
+ if (response.ok) {
43
+ const data = await response.json();
44
+ chatMessages.value = data.messages || [];
45
+ }
46
+ } catch (err) {
47
+ console.error('Failed to load messages:', err);
48
+ }
49
+ };
50
+
51
+ // Load messages on mount
52
+ loadMessages();
53
+
54
+ const handleSendMessage = async (e: Event) => {
55
+ e.preventDefault();
56
+
57
+ const messageText = newMessage.value.trim();
58
+ if (!messageText) {
59
+ error.value = 'Please enter a message';
60
+ return;
61
+ }
62
+
63
+ newMessage.value = '';
64
+ error.value = '';
65
+ isLoading.value = true;
66
+
67
+ try {
68
+ const response = await fetch('/api/chat/send', {
69
+ method: 'POST',
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ 'Authorization': `Bearer ${token}`
73
+ },
74
+ body: JSON.stringify({
75
+ roomId: 'general',
76
+ message: messageText
77
+ })
78
+ });
79
+
80
+ if (response.ok) {
81
+ const data = await response.json();
82
+
83
+ // Add the sent message to shared state (syncs with other clients)
84
+ if (data.message) {
85
+ chatMessages.value = [...chatMessages.value, data.message];
86
+ }
87
+ } else {
88
+ const errorData = await response.json();
89
+ error.value = errorData.error || 'Failed to send message';
90
+ }
91
+
92
+ isLoading.value = false;
93
+ } catch (err) {
94
+ error.value = 'Network error. Please try again.';
95
+ isLoading.value = false;
96
+ }
97
+ };
98
+
99
+ // Cleanup shared state on unmount
100
+ const cleanup = () => {
101
+ chatMessages.disconnect();
102
+ };
103
+
104
+ // Poll for new messages every 2 seconds (fallback if WebSocket is not available)
105
+ const pollInterval = setInterval(() => {
106
+ if (!chatMessages.value || chatMessages.value.length === 0) {
107
+ loadMessages();
108
+ }
109
+ }, 2000);
110
+
111
+ return div({ className: 'chat-page' },
112
+ div({ className: 'chat-container' },
113
+ // Chat Header
114
+ div({ className: 'chat-header' },
115
+ div({ className: 'chat-header-info' },
116
+ h2({ className: 'chat-title' }, 'Chat Room'),
117
+ p({ className: 'chat-subtitle' }, `Logged in as ${userData.name}`)
118
+ ),
119
+ button({
120
+ className: 'btn btn-secondary btn-sm',
121
+ onclick: () => {
122
+ clearInterval(pollInterval);
123
+ cleanup();
124
+ router.push('/profile');
125
+ }
126
+ }, 'Back to Profile')
127
+ ),
128
+
129
+ // Messages Area
130
+ div({ className: 'chat-messages' },
131
+ reactive(chatMessages.state, (msgs) => {
132
+ if (!msgs || msgs.length === 0) {
133
+ return div({ className: 'chat-empty' },
134
+ p({ className: 'chat-empty-text' }, 'No messages yet. Start the conversation!')
135
+ );
136
+ }
137
+
138
+ return div({ className: 'chat-messages-list' },
139
+ ...msgs.map(msg =>
140
+ div({
141
+ className: `chat-message ${msg.userId === userData.id ? 'chat-message-user' : 'chat-message-other'}`
142
+ },
143
+ div({ className: 'chat-message-content' },
144
+ span({ className: 'chat-message-sender' },
145
+ msg.userId === userData.id ? 'You' : msg.userName
146
+ ),
147
+ p({ className: 'chat-message-text' }, msg.text)
148
+ ),
149
+ span({ className: 'chat-message-time' },
150
+ new Date(msg.timestamp).toLocaleTimeString()
151
+ )
152
+ )
153
+ )
154
+ );
155
+ }),
156
+ isLoading.value ? div({ className: 'chat-typing' },
157
+ span({ className: 'typing-dot' }, '.'),
158
+ span({ className: 'typing-dot' }, '.'),
159
+ span({ className: 'typing-dot' }, '.')
160
+ ) : null
161
+ ),
162
+
163
+ // Error Display
164
+ reactive(error, (err) => err ? div({ className: 'auth-error' }, err) : null),
165
+
166
+ // Input Area
167
+ form({ className: 'chat-input-area', onsubmit: handleSendMessage },
168
+ input({
169
+ type: 'text',
170
+ className: 'chat-input',
171
+ placeholder: 'Type your message...',
172
+ value: newMessage.value,
173
+ oninput: (e: Event) => {
174
+ newMessage.value = (e.target as HTMLInputElement).value;
175
+ error.value = '';
176
+ }
177
+ }),
178
+ button({
179
+ type: 'submit',
180
+ className: 'btn btn-primary',
181
+ disabled: isLoading.value || !newMessage.value.trim()
182
+ }, isLoading.value ? 'Sending...' : 'Send')
183
+ )
184
+ )
185
+ );
186
+ }