@tulip-systems/create-app 0.12.0 → 0.12.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.
- package/dist/templates/basic/package.json +5 -5
- package/dist/templates/basic/src/app/layout.tsx +6 -1
- package/dist/templates/code/package.json +15 -14
- package/dist/templates/code/src/app/admin/employees/_components/combobox-dropdown.tsx +1 -1
- package/dist/templates/code/src/app/admin/employees/_components/tables.tsx +1 -1
- package/dist/templates/code/src/app/admin/employees/_config/commands.tsx +1 -1
- package/dist/templates/code/src/app/admin/employees/_lib/validators.ts +1 -1
- package/dist/templates/code/src/app/admin/settings/layout.tsx +5 -0
- package/dist/templates/code/src/app/admin/settings/theme/_components/demo-gallery.tsx +632 -0
- package/dist/templates/code/src/app/admin/settings/theme/page.tsx +105 -0
- package/dist/templates/code/src/app/admin/tasks/[id]/(default)/@dashboard/page.tsx +19 -0
- package/dist/templates/code/src/app/admin/tasks/_components/tables.tsx +1 -1
- package/dist/templates/code/src/app/admin/time/_config/columns-data.tsx +1 -1
- package/dist/templates/code/src/app/layout.tsx +6 -1
- package/dist/templates/code/src/server/auth/schema.ts +0 -15
- package/dist/templates/code/src/server/auth/validators.ts +40 -0
- package/dist/templates/spark/package.json +13 -13
- package/dist/templates/spark/src/app/admin/employees/_components/combobox-dropdown.tsx +1 -1
- package/dist/templates/spark/src/app/admin/employees/_components/tables.tsx +1 -1
- package/dist/templates/spark/src/app/admin/employees/_lib/helpers.server.ts +1 -1
- package/dist/templates/spark/src/app/admin/employees/_lib/validators.ts +1 -1
- package/dist/templates/spark/src/app/admin/registrations/_components/tables.tsx +1 -1
- package/dist/templates/spark/src/app/layout.tsx +11 -3
- package/dist/templates/spark/src/server/auth/schema.ts +0 -15
- package/dist/templates/spark/src/server/auth/validators.ts +40 -0
- package/package.json +1 -1
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"@orpc/tanstack-query": "^1.14.15",
|
|
26
26
|
"@tailwindcss/typography": "^0.5.20",
|
|
27
27
|
"@tanstack/react-query": "^5.101.4",
|
|
28
|
-
"@tulip-systems/app": "0.12.
|
|
29
|
-
"@tulip-systems/auth": "0.12.
|
|
30
|
-
"@tulip-systems/commands": "0.12.
|
|
31
|
-
"@tulip-systems/core": "0.12.
|
|
32
|
-
"@tulip-systems/ui": "0.12.
|
|
28
|
+
"@tulip-systems/app": "0.12.2",
|
|
29
|
+
"@tulip-systems/auth": "0.12.2",
|
|
30
|
+
"@tulip-systems/commands": "0.12.2",
|
|
31
|
+
"@tulip-systems/core": "0.12.2",
|
|
32
|
+
"@tulip-systems/ui": "0.12.2",
|
|
33
33
|
"@vercel/functions": "^3.9.0",
|
|
34
34
|
"better-auth": "^1.6.26",
|
|
35
35
|
"class-variance-authority": "^0.7.1",
|
|
@@ -2,7 +2,11 @@ import "./globals.css";
|
|
|
2
2
|
import "../server/router/register";
|
|
3
3
|
|
|
4
4
|
import { Providers } from "@tulip-systems/app/components/client";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ColorThemeScript,
|
|
7
|
+
generateRootLayoutMetadata,
|
|
8
|
+
RootLayout,
|
|
9
|
+
} from "@tulip-systems/app/components/server";
|
|
6
10
|
import type { PropsWithChildren } from "react";
|
|
7
11
|
import { config } from "@/lib/config/base";
|
|
8
12
|
|
|
@@ -11,6 +15,7 @@ export const { metadata, viewport } = generateRootLayoutMetadata(config);
|
|
|
11
15
|
export default function Layout({ children }: PropsWithChildren) {
|
|
12
16
|
return (
|
|
13
17
|
<RootLayout>
|
|
18
|
+
<ColorThemeScript />
|
|
14
19
|
<Providers>{children}</Providers>
|
|
15
20
|
</RootLayout>
|
|
16
21
|
);
|
|
@@ -49,19 +49,20 @@
|
|
|
49
49
|
"@tanstack/react-query": "^5.101.4",
|
|
50
50
|
"@tanstack/react-table": "^8.21.3",
|
|
51
51
|
"@tanstack/react-virtual": "^3.14.9",
|
|
52
|
-
"@tulip-systems/ai": "0.12.
|
|
53
|
-
"@tulip-systems/app": "0.12.
|
|
54
|
-
"@tulip-systems/auth": "0.12.
|
|
55
|
-
"@tulip-systems/commands": "0.12.
|
|
56
|
-
"@tulip-systems/core": "0.12.
|
|
57
|
-
"@tulip-systems/data-tables": "0.12.
|
|
58
|
-
"@tulip-systems/drive": "0.12.
|
|
59
|
-
"@tulip-systems/drive-google": "0.12.
|
|
60
|
-
"@tulip-systems/editor": "0.12.
|
|
61
|
-
"@tulip-systems/
|
|
62
|
-
"@tulip-systems/
|
|
63
|
-
"@tulip-systems/
|
|
64
|
-
"@tulip-systems/
|
|
52
|
+
"@tulip-systems/ai": "0.12.2",
|
|
53
|
+
"@tulip-systems/app": "0.12.2",
|
|
54
|
+
"@tulip-systems/auth": "0.12.2",
|
|
55
|
+
"@tulip-systems/commands": "0.12.2",
|
|
56
|
+
"@tulip-systems/core": "0.12.2",
|
|
57
|
+
"@tulip-systems/data-tables": "0.12.2",
|
|
58
|
+
"@tulip-systems/drive": "0.12.2",
|
|
59
|
+
"@tulip-systems/drive-google": "0.12.2",
|
|
60
|
+
"@tulip-systems/editor": "0.12.2",
|
|
61
|
+
"@tulip-systems/editor-storage": "0.12.2",
|
|
62
|
+
"@tulip-systems/inline": "0.12.2",
|
|
63
|
+
"@tulip-systems/query": "0.12.2",
|
|
64
|
+
"@tulip-systems/storage": "0.12.2",
|
|
65
|
+
"@tulip-systems/ui": "0.12.2",
|
|
65
66
|
"@uidotdev/usehooks": "^2.4.1",
|
|
66
67
|
"@vercel/functions": "^3.9.0",
|
|
67
68
|
"@vercel/otel": "^2.1.3",
|
|
@@ -91,8 +92,8 @@
|
|
|
91
92
|
"react-pdf-tailwind": "^3.0.0",
|
|
92
93
|
"recharts": "3.10.1",
|
|
93
94
|
"resend": "^6.18.1",
|
|
94
|
-
"sharp": "^0.35.3",
|
|
95
95
|
"server-only": "^0.0.1",
|
|
96
|
+
"sharp": "^0.35.3",
|
|
96
97
|
"usehooks-ts": "^3.1.1",
|
|
97
98
|
"uuid": "^14.0.1",
|
|
98
99
|
"xlsx": "^0.18.5",
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "@tulip-systems/ui/command";
|
|
15
15
|
import { Popover, PopoverContent, PopoverTrigger } from "@tulip-systems/ui/popover";
|
|
16
16
|
import { Check, ChevronsUpDownIcon } from "lucide-react";
|
|
17
|
-
import type { User } from "@/server/auth/
|
|
17
|
+
import type { User } from "@/server/auth/validators";
|
|
18
18
|
import type { Employee } from "../_lib/validators";
|
|
19
19
|
|
|
20
20
|
export type EmployeeComboboxDropdownFieldProps = {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
useSorting,
|
|
17
17
|
} from "@tulip-systems/query/client";
|
|
18
18
|
import { type PropsWithChildren, useDeferredValue, useMemo } from "react";
|
|
19
|
-
import type { User } from "@/server/auth/
|
|
19
|
+
import type { User } from "@/server/auth/validators";
|
|
20
20
|
import { orpc } from "@/server/router/client";
|
|
21
21
|
import { employeesFilters } from "../_config/filters";
|
|
22
22
|
import { employeeTableSorts } from "../_config/sorting";
|
|
@@ -15,7 +15,7 @@ import { toast } from "@tulip-systems/ui/sonner";
|
|
|
15
15
|
import { KeyRoundIcon, LockIcon, LockOpenIcon } from "lucide-react";
|
|
16
16
|
import { Icons } from "@/components/icons";
|
|
17
17
|
import { authClient } from "@/server/auth/client";
|
|
18
|
-
import type { User } from "@/server/auth/
|
|
18
|
+
import type { User } from "@/server/auth/validators";
|
|
19
19
|
import { orpc } from "@/server/router/client";
|
|
20
20
|
import { CreateEmployeeCommand, CreateEmployeeCommandTrigger } from "../_components/create-command";
|
|
21
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createUserSchema } from "@tulip-systems/auth";
|
|
2
2
|
import { createInsertSchema, createUpdateSchema } from "drizzle-zod";
|
|
3
3
|
import type { z } from "zod";
|
|
4
|
-
import { updateUserSchema } from "@/server/auth/
|
|
4
|
+
import { updateUserSchema } from "@/server/auth/validators";
|
|
5
5
|
import { employees } from "./schema";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@tulip-systems/ui/badge";
|
|
4
|
+
import { Button } from "@tulip-systems/ui/button";
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@tulip-systems/ui/card";
|
|
6
|
+
import { Input } from "@tulip-systems/ui/input";
|
|
7
|
+
import { Label } from "@tulip-systems/ui/label";
|
|
8
|
+
import { Progress } from "@tulip-systems/ui/progress";
|
|
9
|
+
import {
|
|
10
|
+
type ChartConfig,
|
|
11
|
+
ChartContainer,
|
|
12
|
+
ChartLegend,
|
|
13
|
+
ChartLegendContent,
|
|
14
|
+
ChartTooltip,
|
|
15
|
+
ChartTooltipContent,
|
|
16
|
+
} from "@tulip-systems/ui/recharts";
|
|
17
|
+
import {
|
|
18
|
+
Select,
|
|
19
|
+
SelectContent,
|
|
20
|
+
SelectItem,
|
|
21
|
+
SelectTrigger,
|
|
22
|
+
SelectValue,
|
|
23
|
+
} from "@tulip-systems/ui/select";
|
|
24
|
+
import { Separator } from "@tulip-systems/ui/separator";
|
|
25
|
+
import { Switch } from "@tulip-systems/ui/switch";
|
|
26
|
+
import { Textarea } from "@tulip-systems/ui/textarea";
|
|
27
|
+
import { ArrowRightIcon, Clock3Icon, LockIcon, WalletIcon } from "lucide-react";
|
|
28
|
+
import * as React from "react";
|
|
29
|
+
import {
|
|
30
|
+
Area,
|
|
31
|
+
AreaChart,
|
|
32
|
+
Bar,
|
|
33
|
+
BarChart,
|
|
34
|
+
CartesianGrid,
|
|
35
|
+
Pie,
|
|
36
|
+
PieChart,
|
|
37
|
+
Label as RechartsLabel,
|
|
38
|
+
XAxis,
|
|
39
|
+
} from "recharts";
|
|
40
|
+
|
|
41
|
+
const revenueData = [
|
|
42
|
+
{ period: "Q1", retainers: 120, projects: 84, support: 56, operations: 72, growth: 64 },
|
|
43
|
+
{ period: "Q2", retainers: 148, projects: 94, support: 61, operations: 78, growth: 72 },
|
|
44
|
+
{ period: "Q3", retainers: 136, projects: 88, support: 58, operations: 74, growth: 68 },
|
|
45
|
+
{ period: "Q4", retainers: 164, projects: 102, support: 66, operations: 83, growth: 79 },
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
const revenueConfig = {
|
|
49
|
+
retainers: { label: "Retainers", color: "var(--chart-1)" },
|
|
50
|
+
projects: { label: "Projects", color: "var(--chart-2)" },
|
|
51
|
+
support: { label: "Support", color: "var(--chart-3)" },
|
|
52
|
+
operations: { label: "Ops", color: "var(--chart-4)" },
|
|
53
|
+
growth: { label: "Growth", color: "var(--chart-5)" },
|
|
54
|
+
} satisfies ChartConfig;
|
|
55
|
+
|
|
56
|
+
const channelData = [
|
|
57
|
+
{ name: "Direct", value: 348, fill: "var(--color-direct)" },
|
|
58
|
+
{ name: "Partners", value: 254, fill: "var(--color-partners)" },
|
|
59
|
+
{ name: "Inbound", value: 198, fill: "var(--color-inbound)" },
|
|
60
|
+
{ name: "Referrals", value: 167, fill: "var(--color-referrals)" },
|
|
61
|
+
{ name: "Other", value: 121, fill: "var(--color-other)" },
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
const channelConfig = {
|
|
65
|
+
direct: { label: "Direct", color: "var(--chart-1)" },
|
|
66
|
+
partners: { label: "Partners", color: "var(--chart-2)" },
|
|
67
|
+
inbound: { label: "Inbound", color: "var(--chart-3)" },
|
|
68
|
+
referrals: { label: "Referrals", color: "var(--chart-4)" },
|
|
69
|
+
other: { label: "Other", color: "var(--chart-5)" },
|
|
70
|
+
} satisfies ChartConfig;
|
|
71
|
+
|
|
72
|
+
const trendData = [
|
|
73
|
+
{ month: "Jan", active: 180, target: 160 },
|
|
74
|
+
{ month: "Feb", active: 210, target: 182 },
|
|
75
|
+
{ month: "Mar", active: 198, target: 190 },
|
|
76
|
+
{ month: "Apr", active: 246, target: 205 },
|
|
77
|
+
{ month: "May", active: 232, target: 214 },
|
|
78
|
+
{ month: "Jun", active: 274, target: 228 },
|
|
79
|
+
] as const;
|
|
80
|
+
|
|
81
|
+
const trendConfig = {
|
|
82
|
+
active: { label: "Active", color: "var(--chart-1)" },
|
|
83
|
+
target: { label: "Target", color: "var(--chart-5)" },
|
|
84
|
+
} satisfies ChartConfig;
|
|
85
|
+
|
|
86
|
+
const transactions = [
|
|
87
|
+
{ title: "Blue Bottle Coffee", meta: "Food & Drink", date: "Today, 10:24", amount: "-$6.50" },
|
|
88
|
+
{ title: "Whole Foods Market", meta: "Groceries", date: "Yesterday", amount: "-$142.30" },
|
|
89
|
+
{ title: "Stripe Payout", meta: "Income", date: "Oct 12", amount: "+$4,200.00" },
|
|
90
|
+
{ title: "Uber Technologies", meta: "Transport", date: "Oct 11", amount: "-$24.10" },
|
|
91
|
+
] as const;
|
|
92
|
+
|
|
93
|
+
const targets = [
|
|
94
|
+
{ label: "Retainers", value: "$420,000", progress: 65, meta: "$273,000 secured" },
|
|
95
|
+
{ label: "Implementations", value: "$85,000", progress: 32, meta: "$27,200 secured" },
|
|
96
|
+
] as const;
|
|
97
|
+
|
|
98
|
+
const statCards = [
|
|
99
|
+
{ label: "Card balance", value: "US$12.94", meta: "US$11,337.06 available" },
|
|
100
|
+
{ label: "Payment due", value: "1 Apr", meta: "Pay early available" },
|
|
101
|
+
{ label: "Review queue", value: "08", meta: "2 blocked items" },
|
|
102
|
+
] as const;
|
|
103
|
+
|
|
104
|
+
const activityRows = [
|
|
105
|
+
{ label: "Releases", value: 76, color: "var(--chart-1)" },
|
|
106
|
+
{ label: "Invoices", value: 58, color: "var(--chart-2)" },
|
|
107
|
+
{ label: "Approvals", value: 43, color: "var(--chart-3)" },
|
|
108
|
+
{ label: "Payouts", value: 65, color: "var(--chart-4)" },
|
|
109
|
+
{ label: "Reviews", value: 51, color: "var(--chart-5)" },
|
|
110
|
+
] as const;
|
|
111
|
+
|
|
112
|
+
const bottomStats = [
|
|
113
|
+
{
|
|
114
|
+
label: "Tax reserve",
|
|
115
|
+
value: "$3,240",
|
|
116
|
+
meta: "Held back automatically from quarterly payouts.",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: "Next review",
|
|
120
|
+
value: "Thu 14:00",
|
|
121
|
+
meta: "Operational sync for invoices, approvals, and transfers.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
label: "Export status",
|
|
125
|
+
value: "Ready",
|
|
126
|
+
meta: "Latest payout statement is prepared for accounting.",
|
|
127
|
+
},
|
|
128
|
+
] as const;
|
|
129
|
+
|
|
130
|
+
const accountStats = [
|
|
131
|
+
{
|
|
132
|
+
label: "Connected account",
|
|
133
|
+
value: "Main Checking",
|
|
134
|
+
meta: "••8402 — $12,450.00 available",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: "Transfer target",
|
|
138
|
+
value: "High Yield Savings",
|
|
139
|
+
meta: "••1192 — $42,100.00 balance",
|
|
140
|
+
},
|
|
141
|
+
] as const;
|
|
142
|
+
|
|
143
|
+
export function ThemeDemoGallery() {
|
|
144
|
+
const totalChannelValue = React.useMemo(
|
|
145
|
+
() => channelData.reduce((sum, item) => sum + item.value, 0),
|
|
146
|
+
[],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="rounded-2xl border bg-muted/20 p-4 sm:p-6">
|
|
151
|
+
<div className="columns-1 gap-x-4 md:columns-2 2xl:columns-3">
|
|
152
|
+
<Card className="mb-4 break-inside-avoid">
|
|
153
|
+
<CardHeader className="pb-3">
|
|
154
|
+
<CardTitle className="text-base">Revenue Mix</CardTitle>
|
|
155
|
+
<CardDescription>
|
|
156
|
+
Compare the five chart tokens in a denser chart with labels, grid lines, and legend.
|
|
157
|
+
</CardDescription>
|
|
158
|
+
</CardHeader>
|
|
159
|
+
<CardContent>
|
|
160
|
+
<ChartContainer config={revenueConfig} className="h-64 w-full">
|
|
161
|
+
<BarChart accessibilityLayer data={revenueData} barGap={8}>
|
|
162
|
+
<CartesianGrid vertical={false} />
|
|
163
|
+
<XAxis dataKey="period" tickLine={false} axisLine={false} tickMargin={10} />
|
|
164
|
+
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
|
165
|
+
<ChartLegend content={<ChartLegendContent />} />
|
|
166
|
+
<Bar dataKey="retainers" fill="var(--color-retainers)" radius={6} />
|
|
167
|
+
<Bar dataKey="projects" fill="var(--color-projects)" radius={6} />
|
|
168
|
+
<Bar dataKey="support" fill="var(--color-support)" radius={6} />
|
|
169
|
+
<Bar dataKey="operations" fill="var(--color-operations)" radius={6} />
|
|
170
|
+
<Bar dataKey="growth" fill="var(--color-growth)" radius={6} />
|
|
171
|
+
</BarChart>
|
|
172
|
+
</ChartContainer>
|
|
173
|
+
</CardContent>
|
|
174
|
+
</Card>
|
|
175
|
+
|
|
176
|
+
<Card className="mb-4 break-inside-avoid">
|
|
177
|
+
<CardHeader className="pb-0">
|
|
178
|
+
<CardTitle className="text-base">Account Access</CardTitle>
|
|
179
|
+
<CardDescription>
|
|
180
|
+
Inputs, labels, buttons, and quiet borders together in one compact form.
|
|
181
|
+
</CardDescription>
|
|
182
|
+
</CardHeader>
|
|
183
|
+
<CardContent className="flex flex-col gap-4 pt-6">
|
|
184
|
+
<div className="space-y-2">
|
|
185
|
+
<Label htmlFor="theme-email">Email address</Label>
|
|
186
|
+
<Input id="theme-email" defaultValue="artist@studio.inc" />
|
|
187
|
+
</div>
|
|
188
|
+
<div className="space-y-2">
|
|
189
|
+
<div className="flex items-center justify-between gap-2">
|
|
190
|
+
<Label htmlFor="theme-password">Current password</Label>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
className="text-muted-foreground text-xs uppercase tracking-[0.12em]"
|
|
194
|
+
>
|
|
195
|
+
Forgot?
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
<Input id="theme-password" type="password" defaultValue="secret-password" />
|
|
199
|
+
</div>
|
|
200
|
+
<Separator />
|
|
201
|
+
<Button className="w-full">
|
|
202
|
+
<LockIcon data-icon="inline-start" />
|
|
203
|
+
Update security
|
|
204
|
+
</Button>
|
|
205
|
+
</CardContent>
|
|
206
|
+
</Card>
|
|
207
|
+
|
|
208
|
+
<Card className="mb-4 break-inside-avoid">
|
|
209
|
+
<CardHeader className="pb-0">
|
|
210
|
+
<CardTitle className="text-base">Channel Split</CardTitle>
|
|
211
|
+
<CardDescription>Palette balance in a smaller circular chart.</CardDescription>
|
|
212
|
+
</CardHeader>
|
|
213
|
+
<CardContent className="pb-0">
|
|
214
|
+
<ChartContainer config={channelConfig} className="mx-auto h-64">
|
|
215
|
+
<PieChart>
|
|
216
|
+
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
|
217
|
+
<Pie
|
|
218
|
+
data={channelData}
|
|
219
|
+
dataKey="value"
|
|
220
|
+
nameKey="name"
|
|
221
|
+
innerRadius={62}
|
|
222
|
+
strokeWidth={6}
|
|
223
|
+
>
|
|
224
|
+
<RechartsLabel
|
|
225
|
+
content={({ viewBox }) => {
|
|
226
|
+
if (!viewBox || !("cx" in viewBox) || !("cy" in viewBox)) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<text
|
|
232
|
+
x={viewBox.cx}
|
|
233
|
+
y={viewBox.cy}
|
|
234
|
+
textAnchor="middle"
|
|
235
|
+
dominantBaseline="middle"
|
|
236
|
+
>
|
|
237
|
+
<tspan
|
|
238
|
+
x={viewBox.cx}
|
|
239
|
+
y={viewBox.cy}
|
|
240
|
+
className="fill-foreground font-semibold text-2xl"
|
|
241
|
+
>
|
|
242
|
+
{totalChannelValue}
|
|
243
|
+
</tspan>
|
|
244
|
+
<tspan
|
|
245
|
+
x={viewBox.cx}
|
|
246
|
+
y={(viewBox.cy || 0) + 24}
|
|
247
|
+
className="fill-muted-foreground text-xs"
|
|
248
|
+
>
|
|
249
|
+
total leads
|
|
250
|
+
</tspan>
|
|
251
|
+
</text>
|
|
252
|
+
);
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
</Pie>
|
|
256
|
+
</PieChart>
|
|
257
|
+
</ChartContainer>
|
|
258
|
+
</CardContent>
|
|
259
|
+
</Card>
|
|
260
|
+
|
|
261
|
+
<Card className="mb-4 break-inside-avoid">
|
|
262
|
+
<CardHeader className="pb-3">
|
|
263
|
+
<CardTitle className="text-base">Payout Threshold</CardTitle>
|
|
264
|
+
<CardDescription>Form controls, progress feedback, and secondary copy.</CardDescription>
|
|
265
|
+
</CardHeader>
|
|
266
|
+
<CardContent className="space-y-4">
|
|
267
|
+
<div className="space-y-2">
|
|
268
|
+
<Label>Preferred currency</Label>
|
|
269
|
+
<Select defaultValue="usd">
|
|
270
|
+
<SelectTrigger className="w-full">
|
|
271
|
+
<SelectValue placeholder="Choose a currency" />
|
|
272
|
+
</SelectTrigger>
|
|
273
|
+
<SelectContent>
|
|
274
|
+
<SelectItem value="usd">USD - United States Dollar</SelectItem>
|
|
275
|
+
<SelectItem value="eur">EUR - Euro</SelectItem>
|
|
276
|
+
<SelectItem value="gbp">GBP - British Pound</SelectItem>
|
|
277
|
+
</SelectContent>
|
|
278
|
+
</Select>
|
|
279
|
+
</div>
|
|
280
|
+
<div className="space-y-2">
|
|
281
|
+
<div className="flex items-center justify-between gap-4">
|
|
282
|
+
<Label htmlFor="theme-threshold">Minimum payout amount</Label>
|
|
283
|
+
<span className="font-semibold text-2xl">$2,500.00</span>
|
|
284
|
+
</div>
|
|
285
|
+
<Progress value={25} className="h-2" />
|
|
286
|
+
<div className="flex items-center justify-between text-muted-foreground text-sm">
|
|
287
|
+
<span>$50 minimum</span>
|
|
288
|
+
<span>$10,000 maximum</span>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div className="space-y-2">
|
|
292
|
+
<Label htmlFor="theme-notes">Notes</Label>
|
|
293
|
+
<Textarea
|
|
294
|
+
id="theme-notes"
|
|
295
|
+
rows={4}
|
|
296
|
+
defaultValue="Add any notes for this payout configuration or payout batching rule."
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
<Button className="w-full">Save threshold</Button>
|
|
300
|
+
</CardContent>
|
|
301
|
+
</Card>
|
|
302
|
+
|
|
303
|
+
<div className="mb-4 grid break-inside-avoid gap-4 sm:grid-cols-3 md:grid-cols-1 xl:grid-cols-1 2xl:grid-cols-3">
|
|
304
|
+
{statCards.map((item) => (
|
|
305
|
+
<Card key={item.label}>
|
|
306
|
+
<CardContent className="p-4">
|
|
307
|
+
<div className="text-muted-foreground text-sm">{item.label}</div>
|
|
308
|
+
<div className="mt-2 font-semibold text-3xl leading-none">{item.value}</div>
|
|
309
|
+
<div className="mt-2 text-muted-foreground text-sm">{item.meta}</div>
|
|
310
|
+
</CardContent>
|
|
311
|
+
</Card>
|
|
312
|
+
))}
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<Card className="mb-4 break-inside-avoid">
|
|
316
|
+
<CardHeader className="pb-3">
|
|
317
|
+
<CardTitle className="text-base">Release Snapshot</CardTitle>
|
|
318
|
+
<CardDescription>
|
|
319
|
+
A simple mix of stronger primary surfaces with calmer supporting panels.
|
|
320
|
+
</CardDescription>
|
|
321
|
+
</CardHeader>
|
|
322
|
+
<CardContent className="grid gap-3 sm:grid-cols-[1.15fr_0.85fr]">
|
|
323
|
+
<div className="rounded-xl border border-primary px-4 py-5 text-primary-foreground shadow">
|
|
324
|
+
<div className="text-primary-foreground/75 text-xs uppercase tracking-[0.18em]">
|
|
325
|
+
Distribution track
|
|
326
|
+
</div>
|
|
327
|
+
<div className="mt-3 font-semibold text-3xl leading-none">Northern Lights EP</div>
|
|
328
|
+
<div className="mt-2 max-w-sm text-primary-foreground/80 text-sm">
|
|
329
|
+
Scheduled assets, metadata, and publishing checks are all lined up for next week.
|
|
330
|
+
</div>
|
|
331
|
+
<div className="mt-5 flex items-center gap-2">
|
|
332
|
+
<Badge className="border-primary-foreground/15 bg-primary-foreground/10 text-primary-foreground hover:bg-primary-foreground/10">
|
|
333
|
+
Ready to publish
|
|
334
|
+
</Badge>
|
|
335
|
+
<span className="text-primary-foreground/75 text-xs">12 files reviewed</span>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
<div className="grid gap-3">
|
|
339
|
+
<div className="rounded-2xl border bg-card p-4">
|
|
340
|
+
<div className="text-muted-foreground text-xs uppercase tracking-[0.16em]">
|
|
341
|
+
Next payout
|
|
342
|
+
</div>
|
|
343
|
+
<div className="mt-3 font-semibold text-2xl">May 25, 2024</div>
|
|
344
|
+
<div className="mt-1 text-muted-foreground text-sm">$1,000 scheduled</div>
|
|
345
|
+
</div>
|
|
346
|
+
<div className="rounded-2xl border bg-accent/35 p-4 text-accent-foreground">
|
|
347
|
+
<div className="flex items-center gap-2">
|
|
348
|
+
<Clock3Icon className="size-4" />
|
|
349
|
+
<span className="font-medium text-sm">Auto-save plan</span>
|
|
350
|
+
</div>
|
|
351
|
+
<div className="mt-3 font-semibold text-2xl">Accelerated</div>
|
|
352
|
+
<div className="mt-1 text-accent-foreground/80 text-sm">Recurring weekly</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</CardContent>
|
|
356
|
+
</Card>
|
|
357
|
+
|
|
358
|
+
<Card className="mb-4 break-inside-avoid">
|
|
359
|
+
<CardHeader className="pb-3">
|
|
360
|
+
<CardTitle className="text-base">Preferences</CardTitle>
|
|
361
|
+
<CardDescription>
|
|
362
|
+
Muted surfaces, switches, badges, and supporting text.
|
|
363
|
+
</CardDescription>
|
|
364
|
+
</CardHeader>
|
|
365
|
+
<CardContent className="space-y-4">
|
|
366
|
+
<div className="flex items-start justify-between gap-4 rounded-xl border bg-card p-4">
|
|
367
|
+
<div>
|
|
368
|
+
<div className="font-medium text-sm">Weekly summary</div>
|
|
369
|
+
<div className="mt-1 text-muted-foreground text-sm">
|
|
370
|
+
Receive a Friday summary of revenue, payouts, and overdue invoices.
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
<Switch defaultChecked aria-label="Weekly summary" />
|
|
374
|
+
</div>
|
|
375
|
+
<div className="flex items-start justify-between gap-4 rounded-xl border bg-card p-4">
|
|
376
|
+
<div>
|
|
377
|
+
<div className="font-medium text-sm">Approval alerts</div>
|
|
378
|
+
<div className="mt-1 text-muted-foreground text-sm">
|
|
379
|
+
Notify finance when large orders need a second review.
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
<Switch aria-label="Approval alerts" />
|
|
383
|
+
</div>
|
|
384
|
+
<Separator />
|
|
385
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
386
|
+
<Badge variant="outline">Draft</Badge>
|
|
387
|
+
<Badge variant="outline">Preview</Badge>
|
|
388
|
+
<span className="text-muted-foreground text-sm">
|
|
389
|
+
These compact states help compare accent and border contrast.
|
|
390
|
+
</span>
|
|
391
|
+
</div>
|
|
392
|
+
</CardContent>
|
|
393
|
+
</Card>
|
|
394
|
+
|
|
395
|
+
<Card className="mb-4 break-inside-avoid">
|
|
396
|
+
<CardHeader className="pb-3">
|
|
397
|
+
<CardTitle className="text-base">Quick Notes</CardTitle>
|
|
398
|
+
<CardDescription>
|
|
399
|
+
Small neutral utility card to judge quiet surfaces and text hierarchy.
|
|
400
|
+
</CardDescription>
|
|
401
|
+
</CardHeader>
|
|
402
|
+
<CardContent className="space-y-3">
|
|
403
|
+
<div className="rounded-xl border bg-card p-4 text-sm">
|
|
404
|
+
<div className="font-medium">Transfer funds</div>
|
|
405
|
+
<div className="mt-1 text-muted-foreground">
|
|
406
|
+
Move money between connected accounts before the next payout run.
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
<div className="rounded-xl border bg-muted/25 p-4 text-sm">
|
|
410
|
+
<div className="font-medium">Change transfer limit</div>
|
|
411
|
+
<div className="mt-1 text-muted-foreground">
|
|
412
|
+
Adjust how much can be sent from your balance each day.
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
</CardContent>
|
|
416
|
+
</Card>
|
|
417
|
+
|
|
418
|
+
<Card className="mb-4 break-inside-avoid">
|
|
419
|
+
<CardHeader className="pb-3">
|
|
420
|
+
<CardTitle className="text-base">Recent Transactions</CardTitle>
|
|
421
|
+
<CardDescription>
|
|
422
|
+
Denser list styling to preview separators, icons, muted labels, and positive states.
|
|
423
|
+
</CardDescription>
|
|
424
|
+
</CardHeader>
|
|
425
|
+
<CardContent className="space-y-3">
|
|
426
|
+
{transactions.map((transaction, index) => (
|
|
427
|
+
<div key={transaction.title}>
|
|
428
|
+
<div className="flex items-center gap-3 rounded-xl px-1 py-2">
|
|
429
|
+
<div className="flex size-10 items-center justify-center rounded-xl bg-muted text-muted-foreground">
|
|
430
|
+
<WalletIcon className="size-4" />
|
|
431
|
+
</div>
|
|
432
|
+
<div className="min-w-0 flex-1">
|
|
433
|
+
<div className="font-medium text-sm">{transaction.title}</div>
|
|
434
|
+
<div className="text-muted-foreground text-sm">{transaction.meta}</div>
|
|
435
|
+
</div>
|
|
436
|
+
<div className="text-right">
|
|
437
|
+
<div className="text-muted-foreground text-sm">{transaction.date}</div>
|
|
438
|
+
<div
|
|
439
|
+
className={
|
|
440
|
+
transaction.amount.startsWith("+")
|
|
441
|
+
? "font-medium text-primary"
|
|
442
|
+
: "font-medium"
|
|
443
|
+
}
|
|
444
|
+
>
|
|
445
|
+
{transaction.amount}
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
{index < transactions.length - 1 && <Separator />}
|
|
450
|
+
</div>
|
|
451
|
+
))}
|
|
452
|
+
</CardContent>
|
|
453
|
+
</Card>
|
|
454
|
+
|
|
455
|
+
<Card className="mb-4 break-inside-avoid">
|
|
456
|
+
<CardHeader className="pb-3">
|
|
457
|
+
<CardTitle className="text-base">Claimable Balance</CardTitle>
|
|
458
|
+
<CardDescription>
|
|
459
|
+
A compact KPI strip with stronger hierarchy, utility rows, and a quieter CTA zone.
|
|
460
|
+
</CardDescription>
|
|
461
|
+
</CardHeader>
|
|
462
|
+
<CardContent className="grid gap-4 lg:grid-cols-[0.9fr_1.1fr_0.8fr]">
|
|
463
|
+
<div>
|
|
464
|
+
<div className="font-semibold text-5xl leading-none">$12,480</div>
|
|
465
|
+
<div className="mt-3 flex items-center gap-2">
|
|
466
|
+
<Badge variant="outline">Pending setup</Badge>
|
|
467
|
+
<span className="text-muted-foreground text-sm">
|
|
468
|
+
Monthly distribution on the 15th
|
|
469
|
+
</span>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
<div className="rounded-2xl border bg-card p-4">
|
|
473
|
+
<div className="space-y-2 text-sm">
|
|
474
|
+
<div className="flex items-center justify-between">
|
|
475
|
+
<span className="text-muted-foreground">Net royalties</span>
|
|
476
|
+
<span>$12,640.00</span>
|
|
477
|
+
</div>
|
|
478
|
+
<div className="flex items-center justify-between">
|
|
479
|
+
<span className="text-muted-foreground">Processing fees</span>
|
|
480
|
+
<span>-$160.00</span>
|
|
481
|
+
</div>
|
|
482
|
+
<Separator className="my-3" />
|
|
483
|
+
<div className="flex items-center justify-between font-medium">
|
|
484
|
+
<span>Total ready to claim</span>
|
|
485
|
+
<span>$12,480.00</span>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
<div className="flex flex-col justify-between gap-4 rounded-2xl border bg-muted/20 p-4">
|
|
490
|
+
<div>
|
|
491
|
+
<div className="text-muted-foreground text-xs uppercase tracking-[0.16em]">
|
|
492
|
+
Processing
|
|
493
|
+
</div>
|
|
494
|
+
<div className="mt-3 font-semibold text-2xl">1.28%</div>
|
|
495
|
+
<div className="mt-1 text-muted-foreground text-sm">
|
|
496
|
+
Fees are automatically reconciled before every release.
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<Button className="w-full">
|
|
500
|
+
View payout details
|
|
501
|
+
<ArrowRightIcon data-icon="inline-end" />
|
|
502
|
+
</Button>
|
|
503
|
+
</div>
|
|
504
|
+
</CardContent>
|
|
505
|
+
</Card>
|
|
506
|
+
|
|
507
|
+
<Card className="mb-4 break-inside-avoid">
|
|
508
|
+
<CardHeader className="pb-3">
|
|
509
|
+
<CardTitle className="text-base">Activity Snapshot</CardTitle>
|
|
510
|
+
<CardDescription>
|
|
511
|
+
Compact activity rows to compare chart colors against labels, borders, and muted
|
|
512
|
+
surfaces.
|
|
513
|
+
</CardDescription>
|
|
514
|
+
</CardHeader>
|
|
515
|
+
<CardContent>
|
|
516
|
+
<div className="space-y-4 rounded-2xl border bg-card p-4">
|
|
517
|
+
<div className="mb-4 flex items-center justify-between">
|
|
518
|
+
<span className="text-muted-foreground text-sm">Last 30 days</span>
|
|
519
|
+
<Badge variant="outline">Live mix</Badge>
|
|
520
|
+
</div>
|
|
521
|
+
<div className="space-y-3">
|
|
522
|
+
{activityRows.map((item) => (
|
|
523
|
+
<div key={item.label} className="space-y-1.5">
|
|
524
|
+
<div className="flex items-center justify-between text-sm">
|
|
525
|
+
<span className="text-muted-foreground">{item.label}</span>
|
|
526
|
+
<span className="font-medium">{item.value}</span>
|
|
527
|
+
</div>
|
|
528
|
+
<div className="h-2 rounded-full bg-muted">
|
|
529
|
+
<div
|
|
530
|
+
className="h-full rounded-full"
|
|
531
|
+
style={{ width: `${item.value}%`, backgroundColor: item.color }}
|
|
532
|
+
/>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
))}
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
</CardContent>
|
|
539
|
+
</Card>
|
|
540
|
+
|
|
541
|
+
<Card className="mb-4 break-inside-avoid">
|
|
542
|
+
<CardHeader className="pb-3">
|
|
543
|
+
<CardTitle className="text-base">Savings Targets</CardTitle>
|
|
544
|
+
<CardDescription>
|
|
545
|
+
Compare borders, muted fills, and progress colors in a calmer stack.
|
|
546
|
+
</CardDescription>
|
|
547
|
+
</CardHeader>
|
|
548
|
+
<CardContent className="space-y-3">
|
|
549
|
+
{targets.map((target) => (
|
|
550
|
+
<div key={target.label} className="rounded-xl border bg-muted/30 p-4">
|
|
551
|
+
<div className="mb-3 flex items-start justify-between gap-3">
|
|
552
|
+
<div>
|
|
553
|
+
<div className="text-muted-foreground text-xs uppercase tracking-[0.16em]">
|
|
554
|
+
{target.label}
|
|
555
|
+
</div>
|
|
556
|
+
<div className="mt-2 font-semibold text-3xl leading-none">{target.value}</div>
|
|
557
|
+
</div>
|
|
558
|
+
<Badge variant="outline">{target.progress}%</Badge>
|
|
559
|
+
</div>
|
|
560
|
+
<div className="h-1.5 rounded-full bg-border/70">
|
|
561
|
+
<div
|
|
562
|
+
className="h-full rounded-full bg-primary"
|
|
563
|
+
style={{ width: `${target.progress}%` }}
|
|
564
|
+
/>
|
|
565
|
+
</div>
|
|
566
|
+
<div className="mt-3 text-muted-foreground text-sm">{target.meta}</div>
|
|
567
|
+
</div>
|
|
568
|
+
))}
|
|
569
|
+
</CardContent>
|
|
570
|
+
</Card>
|
|
571
|
+
|
|
572
|
+
<Card className="mb-4 break-inside-avoid">
|
|
573
|
+
<CardHeader className="pb-3">
|
|
574
|
+
<CardTitle className="text-base">Active Pipelines</CardTitle>
|
|
575
|
+
<CardDescription>
|
|
576
|
+
Primary and fifth chart colors side by side in a simpler trend view.
|
|
577
|
+
</CardDescription>
|
|
578
|
+
</CardHeader>
|
|
579
|
+
<CardContent>
|
|
580
|
+
<ChartContainer config={trendConfig} className="h-64 w-full">
|
|
581
|
+
<AreaChart accessibilityLayer data={trendData} margin={{ left: 0, right: 0 }}>
|
|
582
|
+
<CartesianGrid vertical={false} />
|
|
583
|
+
<XAxis dataKey="month" tickLine={false} axisLine={false} tickMargin={10} />
|
|
584
|
+
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
|
585
|
+
<Area
|
|
586
|
+
dataKey="target"
|
|
587
|
+
type="monotone"
|
|
588
|
+
fill="var(--color-target)"
|
|
589
|
+
fillOpacity={0.08}
|
|
590
|
+
stroke="var(--color-target)"
|
|
591
|
+
strokeWidth={2}
|
|
592
|
+
/>
|
|
593
|
+
<Area
|
|
594
|
+
dataKey="active"
|
|
595
|
+
type="monotone"
|
|
596
|
+
fill="var(--color-active)"
|
|
597
|
+
fillOpacity={0.18}
|
|
598
|
+
stroke="var(--color-active)"
|
|
599
|
+
strokeWidth={2}
|
|
600
|
+
/>
|
|
601
|
+
</AreaChart>
|
|
602
|
+
</ChartContainer>
|
|
603
|
+
</CardContent>
|
|
604
|
+
</Card>
|
|
605
|
+
|
|
606
|
+
<div className="mb-4 grid break-inside-avoid gap-4 sm:grid-cols-2 xl:grid-cols-1 2xl:grid-cols-3">
|
|
607
|
+
{bottomStats.map((item) => (
|
|
608
|
+
<Card key={item.label}>
|
|
609
|
+
<CardContent className="p-4">
|
|
610
|
+
<div className="text-muted-foreground text-sm">{item.label}</div>
|
|
611
|
+
<div className="mt-2 font-semibold text-3xl leading-none">{item.value}</div>
|
|
612
|
+
<div className="mt-2 text-muted-foreground text-sm">{item.meta}</div>
|
|
613
|
+
</CardContent>
|
|
614
|
+
</Card>
|
|
615
|
+
))}
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<div className="mb-4 grid break-inside-avoid gap-4 sm:grid-cols-2">
|
|
619
|
+
{accountStats.map((item) => (
|
|
620
|
+
<Card key={item.label}>
|
|
621
|
+
<CardContent className="p-4">
|
|
622
|
+
<div className="text-muted-foreground text-sm">{item.label}</div>
|
|
623
|
+
<div className="mt-2 font-semibold text-2xl leading-tight">{item.value}</div>
|
|
624
|
+
<div className="mt-2 text-muted-foreground text-sm">{item.meta}</div>
|
|
625
|
+
</CardContent>
|
|
626
|
+
</Card>
|
|
627
|
+
))}
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
);
|
|
632
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useColorTheme } from "@tulip-systems/app/components/client";
|
|
4
|
+
import { Badge } from "@tulip-systems/ui/badge";
|
|
5
|
+
import { cn } from "@tulip-systems/ui/cn";
|
|
6
|
+
import { ThemeDemoGallery } from "./_components/demo-gallery";
|
|
7
|
+
|
|
8
|
+
const themeLabels: Record<string, string> = {
|
|
9
|
+
tulip: "Tulip",
|
|
10
|
+
rose: "Rose",
|
|
11
|
+
iris: "Iris",
|
|
12
|
+
sage: "Sage",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function Page() {
|
|
16
|
+
const { colorTheme, setColorTheme, colorThemes } = useColorTheme();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="space-y-10 p-content">
|
|
20
|
+
<section className="space-y-4">
|
|
21
|
+
<h2 className="font-bold text-lg">Weergave</h2>
|
|
22
|
+
<p className="text-muted-foreground text-sm">
|
|
23
|
+
Kies een kleurenthema voor je interface. Donker/licht modus kan je wijzigen via het
|
|
24
|
+
gebruikersmenu.
|
|
25
|
+
</p>
|
|
26
|
+
</section>
|
|
27
|
+
|
|
28
|
+
<section className="space-y-4">
|
|
29
|
+
<div className="flex items-center justify-between gap-3">
|
|
30
|
+
<div>
|
|
31
|
+
<h3 className="font-semibold text-base">Kleurenthema</h3>
|
|
32
|
+
<p className="text-muted-foreground text-sm">
|
|
33
|
+
Elke optie behoudt hetzelfde systeem, maar verschuift de nadruk in primaries, accents
|
|
34
|
+
en chartvolgorde.
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
<Badge variant="outline">Live preview</Badge>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
41
|
+
{colorThemes.map((themeOption) => {
|
|
42
|
+
const isActive = colorTheme === themeOption;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
key={themeOption}
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={() => setColorTheme(themeOption)}
|
|
49
|
+
className={cn(
|
|
50
|
+
"rounded-lg border p-4 text-left transition-colors",
|
|
51
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
52
|
+
isActive
|
|
53
|
+
? "border-primary bg-primary/10"
|
|
54
|
+
: "border-border bg-card hover:border-primary/50",
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<div className="mb-4 flex items-center justify-between">
|
|
58
|
+
<span className="font-medium">{themeLabels[themeOption] ?? themeOption}</span>
|
|
59
|
+
{isActive && <span className="text-primary text-xs">Actief</span>}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div
|
|
63
|
+
data-theme={themeOption}
|
|
64
|
+
className="space-y-3 rounded-md border bg-background p-3"
|
|
65
|
+
>
|
|
66
|
+
<div className="grid grid-cols-4 gap-2">
|
|
67
|
+
<span className="h-10 rounded-md border bg-primary" />
|
|
68
|
+
<span className="h-10 rounded-md border bg-accent" />
|
|
69
|
+
<span className="h-10 rounded-md border bg-secondary" />
|
|
70
|
+
<span className="h-10 rounded-md border bg-muted" />
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div className="flex items-center gap-1.5">
|
|
74
|
+
<span className="h-3 w-3 rounded-full border bg-chart-1" />
|
|
75
|
+
<span className="h-3 w-3 rounded-full border bg-chart-2" />
|
|
76
|
+
<span className="h-3 w-3 rounded-full border bg-chart-3" />
|
|
77
|
+
<span className="h-3 w-3 rounded-full border bg-chart-4" />
|
|
78
|
+
<span className="h-3 w-3 rounded-full border bg-chart-5" />
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="flex items-center justify-between text-xs">
|
|
82
|
+
<span className="text-muted-foreground">Primary, accent, surfaces</span>
|
|
83
|
+
<span className="text-muted-foreground">+ chart set</span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</button>
|
|
87
|
+
);
|
|
88
|
+
})}
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
91
|
+
|
|
92
|
+
<section className="space-y-4">
|
|
93
|
+
<div className="space-y-1">
|
|
94
|
+
<h3 className="font-semibold text-base">Component gallery</h3>
|
|
95
|
+
<p className="text-muted-foreground text-sm">
|
|
96
|
+
Een uitgebreid overzicht van charts, cards, forms, inputs en feedback states binnen je
|
|
97
|
+
huidige combinatie van kleurenthema en lichtmodus.
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<ThemeDemoGallery />
|
|
102
|
+
</section>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useSuspenseQueries, useSuspenseQuery } from "@tanstack/react-query";
|
|
4
4
|
import { EditorContent, EditorMenuFixed } from "@tulip-systems/editor/client";
|
|
5
|
+
import { createFileHandlerUploadStrategy } from "@tulip-systems/editor-storage";
|
|
5
6
|
import {
|
|
6
7
|
InlineComboboxDropdown,
|
|
7
8
|
InlineEditor,
|
|
@@ -17,10 +18,12 @@ import {
|
|
|
17
18
|
InlineStringInput,
|
|
18
19
|
} from "@tulip-systems/inline/client";
|
|
19
20
|
import { DataStackLayout, DataStackRow } from "@tulip-systems/ui/data-stack";
|
|
21
|
+
import { toast } from "@tulip-systems/ui/sonner";
|
|
20
22
|
import { notFound } from "next/navigation";
|
|
21
23
|
import { use } from "react";
|
|
22
24
|
import { z } from "zod";
|
|
23
25
|
import { orpc } from "@/server/router/client";
|
|
26
|
+
import { uploadClient } from "@/server/storage/client";
|
|
24
27
|
import { storyPointsConfig } from "../../../_lib/constants";
|
|
25
28
|
|
|
26
29
|
const taskMutation = orpc.tasks.update.mutationOptions();
|
|
@@ -172,6 +175,22 @@ export default function TaskDetailPage(props: PageProps<"/admin/tasks/[id]">) {
|
|
|
172
175
|
mutate: { on: "change", debounceMs: 700 },
|
|
173
176
|
validate: { change: z.any() },
|
|
174
177
|
}}
|
|
178
|
+
extensions={{
|
|
179
|
+
fileHandler: createFileHandlerUploadStrategy({
|
|
180
|
+
allowedMimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"],
|
|
181
|
+
variables: {},
|
|
182
|
+
uploadClient,
|
|
183
|
+
onUploadCompleted: (fileNode) => {
|
|
184
|
+
toast.success(`Succesvol geupload: ${fileNode.name}`);
|
|
185
|
+
},
|
|
186
|
+
onUploadFailed: (error) => {
|
|
187
|
+
console.error("Upload error: ", error);
|
|
188
|
+
toast.error("Bestand uploaden mislukt", {
|
|
189
|
+
description: error instanceof Error ? error.message : undefined,
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
}}
|
|
175
194
|
className="min-h-40"
|
|
176
195
|
>
|
|
177
196
|
<EditorMenuFixed />
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
useSorting,
|
|
18
18
|
} from "@tulip-systems/query/client";
|
|
19
19
|
import { type PropsWithChildren, useDeferredValue, useMemo } from "react";
|
|
20
|
-
import type { User } from "@/server/auth/
|
|
20
|
+
import type { User } from "@/server/auth/validators";
|
|
21
21
|
import { orpc } from "@/server/router/client";
|
|
22
22
|
import type { Domain } from "../../domains/_lib/validators";
|
|
23
23
|
import type { Employee } from "../../employees/_lib/validators";
|
|
@@ -4,7 +4,7 @@ import { formatDateTime, formatDuration } from "@tulip-systems/core";
|
|
|
4
4
|
import { TableCell, type TableColumnDef, TableColumnHeader } from "@tulip-systems/data-tables";
|
|
5
5
|
import { createTableSelectCell } from "@tulip-systems/data-tables/client";
|
|
6
6
|
import Link from "next/link";
|
|
7
|
-
import type { User } from "@/server/auth/
|
|
7
|
+
import type { User } from "@/server/auth/validators";
|
|
8
8
|
import type { Customer } from "../../customers/_lib/validators";
|
|
9
9
|
import type { Employee } from "../../employees/_lib/validators";
|
|
10
10
|
import type { Project } from "../../projects/_lib/validators";
|
|
@@ -2,7 +2,11 @@ import "./globals.css";
|
|
|
2
2
|
import "../server/router/register";
|
|
3
3
|
|
|
4
4
|
import { ColorThemeProvider, Providers } from "@tulip-systems/app/components/client";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ColorThemeScript,
|
|
7
|
+
generateRootLayoutMetadata,
|
|
8
|
+
RootLayout,
|
|
9
|
+
} from "@tulip-systems/app/components/server";
|
|
6
10
|
import { DriveViewScript } from "@tulip-systems/drive/server";
|
|
7
11
|
import type { PropsWithChildren } from "react";
|
|
8
12
|
import { config } from "@/lib/config/base";
|
|
@@ -12,6 +16,7 @@ export const { metadata, viewport } = generateRootLayoutMetadata(config);
|
|
|
12
16
|
export default function Layout({ children }: PropsWithChildren) {
|
|
13
17
|
return (
|
|
14
18
|
<RootLayout>
|
|
19
|
+
<ColorThemeScript />
|
|
15
20
|
<DriveViewScript />
|
|
16
21
|
|
|
17
22
|
<ColorThemeProvider defaultTheme="tulip">
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { relations, type SQL, sql } from "drizzle-orm";
|
|
2
2
|
import { index, pgTable } from "drizzle-orm/pg-core";
|
|
3
|
-
import { createInsertSchema } from "drizzle-zod";
|
|
4
|
-
import type { z } from "zod";
|
|
5
3
|
import { baseColumns } from "@/server/db/helpers";
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -124,19 +122,6 @@ export const passkeysRelations = relations(passkeys, ({ one }) => ({
|
|
|
124
122
|
}),
|
|
125
123
|
}));
|
|
126
124
|
|
|
127
|
-
export type User = typeof users.$inferSelect;
|
|
128
|
-
|
|
129
|
-
export const selectUserSchema = createInsertSchema(users);
|
|
130
|
-
export type SelectUserSchema = z.infer<typeof selectUserSchema>;
|
|
131
|
-
|
|
132
|
-
export const updateUserSchema = createInsertSchema(users).pick({
|
|
133
|
-
firstName: true,
|
|
134
|
-
lastName: true,
|
|
135
|
-
email: true,
|
|
136
|
-
image: true,
|
|
137
|
-
});
|
|
138
|
-
export type UpdateUserSchema = z.infer<typeof updateUserSchema>;
|
|
139
|
-
|
|
140
125
|
/**
|
|
141
126
|
* The jwkss table stores the JSON Web Key Sets (JWKS) used for signing and verifying JWTs in the authentication system. Each entry in the table represents a JWKS, which contains a set of cryptographic keys.
|
|
142
127
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createInsertSchema } from "drizzle-zod";
|
|
2
|
+
import type z from "zod";
|
|
3
|
+
import { accounts, users } from "./schema";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Users
|
|
7
|
+
*/
|
|
8
|
+
export type User = typeof users.$inferSelect;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Select user schema
|
|
12
|
+
*/
|
|
13
|
+
export const selectUserSchema = createInsertSchema(users);
|
|
14
|
+
export type SelectUserSchema = z.infer<typeof selectUserSchema>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create user schema
|
|
18
|
+
*/
|
|
19
|
+
export const createUserSchema = createInsertSchema(users)
|
|
20
|
+
.pick({
|
|
21
|
+
firstName: true,
|
|
22
|
+
lastName: true,
|
|
23
|
+
email: true,
|
|
24
|
+
role: true,
|
|
25
|
+
})
|
|
26
|
+
.extend({
|
|
27
|
+
password: createInsertSchema(accounts).shape.password.unwrap(),
|
|
28
|
+
});
|
|
29
|
+
export type CreateUserSchema = z.infer<typeof createUserSchema>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Update user schema
|
|
33
|
+
*/
|
|
34
|
+
export const updateUserSchema = createInsertSchema(users).pick({
|
|
35
|
+
firstName: true,
|
|
36
|
+
lastName: true,
|
|
37
|
+
email: true,
|
|
38
|
+
image: true,
|
|
39
|
+
});
|
|
40
|
+
export type UpdateUserSchema = z.infer<typeof updateUserSchema>;
|
|
@@ -35,19 +35,19 @@
|
|
|
35
35
|
"@tanstack/db": "^0.6.17",
|
|
36
36
|
"@tanstack/react-query": "^5.101.4",
|
|
37
37
|
"@tanstack/react-table": "^8.21.3",
|
|
38
|
-
"@tulip-systems/app": "0.12.
|
|
39
|
-
"@tulip-systems/auth": "0.12.
|
|
40
|
-
"@tulip-systems/commands": "0.12.
|
|
41
|
-
"@tulip-systems/core": "0.12.
|
|
42
|
-
"@tulip-systems/data-tables": "0.12.
|
|
43
|
-
"@tulip-systems/drive": "0.12.
|
|
44
|
-
"@tulip-systems/drive-local": "0.12.
|
|
45
|
-
"@tulip-systems/editor": "0.12.
|
|
46
|
-
"@tulip-systems/editor-storage": "0.12.
|
|
47
|
-
"@tulip-systems/inline": "0.12.
|
|
48
|
-
"@tulip-systems/query": "0.12.
|
|
49
|
-
"@tulip-systems/storage": "0.12.
|
|
50
|
-
"@tulip-systems/ui": "0.12.
|
|
38
|
+
"@tulip-systems/app": "0.12.2",
|
|
39
|
+
"@tulip-systems/auth": "0.12.2",
|
|
40
|
+
"@tulip-systems/commands": "0.12.2",
|
|
41
|
+
"@tulip-systems/core": "0.12.2",
|
|
42
|
+
"@tulip-systems/data-tables": "0.12.2",
|
|
43
|
+
"@tulip-systems/drive": "0.12.2",
|
|
44
|
+
"@tulip-systems/drive-local": "0.12.2",
|
|
45
|
+
"@tulip-systems/editor": "0.12.2",
|
|
46
|
+
"@tulip-systems/editor-storage": "0.12.2",
|
|
47
|
+
"@tulip-systems/inline": "0.12.2",
|
|
48
|
+
"@tulip-systems/query": "0.12.2",
|
|
49
|
+
"@tulip-systems/storage": "0.12.2",
|
|
50
|
+
"@tulip-systems/ui": "0.12.2",
|
|
51
51
|
"@vercel/functions": "^3.9.0",
|
|
52
52
|
"@zip.js/zip.js": "^2.8.34",
|
|
53
53
|
"better-auth": "^1.6.26",
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "@tulip-systems/ui/command";
|
|
15
15
|
import { Popover, PopoverContent, PopoverTrigger } from "@tulip-systems/ui/popover";
|
|
16
16
|
import { Check, ChevronsUpDownIcon } from "lucide-react";
|
|
17
|
-
import type { User } from "@/server/auth/
|
|
17
|
+
import type { User } from "@/server/auth/validators";
|
|
18
18
|
|
|
19
19
|
export type EmployeeComboboxDropdownFieldProps = {
|
|
20
20
|
values: string[];
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
useSorting,
|
|
17
17
|
} from "@tulip-systems/query/client";
|
|
18
18
|
import { type PropsWithChildren, useDeferredValue, useMemo } from "react";
|
|
19
|
-
import type { User } from "@/server/auth/
|
|
19
|
+
import type { User } from "@/server/auth/validators";
|
|
20
20
|
import { orpc } from "@/server/router/client";
|
|
21
21
|
import { employeesFilters } from "../_config/filters";
|
|
22
22
|
import { employeeTableSorts } from "../_config/sorting";
|
|
@@ -3,7 +3,7 @@ import "server-cli-only";
|
|
|
3
3
|
import { eq } from "drizzle-orm";
|
|
4
4
|
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
5
5
|
import { DRIVE_NAMESPACES } from "@/app/admin/drive/_config/namespaces";
|
|
6
|
-
import type { User } from "@/server/auth/
|
|
6
|
+
import type { User } from "@/server/auth/validators";
|
|
7
7
|
import type { DatabaseSchema } from "@/server/db/types";
|
|
8
8
|
import { drive } from "../../drive/_lib/service.server";
|
|
9
9
|
import { employees } from "./schema";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createUserSchema } from "@tulip-systems/auth";
|
|
2
2
|
import { createInsertSchema, createSelectSchema, createUpdateSchema } from "drizzle-zod";
|
|
3
3
|
import type { z } from "zod";
|
|
4
|
-
import { selectUserSchema, updateUserSchema } from "@/server/auth/
|
|
4
|
+
import { selectUserSchema, updateUserSchema } from "@/server/auth/validators";
|
|
5
5
|
|
|
6
6
|
export type { EmployeeFiltersSchema } from "../_config/filters";
|
|
7
7
|
export { employeeFiltersSchema } from "../_config/filters";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
useSorting,
|
|
17
17
|
} from "@tulip-systems/query/client";
|
|
18
18
|
import { type PropsWithChildren, useDeferredValue, useMemo } from "react";
|
|
19
|
-
import type { User } from "@/server/auth/
|
|
19
|
+
import type { User } from "@/server/auth/validators";
|
|
20
20
|
import { orpc } from "@/server/router/client";
|
|
21
21
|
import type { Employee } from "../../employees/_lib/validators";
|
|
22
22
|
import { registrationsFilters } from "../_config/filters";
|
|
@@ -2,17 +2,25 @@ import "./globals.css";
|
|
|
2
2
|
import "../server/router/register";
|
|
3
3
|
|
|
4
4
|
import { ColorThemeProvider, Providers } from "@tulip-systems/app/components/client";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ColorThemeScript,
|
|
7
|
+
generateRootLayoutMetadata,
|
|
8
|
+
RootLayout,
|
|
9
|
+
} from "@tulip-systems/app/components/server";
|
|
6
10
|
import { DriveViewScript } from "@tulip-systems/drive/server";
|
|
7
11
|
import { config } from "@/lib/config/base";
|
|
8
12
|
|
|
9
13
|
export const { metadata, viewport } = generateRootLayoutMetadata(config);
|
|
10
14
|
|
|
15
|
+
const defaultTheme = "iris";
|
|
16
|
+
|
|
11
17
|
export default function Layout({ children }: LayoutProps<"/">) {
|
|
12
18
|
return (
|
|
13
|
-
<RootLayout
|
|
19
|
+
<RootLayout>
|
|
20
|
+
<ColorThemeScript defaultTheme={defaultTheme} />
|
|
14
21
|
<DriveViewScript />
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
<ColorThemeProvider defaultTheme={defaultTheme}>
|
|
16
24
|
<Providers>{children}</Providers>
|
|
17
25
|
</ColorThemeProvider>
|
|
18
26
|
</RootLayout>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { relations, type SQL, sql } from "drizzle-orm";
|
|
2
2
|
import { pgTable } from "drizzle-orm/pg-core";
|
|
3
|
-
import { createInsertSchema } from "drizzle-zod";
|
|
4
|
-
import type { z } from "zod";
|
|
5
3
|
import { baseColumns } from "@/server/db/helpers";
|
|
6
4
|
|
|
7
5
|
export const users = pgTable("users", (t) => ({
|
|
@@ -108,16 +106,3 @@ export const passkeysRelations = relations(passkeys, ({ one }) => ({
|
|
|
108
106
|
references: [users.id],
|
|
109
107
|
}),
|
|
110
108
|
}));
|
|
111
|
-
|
|
112
|
-
export type User = typeof users.$inferSelect;
|
|
113
|
-
|
|
114
|
-
export const selectUserSchema = createInsertSchema(users);
|
|
115
|
-
export type SelectUserSchema = z.infer<typeof selectUserSchema>;
|
|
116
|
-
|
|
117
|
-
export const updateUserSchema = createInsertSchema(users).pick({
|
|
118
|
-
firstName: true,
|
|
119
|
-
lastName: true,
|
|
120
|
-
email: true,
|
|
121
|
-
image: true,
|
|
122
|
-
});
|
|
123
|
-
export type UpdateUserSchema = z.infer<typeof updateUserSchema>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createInsertSchema } from "drizzle-zod";
|
|
2
|
+
import type z from "zod";
|
|
3
|
+
import { accounts, users } from "./schema";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Users
|
|
7
|
+
*/
|
|
8
|
+
export type User = typeof users.$inferSelect;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Select user schema
|
|
12
|
+
*/
|
|
13
|
+
export const selectUserSchema = createInsertSchema(users);
|
|
14
|
+
export type SelectUserSchema = z.infer<typeof selectUserSchema>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create user schema
|
|
18
|
+
*/
|
|
19
|
+
export const createUserSchema = createInsertSchema(users)
|
|
20
|
+
.pick({
|
|
21
|
+
firstName: true,
|
|
22
|
+
lastName: true,
|
|
23
|
+
email: true,
|
|
24
|
+
role: true,
|
|
25
|
+
})
|
|
26
|
+
.extend({
|
|
27
|
+
password: createInsertSchema(accounts).shape.password.unwrap(),
|
|
28
|
+
});
|
|
29
|
+
export type CreateUserSchema = z.infer<typeof createUserSchema>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Update user schema
|
|
33
|
+
*/
|
|
34
|
+
export const updateUserSchema = createInsertSchema(users).pick({
|
|
35
|
+
firstName: true,
|
|
36
|
+
lastName: true,
|
|
37
|
+
email: true,
|
|
38
|
+
image: true,
|
|
39
|
+
});
|
|
40
|
+
export type UpdateUserSchema = z.infer<typeof updateUserSchema>;
|