@lego-box/shell 1.0.5 → 1.0.7

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 (49) hide show
  1. package/.krasrc +13 -0
  2. package/dist/emulator/lego-box-shell-1.0.7.tgz +0 -0
  3. package/package.json +6 -3
  4. package/postcss.config.js +6 -0
  5. package/src/auth/auth-store.ts +33 -0
  6. package/src/auth/auth.ts +176 -0
  7. package/src/components/ProtectedPage.tsx +48 -0
  8. package/src/config/env.node.ts +38 -0
  9. package/src/config/env.ts +105 -0
  10. package/src/context/AbilityContext.tsx +213 -0
  11. package/src/context/PiralInstanceContext.tsx +17 -0
  12. package/src/hooks/index.ts +11 -0
  13. package/src/hooks/useAuditLogs.ts +190 -0
  14. package/src/hooks/useDebounce.ts +34 -0
  15. package/src/hooks/usePermissionGuard.tsx +39 -0
  16. package/src/hooks/usePermissions.ts +190 -0
  17. package/src/hooks/useRoles.ts +233 -0
  18. package/src/hooks/useTickets.ts +214 -0
  19. package/src/hooks/useUserLogins.ts +39 -0
  20. package/src/hooks/useUsers.ts +252 -0
  21. package/src/index.html +16 -0
  22. package/src/index.tsx +296 -0
  23. package/src/layout.tsx +246 -0
  24. package/src/migrations/config.ts +62 -0
  25. package/src/migrations/dev-migrations.ts +75 -0
  26. package/src/migrations/index.ts +13 -0
  27. package/src/migrations/run-migrations.ts +187 -0
  28. package/src/migrations/runner.ts +925 -0
  29. package/src/migrations/types.ts +207 -0
  30. package/src/migrations/utils.ts +264 -0
  31. package/src/pages/AuditLogsPage.tsx +378 -0
  32. package/src/pages/ContactSupportPage.tsx +610 -0
  33. package/src/pages/LandingPage.tsx +221 -0
  34. package/src/pages/LoginPage.tsx +217 -0
  35. package/src/pages/MigrationsPage.tsx +1364 -0
  36. package/src/pages/ProfilePage.tsx +335 -0
  37. package/src/pages/SettingsPage.tsx +101 -0
  38. package/src/pages/SystemHealthCheckPage.tsx +144 -0
  39. package/src/pages/UserManagementPage.tsx +1010 -0
  40. package/src/piral/api.ts +39 -0
  41. package/src/piral/auth-casl.ts +56 -0
  42. package/src/piral/menu.ts +102 -0
  43. package/src/piral/piral.json +4 -0
  44. package/src/services/telemetry.ts +84 -0
  45. package/src/styles/globals.css +1351 -0
  46. package/src/utils/auditLogger.ts +68 -0
  47. package/tailwind.config.js +86 -0
  48. package/webpack.config.js +89 -0
  49. package/dist/emulator/lego-box-shell-1.0.5.tgz +0 -0
@@ -0,0 +1,221 @@
1
+ import * as React from 'react';
2
+ import { useGlobalState } from 'piral';
3
+ import {
4
+ WelcomeBanner,
5
+ TechStackCard,
6
+ FeatureCard,
7
+ CardContent,
8
+ } from '@lego-box/ui-kit';
9
+
10
+ /**
11
+ * Lego Box Landing Page - Comprehensive portal for developers
12
+ *
13
+ * Features:
14
+ * - User Personalization: Welcome section with profile info
15
+ * - Project Introduction: Lego Box as a microfrontends platform
16
+ * - Developer Empowerment: Information about creating pilets
17
+ * - Tech Stack Documentation: Links to core technologies
18
+ * - Full Dark/Light mode support using CSS variables
19
+ */
20
+ export function LandingPage() {
21
+ const userFromState = useGlobalState((s) => (s as { user?: { mail?: string; firstName?: string; lastName?: string } }).user);
22
+
23
+ const userName = userFromState
24
+ ? [userFromState.firstName, userFromState.lastName].filter(Boolean).join(' ').trim() || userFromState.mail
25
+ : 'Developer';
26
+
27
+ const techStack = [
28
+ {
29
+ name: 'Piral',
30
+ description: 'Open-source framework for building modular frontend applications with microfrontends.',
31
+ url: 'https://piral.io',
32
+ icon: (
33
+ <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
35
+ </svg>
36
+ ),
37
+ },
38
+ {
39
+ name: 'PocketBase',
40
+ description: 'Open-source backend for your next SaaS and Mobile app in 1 file.',
41
+ url: 'https://pocketbase.io',
42
+ icon: (
43
+ <svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
45
+ </svg>
46
+ ),
47
+ },
48
+ {
49
+ name: 'shadcn/ui',
50
+ description: 'Beautifully designed components built with Radix UI and Tailwind CSS.',
51
+ url: 'https://ui.shadcn.com',
52
+ icon: (
53
+ <svg className="w-6 h-6 text-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
54
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
55
+ </svg>
56
+ ),
57
+ },
58
+ {
59
+ name: 'React',
60
+ description: 'A JavaScript library for building user interfaces.',
61
+ url: 'https://react.dev',
62
+ icon: (
63
+ <svg className="w-6 h-6 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
65
+ </svg>
66
+ ),
67
+ },
68
+ {
69
+ name: 'Tailwind CSS',
70
+ description: 'A utility-first CSS framework for rapidly building custom designs.',
71
+ url: 'https://tailwindcss.com',
72
+ icon: (
73
+ <svg className="w-6 h-6 text-sky-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
74
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
75
+ </svg>
76
+ ),
77
+ },
78
+ {
79
+ name: 'TypeScript',
80
+ description: 'Strongly typed programming language that builds on JavaScript.',
81
+ url: 'https://www.typescriptlang.org',
82
+ icon: (
83
+ <svg className="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
84
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
85
+ </svg>
86
+ ),
87
+ },
88
+ ];
89
+
90
+ const features = [
91
+ { label: 'Independent deployments' },
92
+ { label: 'Shared UI components' },
93
+ { label: 'Runtime integration' },
94
+ { label: 'Team autonomy' },
95
+ ];
96
+
97
+ const gettingStartedSteps = [
98
+ 'Use the shared UI Kit for consistent design',
99
+ 'Leverage PocketBase for backend services',
100
+ 'Register pages and components via Piral API',
101
+ 'Deploy pilets independently to the feed service',
102
+ ];
103
+
104
+ return (
105
+ <div className="max-w-7xl mx-auto space-y-8">
106
+ {/* Welcome Section - User Personalization */}
107
+ <WelcomeBanner
108
+ userName={userName}
109
+ subtitle="You're now in the Lego Box ecosystem – a powerful Piral instance designed for building, deploying, and managing microfrontend applications at scale."
110
+ />
111
+
112
+ {/* Project Introduction */}
113
+ <section className="grid grid-cols-1 lg:grid-cols-2 gap-6">
114
+ <FeatureCard
115
+ title="What is Lego Box?"
116
+ description="A comprehensive microfrontend development platform"
117
+ variant="primary"
118
+ icon={
119
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
120
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
121
+ </svg>
122
+ }
123
+ >
124
+ <CardContent className="space-y-4">
125
+ <p className="text-muted-foreground">
126
+ <strong className="text-foreground">Lego Box</strong> is a production-ready Piral instance that empowers teams to build modular, scalable web applications using microfrontends architecture. Think of it as your LEGO set for building complex applications – piece by piece, independently.
127
+ </p>
128
+ <div className="grid grid-cols-2 gap-3">
129
+ {features.map((feature, index) => (
130
+ <div key={index} className="flex items-start gap-2">
131
+ <svg className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
132
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
133
+ </svg>
134
+ <span className="text-sm text-muted-foreground">{feature.label}</span>
135
+ </div>
136
+ ))}
137
+ </div>
138
+ </CardContent>
139
+ </FeatureCard>
140
+
141
+ <FeatureCard
142
+ title="Create Your Pilets"
143
+ description="Extend the platform with your own microfrontends"
144
+ variant="secondary"
145
+ icon={
146
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
147
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
148
+ </svg>
149
+ }
150
+ >
151
+ <CardContent className="space-y-4">
152
+ <p className="text-muted-foreground">
153
+ As a developer in the Lego Box ecosystem, you can create and deploy your own <strong className="text-foreground">pilets</strong> – self-contained microfrontend modules that seamlessly integrate with the application shell. Build pages, register menu items, share components, and more.
154
+ </p>
155
+ <div className="bg-muted rounded-lg p-4 space-y-2">
156
+ <p className="text-sm font-medium text-foreground">Getting Started:</p>
157
+ <ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
158
+ {gettingStartedSteps.map((step, index) => (
159
+ <li key={index}>{step}</li>
160
+ ))}
161
+ </ol>
162
+ </div>
163
+ </CardContent>
164
+ </FeatureCard>
165
+ </section>
166
+
167
+ {/* Tech Stack Documentation */}
168
+ <section>
169
+ <div className="flex items-center gap-3 mb-6">
170
+ <div className="w-10 h-10 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
171
+ <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
172
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
173
+ </svg>
174
+ </div>
175
+ <div>
176
+ <h2 className="text-xl font-bold text-foreground">Technology Stack</h2>
177
+ <p className="text-sm text-muted-foreground">Core technologies powering Lego Box</p>
178
+ </div>
179
+ </div>
180
+
181
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
182
+ {techStack.map((tech) => (
183
+ <TechStackCard
184
+ key={tech.name}
185
+ href={tech.url}
186
+ name={tech.name}
187
+ description={tech.description}
188
+ icon={tech.icon}
189
+ />
190
+ ))}
191
+ </div>
192
+ </section>
193
+
194
+ {/* Quick Actions */}
195
+ <section className="bg-muted/50 rounded-2xl p-6 lg:p-8 border border-border">
196
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
197
+ <div>
198
+ <h3 className="text-lg font-semibold text-foreground mb-2">Ready to build?</h3>
199
+ <p className="text-muted-foreground">
200
+ Start developing your first pilet or explore the existing features in the system.
201
+ </p>
202
+ </div>
203
+ <div className="flex flex-wrap gap-3">
204
+ <button className="inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 transition-colors">
205
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
206
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
207
+ </svg>
208
+ Create New Pilet
209
+ </button>
210
+ <button className="inline-flex items-center gap-2 px-4 py-2 bg-card border border-border text-foreground rounded-lg font-medium hover:bg-accent transition-colors">
211
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
212
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
213
+ </svg>
214
+ View Documentation
215
+ </button>
216
+ </div>
217
+ </div>
218
+ </section>
219
+ </div>
220
+ );
221
+ }
@@ -0,0 +1,217 @@
1
+ import * as React from 'react';
2
+ // import { z } from 'zod';
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from '@lego-box/ui-kit';
9
+ import { Button, Label, PasswordInput, Checkbox, Link } from '@lego-box/ui-kit';
10
+ import { usePiralInstance } from '../context/PiralInstanceContext';
11
+ import { useGlobalStateContext } from 'piral';
12
+
13
+ // const loginSchema = z.object({
14
+ // email: z
15
+ // .string()
16
+ // .min(1, 'Email is required')
17
+ // .email('Please enter a valid email address'),
18
+ // password: z.string().min(1, 'Password is required'),
19
+ // });
20
+
21
+ // type LoginFormData = z.infer<typeof loginSchema>;
22
+ type LoginFormData = { email: string; password: string };
23
+
24
+ export function LoginPage() {
25
+ const instance = usePiralInstance();
26
+ const ctx = useGlobalStateContext();
27
+ const [email, setEmail] = React.useState('superuser@legobox.local');
28
+ const [password, setPassword] = React.useState("SuperUser123!");
29
+ const [rememberMe, setRememberMe] = React.useState(false);
30
+ const [error, setError] = React.useState('');
31
+ const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<keyof LoginFormData, string>>>({});
32
+ const [loading, setLoading] = React.useState(false);
33
+
34
+ const root = instance?.root as
35
+ | {
36
+ isAuthenticated(): boolean;
37
+ login(email: string, password: string): Promise<void>;
38
+ }
39
+ | undefined;
40
+
41
+ const isAuthenticated = root?.isAuthenticated?.() ?? false;
42
+
43
+ React.useEffect(() => {
44
+ if (isAuthenticated && ctx?.navigation) {
45
+ ctx.navigation.replace('/');
46
+ }
47
+ }, [isAuthenticated, ctx?.navigation]);
48
+
49
+ const handleSubmit = async (e: React.FormEvent) => {
50
+ e.preventDefault();
51
+ setError('');
52
+ setFieldErrors({});
53
+
54
+ // Manual validation without Zod
55
+ const errors: Partial<Record<keyof LoginFormData, string>> = {};
56
+ if (!email) {
57
+ errors.email = 'Email is required';
58
+ } else if (!email.includes('@')) {
59
+ errors.email = 'Please enter a valid email address';
60
+ }
61
+ if (!password) {
62
+ errors.password = 'Password is required';
63
+ }
64
+ if (Object.keys(errors).length > 0) {
65
+ setFieldErrors(errors);
66
+ return;
67
+ }
68
+
69
+ setLoading(true);
70
+ try {
71
+ if (root?.login) await root.login(email, password);
72
+ if (ctx?.navigation) ctx.navigation.replace('/');
73
+ } catch (err: unknown) {
74
+ setError(err instanceof Error ? err.message : 'Invalid email or password');
75
+ } finally {
76
+ setLoading(false);
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="min-h-screen w-full flex items-center justify-center bg-slate-50 p-4">
82
+ <Card className="w-full max-w-[440px]">
83
+ <CardHeader className="text-center pb-2">
84
+ {/* Lego Logo - Stacked brick with studs */}
85
+ <div className="flex justify-center mb-6">
86
+ <div className="relative w-16 h-16 flex items-center justify-center">
87
+ {/* Back brick - offset */}
88
+ <div
89
+ className="absolute w-10 h-10 rounded-lg shadow-md"
90
+ style={{ backgroundColor: 'hsl(217, 91%, 70%)', bottom: 4, right: 4 }}
91
+ />
92
+ {/* Front brick - primary */}
93
+ <div className="absolute w-10 h-10 bg-primary rounded-lg shadow-lg left-0 top-0 flex items-center justify-center">
94
+ {/* Studs (2x2) */}
95
+ <div
96
+ className="grid grid-cols-2 flex items-center justify-center"
97
+ style={{ gap: 3, marginTop: -2 }}
98
+ >
99
+ {[1, 2, 3, 4].map((i) => (
100
+ <div
101
+ key={i}
102
+ className="rounded-full bg-primary-foreground flex-shrink-0"
103
+ style={{
104
+ width: 6,
105
+ height: 6,
106
+ boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.3)',
107
+ }}
108
+ />
109
+ ))}
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <CardTitle className="text-2xl font-bold text-slate-900 mb-2">
115
+ Lego Box
116
+ </CardTitle>
117
+ <p className="text-base text-slate-500 leading-relaxed max-w-sm mx-auto">
118
+ Welcome back! Sign in to pick up where you left off.
119
+ </p>
120
+ </CardHeader>
121
+ <CardContent className="pt-6">
122
+ <form onSubmit={handleSubmit} className="space-y-6">
123
+ {/* Email Field */}
124
+ <div className="space-y-2">
125
+ <Label htmlFor="email">Email</Label>
126
+ <input
127
+ id="email"
128
+ type="email"
129
+ placeholder="you@example.com"
130
+ value={email}
131
+ onChange={(e) => {
132
+ setEmail(e.target.value);
133
+ if (fieldErrors.email) setFieldErrors((prev) => ({ ...prev, email: undefined }));
134
+ }}
135
+ autoComplete="email"
136
+ aria-invalid={!!fieldErrors.email}
137
+ aria-describedby={fieldErrors.email ? 'email-error' : undefined}
138
+ className={`flex h-11 w-full rounded-lg border bg-white px-4 py-2.5 text-sm text-foreground placeholder:text-slate-400 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 ${
139
+ fieldErrors.email
140
+ ? 'border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20'
141
+ : 'border-slate-200 focus-visible:border-primary focus-visible:ring-primary/20'
142
+ }`}
143
+ />
144
+ {fieldErrors.email && (
145
+ <p id="email-error" className="text-sm text-destructive" role="alert">
146
+ {fieldErrors.email}
147
+ </p>
148
+ )}
149
+ </div>
150
+
151
+ {/* Password Field */}
152
+ <div className="space-y-2">
153
+ <Label htmlFor="password">Password</Label>
154
+ <PasswordInput
155
+ id="password"
156
+ placeholder="••••••••"
157
+ value={password}
158
+ onChange={(e) => {
159
+ setPassword(e.target.value);
160
+ if (fieldErrors.password) setFieldErrors((prev) => ({ ...prev, password: undefined }));
161
+ }}
162
+ autoComplete="current-password"
163
+ aria-invalid={!!fieldErrors.password}
164
+ aria-describedby={fieldErrors.password ? 'password-error' : undefined}
165
+ className={fieldErrors.password ? 'border-destructive' : undefined}
166
+ />
167
+ {fieldErrors.password && (
168
+ <p id="password-error" className="text-sm text-destructive" role="alert">
169
+ {fieldErrors.password}
170
+ </p>
171
+ )}
172
+ </div>
173
+
174
+ {/* Utility Row: Remember me + Forgot password */}
175
+ <div className="flex items-center justify-between">
176
+ <Checkbox
177
+ id="remember"
178
+ checked={rememberMe}
179
+ onChange={(e) => setRememberMe(e.target.checked)}
180
+ label="Remember me"
181
+ />
182
+ <Link
183
+ href="#"
184
+ variant="primary"
185
+ onClick={(e) => e.preventDefault()}
186
+ >
187
+ Forgot password?
188
+ </Link>
189
+ </div>
190
+
191
+ {error && (
192
+ <div className="p-4 rounded-lg bg-destructive/10 border border-destructive/30">
193
+ <p className="text-sm text-destructive font-medium">{error}</p>
194
+ </div>
195
+ )}
196
+
197
+ <Button
198
+ type="submit"
199
+ className="w-full h-12 text-base font-semibold"
200
+ disabled={loading}
201
+ >
202
+ {loading ? (
203
+ <span className="flex items-center gap-2">
204
+ <svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
205
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
206
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
207
+ </svg>
208
+ Signing in...
209
+ </span>
210
+ ) : 'Sign in'}
211
+ </Button>
212
+ </form>
213
+ </CardContent>
214
+ </Card>
215
+ </div>
216
+ );
217
+ }