helix-lang 11.0.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/README.md +168 -0
- package/dist/architect.d.ts +14 -0
- package/dist/architect.d.ts.map +1 -0
- package/dist/architect.js +127 -0
- package/dist/architect.js.map +1 -0
- package/dist/bin/helix.d.ts +20 -0
- package/dist/bin/helix.d.ts.map +1 -0
- package/dist/bin/helix.js +921 -0
- package/dist/bin/helix.js.map +1 -0
- package/dist/commands/collaborate/index.d.ts +2 -0
- package/dist/commands/collaborate/index.d.ts.map +1 -0
- package/dist/commands/collaborate/index.js +129 -0
- package/dist/commands/collaborate/index.js.map +1 -0
- package/dist/commands/collaborate/server.d.ts +31 -0
- package/dist/commands/collaborate/server.d.ts.map +1 -0
- package/dist/commands/collaborate/server.js +159 -0
- package/dist/commands/collaborate/server.js.map +1 -0
- package/dist/commands/deploy/index.d.ts +25 -0
- package/dist/commands/deploy/index.d.ts.map +1 -0
- package/dist/commands/deploy/index.js +130 -0
- package/dist/commands/deploy/index.js.map +1 -0
- package/dist/commands/deploy/platforms/fly.d.ts +9 -0
- package/dist/commands/deploy/platforms/fly.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/fly.js +68 -0
- package/dist/commands/deploy/platforms/fly.js.map +1 -0
- package/dist/commands/deploy/platforms/railway.d.ts +9 -0
- package/dist/commands/deploy/platforms/railway.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/railway.js +115 -0
- package/dist/commands/deploy/platforms/railway.js.map +1 -0
- package/dist/commands/deploy/platforms/vercel.d.ts +10 -0
- package/dist/commands/deploy/platforms/vercel.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/vercel.js +126 -0
- package/dist/commands/deploy/platforms/vercel.js.map +1 -0
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +56 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/evolve/analyzers/performance.d.ts +13 -0
- package/dist/commands/evolve/analyzers/performance.d.ts.map +1 -0
- package/dist/commands/evolve/analyzers/performance.js +591 -0
- package/dist/commands/evolve/analyzers/performance.js.map +1 -0
- package/dist/commands/evolve/analyzers/security.d.ts +21 -0
- package/dist/commands/evolve/analyzers/security.d.ts.map +1 -0
- package/dist/commands/evolve/analyzers/security.js +280 -0
- package/dist/commands/evolve/analyzers/security.js.map +1 -0
- package/dist/commands/evolve/index.d.ts +2 -0
- package/dist/commands/evolve/index.d.ts.map +1 -0
- package/dist/commands/evolve/index.js +122 -0
- package/dist/commands/evolve/index.js.map +1 -0
- package/dist/commands/generate.d.ts +6 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +277 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +176 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/library/index.d.ts +27 -0
- package/dist/commands/library/index.d.ts.map +1 -0
- package/dist/commands/library/index.js +126 -0
- package/dist/commands/library/index.js.map +1 -0
- package/dist/commands/migrate.d.ts +5 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +258 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/new.d.ts +6 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +195 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/preflight.d.ts +20 -0
- package/dist/commands/preflight.d.ts.map +1 -0
- package/dist/commands/preflight.js +182 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/preview.d.ts +13 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +260 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +96 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/spawn.d.ts +11 -0
- package/dist/commands/spawn.d.ts.map +1 -0
- package/dist/commands/spawn.js +916 -0
- package/dist/commands/spawn.js.map +1 -0
- package/dist/compiler.d.ts +12 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +92 -0
- package/dist/compiler.js.map +1 -0
- package/dist/core/file-writer.d.ts +36 -0
- package/dist/core/file-writer.d.ts.map +1 -0
- package/dist/core/file-writer.js +268 -0
- package/dist/core/file-writer.js.map +1 -0
- package/dist/core/registry.d.ts +57 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +222 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/self-healing.d.ts +47 -0
- package/dist/core/self-healing.d.ts.map +1 -0
- package/dist/core/self-healing.js +250 -0
- package/dist/core/self-healing.js.map +1 -0
- package/dist/core/types.d.ts +126 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generators/databases/mongodb.d.ts +10 -0
- package/dist/generators/databases/mongodb.d.ts.map +1 -0
- package/dist/generators/databases/mongodb.js +83 -0
- package/dist/generators/databases/mongodb.js.map +1 -0
- package/dist/generators/databases/redis.d.ts +2 -0
- package/dist/generators/databases/redis.d.ts.map +1 -0
- package/dist/generators/databases/redis.js +140 -0
- package/dist/generators/databases/redis.js.map +1 -0
- package/dist/generators/flutter.d.ts +32 -0
- package/dist/generators/flutter.d.ts.map +1 -0
- package/dist/generators/flutter.js +628 -0
- package/dist/generators/flutter.js.map +1 -0
- package/dist/openrouter.d.ts +68 -0
- package/dist/openrouter.d.ts.map +1 -0
- package/dist/openrouter.js +241 -0
- package/dist/openrouter.js.map +1 -0
- package/dist/page-generator.d.ts +22 -0
- package/dist/page-generator.d.ts.map +1 -0
- package/dist/page-generator.js +192 -0
- package/dist/page-generator.js.map +1 -0
- package/dist/parser.d.ts +76 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +691 -0
- package/dist/parser.js.map +1 -0
- package/dist/prompts/master-architect.d.ts +9 -0
- package/dist/prompts/master-architect.d.ts.map +1 -0
- package/dist/prompts/master-architect.js +150 -0
- package/dist/prompts/master-architect.js.map +1 -0
- package/dist/researcher.d.ts +12 -0
- package/dist/researcher.d.ts.map +1 -0
- package/dist/researcher.js +85 -0
- package/dist/researcher.js.map +1 -0
- package/dist/self-heal.d.ts +29 -0
- package/dist/self-heal.d.ts.map +1 -0
- package/dist/self-heal.js +260 -0
- package/dist/self-heal.js.map +1 -0
- package/dist/services/SupabaseDeployer.d.ts +9 -0
- package/dist/services/SupabaseDeployer.d.ts.map +1 -0
- package/dist/services/SupabaseDeployer.js +50 -0
- package/dist/services/SupabaseDeployer.js.map +1 -0
- package/dist/test-generator.d.ts +18 -0
- package/dist/test-generator.d.ts.map +1 -0
- package/dist/test-generator.js +180 -0
- package/dist/test-generator.js.map +1 -0
- package/dist/themes/index.d.ts +52 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +273 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/constitutional-validator.d.ts +73 -0
- package/dist/utils/constitutional-validator.d.ts.map +1 -0
- package/dist/utils/constitutional-validator.js +249 -0
- package/dist/utils/constitutional-validator.js.map +1 -0
- package/library/auth-flow/app/api/auth/[...nextauth]/route.ts +31 -0
- package/library/auth-flow/app/api/auth/login/route.ts +90 -0
- package/library/auth-flow/app/api/auth/register/route.ts +91 -0
- package/library/auth-flow/components/auth/AuthMiddleware.tsx +139 -0
- package/library/auth-flow/components/auth/LoginForm.tsx +125 -0
- package/library/auth-flow/components/auth/RegisterForm.tsx +168 -0
- package/library/auth-flow/components/auth/nextauth-config.ts +99 -0
- package/library/auth-flow/manifest.json +29 -0
- package/library/auth-flow/schema.prisma +45 -0
- package/library/dashboard-analytics/components/dashboard/ActivityFeed.tsx +109 -0
- package/library/dashboard-analytics/components/dashboard/LineChart.tsx +180 -0
- package/library/dashboard-analytics/components/dashboard/StatsCard.tsx +47 -0
- package/library/dashboard-analytics/components/dashboard/SummaryGrid.tsx +39 -0
- package/library/dashboard-analytics/manifest.json +19 -0
- package/library/data-table/components/table/BulkActions.tsx +59 -0
- package/library/data-table/components/table/ColumnToggle.tsx +65 -0
- package/library/data-table/components/table/DataTable.tsx +318 -0
- package/library/data-table/components/table/ExportCSV.tsx +52 -0
- package/library/data-table/components/table/SearchFilter.tsx +48 -0
- package/library/data-table/manifest.json +20 -0
- package/library/file-upload/app/api/upload/route.ts +107 -0
- package/library/file-upload/components/upload/DropZone.tsx +268 -0
- package/library/file-upload/components/upload/FilePreview.tsx +82 -0
- package/library/file-upload/components/upload/UploadProgress.tsx +92 -0
- package/library/file-upload/components/upload/fileStorage.ts +142 -0
- package/library/file-upload/manifest.json +21 -0
- package/library/notification-system/app/api/notifications/route.ts +121 -0
- package/library/notification-system/components/notifications/NotificationBell.tsx +154 -0
- package/library/notification-system/components/notifications/NotificationProvider.tsx +161 -0
- package/library/notification-system/components/notifications/Toast.tsx +112 -0
- package/library/notification-system/manifest.json +20 -0
- package/package.json +66 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-Sent Events endpoint for real-time notifications.
|
|
5
|
+
*
|
|
6
|
+
* GET /api/notifications - Opens an SSE stream for the authenticated user.
|
|
7
|
+
* POST /api/notifications - Sends a notification (triggers SSE to all connected clients).
|
|
8
|
+
*
|
|
9
|
+
* Integration:
|
|
10
|
+
* const eventSource = new EventSource('/api/notifications');
|
|
11
|
+
* eventSource.onmessage = (event) => {
|
|
12
|
+
* const notification = JSON.parse(event.data);
|
|
13
|
+
* // Handle notification
|
|
14
|
+
* };
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface SSEClient {
|
|
18
|
+
id: string;
|
|
19
|
+
controller: ReadableStreamDefaultController;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// In-memory client registry (replace with Redis pub/sub for multi-instance)
|
|
23
|
+
const clients = new Map<string, SSEClient>();
|
|
24
|
+
|
|
25
|
+
function broadcastNotification(data: Record<string, unknown>) {
|
|
26
|
+
const message = `data: ${JSON.stringify(data)}\n\n`;
|
|
27
|
+
const encoder = new TextEncoder();
|
|
28
|
+
const encoded = encoder.encode(message);
|
|
29
|
+
|
|
30
|
+
for (const [id, client] of clients.entries()) {
|
|
31
|
+
try {
|
|
32
|
+
client.controller.enqueue(encoded);
|
|
33
|
+
} catch {
|
|
34
|
+
clients.delete(id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function GET(request: NextRequest) {
|
|
40
|
+
const clientId = request.headers.get('x-client-id') || crypto.randomUUID();
|
|
41
|
+
|
|
42
|
+
const stream = new ReadableStream({
|
|
43
|
+
start(controller) {
|
|
44
|
+
clients.set(clientId, { id: clientId, controller });
|
|
45
|
+
|
|
46
|
+
// Send initial connection message
|
|
47
|
+
const encoder = new TextEncoder();
|
|
48
|
+
controller.enqueue(
|
|
49
|
+
encoder.encode(`data: ${JSON.stringify({ type: 'connected', clientId })}\n\n`)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Heartbeat every 30s to keep connection alive
|
|
53
|
+
const heartbeat = setInterval(() => {
|
|
54
|
+
try {
|
|
55
|
+
controller.enqueue(encoder.encode(': heartbeat\n\n'));
|
|
56
|
+
} catch {
|
|
57
|
+
clearInterval(heartbeat);
|
|
58
|
+
clients.delete(clientId);
|
|
59
|
+
}
|
|
60
|
+
}, 30000);
|
|
61
|
+
|
|
62
|
+
// Cleanup on close
|
|
63
|
+
request.signal.addEventListener('abort', () => {
|
|
64
|
+
clearInterval(heartbeat);
|
|
65
|
+
clients.delete(clientId);
|
|
66
|
+
try {
|
|
67
|
+
controller.close();
|
|
68
|
+
} catch {
|
|
69
|
+
// Already closed
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
cancel() {
|
|
74
|
+
clients.delete(clientId);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return new NextResponse(stream, {
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'text/event-stream',
|
|
81
|
+
'Cache-Control': 'no-cache',
|
|
82
|
+
'Connection': 'keep-alive',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function POST(request: NextRequest) {
|
|
88
|
+
try {
|
|
89
|
+
const body = await request.json();
|
|
90
|
+
|
|
91
|
+
const { type = 'info', title, message, targetUserId } = body;
|
|
92
|
+
|
|
93
|
+
if (!title) {
|
|
94
|
+
return NextResponse.json(
|
|
95
|
+
{ error: 'Title is required' },
|
|
96
|
+
{ status: 400 }
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const notification = {
|
|
101
|
+
id: crypto.randomUUID(),
|
|
102
|
+
type,
|
|
103
|
+
title,
|
|
104
|
+
message: message || null,
|
|
105
|
+
targetUserId: targetUserId || null,
|
|
106
|
+
createdAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Broadcast to connected clients
|
|
110
|
+
broadcastNotification(notification);
|
|
111
|
+
|
|
112
|
+
return NextResponse.json({
|
|
113
|
+
message: 'Notification sent',
|
|
114
|
+
notification,
|
|
115
|
+
connectedClients: clients.size,
|
|
116
|
+
});
|
|
117
|
+
} catch (error: unknown) {
|
|
118
|
+
const msg = error instanceof Error ? error.message : 'Failed to send notification';
|
|
119
|
+
return NextResponse.json({ error: msg }, { status: 500 });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { useNotifications } from './NotificationProvider';
|
|
5
|
+
|
|
6
|
+
interface NotificationBellProps {
|
|
7
|
+
maxVisible?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatRelativeTime(date: Date): string {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
const diffMs = now - date.getTime();
|
|
13
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
14
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
15
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
16
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
17
|
+
|
|
18
|
+
if (diffSec < 60) return 'just now';
|
|
19
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
20
|
+
if (diffHour < 24) return `${diffHour}h ago`;
|
|
21
|
+
if (diffDay < 7) return `${diffDay}d ago`;
|
|
22
|
+
return date.toLocaleDateString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const typeColors: Record<string, string> = {
|
|
26
|
+
success: 'bg-emerald-400',
|
|
27
|
+
error: 'bg-red-400',
|
|
28
|
+
warning: 'bg-amber-400',
|
|
29
|
+
info: 'bg-cyan-400',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default function NotificationBell({ maxVisible = 10 }: NotificationBellProps) {
|
|
33
|
+
const { notifications, unreadCount, markAsRead, markAllAsRead, removeNotification } =
|
|
34
|
+
useNotifications();
|
|
35
|
+
const [open, setOpen] = useState(false);
|
|
36
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
function handleClickOutside(e: MouseEvent) {
|
|
40
|
+
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
41
|
+
setOpen(false);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
45
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const visibleNotifications = notifications.slice(0, maxVisible);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="relative" ref={ref}>
|
|
52
|
+
{/* Bell button */}
|
|
53
|
+
<button
|
|
54
|
+
onClick={() => setOpen(!open)}
|
|
55
|
+
className="relative p-2 text-white/60 hover:text-white transition-colors rounded-lg hover:bg-white/5"
|
|
56
|
+
>
|
|
57
|
+
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
58
|
+
<path
|
|
59
|
+
strokeLinecap="round"
|
|
60
|
+
strokeLinejoin="round"
|
|
61
|
+
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
|
|
62
|
+
/>
|
|
63
|
+
</svg>
|
|
64
|
+
|
|
65
|
+
{/* Badge */}
|
|
66
|
+
{unreadCount > 0 && (
|
|
67
|
+
<span className="absolute -top-0.5 -right-0.5 flex items-center justify-center min-w-[18px] h-[18px] px-1 bg-cyan-500 text-white text-[10px] font-bold rounded-full shadow-lg shadow-cyan-500/30">
|
|
68
|
+
{unreadCount > 99 ? '99+' : unreadCount}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
</button>
|
|
72
|
+
|
|
73
|
+
{/* Dropdown */}
|
|
74
|
+
{open && (
|
|
75
|
+
<div className="absolute right-0 top-full mt-2 w-80 backdrop-blur-xl bg-gray-900/95 border border-white/10 rounded-xl shadow-2xl z-50 overflow-hidden">
|
|
76
|
+
{/* Header */}
|
|
77
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-white/5">
|
|
78
|
+
<h3 className="text-sm font-semibold text-white">Notifications</h3>
|
|
79
|
+
{unreadCount > 0 && (
|
|
80
|
+
<button
|
|
81
|
+
onClick={markAllAsRead}
|
|
82
|
+
className="text-xs text-cyan-400 hover:text-cyan-300 transition-colors"
|
|
83
|
+
>
|
|
84
|
+
Mark all read
|
|
85
|
+
</button>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* List */}
|
|
90
|
+
<div className="max-h-96 overflow-y-auto">
|
|
91
|
+
{visibleNotifications.length === 0 ? (
|
|
92
|
+
<div className="px-4 py-8 text-center">
|
|
93
|
+
<svg className="w-8 h-8 mx-auto text-white/20 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
94
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
95
|
+
</svg>
|
|
96
|
+
<p className="text-sm text-white/30">No notifications</p>
|
|
97
|
+
</div>
|
|
98
|
+
) : (
|
|
99
|
+
visibleNotifications.map((notification) => (
|
|
100
|
+
<div
|
|
101
|
+
key={notification.id}
|
|
102
|
+
onClick={() => markAsRead(notification.id)}
|
|
103
|
+
className={`flex items-start gap-3 px-4 py-3 hover:bg-white/5 cursor-pointer transition-colors border-b border-white/5 last:border-0 ${
|
|
104
|
+
!notification.read ? 'bg-white/[0.02]' : ''
|
|
105
|
+
}`}
|
|
106
|
+
>
|
|
107
|
+
{/* Type indicator */}
|
|
108
|
+
<div className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${typeColors[notification.type] || 'bg-white/30'}`} />
|
|
109
|
+
|
|
110
|
+
{/* Content */}
|
|
111
|
+
<div className="flex-1 min-w-0">
|
|
112
|
+
<p className={`text-sm ${!notification.read ? 'text-white font-medium' : 'text-white/70'}`}>
|
|
113
|
+
{notification.title}
|
|
114
|
+
</p>
|
|
115
|
+
{notification.message && (
|
|
116
|
+
<p className="text-xs text-white/40 mt-0.5 truncate">
|
|
117
|
+
{notification.message}
|
|
118
|
+
</p>
|
|
119
|
+
)}
|
|
120
|
+
<p className="text-[11px] text-white/20 mt-1">
|
|
121
|
+
{formatRelativeTime(notification.createdAt)}
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Remove */}
|
|
126
|
+
<button
|
|
127
|
+
onClick={(e) => {
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
removeNotification(notification.id);
|
|
130
|
+
}}
|
|
131
|
+
className="flex-shrink-0 p-0.5 text-white/20 hover:text-white/50 opacity-0 group-hover:opacity-100 transition-all"
|
|
132
|
+
>
|
|
133
|
+
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
134
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
135
|
+
</svg>
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
))
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Footer */}
|
|
143
|
+
{notifications.length > maxVisible && (
|
|
144
|
+
<div className="px-4 py-2 border-t border-white/5 text-center">
|
|
145
|
+
<span className="text-xs text-white/30">
|
|
146
|
+
+{notifications.length - maxVisible} more notifications
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useCallback,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import Toast, { type ToastData, type ToastType } from './Toast';
|
|
11
|
+
|
|
12
|
+
export interface Notification {
|
|
13
|
+
id: string;
|
|
14
|
+
type: ToastType;
|
|
15
|
+
title: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
read: boolean;
|
|
18
|
+
createdAt: Date;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface NotificationContextValue {
|
|
22
|
+
/** Persistent notifications (for bell icon / notification panel) */
|
|
23
|
+
notifications: Notification[];
|
|
24
|
+
unreadCount: number;
|
|
25
|
+
addNotification: (notification: Omit<Notification, 'id' | 'read' | 'createdAt'>) => void;
|
|
26
|
+
markAsRead: (id: string) => void;
|
|
27
|
+
markAllAsRead: () => void;
|
|
28
|
+
removeNotification: (id: string) => void;
|
|
29
|
+
clearAll: () => void;
|
|
30
|
+
|
|
31
|
+
/** Toast helpers */
|
|
32
|
+
toast: {
|
|
33
|
+
success: (title: string, message?: string) => void;
|
|
34
|
+
error: (title: string, message?: string) => void;
|
|
35
|
+
warning: (title: string, message?: string) => void;
|
|
36
|
+
info: (title: string, message?: string) => void;
|
|
37
|
+
custom: (data: Omit<ToastData, 'id'>) => void;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const NotificationContext = createContext<NotificationContextValue | null>(null);
|
|
42
|
+
|
|
43
|
+
export function useNotifications(): NotificationContextValue {
|
|
44
|
+
const ctx = useContext(NotificationContext);
|
|
45
|
+
if (!ctx) {
|
|
46
|
+
throw new Error('useNotifications must be used within a NotificationProvider');
|
|
47
|
+
}
|
|
48
|
+
return ctx;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface NotificationProviderProps {
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
maxToasts?: number;
|
|
54
|
+
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function NotificationProvider({
|
|
58
|
+
children,
|
|
59
|
+
maxToasts = 5,
|
|
60
|
+
position = 'top-right',
|
|
61
|
+
}: NotificationProviderProps) {
|
|
62
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
63
|
+
const [toasts, setToasts] = useState<ToastData[]>([]);
|
|
64
|
+
|
|
65
|
+
// --- Notifications ---
|
|
66
|
+
|
|
67
|
+
const addNotification = useCallback(
|
|
68
|
+
(data: Omit<Notification, 'id' | 'read' | 'createdAt'>) => {
|
|
69
|
+
const notification: Notification = {
|
|
70
|
+
...data,
|
|
71
|
+
id: crypto.randomUUID(),
|
|
72
|
+
read: false,
|
|
73
|
+
createdAt: new Date(),
|
|
74
|
+
};
|
|
75
|
+
setNotifications((prev) => [notification, ...prev]);
|
|
76
|
+
},
|
|
77
|
+
[]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const markAsRead = useCallback((id: string) => {
|
|
81
|
+
setNotifications((prev) =>
|
|
82
|
+
prev.map((n) => (n.id === id ? { ...n, read: true } : n))
|
|
83
|
+
);
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const markAllAsRead = useCallback(() => {
|
|
87
|
+
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const removeNotification = useCallback((id: string) => {
|
|
91
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const clearAll = useCallback(() => {
|
|
95
|
+
setNotifications([]);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
// --- Toasts ---
|
|
99
|
+
|
|
100
|
+
const showToast = useCallback(
|
|
101
|
+
(data: Omit<ToastData, 'id'>) => {
|
|
102
|
+
const id = crypto.randomUUID();
|
|
103
|
+
setToasts((prev) => {
|
|
104
|
+
const next = [...prev, { ...data, id }];
|
|
105
|
+
return next.slice(-maxToasts);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
[maxToasts]
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const dismissToast = useCallback((id: string) => {
|
|
112
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const toast = {
|
|
116
|
+
success: (title: string, message?: string) =>
|
|
117
|
+
showToast({ type: 'success', title, message }),
|
|
118
|
+
error: (title: string, message?: string) =>
|
|
119
|
+
showToast({ type: 'error', title, message }),
|
|
120
|
+
warning: (title: string, message?: string) =>
|
|
121
|
+
showToast({ type: 'warning', title, message }),
|
|
122
|
+
info: (title: string, message?: string) =>
|
|
123
|
+
showToast({ type: 'info', title, message }),
|
|
124
|
+
custom: (data: Omit<ToastData, 'id'>) => showToast(data),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const unreadCount = notifications.filter((n) => !n.read).length;
|
|
128
|
+
|
|
129
|
+
const positionClasses: Record<string, string> = {
|
|
130
|
+
'top-right': 'top-4 right-4',
|
|
131
|
+
'top-left': 'top-4 left-4',
|
|
132
|
+
'bottom-right': 'bottom-4 right-4',
|
|
133
|
+
'bottom-left': 'bottom-4 left-4',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<NotificationContext.Provider
|
|
138
|
+
value={{
|
|
139
|
+
notifications,
|
|
140
|
+
unreadCount,
|
|
141
|
+
addNotification,
|
|
142
|
+
markAsRead,
|
|
143
|
+
markAllAsRead,
|
|
144
|
+
removeNotification,
|
|
145
|
+
clearAll,
|
|
146
|
+
toast,
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{children}
|
|
150
|
+
|
|
151
|
+
{/* Toast container */}
|
|
152
|
+
<div className={`fixed ${positionClasses[position]} z-50 flex flex-col gap-2 pointer-events-none`}>
|
|
153
|
+
{toasts.map((t) => (
|
|
154
|
+
<div key={t.id} className="pointer-events-auto">
|
|
155
|
+
<Toast toast={t} onDismiss={dismissToast} />
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
</NotificationContext.Provider>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export type ToastType = 'success' | 'error' | 'info' | 'warning';
|
|
6
|
+
|
|
7
|
+
export interface ToastData {
|
|
8
|
+
id: string;
|
|
9
|
+
type: ToastType;
|
|
10
|
+
title: string;
|
|
11
|
+
message?: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ToastProps {
|
|
16
|
+
toast: ToastData;
|
|
17
|
+
onDismiss: (id: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const typeStyles: Record<ToastType, { bg: string; border: string; icon: string; iconColor: string }> = {
|
|
21
|
+
success: {
|
|
22
|
+
bg: 'bg-emerald-500/10',
|
|
23
|
+
border: 'border-emerald-500/20',
|
|
24
|
+
icon: 'M5 13l4 4L19 7',
|
|
25
|
+
iconColor: 'text-emerald-400',
|
|
26
|
+
},
|
|
27
|
+
error: {
|
|
28
|
+
bg: 'bg-red-500/10',
|
|
29
|
+
border: 'border-red-500/20',
|
|
30
|
+
icon: 'M6 18L18 6M6 6l12 12',
|
|
31
|
+
iconColor: 'text-red-400',
|
|
32
|
+
},
|
|
33
|
+
warning: {
|
|
34
|
+
bg: 'bg-amber-500/10',
|
|
35
|
+
border: 'border-amber-500/20',
|
|
36
|
+
icon: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z',
|
|
37
|
+
iconColor: 'text-amber-400',
|
|
38
|
+
},
|
|
39
|
+
info: {
|
|
40
|
+
bg: 'bg-cyan-500/10',
|
|
41
|
+
border: 'border-cyan-500/20',
|
|
42
|
+
icon: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
43
|
+
iconColor: 'text-cyan-400',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default function Toast({ toast, onDismiss }: ToastProps) {
|
|
48
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
49
|
+
const [isLeaving, setIsLeaving] = useState(false);
|
|
50
|
+
const style = typeStyles[toast.type];
|
|
51
|
+
const duration = toast.duration ?? 5000;
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
// Enter animation
|
|
55
|
+
const showTimer = setTimeout(() => setIsVisible(true), 10);
|
|
56
|
+
|
|
57
|
+
// Auto-dismiss
|
|
58
|
+
let dismissTimer: ReturnType<typeof setTimeout> | undefined;
|
|
59
|
+
if (duration > 0) {
|
|
60
|
+
dismissTimer = setTimeout(() => {
|
|
61
|
+
handleDismiss();
|
|
62
|
+
}, duration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
clearTimeout(showTimer);
|
|
67
|
+
if (dismissTimer) clearTimeout(dismissTimer);
|
|
68
|
+
};
|
|
69
|
+
}, [duration]);
|
|
70
|
+
|
|
71
|
+
const handleDismiss = () => {
|
|
72
|
+
setIsLeaving(true);
|
|
73
|
+
setTimeout(() => onDismiss(toast.id), 300);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
className={`
|
|
79
|
+
max-w-sm w-full backdrop-blur-xl ${style.bg} border ${style.border} rounded-xl p-4 shadow-2xl
|
|
80
|
+
transform transition-all duration-300 ease-out
|
|
81
|
+
${isVisible && !isLeaving ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
|
|
82
|
+
`}
|
|
83
|
+
>
|
|
84
|
+
<div className="flex items-start gap-3">
|
|
85
|
+
{/* Icon */}
|
|
86
|
+
<div className="flex-shrink-0 mt-0.5">
|
|
87
|
+
<svg className={`w-5 h-5 ${style.iconColor}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
88
|
+
<path strokeLinecap="round" strokeLinejoin="round" d={style.icon} />
|
|
89
|
+
</svg>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Content */}
|
|
93
|
+
<div className="flex-1 min-w-0">
|
|
94
|
+
<p className="text-sm font-medium text-white">{toast.title}</p>
|
|
95
|
+
{toast.message && (
|
|
96
|
+
<p className="text-xs text-white/50 mt-0.5">{toast.message}</p>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Close */}
|
|
101
|
+
<button
|
|
102
|
+
onClick={handleDismiss}
|
|
103
|
+
className="flex-shrink-0 p-0.5 text-white/30 hover:text-white/60 transition-colors"
|
|
104
|
+
>
|
|
105
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
106
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
107
|
+
</svg>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "notification-system",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Complete notification system with toast messages, React context provider, SSE API route, and bell icon badge",
|
|
5
|
+
"author": "AD AI Engine",
|
|
6
|
+
"category": "notifications",
|
|
7
|
+
"dependencies": [],
|
|
8
|
+
"files": {
|
|
9
|
+
"components": [
|
|
10
|
+
"components/notifications/Toast.tsx",
|
|
11
|
+
"components/notifications/NotificationProvider.tsx",
|
|
12
|
+
"components/notifications/NotificationBell.tsx"
|
|
13
|
+
],
|
|
14
|
+
"api": [
|
|
15
|
+
"app/api/notifications/route.ts"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"schema": null,
|
|
19
|
+
"instructions": "1. Wrap your app with NotificationProvider\n2. Use the useNotifications() hook to show toasts: toast.success('Done!')\n3. Place NotificationBell in your header for the bell icon with badge count\n4. The SSE API route streams real-time notifications to connected clients\n5. Call addNotification() from the provider to push persistent notifications"
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "helix-lang",
|
|
3
|
+
"version": "11.0.0",
|
|
4
|
+
"description": "Helix - AI-Native Development Platform with Full-Stack Generation, Preview & Deploy",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/",
|
|
8
|
+
"library/",
|
|
9
|
+
"package.json",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"helix": "./dist/bin/helix.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"start": "node dist/bin/helix.js",
|
|
19
|
+
"dev": "npx ts-node src/bin/helix.ts",
|
|
20
|
+
"helix": "npx ts-node src/bin/helix.ts",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"helix",
|
|
25
|
+
"ai",
|
|
26
|
+
"code-generation",
|
|
27
|
+
"cli",
|
|
28
|
+
"react",
|
|
29
|
+
"nextjs",
|
|
30
|
+
"flutter",
|
|
31
|
+
"prisma",
|
|
32
|
+
"full-stack",
|
|
33
|
+
"deploy",
|
|
34
|
+
"preview"
|
|
35
|
+
],
|
|
36
|
+
"author": "Ayman Dwidar <ayman.dwidar@gmail.com>",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"homepage": "https://github.com/aymandwidar/helix",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/aymandwidar/helix.git"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
48
|
+
"@types/pg": "^8.16.0",
|
|
49
|
+
"chalk": "^4.1.2",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"dotenv": "^16.4.7",
|
|
52
|
+
"execa": "^5.1.1",
|
|
53
|
+
"fs-extra": "^11.2.0",
|
|
54
|
+
"glob": "^13.0.0",
|
|
55
|
+
"ora": "^5.4.1",
|
|
56
|
+
"pg": "^8.17.2",
|
|
57
|
+
"ws": "^8.19.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/fs-extra": "^11.0.4",
|
|
61
|
+
"@types/node": "^22.10.7",
|
|
62
|
+
"@types/ws": "^8.18.1",
|
|
63
|
+
"ts-node": "^10.9.2",
|
|
64
|
+
"typescript": "^5.7.3"
|
|
65
|
+
}
|
|
66
|
+
}
|