@viliha/vui-ui 1.1.7 → 1.2.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.
Files changed (138) hide show
  1. package/AGENT.md +79 -9
  2. package/README.md +60 -0
  3. package/bin/vui.mjs +211 -0
  4. package/package.json +10 -2
  5. package/src/record-view.tsx +10 -5
  6. package/template/.env.example +20 -0
  7. package/template/app/(app)/branches/branches-table.tsx +69 -0
  8. package/template/app/(app)/branches/page.tsx +13 -0
  9. package/template/app/(app)/businesses/businesses-table.tsx +39 -0
  10. package/template/app/(app)/businesses/page.tsx +13 -0
  11. package/template/app/(app)/calendar/page.tsx +725 -0
  12. package/template/app/(app)/charts/charts-content.tsx +190 -0
  13. package/template/app/(app)/charts/layout.tsx +8 -0
  14. package/template/app/(app)/charts/page.tsx +44 -0
  15. package/template/app/(app)/chat/layout.tsx +14 -0
  16. package/template/app/(app)/chat/page.tsx +311 -0
  17. package/template/app/(app)/components/layout.tsx +8 -0
  18. package/template/app/(app)/components/page.tsx +464 -0
  19. package/template/app/(app)/crm/companies/companies-table.tsx +56 -0
  20. package/template/app/(app)/crm/companies/page.tsx +13 -0
  21. package/template/app/(app)/crm/opportunities/opportunities-board.tsx +484 -0
  22. package/template/app/(app)/crm/opportunities/page.tsx +13 -0
  23. package/template/app/(app)/crm/people/page.tsx +13 -0
  24. package/template/app/(app)/crm/people/people-table.tsx +50 -0
  25. package/template/app/(app)/dashboard/layout.tsx +8 -0
  26. package/template/app/(app)/dashboard/page.tsx +239 -0
  27. package/template/app/(app)/departments/departments-table.tsx +55 -0
  28. package/template/app/(app)/departments/page.tsx +13 -0
  29. package/template/app/(app)/employees/employees-table.tsx +64 -0
  30. package/template/app/(app)/employees/page.tsx +13 -0
  31. package/template/app/(app)/forms/layout.tsx +8 -0
  32. package/template/app/(app)/forms/page.tsx +388 -0
  33. package/template/app/(app)/layout.tsx +74 -0
  34. package/template/app/(app)/markets/markets-table.tsx +76 -0
  35. package/template/app/(app)/markets/page.tsx +13 -0
  36. package/template/app/(app)/organizations/edit/page.tsx +72 -0
  37. package/template/app/(app)/organizations/new/page.tsx +46 -0
  38. package/template/app/(app)/organizations/organizations-config.tsx +97 -0
  39. package/template/app/(app)/organizations/organizations-table.tsx +42 -0
  40. package/template/app/(app)/organizations/page.tsx +13 -0
  41. package/template/app/(app)/settings/layout.tsx +8 -0
  42. package/template/app/(app)/settings/page.tsx +255 -0
  43. package/template/app/(app)/steps/page.tsx +263 -0
  44. package/template/app/(app)/support/layout.tsx +14 -0
  45. package/template/app/(app)/support/page.tsx +345 -0
  46. package/template/app/(app)/system/cities/cities-table.tsx +34 -0
  47. package/template/app/(app)/system/cities/page.tsx +13 -0
  48. package/template/app/(app)/system/countries/countries-table.tsx +34 -0
  49. package/template/app/(app)/system/countries/page.tsx +13 -0
  50. package/template/app/(app)/system/currencies/currencies-table.tsx +34 -0
  51. package/template/app/(app)/system/currencies/page.tsx +13 -0
  52. package/template/app/(app)/system/languages/languages-table.tsx +32 -0
  53. package/template/app/(app)/system/languages/page.tsx +13 -0
  54. package/template/app/(app)/system/regions/page.tsx +13 -0
  55. package/template/app/(app)/system/regions/regions-table.tsx +32 -0
  56. package/template/app/(app)/users/page.tsx +13 -0
  57. package/template/app/(app)/users/users-table.tsx +97 -0
  58. package/template/app/_components/app-sidebar.tsx +565 -0
  59. package/template/app/_components/auth.tsx +184 -0
  60. package/template/app/_components/breadcrumbs.tsx +26 -0
  61. package/template/app/_components/global-search.tsx +209 -0
  62. package/template/app/_components/logo.tsx +49 -0
  63. package/template/app/_components/nav-config.ts +138 -0
  64. package/template/app/_components/open-tabs.tsx +530 -0
  65. package/template/app/_components/quick-actions.tsx +140 -0
  66. package/template/app/_components/route-meta.ts +137 -0
  67. package/template/app/_components/set-page-title.tsx +15 -0
  68. package/template/app/_components/showcase.tsx +29 -0
  69. package/template/app/_components/stat-card.tsx +56 -0
  70. package/template/app/_components/theme-toggle.tsx +46 -0
  71. package/template/app/_components/top-bar.tsx +109 -0
  72. package/template/app/_components/user-menu.tsx +109 -0
  73. package/template/app/_components/wordmark.tsx +45 -0
  74. package/template/app/apple-icon.png +0 -0
  75. package/template/app/auth/forgot-password/page.tsx +102 -0
  76. package/template/app/auth/layout.tsx +43 -0
  77. package/template/app/auth/page.tsx +13 -0
  78. package/template/app/auth/reset-password/page.tsx +108 -0
  79. package/template/app/auth/signin/page.tsx +238 -0
  80. package/template/app/auth/signup/page.tsx +159 -0
  81. package/template/app/auth/verify/page.tsx +86 -0
  82. package/template/app/error.tsx +56 -0
  83. package/template/app/globals.css +6 -0
  84. package/template/app/icon.png +0 -0
  85. package/template/app/icon.svg +4 -0
  86. package/template/app/layout.tsx +103 -0
  87. package/template/app/not-found.tsx +29 -0
  88. package/template/app/onboarding/layout.tsx +31 -0
  89. package/template/app/onboarding/page.tsx +399 -0
  90. package/template/app/page.tsx +13 -0
  91. package/template/app/register-business/layout.tsx +30 -0
  92. package/template/app/register-business/page.tsx +234 -0
  93. package/template/components/ui/accordion.tsx +66 -0
  94. package/template/components/ui/alert-dialog.tsx +196 -0
  95. package/template/components/ui/alert.tsx +66 -0
  96. package/template/components/ui/aspect-ratio.tsx +11 -0
  97. package/template/components/ui/avatar.tsx +109 -0
  98. package/template/components/ui/badge.tsx +48 -0
  99. package/template/components/ui/breadcrumb.tsx +109 -0
  100. package/template/components/ui/button.tsx +64 -0
  101. package/template/components/ui/calendar.tsx +220 -0
  102. package/template/components/ui/card.tsx +92 -0
  103. package/template/components/ui/checkbox.tsx +32 -0
  104. package/template/components/ui/collapsible.tsx +33 -0
  105. package/template/components/ui/command.tsx +184 -0
  106. package/template/components/ui/dialog.tsx +158 -0
  107. package/template/components/ui/dropdown-menu.tsx +257 -0
  108. package/template/components/ui/form.tsx +167 -0
  109. package/template/components/ui/hover-card.tsx +44 -0
  110. package/template/components/ui/input-otp.tsx +77 -0
  111. package/template/components/ui/input.tsx +21 -0
  112. package/template/components/ui/label.tsx +24 -0
  113. package/template/components/ui/popover.tsx +89 -0
  114. package/template/components/ui/progress.tsx +31 -0
  115. package/template/components/ui/radio-group.tsx +45 -0
  116. package/template/components/ui/scroll-area.tsx +58 -0
  117. package/template/components/ui/select.tsx +190 -0
  118. package/template/components/ui/separator.tsx +28 -0
  119. package/template/components/ui/sheet.tsx +143 -0
  120. package/template/components/ui/skeleton.tsx +13 -0
  121. package/template/components/ui/slider.tsx +63 -0
  122. package/template/components/ui/sonner.tsx +40 -0
  123. package/template/components/ui/switch.tsx +35 -0
  124. package/template/components/ui/table.tsx +116 -0
  125. package/template/components/ui/tabs.tsx +91 -0
  126. package/template/components/ui/textarea.tsx +18 -0
  127. package/template/components/ui/toggle-group.tsx +83 -0
  128. package/template/components/ui/toggle.tsx +47 -0
  129. package/template/components/ui/tooltip.tsx +57 -0
  130. package/template/lib/auth-demo.ts +94 -0
  131. package/template/lib/crm-data.ts +80 -0
  132. package/template/lib/demo-data.ts +267 -0
  133. package/template/lib/mock-data.ts +169 -0
  134. package/template/lib/org-store.ts +50 -0
  135. package/template/lib/seo.ts +188 -0
  136. package/template/lib/utils.ts +3 -0
  137. package/template/next.config.mjs +13 -0
  138. package/template/postcss.config.mjs +7 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * In-memory demo data for the backoffice homepage (organizations, branches,
3
+ * departments, employees, markets, countries/regions). Swap this for a real
4
+ * data/API layer when you wire up a backend.
5
+ */
6
+
7
+ export type OrganizationStatus = "active" | "trial" | "suspended";
8
+
9
+ export interface DemoOrganization {
10
+ id: number;
11
+ name: string;
12
+ url: string;
13
+ email: string;
14
+ countryCode: string;
15
+ country: string;
16
+ branches: number;
17
+ employees: number;
18
+ status: OrganizationStatus;
19
+ /** Fallback initials for the avatar. */
20
+ initials: string;
21
+ /** Relative "created/updated" label for the recent list. */
22
+ updated: string;
23
+ }
24
+
25
+ export interface DemoEmployee {
26
+ id: number;
27
+ code: string;
28
+ firstName: string;
29
+ lastName: string;
30
+ email: string;
31
+ department: string;
32
+ branch: string;
33
+ organization: string;
34
+ isActive: boolean;
35
+ }
36
+
37
+ export interface DemoMarket {
38
+ id: number;
39
+ name: string;
40
+ organization: string;
41
+ radiusMiles: number | null;
42
+ }
43
+
44
+ export interface RegionBreakdown {
45
+ region: string;
46
+ organizations: number;
47
+ }
48
+
49
+ export const organizations: DemoOrganization[] = [
50
+ {
51
+ id: 1,
52
+ name: "Northwind Retail",
53
+ url: "northwind.example.com",
54
+ email: "ops@northwind.example.com",
55
+ countryCode: "US",
56
+ country: "United States",
57
+ branches: 12,
58
+ employees: 340,
59
+ status: "active",
60
+ initials: "NR",
61
+ updated: "2h ago",
62
+ },
63
+ {
64
+ id: 2,
65
+ name: "Sakura Foods",
66
+ url: "sakura.example.jp",
67
+ email: "hello@sakura.example.jp",
68
+ countryCode: "JP",
69
+ country: "Japan",
70
+ branches: 8,
71
+ employees: 210,
72
+ status: "active",
73
+ initials: "SF",
74
+ updated: "5h ago",
75
+ },
76
+ {
77
+ id: 3,
78
+ name: "Alpine Logistics",
79
+ url: "alpine.example.ch",
80
+ email: "contact@alpine.example.ch",
81
+ countryCode: "CH",
82
+ country: "Switzerland",
83
+ branches: 5,
84
+ employees: 96,
85
+ status: "trial",
86
+ initials: "AL",
87
+ updated: "yesterday",
88
+ },
89
+ {
90
+ id: 4,
91
+ name: "Lagos Digital",
92
+ url: "lagosdigital.example.ng",
93
+ email: "team@lagosdigital.example.ng",
94
+ countryCode: "NG",
95
+ country: "Nigeria",
96
+ branches: 3,
97
+ employees: 54,
98
+ status: "active",
99
+ initials: "LD",
100
+ updated: "yesterday",
101
+ },
102
+ {
103
+ id: 5,
104
+ name: "Meridian Media",
105
+ url: "meridian.example.uk",
106
+ email: "press@meridian.example.uk",
107
+ countryCode: "GB",
108
+ country: "United Kingdom",
109
+ branches: 7,
110
+ employees: 178,
111
+ status: "suspended",
112
+ initials: "MM",
113
+ updated: "2 days ago",
114
+ },
115
+ {
116
+ id: 6,
117
+ name: "Pampas Commerce",
118
+ url: "pampas.example.ar",
119
+ email: "ventas@pampas.example.ar",
120
+ countryCode: "AR",
121
+ country: "Argentina",
122
+ branches: 4,
123
+ employees: 71,
124
+ status: "trial",
125
+ initials: "PC",
126
+ updated: "3 days ago",
127
+ },
128
+ {
129
+ id: 7,
130
+ name: "Nordic Wear",
131
+ url: "nordicwear.example.se",
132
+ email: "hej@nordicwear.example.se",
133
+ countryCode: "SE",
134
+ country: "Sweden",
135
+ branches: 6,
136
+ employees: 133,
137
+ status: "active",
138
+ initials: "NW",
139
+ updated: "4 days ago",
140
+ },
141
+ {
142
+ id: 8,
143
+ name: "Coral Bay Resorts",
144
+ url: "coralbay.example.au",
145
+ email: "stay@coralbay.example.au",
146
+ countryCode: "AU",
147
+ country: "Australia",
148
+ branches: 9,
149
+ employees: 264,
150
+ status: "active",
151
+ initials: "CB",
152
+ updated: "5 days ago",
153
+ },
154
+ ];
155
+
156
+ export const employees: DemoEmployee[] = [
157
+ {
158
+ id: 1,
159
+ code: "NR-0142",
160
+ firstName: "Ava",
161
+ lastName: "Bennett",
162
+ email: "ava.bennett@northwind.example.com",
163
+ department: "Operations",
164
+ branch: "Seattle HQ",
165
+ organization: "Northwind Retail",
166
+ isActive: true,
167
+ },
168
+ {
169
+ id: 2,
170
+ code: "SF-0098",
171
+ firstName: "Haruto",
172
+ lastName: "Tanaka",
173
+ email: "haruto.tanaka@sakura.example.jp",
174
+ department: "Supply Chain",
175
+ branch: "Osaka",
176
+ organization: "Sakura Foods",
177
+ isActive: true,
178
+ },
179
+ {
180
+ id: 3,
181
+ code: "AL-0031",
182
+ firstName: "Lena",
183
+ lastName: "Fischer",
184
+ email: "lena.fischer@alpine.example.ch",
185
+ department: "Fleet",
186
+ branch: "Zürich",
187
+ organization: "Alpine Logistics",
188
+ isActive: false,
189
+ },
190
+ {
191
+ id: 4,
192
+ code: "LD-0007",
193
+ firstName: "Chidi",
194
+ lastName: "Okafor",
195
+ email: "chidi.okafor@lagosdigital.example.ng",
196
+ department: "Engineering",
197
+ branch: "Lagos",
198
+ organization: "Lagos Digital",
199
+ isActive: true,
200
+ },
201
+ {
202
+ id: 5,
203
+ code: "CB-0210",
204
+ firstName: "Mia",
205
+ lastName: "Nguyen",
206
+ email: "mia.nguyen@coralbay.example.au",
207
+ department: "Guest Services",
208
+ branch: "Cairns",
209
+ organization: "Coral Bay Resorts",
210
+ isActive: true,
211
+ },
212
+ {
213
+ id: 6,
214
+ code: "NW-0064",
215
+ firstName: "Erik",
216
+ lastName: "Lindqvist",
217
+ email: "erik.lindqvist@nordicwear.example.se",
218
+ department: "Retail",
219
+ branch: "Stockholm",
220
+ organization: "Nordic Wear",
221
+ isActive: true,
222
+ },
223
+ ];
224
+
225
+ export const markets: DemoMarket[] = [
226
+ { id: 1, name: "Pacific Northwest", organization: "Northwind Retail", radiusMiles: 150 },
227
+ { id: 2, name: "Kansai", organization: "Sakura Foods", radiusMiles: 60 },
228
+ { id: 3, name: "DACH Core", organization: "Alpine Logistics", radiusMiles: null },
229
+ { id: 4, name: "Greater Sydney", organization: "Coral Bay Resorts", radiusMiles: 90 },
230
+ ];
231
+
232
+ export const regionBreakdown: RegionBreakdown[] = [
233
+ { region: "Americas", organizations: 3 },
234
+ { region: "EMEA", organizations: 3 },
235
+ { region: "APAC", organizations: 2 },
236
+ ];
237
+
238
+ function sum(values: number[]): number {
239
+ return values.reduce((total, value) => total + value, 0);
240
+ }
241
+
242
+ export const stats = {
243
+ organizations: {
244
+ label: "Organizations",
245
+ value: organizations.length,
246
+ delta: "+2 this month",
247
+ trend: "up" as const,
248
+ },
249
+ employees: {
250
+ label: "Employees",
251
+ value: sum(organizations.map((org) => org.employees)),
252
+ delta: "+128 this month",
253
+ trend: "up" as const,
254
+ },
255
+ activeMarkets: {
256
+ label: "Active markets",
257
+ value: markets.length,
258
+ delta: "+1 this week",
259
+ trend: "up" as const,
260
+ },
261
+ branches: {
262
+ label: "Branches",
263
+ value: sum(organizations.map((org) => org.branches)),
264
+ delta: "No change",
265
+ trend: "flat" as const,
266
+ },
267
+ };
@@ -0,0 +1,169 @@
1
+ /**
2
+ * In-memory mock data for the backoffice entity tables. Foreign keys are
3
+ * flattened to display strings (e.g. `organization` instead of
4
+ * `organization_id`). Swap this for a real data/API layer when you wire up a
5
+ * backend.
6
+ *
7
+ * Organizations and employees live in `./demo-data` and are reused by their
8
+ * tables; everything else is defined here.
9
+ */
10
+
11
+ // ── Records ────────────────────────────────────────────────────────────────
12
+
13
+ export interface Branch {
14
+ id: number;
15
+ organization: string;
16
+ name: string;
17
+ code: string;
18
+ email: string;
19
+ phone: string;
20
+ city: string;
21
+ isHeadquarters: boolean;
22
+ }
23
+
24
+ export const branches: Branch[] = [
25
+ { id: 1, organization: "Northwind Retail", name: "Seattle HQ", code: "SEA", email: "sea@northwind.example.com", phone: "+1 206 555 0110", city: "Seattle", isHeadquarters: true },
26
+ { id: 2, organization: "Northwind Retail", name: "Portland", code: "PDX", email: "pdx@northwind.example.com", phone: "+1 503 555 0134", city: "Portland", isHeadquarters: false },
27
+ { id: 3, organization: "Sakura Foods", name: "Osaka", code: "OSA", email: "osaka@sakura.example.jp", phone: "+81 6 6555 0177", city: "Osaka", isHeadquarters: true },
28
+ { id: 4, organization: "Sakura Foods", name: "Tokyo", code: "TYO", email: "tokyo@sakura.example.jp", phone: "+81 3 6555 0188", city: "Tokyo", isHeadquarters: false },
29
+ { id: 5, organization: "Alpine Logistics", name: "Zürich", code: "ZRH", email: "zurich@alpine.example.ch", phone: "+41 44 555 0143", city: "Zürich", isHeadquarters: true },
30
+ { id: 6, organization: "Coral Bay Resorts", name: "Cairns", code: "CNS", email: "cairns@coralbay.example.au", phone: "+61 7 5555 0166", city: "Cairns", isHeadquarters: true },
31
+ { id: 7, organization: "Nordic Wear", name: "Stockholm", code: "STO", email: "sto@nordicwear.example.se", phone: "+46 8 555 0199", city: "Stockholm", isHeadquarters: true },
32
+ { id: 8, organization: "Lagos Digital", name: "Lagos", code: "LOS", email: "lagos@lagosdigital.example.ng", phone: "+234 1 555 0122", city: "Lagos", isHeadquarters: true },
33
+ ];
34
+
35
+ export interface Department {
36
+ id: number;
37
+ organization: string;
38
+ title: string;
39
+ code: string;
40
+ employees: number;
41
+ }
42
+
43
+ export const departments: Department[] = [
44
+ { id: 1, organization: "Northwind Retail", title: "Operations", code: "OPS", employees: 84 },
45
+ { id: 2, organization: "Northwind Retail", title: "Merchandising", code: "MER", employees: 41 },
46
+ { id: 3, organization: "Sakura Foods", title: "Supply Chain", code: "SCM", employees: 58 },
47
+ { id: 4, organization: "Alpine Logistics", title: "Fleet", code: "FLT", employees: 33 },
48
+ { id: 5, organization: "Coral Bay Resorts", title: "Guest Services", code: "GST", employees: 96 },
49
+ { id: 6, organization: "Nordic Wear", title: "Retail", code: "RET", employees: 72 },
50
+ { id: 7, organization: "Lagos Digital", title: "Engineering", code: "ENG", employees: 27 },
51
+ { id: 8, organization: "Meridian Media", title: "Editorial", code: "EDT", employees: 45 },
52
+ ];
53
+
54
+ export interface Market {
55
+ id: number;
56
+ organization: string;
57
+ name: string;
58
+ centerLatitude: number | null;
59
+ centerLongitude: number | null;
60
+ radiusMiles: number | null;
61
+ }
62
+
63
+ export const markets: Market[] = [
64
+ { id: 1, organization: "Northwind Retail", name: "Pacific Northwest", centerLatitude: 47.6062, centerLongitude: -122.3321, radiusMiles: 150 },
65
+ { id: 2, organization: "Sakura Foods", name: "Kansai", centerLatitude: 34.6937, centerLongitude: 135.5023, radiusMiles: 60 },
66
+ { id: 3, organization: "Alpine Logistics", name: "DACH Core", centerLatitude: null, centerLongitude: null, radiusMiles: null },
67
+ { id: 4, organization: "Coral Bay Resorts", name: "Greater Sydney", centerLatitude: -33.8688, centerLongitude: 151.2093, radiusMiles: 90 },
68
+ { id: 5, organization: "Nordic Wear", name: "Mälardalen", centerLatitude: 59.3293, centerLongitude: 18.0686, radiusMiles: 75 },
69
+ { id: 6, organization: "Lagos Digital", name: "Lagos Metro", centerLatitude: 6.5244, centerLongitude: 3.3792, radiusMiles: 40 },
70
+ ];
71
+
72
+ export interface Business {
73
+ id: number;
74
+ title: string;
75
+ code: string;
76
+ description: string;
77
+ }
78
+
79
+ export const businesses: Business[] = [
80
+ { id: 1, title: "Retail", code: "RETAIL", description: "Brick-and-mortar and online consumer sales." },
81
+ { id: 2, title: "Food & Beverage", code: "FNB", description: "Food production, distribution, and hospitality." },
82
+ { id: 3, title: "Logistics", code: "LOGISTICS", description: "Freight, fleet, and last-mile delivery." },
83
+ { id: 4, title: "Media", code: "MEDIA", description: "Publishing, broadcast, and digital content." },
84
+ { id: 5, title: "Hospitality", code: "HOSPITALITY", description: "Hotels, resorts, and travel services." },
85
+ { id: 6, title: "Technology", code: "TECH", description: "Software, platforms, and IT services." },
86
+ ];
87
+
88
+ // ── System / reference data ──────────────────────────────────────────────────
89
+
90
+ export interface Region {
91
+ id: number;
92
+ name: string;
93
+ code: string;
94
+ }
95
+
96
+ export const regions: Region[] = [
97
+ { id: 1, name: "Americas", code: "AMER" },
98
+ { id: 2, name: "Europe, Middle East & Africa", code: "EMEA" },
99
+ { id: 3, name: "Asia-Pacific", code: "APAC" },
100
+ ];
101
+
102
+ export interface Country {
103
+ id: number;
104
+ name: string;
105
+ code: string;
106
+ region: string;
107
+ }
108
+
109
+ export const countries: Country[] = [
110
+ { id: 1, name: "United States", code: "US", region: "Americas" },
111
+ { id: 2, name: "Argentina", code: "AR", region: "Americas" },
112
+ { id: 3, name: "United Kingdom", code: "GB", region: "Europe, Middle East & Africa" },
113
+ { id: 4, name: "Switzerland", code: "CH", region: "Europe, Middle East & Africa" },
114
+ { id: 5, name: "Sweden", code: "SE", region: "Europe, Middle East & Africa" },
115
+ { id: 6, name: "Nigeria", code: "NG", region: "Europe, Middle East & Africa" },
116
+ { id: 7, name: "Japan", code: "JP", region: "Asia-Pacific" },
117
+ { id: 8, name: "Australia", code: "AU", region: "Asia-Pacific" },
118
+ ];
119
+
120
+ export interface City {
121
+ id: number;
122
+ name: string;
123
+ state: string;
124
+ country: string;
125
+ }
126
+
127
+ export const cities: City[] = [
128
+ { id: 1, name: "Seattle", state: "Washington", country: "United States" },
129
+ { id: 2, name: "Portland", state: "Oregon", country: "United States" },
130
+ { id: 3, name: "Osaka", state: "Osaka", country: "Japan" },
131
+ { id: 4, name: "Tokyo", state: "Tokyo", country: "Japan" },
132
+ { id: 5, name: "Zürich", state: "Zürich", country: "Switzerland" },
133
+ { id: 6, name: "Cairns", state: "Queensland", country: "Australia" },
134
+ { id: 7, name: "Stockholm", state: "Stockholm", country: "Sweden" },
135
+ { id: 8, name: "Lagos", state: "Lagos", country: "Nigeria" },
136
+ ];
137
+
138
+ export interface Currency {
139
+ id: number;
140
+ name: string;
141
+ code: string;
142
+ symbol: string;
143
+ }
144
+
145
+ export const currencies: Currency[] = [
146
+ { id: 1, name: "US Dollar", code: "USD", symbol: "$" },
147
+ { id: 2, name: "Euro", code: "EUR", symbol: "€" },
148
+ { id: 3, name: "British Pound", code: "GBP", symbol: "£" },
149
+ { id: 4, name: "Japanese Yen", code: "JPY", symbol: "¥" },
150
+ { id: 5, name: "Swiss Franc", code: "CHF", symbol: "Fr" },
151
+ { id: 6, name: "Swedish Krona", code: "SEK", symbol: "kr" },
152
+ { id: 7, name: "Australian Dollar", code: "AUD", symbol: "A$" },
153
+ { id: 8, name: "Nigerian Naira", code: "NGN", symbol: "₦" },
154
+ ];
155
+
156
+ export interface Language {
157
+ id: number;
158
+ name: string;
159
+ code: string;
160
+ }
161
+
162
+ export const languages: Language[] = [
163
+ { id: 1, name: "English", code: "en" },
164
+ { id: 2, name: "Japanese", code: "ja" },
165
+ { id: 3, name: "German", code: "de" },
166
+ { id: 4, name: "Swedish", code: "sv" },
167
+ { id: 5, name: "Spanish", code: "es" },
168
+ { id: 6, name: "French", code: "fr" },
169
+ ];
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { useSyncExternalStore } from "react";
4
+
5
+ import { organizations as seed, type DemoOrganization } from "./demo-data";
6
+
7
+ // Client-side store shared by the organizations table and the create/edit
8
+ // routes so a record added on /organizations/new shows up in the list.
9
+ // A real app would back this with an API; the shape is intentionally simple.
10
+ // ponytail: module-level array is fine for a demo/template; swap for a data
11
+ // layer (React Query, server actions) when wiring a backend.
12
+ let rows: DemoOrganization[] = [...seed];
13
+ const listeners = new Set<() => void>();
14
+
15
+ function emit() {
16
+ for (const listener of listeners) listener();
17
+ }
18
+
19
+ export const orgStore = {
20
+ subscribe(listener: () => void) {
21
+ listeners.add(listener);
22
+ return () => {
23
+ listeners.delete(listener);
24
+ };
25
+ },
26
+ snapshot: () => rows,
27
+ set(next: DemoOrganization[]) {
28
+ rows = next;
29
+ emit();
30
+ },
31
+ add(row: DemoOrganization) {
32
+ rows = [row, ...rows];
33
+ emit();
34
+ },
35
+ update(row: DemoOrganization) {
36
+ rows = rows.map((r) => (r.id === row.id ? row : r));
37
+ emit();
38
+ },
39
+ get(id: number) {
40
+ return rows.find((r) => r.id === id) ?? null;
41
+ },
42
+ };
43
+
44
+ export function useOrganizations(): DemoOrganization[] {
45
+ return useSyncExternalStore(
46
+ orgStore.subscribe,
47
+ orgStore.snapshot,
48
+ orgStore.snapshot,
49
+ );
50
+ }
@@ -0,0 +1,188 @@
1
+ import type { Metadata } from "next";
2
+
3
+ /** Single source of truth for site-wide SEO. Deployed as a static export to a
4
+ * custom domain (see ../CNAME), so URLs are absolute against SITE.url. */
5
+ export const SITE = {
6
+ name: "Vui Starter",
7
+ tagline: "React Admin & CRM Design System",
8
+ url: "https://vui.viliha.com",
9
+ description:
10
+ "Vui Starter is a free, open-source React admin & CRM design system — a token-driven component library (@viliha/vui-ui) plus a full backoffice demo.",
11
+ ogImage: "/brand/pulse-wordmark.png",
12
+ author: "Suman Bonakurthi",
13
+ // Footer identity — overridable per deployment via env (see .env.example).
14
+ // NEXT_PUBLIC_ so the value is inlined into the client bundle at build time.
15
+ company: process.env.NEXT_PUBLIC_COMPANY_NAME ?? "VILIHA PTE. LTD.",
16
+ // Optional link for the company name in the footer (e.g. https://viliha.com).
17
+ companyUrl: process.env.NEXT_PUBLIC_COMPANY_URL ?? "",
18
+ // Always the current year — inlined at build, so a rebuild/deploy keeps it fresh.
19
+ copyrightYear: new Date().getFullYear(),
20
+ license: process.env.NEXT_PUBLIC_LICENSE ?? "MIT Licensed",
21
+ } as const;
22
+
23
+ /** Footer copyright line (plain string). Set NEXT_PUBLIC_FOOTER_NOTICE to
24
+ * override the whole thing, or set company / url / license vars above (the year
25
+ * is always the current year). The app footer links the company to its url. */
26
+ export const FOOTER_NOTICE =
27
+ process.env.NEXT_PUBLIC_FOOTER_NOTICE ??
28
+ `© ${SITE.copyrightYear} ${SITE.company} · ${SITE.license}`;
29
+
30
+ /** True when the footer line is a custom override (render it verbatim). */
31
+ export const FOOTER_OVERRIDDEN = Boolean(process.env.NEXT_PUBLIC_FOOTER_NOTICE);
32
+
33
+ /** Per-route title + description for the backoffice demo pages. Keeping it in
34
+ * one table means a page file only names its route, not its copy. */
35
+ const ROUTE_META: Record<string, { title: string; description: string }> = {
36
+ "/dashboard": {
37
+ title: "Dashboard",
38
+ description:
39
+ "Admin dashboard demo with KPI stat cards, activity tables and charts, built on the Vui Starter React design system.",
40
+ },
41
+ "/charts": {
42
+ title: "Charts",
43
+ description:
44
+ "Chart component showcase — line, bar, area and pie charts styled with Vui Starter design tokens.",
45
+ },
46
+ "/components": {
47
+ title: "Components",
48
+ description:
49
+ "Live component gallery for @viliha/vui-ui — buttons, forms, tables, dialogs, menus and more.",
50
+ },
51
+ "/forms": {
52
+ title: "Forms",
53
+ description:
54
+ "Form patterns demo — inputs, selects, validation and multi-step forms built with React Hook Form and vui-ui.",
55
+ },
56
+ "/steps": {
57
+ title: "Steps",
58
+ description:
59
+ "Multi-step wizard demo built on the Steps indicator — stepper, per-step forms and Back/Next in the Vui Starter admin.",
60
+ },
61
+ "/calendar": {
62
+ title: "Calendar",
63
+ description:
64
+ "Calendar demo — a Google-style month view with appointments you can add and remove, built on the Vui Starter design system.",
65
+ },
66
+ "/settings": {
67
+ title: "Settings",
68
+ description:
69
+ "Settings screen demo — profile, appearance and preferences in the Vui Starter admin template.",
70
+ },
71
+ "/organizations": {
72
+ title: "Organizations",
73
+ description:
74
+ "Organizations data-table demo with sorting, filtering and pagination, from the Vui Starter admin.",
75
+ },
76
+ "/branches": {
77
+ title: "Branches",
78
+ description:
79
+ "Branches management screen — an example data table in the Vui Starter React admin template.",
80
+ },
81
+ "/departments": {
82
+ title: "Departments",
83
+ description:
84
+ "Departments management screen — an example data table in the Vui Starter React admin template.",
85
+ },
86
+ "/employees": {
87
+ title: "Employees",
88
+ description:
89
+ "Employees directory demo — a sortable, filterable data table from the Vui Starter admin template.",
90
+ },
91
+ "/markets": {
92
+ title: "Markets",
93
+ description:
94
+ "Markets management screen — an example data table in the Vui Starter React admin template.",
95
+ },
96
+ "/businesses": {
97
+ title: "Businesses",
98
+ description:
99
+ "Businesses management screen — an example data table in the Vui Starter React admin template.",
100
+ },
101
+ "/users": {
102
+ title: "Users",
103
+ description:
104
+ "User management demo — roles, status and access control in the Vui Starter admin template.",
105
+ },
106
+ "/crm/companies": {
107
+ title: "Companies",
108
+ description:
109
+ "CRM companies demo — manage accounts in the Vui Starter React admin & CRM design system.",
110
+ },
111
+ "/crm/people": {
112
+ title: "People",
113
+ description:
114
+ "CRM contacts demo — manage people and leads in the Vui Starter React admin & CRM design system.",
115
+ },
116
+ "/crm/opportunities": {
117
+ title: "Opportunities",
118
+ description:
119
+ "CRM pipeline demo — a kanban opportunities board in the Vui Starter admin & CRM design system.",
120
+ },
121
+ "/system/regions": {
122
+ title: "Regions",
123
+ description:
124
+ "System regions reference data — an example data table in the Vui Starter admin.",
125
+ },
126
+ "/system/countries": {
127
+ title: "Countries",
128
+ description:
129
+ "System countries reference data — an example data table in the Vui Starter admin.",
130
+ },
131
+ "/system/cities": {
132
+ title: "Cities",
133
+ description:
134
+ "System cities reference data — an example data table in the Vui Starter admin.",
135
+ },
136
+ "/system/currencies": {
137
+ title: "Currencies",
138
+ description:
139
+ "System currencies reference data — an example data table in the Vui Starter admin.",
140
+ },
141
+ "/system/languages": {
142
+ title: "Languages",
143
+ description:
144
+ "System languages reference data — an example data table in the Vui Starter admin.",
145
+ },
146
+ };
147
+
148
+ /** All public routes, for the sitemap. */
149
+ export const PUBLIC_ROUTES: string[] = [
150
+ "/",
151
+ ...Object.keys(ROUTE_META),
152
+ "/docs",
153
+ "/docs/installation",
154
+ "/docs/configuration",
155
+ "/docs/theming",
156
+ "/docs/layout",
157
+ "/docs/components",
158
+ "/docs/data-table",
159
+ "/docs/steps",
160
+ "/docs/charts",
161
+ "/docs/auth",
162
+ "/docs/shadcn-ui",
163
+ "/docs/ai-agents",
164
+ "/docs/templates",
165
+ "/docs/contributing",
166
+ ];
167
+
168
+ /** Self-referencing canonical for a route. The app is exported with
169
+ * `trailingSlash: true`, so pages are served at `/foo/` — the canonical must
170
+ * match that exactly, otherwise it points at a URL the host 301s away from. */
171
+ export function canonicalFor(path: string): string {
172
+ return path === "/" ? "/" : `${path}/`;
173
+ }
174
+
175
+ /** Page metadata for a backoffice route. Sets a per-page title, description
176
+ * and self-canonical (Open Graph / Twitter inherit the rest from root). */
177
+ export function pageMeta(path: string): Metadata {
178
+ const m = ROUTE_META[path];
179
+ if (!m) return {};
180
+ const { title, description } = m;
181
+ return {
182
+ title,
183
+ description,
184
+ alternates: { canonical: canonicalFor(path) },
185
+ openGraph: { title, description, url: canonicalFor(path) },
186
+ twitter: { title, description },
187
+ };
188
+ }
@@ -0,0 +1,3 @@
1
+ // Single source of truth for cn() is @viliha/vui-ui. Re-exported here so shadcn's
2
+ // "@/lib/utils" alias keeps resolving. Do not reintroduce a local clsx/tailwind-merge impl.
3
+ export { cn } from "@viliha/vui-ui/utils";
@@ -0,0 +1,13 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ // VUI ships as TypeScript source — Next must transpile it.
5
+ transpilePackages: ["@viliha/vui-ui"],
6
+ // Static export makes the open-tabs keep-alive work (all client at runtime).
7
+ // Remove these three if you need server components rendered per route.
8
+ output: "export",
9
+ images: { unoptimized: true },
10
+ trailingSlash: true,
11
+ };
12
+
13
+ export default nextConfig;