@ramme-io/create-app 1.2.1 → 1.2.2

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 (89) hide show
  1. package/package.json +1 -2
  2. package/template/package.json +41 -0
  3. package/template/pkg.json +1 -1
  4. package/template/src/App.tsx +62 -31
  5. package/template/src/components/AIChatWidget.tsx +2 -2
  6. package/template/src/components/AppHeader.tsx +2 -2
  7. package/template/src/components/AutoForm.tsx +13 -0
  8. package/template/src/{pages/styleguide → components}/NotFound.tsx +1 -1
  9. package/template/src/components/PageTitleUpdater.tsx +2 -2
  10. package/template/src/components/ProtectedRoute.tsx +18 -1
  11. package/template/src/components/ScrollToTop.tsx +19 -0
  12. package/template/src/config/app.manifest.ts +3 -1
  13. package/template/src/{core → config}/component-registry.tsx +1 -1
  14. package/template/src/config/navigation.ts +1 -1
  15. package/template/src/data/mock-charts.ts +32 -28
  16. package/template/src/{components → engine/renderers}/DynamicBlock.tsx +27 -7
  17. package/template/src/{pages → engine/renderers}/DynamicPage.tsx +23 -4
  18. package/template/src/{contexts → engine/runtime}/MqttContext.tsx +25 -11
  19. package/template/src/{contexts → engine/runtime}/SitemapContext.tsx +1 -1
  20. package/template/src/{core → engine/runtime}/data-seeder.ts +15 -5
  21. package/template/src/{hooks → engine/runtime}/useAction.ts +19 -8
  22. package/template/src/{hooks → engine/runtime}/useCrudLocalStorage.ts +27 -8
  23. package/template/src/{hooks → engine/runtime}/useDataQuery.ts +15 -1
  24. package/template/src/engine/runtime/useSignal.ts +51 -0
  25. package/template/src/{generated/hooks.ts → engine/runtime/useSignalStore.ts} +35 -8
  26. package/template/src/{hooks → engine/runtime}/useWorkflowEngine.ts +34 -13
  27. package/template/src/{core → engine/types}/manifest-types.ts +35 -3
  28. package/template/src/{types → engine/validation}/schema.ts +17 -0
  29. package/template/src/{pages → features/ai/pages}/AiChat.tsx +1 -1
  30. package/template/src/features/auth/AuthContext.tsx +118 -0
  31. package/template/src/features/auth/pages/AuthLayout.tsx +55 -0
  32. package/template/src/features/auth/pages/LoginPage.tsx +106 -0
  33. package/template/src/features/auth/pages/SignupPage.tsx +96 -0
  34. package/template/src/{blocks → features/datagrid}/SmartTable.tsx +4 -6
  35. package/template/src/{pages → features/onboarding/pages}/Welcome.tsx +0 -1
  36. package/template/src/features/overview/index.ts +1 -0
  37. package/template/src/features/overview/pages/OverviewPage.tsx +127 -0
  38. package/template/src/{pages → features/playground/pages}/AccountingLedgerPage.tsx +1 -1
  39. package/template/src/{pages/prototypes → features/playground/pages}/ItemSelectorPage.tsx +1 -1
  40. package/template/src/{pages/settings → features/settings/pages}/BillingPage.tsx +1 -1
  41. package/template/src/features/settings/pages/ProfilePage.tsx +153 -0
  42. package/template/src/{pages/settings → features/settings/pages}/TeamPage.tsx +1 -1
  43. package/template/src/features/styleguide/Styleguide.tsx +75 -0
  44. package/template/src/features/users/components/UserDrawer.tsx +138 -0
  45. package/template/src/features/users/index.ts +2 -0
  46. package/template/src/features/users/pages/UsersPage.tsx +151 -0
  47. package/template/src/index.css +1 -1
  48. package/template/src/main.tsx +3 -3
  49. package/template/src/templates/dashboard/DashboardLayout.tsx +75 -106
  50. package/template/src/templates/dashboard/dashboard.sitemap.ts +26 -22
  51. package/template/src/templates/docs/DocsLayout.tsx +49 -38
  52. package/template/src/templates/docs/docs.sitemap.ts +22 -34
  53. package/template/src/templates/settings/SettingsLayout.tsx +83 -143
  54. package/template/src/templates/settings/settings.sitemap.ts +6 -6
  55. package/template/vite.config.ts +12 -9
  56. package/template/src/adaptors/.gitkeep +0 -0
  57. package/template/src/components/LocalSideNav.tsx +0 -120
  58. package/template/src/components/PageWithSideNav.tsx +0 -69
  59. package/template/src/config/dashboard.layout.ts +0 -110
  60. package/template/src/contexts/AuthContext.tsx +0 -64
  61. package/template/src/data/mockUsers.ts +0 -18
  62. package/template/src/hooks/useSignal.ts +0 -83
  63. package/template/src/layouts/DataLayout.tsx +0 -37
  64. package/template/src/layouts/SideNavLayout.tsx +0 -28
  65. package/template/src/pages/Dashboard.tsx +0 -60
  66. package/template/src/pages/DataGridPage.tsx +0 -184
  67. package/template/src/pages/LoginPage.tsx +0 -58
  68. package/template/src/pages/settings/ProfilePage.tsx +0 -10
  69. package/template/src/pages/styleguide/Styleguide.tsx +0 -40
  70. package/template/src/templates/docs/pages/Introduction.tsx +0 -13
  71. package/template/src/types/signal.ts +0 -23
  72. /package/template/src/{core → engine/renderers}/route-generator.tsx +0 -0
  73. /package/template/src/{core → engine/types}/sitemap-entry.ts +0 -0
  74. /package/template/src/{pages → features}/GenericContentPage.tsx +0 -0
  75. /package/template/src/{hooks → features/assistant}/useMockChat.ts +0 -0
  76. /package/template/src/{components/dev → features/developer}/GhostOverlay.tsx +0 -0
  77. /package/template/src/{hooks → features/developer}/useDevTools.ts +0 -0
  78. /package/template/src/{pages → features}/styleguide/sections/charts/ChartsSection.tsx +0 -0
  79. /package/template/src/{pages → features}/styleguide/sections/colors/ColorsSection.tsx +0 -0
  80. /package/template/src/{pages → features}/styleguide/sections/elements/ElementsSection.tsx +0 -0
  81. /package/template/src/{pages → features}/styleguide/sections/feedback/FeedbackSection.tsx +0 -0
  82. /package/template/src/{pages → features}/styleguide/sections/forms/FormsSection.tsx +0 -0
  83. /package/template/src/{pages → features}/styleguide/sections/icons/IconsSection.tsx +0 -0
  84. /package/template/src/{pages → features}/styleguide/sections/layout/LayoutSection.tsx +0 -0
  85. /package/template/src/{pages → features}/styleguide/sections/navigation/NavigationSection.tsx +0 -0
  86. /package/template/src/{pages → features}/styleguide/sections/tables/TablesSection.tsx +0 -0
  87. /package/template/src/{pages → features}/styleguide/sections/templates/TemplatesSection.tsx +0 -0
  88. /package/template/src/{pages → features}/styleguide/sections/theming/ThemingSection.tsx +0 -0
  89. /package/template/src/{pages → features}/styleguide/sections/utilities/UtilitiesSection.tsx +0 -0
@@ -0,0 +1,106 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate, Link } from 'react-router-dom';
3
+ import { useAuth } from '../AuthContext';
4
+ // 1. Remove 'Card' from imports
5
+ import { Button, Alert, Icon, Input } from '@ramme-io/ui';
6
+
7
+ const LoginPage: React.FC = () => {
8
+ const navigate = useNavigate();
9
+ const { login } = useAuth();
10
+
11
+ const [formData, setFormData] = useState({ email: '', password: '' });
12
+ const [error, setError] = useState<string | null>(null);
13
+ const [isLoading, setIsLoading] = useState(false);
14
+
15
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
16
+ setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }));
17
+ if (error) setError(null);
18
+ };
19
+
20
+ const handleSubmit = async (e: React.FormEvent) => {
21
+ e.preventDefault();
22
+ setIsLoading(true);
23
+ setError(null);
24
+ try {
25
+ await login(formData.email, formData.password);
26
+ navigate('/dashboard');
27
+ } catch (err: any) {
28
+ setError(err.message || 'Invalid email or password.');
29
+ } finally {
30
+ setIsLoading(false);
31
+ }
32
+ };
33
+
34
+ return (
35
+ <div className="flex min-h-screen items-center justify-center bg-background p-4 transition-colors duration-300">
36
+
37
+ {/* 2. REPLACED <Card> with a native <div>
38
+ - Removes conflicting library styles (border-border vs border-t-primary).
39
+ - Adds 'sm:w-[400px]' for better mobile responsiveness.
40
+ - Adds 'bg-card' explicitly for theme support.
41
+ */}
42
+ <div className="w-full sm:w-[400px] p-8 rounded-xl shadow-2xl border-t-4 border-t-primary bg-card ring-1 ring-black/5 dark:ring-white/10">
43
+
44
+ {/* Header */}
45
+ <div className="text-center mb-8">
46
+ <div className="mx-auto w-12 h-12 bg-primary/10 rounded-xl flex items-center justify-center mb-4 text-primary transition-transform hover:scale-110">
47
+ <Icon name="layout-template" size={24} />
48
+ </div>
49
+ <h1 className="text-2xl font-bold text-foreground tracking-tight">Welcome Back</h1>
50
+ <p className="text-sm text-muted-foreground mt-2">Enter your credentials to access your account</p>
51
+ </div>
52
+
53
+ {/* Error State */}
54
+ {error && (
55
+ <Alert variant="danger" title="Access Denied" className="mb-6 animate-in fade-in slide-in-from-top-2">
56
+ {error}
57
+ </Alert>
58
+ )}
59
+
60
+ {/* Form */}
61
+ <form onSubmit={handleSubmit} className="flex flex-col gap-4">
62
+ <Input
63
+ name="email"
64
+ label="Email Address"
65
+ type="email"
66
+ placeholder="alex@example.com"
67
+ required
68
+ autoComplete="email"
69
+ value={formData.email}
70
+ onChange={handleChange}
71
+ // Explicit background to prevent transparency issues
72
+ className="bg-background"
73
+ />
74
+
75
+ <div className="space-y-1">
76
+ <Input
77
+ name="password"
78
+ label="Password"
79
+ type="password"
80
+ placeholder="••••••••"
81
+ required
82
+ autoComplete="current-password"
83
+ value={formData.password}
84
+ onChange={handleChange}
85
+ className="bg-background"
86
+ />
87
+ </div>
88
+
89
+ <Button type="submit" loading={isLoading} className="w-full mt-2 font-medium" size="lg">
90
+ Sign In
91
+ </Button>
92
+ </form>
93
+
94
+ {/* Footer */}
95
+ <div className="mt-8 pt-6 border-t border-border text-center text-sm">
96
+ <span className="text-muted-foreground">Don't have an account? </span>
97
+ <Link to="/signup" className="font-semibold text-primary hover:text-primary/80 transition-colors">
98
+ Create one
99
+ </Link>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ );
104
+ };
105
+
106
+ export default LoginPage;
@@ -0,0 +1,96 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate, Link } from 'react-router-dom';
3
+ import { useAuth } from '../AuthContext';
4
+ import { useCrudLocalStorage } from '../../../engine/runtime/useCrudLocalStorage';
5
+ import { Button, Card, FormTemplate, Alert, Icon, type FormField } from '@ramme-io/ui';
6
+ // Import SEED_USERS to safe-guard initialization if the DB is empty
7
+ import { SEED_USERS, type User } from '../../../data/mockData';
8
+
9
+ const SignupPage: React.FC = () => {
10
+ const navigate = useNavigate();
11
+ const { login } = useAuth();
12
+
13
+ // ✅ DATA HOOK: Direct access to the User Data Lake
14
+ // This ensures new users are saved to 'ramme_db_users' alongside seed data
15
+ const { data: users, createItem } = useCrudLocalStorage<User>('ramme_db_users', SEED_USERS);
16
+
17
+ const [error, setError] = useState<string | null>(null);
18
+ const [isLoading, setIsLoading] = useState(false);
19
+
20
+ const handleSignup = async (formData: Record<string, any>) => {
21
+ setIsLoading(true);
22
+ setError(null);
23
+
24
+ // 1. Validation: Check for duplicates
25
+ if (users.some(u => u.email.toLowerCase() === formData.email.toLowerCase())) {
26
+ setError('An account with this email already exists.');
27
+ setIsLoading(false);
28
+ return;
29
+ }
30
+
31
+ try {
32
+ // 2. Create the User Record in the Data Lake
33
+ // We cast to 'User' because we handle the 'id' generation in createItem
34
+ const newUser = {
35
+ name: formData.name,
36
+ email: formData.email,
37
+ role: 'viewer', // Default role for new signups
38
+ status: 'active',
39
+ joinedAt: new Date().toISOString().split('T')[0],
40
+ // In a real app, you'd hash the password here.
41
+ // For this prototype, the presence of the record implies access.
42
+ } as User;
43
+
44
+ createItem(newUser);
45
+
46
+ // 3. Auto-Login immediately after creation
47
+ await login(formData.email, formData.password);
48
+ navigate('/dashboard');
49
+
50
+ } catch (err: any) {
51
+ setError(err.message || 'Failed to create account.');
52
+ } finally {
53
+ setIsLoading(false);
54
+ }
55
+ };
56
+
57
+ const formFields: FormField[] = [
58
+ { name: 'name', label: 'Full Name', type: 'text', placeholder: 'Jane Doe', required: true },
59
+ { name: 'email', label: 'Email Address', type: 'email', placeholder: 'jane@example.com', required: true },
60
+ { name: 'password', label: 'Password', type: 'password', placeholder: 'Min. 3 characters', required: true },
61
+ ];
62
+
63
+ return (
64
+ <div className="flex min-h-screen items-center justify-center bg-background p-4">
65
+ <Card className="w-full max-w-md p-8 shadow-xl border-t-4 border-t-secondary">
66
+ <div className="text-center mb-8">
67
+ <div className="mx-auto w-12 h-12 bg-secondary/10 rounded-full flex items-center justify-center mb-4 text-secondary">
68
+ <Icon name="user-plus" size={24} />
69
+ </div>
70
+ <h1 className="text-3xl font-bold text-foreground">Create Account</h1>
71
+ <p className="text-muted-foreground mt-2">Join the platform to get started</p>
72
+ </div>
73
+
74
+ {error && <Alert variant="danger" title="Registration Failed" className="mb-4">{error}</Alert>}
75
+
76
+ <FormTemplate
77
+ fields={formFields}
78
+ onSubmit={handleSignup}
79
+ >
80
+ <Button type="submit" loading={isLoading} className="w-full mt-4" size="lg" variant="secondary">
81
+ Create Account
82
+ </Button>
83
+ </FormTemplate>
84
+
85
+ <div className="mt-6 text-center text-sm">
86
+ <span className="text-muted-foreground">Already have an account? </span>
87
+ <Link to="/login" className="font-semibold text-primary hover:underline">
88
+ Sign In
89
+ </Link>
90
+ </div>
91
+ </Card>
92
+ </div>
93
+ );
94
+ };
95
+
96
+ export default SignupPage;
@@ -9,9 +9,9 @@ import {
9
9
  type ColDef,
10
10
  type GridApi
11
11
  } from '@ramme-io/ui';
12
- import { getResourceMeta, getMockData } from '../data/mockData';
13
- import { AutoForm } from '../components/AutoForm';
14
- import { useCrudLocalStorage } from '../hooks/useCrudLocalStorage';
12
+ import { getResourceMeta, getMockData } from '../../data/mockData';
13
+ import { AutoForm } from '../../components/AutoForm';
14
+ import { useCrudLocalStorage } from '../../engine/runtime/useCrudLocalStorage';
15
15
 
16
16
  interface SmartTableProps {
17
17
  dataId: string;
@@ -21,9 +21,7 @@ interface SmartTableProps {
21
21
 
22
22
  export const SmartTable: React.FC<SmartTableProps> = ({
23
23
  dataId,
24
- title,
25
- initialFilter
26
- }) => {
24
+ title}) => {
27
25
  const { addToast } = useToast();
28
26
 
29
27
  // 1. DATA KERNEL
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
3
  import {
4
- PageHeader,
5
4
  Card,
6
5
  Button,
7
6
  Icon,
@@ -0,0 +1 @@
1
+ export { OverviewPage } from './pages/OverviewPage';
@@ -0,0 +1,127 @@
1
+ import React from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import {
4
+ PageHeader,
5
+ Card,
6
+ BarChart,
7
+ Button,
8
+ Icon,
9
+ Badge
10
+ } from '@ramme-io/ui';
11
+
12
+ // 1. REMOVE: The Zombie Service Import
13
+ // import { userService } from '../../users';
14
+
15
+ // 2. ADD: The Engine Hook & Shared Data
16
+ // (Adjust path '../../engine/...' if needed based on your folder structure)
17
+ import { useCrudLocalStorage } from '../../../engine/runtime/useCrudLocalStorage';
18
+ import { SEED_USERS, type User } from '../../../data/mockData';
19
+
20
+ // Mock Data for Chart
21
+ const revenueData = [
22
+ { name: 'Mon', revenue: 4000, cost: 2400 },
23
+ { name: 'Tue', revenue: 3000, cost: 1398 },
24
+ { name: 'Wed', revenue: 2000, cost: 9800 },
25
+ { name: 'Thu', revenue: 2780, cost: 3908 },
26
+ { name: 'Fri', revenue: 1890, cost: 4800 },
27
+ { name: 'Sat', revenue: 2390, cost: 3800 },
28
+ { name: 'Sun', revenue: 3490, cost: 4300 },
29
+ ];
30
+
31
+ // Recreating the StatCard locally (since the old one was custom)
32
+ const StatCard = ({ title, value, trend, icon, onClick, className }: any) => (
33
+ <Card
34
+ className={`p-6 flex items-center justify-between space-x-4 transition-all hover:border-primary/50 ${className || ''}`}
35
+ onClick={onClick}
36
+ >
37
+ <div>
38
+ <p className="text-sm font-medium text-muted-foreground">{title}</p>
39
+ <h3 className="text-2xl font-bold mt-1">{value}</h3>
40
+ <div className={`flex items-center text-xs mt-1 ${trend > 0 ? 'text-green-500' : 'text-red-500'}`}>
41
+ <Icon name={trend > 0 ? 'trending-up' : 'trending-down'} className="h-3 w-3 mr-1" />
42
+ {Math.abs(trend)}% from last month
43
+ </div>
44
+ </div>
45
+ <div className="p-3 bg-muted/50 rounded-full">
46
+ <Icon name={icon} className="h-5 w-5 text-primary" />
47
+ </div>
48
+ </Card>
49
+ );
50
+
51
+ export const OverviewPage: React.FC = () => {
52
+ const navigate = useNavigate();
53
+
54
+ // 3. REPLACE: Manual fetching with the Reactive Engine
55
+ // This automatically connects to 'ramme_db_users' and keeps the count live
56
+ const { data: users } = useCrudLocalStorage<User>('ramme_db_users', SEED_USERS);
57
+
58
+ // Calculate count directly from the hook data
59
+ const userCount = users.length;
60
+
61
+ return (
62
+ <div className="space-y-8 pb-10">
63
+ <div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
64
+ <div>
65
+ <PageHeader
66
+ title="Dashboard"
67
+ description="Overview of your application performance."
68
+ />
69
+ </div>
70
+ <div className="flex gap-2">
71
+ <Button variant="outline" iconLeft="download">Export</Button>
72
+ <Button iconLeft="plus">New Report</Button>
73
+ </div>
74
+ </div>
75
+
76
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
77
+ <StatCard
78
+ title="Total Users"
79
+ value={userCount} // ✅ Uses live data from Data Lake
80
+ trend={+12.5}
81
+ icon="users"
82
+ className="cursor-pointer bg-primary/5 border-primary/20"
83
+ onClick={() => navigate('/dashboard/users')}
84
+ />
85
+ <StatCard title="Total Revenue" value="$45,231" trend={+20.1} icon="dollar-sign" />
86
+ <StatCard title="Sales" value="+12,234" trend={+19} icon="credit-card" />
87
+ <StatCard title="Active Now" value="+573" trend={-4} icon="activity" />
88
+ </div>
89
+
90
+ {/* Main Content Grid */}
91
+ <div className="grid grid-cols-1 lg:grid-cols-7 gap-8">
92
+ <Card className="lg:col-span-4 p-6">
93
+ <div className="flex items-center justify-between mb-6">
94
+ <h3 className="font-semibold text-lg">Revenue Overview</h3>
95
+ <Badge variant="outline">Weekly</Badge>
96
+ </div>
97
+ <div className="h-[350px] w-full">
98
+ <BarChart
99
+ data={revenueData}
100
+ dataKeyX="name"
101
+ barKeys={['revenue', 'cost']}
102
+ />
103
+ </div>
104
+ </Card>
105
+
106
+ <Card className="lg:col-span-3 p-6 flex flex-col">
107
+ <h3 className="font-semibold text-lg mb-4">Recent Activity</h3>
108
+ <div className="space-y-6 overflow-y-auto pr-2">
109
+ {[1, 2, 3].map((i) => (
110
+ <div key={i} className="flex items-start gap-4">
111
+ <span className="relative flex h-2 w-2 mt-2">
112
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
113
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
114
+ </span>
115
+ <div className="space-y-1">
116
+ <p className="text-sm font-medium leading-none">System Alert</p>
117
+ <p className="text-xs text-muted-foreground">Database backup completed.</p>
118
+ <p className="text-xs text-muted-foreground pt-1">Just now</p>
119
+ </div>
120
+ </div>
121
+ ))}
122
+ </div>
123
+ </Card>
124
+ </div>
125
+ </div>
126
+ );
127
+ };
@@ -8,7 +8,7 @@ import {
8
8
  // 1. Import the specific event type for cell value changes
9
9
  type CellValueChangedEvent,
10
10
  } from '@ramme-io/ui';
11
- import { mockLedgerData, type LedgerEntry } from '../data/mockLedger';
11
+ import { mockLedgerData, type LedgerEntry } from '../../../data/mockLedger';
12
12
 
13
13
  // --- Custom Renderer for Variance ---
14
14
  // 2. Correctly type the component and ensure it returns a ReactNode (JSX or null)
@@ -1,6 +1,6 @@
1
1
  import { useState } from 'react';
2
2
  import { Button, PageHeader } from '@ramme-io/ui';
3
- import { AddItemsModal } from '../../components/AddItemsModal';
3
+ import { AddItemsModal } from '../../../components/AddItemsModal';
4
4
 
5
5
 
6
6
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  // --- 1. Import the new generic page ---
3
- import GenericContentPage from '../GenericContentPage';
3
+ import GenericContentPage from '../../GenericContentPage';
4
4
 
5
5
  // A simple page component for settings
6
6
  const BillingPage: React.FC = () => {
@@ -0,0 +1,153 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ Button,
4
+ Input,
5
+ Card,
6
+ SectionHeader,
7
+ Icon,
8
+ Alert
9
+ } from '@ramme-io/ui';
10
+ import { useAuth } from '../../auth/AuthContext';
11
+
12
+ // 1. REMOVE Zombie Service
13
+ // import { userService } from '../../users/api/user.service';
14
+
15
+ // 2. ADD Engine Hooks & Data
16
+ import { useCrudLocalStorage } from '../../../engine/runtime/useCrudLocalStorage';
17
+ import { SEED_USERS, type User } from '../../../data/mockData';
18
+
19
+ const ProfilePage: React.FC = () => {
20
+ const { user } = useAuth();
21
+ const [isLoading, setIsLoading] = useState(false);
22
+ const [success, setSuccess] = useState(false);
23
+
24
+ // 3. CONNECT to Data Lake
25
+ // We only need 'updateItem' here to modify the existing profile
26
+ const { updateItem } = useCrudLocalStorage<User>('ramme_db_users', SEED_USERS);
27
+
28
+ const [formData, setFormData] = useState({
29
+ name: '',
30
+ email: '',
31
+ role: '',
32
+ bio: 'Product Designer based in San Francisco.'
33
+ });
34
+
35
+ // Load current user data into form
36
+ useEffect(() => {
37
+ if (user) {
38
+ setFormData({
39
+ name: user.name,
40
+ email: user.email,
41
+ role: user.role,
42
+ bio: 'Product Designer based in San Francisco.'
43
+ });
44
+ }
45
+ }, [user]);
46
+
47
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
48
+ setFormData({ ...formData, [e.target.name]: e.target.value });
49
+ };
50
+
51
+ const handleSave = async (e: React.FormEvent) => {
52
+ e.preventDefault();
53
+ setIsLoading(true);
54
+ setSuccess(false);
55
+
56
+ try {
57
+ if (user?.id) {
58
+ // 4. USE ENGINE to update the record in the Data Lake
59
+ // We merge the existing user object with the new form data
60
+ updateItem({ ...user, ...formData } as User);
61
+
62
+ // 5. REFRESH SESSION (Local hack to update UI header immediately)
63
+ // In a real app, AuthContext would listen to storage changes, but this is fine for a prototype.
64
+ localStorage.setItem('ramme_session', JSON.stringify({ ...user, ...formData }));
65
+
66
+ setSuccess(true);
67
+ setTimeout(() => setSuccess(false), 3000);
68
+ }
69
+ } catch (error) {
70
+ console.error(error);
71
+ } finally {
72
+ setIsLoading(false);
73
+ }
74
+ };
75
+
76
+ return (
77
+ <div className="max-w-4xl space-y-6">
78
+ <SectionHeader title="My Profile" />
79
+
80
+ {success && (
81
+ <Alert variant="info" title="Changes Saved">
82
+ Your profile has been updated successfully.
83
+ </Alert>
84
+ )}
85
+
86
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
87
+ {/* Left Column - Avatar Card */}
88
+ <Card className="p-6 flex flex-col items-center text-center space-y-4 h-fit">
89
+ <div className="relative group cursor-pointer">
90
+ <div className="h-24 w-24 rounded-full bg-primary/10 flex items-center justify-center text-2xl font-bold text-primary border-4 border-background shadow-sm">
91
+ {formData.name.charAt(0)}
92
+ </div>
93
+ <div className="absolute inset-0 bg-black/50 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
94
+ <Icon name="camera" className="text-white" />
95
+ </div>
96
+ </div>
97
+ <div>
98
+ <h3 className="font-semibold text-lg">{formData.name}</h3>
99
+ <span className="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80 uppercase">
100
+ {formData.role}
101
+ </span>
102
+ </div>
103
+ <div className="w-full pt-4 border-t">
104
+ <p className="text-xs text-muted-foreground mb-4">Joined December 2024</p>
105
+ <Button variant="outline" className="w-full" size="sm">Change Avatar</Button>
106
+ </div>
107
+ </Card>
108
+
109
+ {/* Right Column - Form */}
110
+ <Card className="md:col-span-2 p-6">
111
+ <form onSubmit={handleSave} className="space-y-4">
112
+ <div className="grid gap-2">
113
+ <h3 className="font-semibold text-lg">Personal Information</h3>
114
+ <p className="text-sm text-muted-foreground">Update your personal details here.</p>
115
+ </div>
116
+
117
+ <div className="grid gap-4 py-4">
118
+ <Input
119
+ label="Full Name"
120
+ name="name"
121
+ value={formData.name}
122
+ onChange={handleChange}
123
+ />
124
+ <Input
125
+ label="Email Address"
126
+ name="email"
127
+ value={formData.email}
128
+ onChange={handleChange}
129
+ />
130
+ <div className="space-y-2">
131
+ <label className="text-sm font-medium leading-none">Bio</label>
132
+ <textarea
133
+ className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
134
+ value={formData.bio}
135
+ disabled
136
+ />
137
+ <p className="text-[0.8rem] text-muted-foreground">Bio editing is disabled in this demo.</p>
138
+ </div>
139
+ </div>
140
+
141
+ <div className="flex justify-end gap-4">
142
+ <Button type="submit" disabled={isLoading}>
143
+ {isLoading ? 'Saving...' : 'Save Changes'}
144
+ </Button>
145
+ </div>
146
+ </form>
147
+ </Card>
148
+ </div>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default ProfilePage;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  // --- 1. Import the new generic page ---
3
- import GenericContentPage from '../GenericContentPage';
3
+ import GenericContentPage from '../../GenericContentPage';
4
4
 
5
5
  // A simple page component for settings
6
6
  const TeamPage: React.FC = () => {
@@ -0,0 +1,75 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
3
+ import { Sidebar, type SidebarItem, type IconName, Icon } from '@ramme-io/ui';
4
+ import { useSitemap } from '../../engine/runtime/SitemapContext';
5
+
6
+ const Styleguide: React.FC = () => {
7
+ const location = useLocation();
8
+ const navigate = useNavigate();
9
+ const sitemap = useSitemap();
10
+
11
+ // 1. Get Styleguide Section Data
12
+ const styleguideSection = useMemo(() => {
13
+ return sitemap.find(item => item.id === 'styleguide');
14
+ }, [sitemap]);
15
+
16
+ // 2. Transform to Sidebar Items
17
+ const navItems = useMemo<SidebarItem[]>(() => {
18
+ if (!styleguideSection?.children) return [];
19
+
20
+ // ✅ FIX: Remove the explicit ": { ... }" type. Let TS infer 'child' automatically.
21
+ return styleguideSection.children.map((child) => ({
22
+ id: child.id,
23
+ label: child.title,
24
+ // We cast to IconName because we know the string is a valid icon,
25
+ // and we provide a fallback 'hash' if it's undefined.
26
+ icon: (child.icon as IconName) || 'hash',
27
+ href: child.path ? `/docs/styleguide/${child.path}` : '/docs/styleguide',
28
+ }));
29
+ }, [styleguideSection]);
30
+
31
+ // 3. Determine Active Item
32
+ const activeItemId = useMemo(() => {
33
+ const active = navItems.find(item =>
34
+ location.pathname === item.href ||
35
+ (item.href && location.pathname.startsWith(item.href))
36
+ );
37
+ return active?.id;
38
+ }, [location.pathname, navItems]);
39
+
40
+ if (!location.pathname.startsWith('/docs') || !styleguideSection) {
41
+ return <Outlet />;
42
+ }
43
+
44
+ return (
45
+ <div className="flex w-full min-h-[calc(100vh-7rem)]">
46
+ {/* SIDEBAR */}
47
+ <div className="sticky top-[7rem] h-[calc(100vh-7rem)] z-20">
48
+ <Sidebar
49
+ className="h-full border-r border-border bg-card/50 backdrop-blur-sm"
50
+ items={navItems}
51
+ activeItemId={activeItemId}
52
+ onNavigate={(item) => {
53
+ if (item.href) navigate(item.href);
54
+ }}
55
+ logo={
56
+ <div className="flex items-center gap-2 px-1 py-2 text-muted-foreground">
57
+ <Icon name="book-open" className="h-4 w-4" />
58
+ <span className="font-semibold text-xs uppercase tracking-wider">Reference</span>
59
+ </div>
60
+ }
61
+ />
62
+ </div>
63
+
64
+ {/* MAIN CONTENT */}
65
+ <main className="flex-1 min-w-0 bg-background overflow-x-hidden">
66
+ {/* ADDED: Max width constraint + centering */}
67
+ <div className="container max-w-7xl mx-auto p-6 md:p-10 animate-in fade-in duration-500">
68
+ <Outlet />
69
+ </div>
70
+ </main>
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export default Styleguide;