@tulip-systems/create-app 0.12.2 → 0.12.4

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 (94) hide show
  1. package/dist/templates/basic/package.json +5 -5
  2. package/dist/templates/code/package.json +33 -14
  3. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-black-italic.otf +0 -0
  4. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-black.otf +0 -0
  5. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-bold-italic.otf +0 -0
  6. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-bold.otf +0 -0
  7. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-extra-light-italic.otf +0 -0
  8. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-extra-light.otf +0 -0
  9. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-light-italic.otf +0 -0
  10. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-light.otf +0 -0
  11. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-medium-italic.otf +0 -0
  12. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-medium.otf +0 -0
  13. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-regular-italic.otf +0 -0
  14. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-regular.otf +0 -0
  15. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-thin-italic.otf +0 -0
  16. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-thin.otf +0 -0
  17. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-ultra-thin-italic.otf +0 -0
  18. package/dist/templates/code/public/fonts/neue-haas-grotesk/neue-haas-grotesk-ultra-thin.otf +0 -0
  19. package/dist/templates/code/public/images/preview/hero-home.webp +0 -0
  20. package/dist/templates/code/src/app/admin/customers/[id]/(default)/@dashboard/page.tsx +2 -0
  21. package/dist/templates/code/src/app/admin/projects/[id]/(default)/page.tsx +2 -0
  22. package/dist/templates/code/src/app/admin/prospects/[id]/(default)/@dashboard/page.tsx +2 -0
  23. package/dist/templates/code/src/app/admin/quotations/(list)/layout.tsx +24 -0
  24. package/dist/templates/code/src/app/admin/quotations/(list)/page.tsx +23 -0
  25. package/dist/templates/code/src/app/admin/quotations/[id]/(dashboard)/layout.tsx +40 -0
  26. package/dist/templates/code/src/app/admin/quotations/[id]/(dashboard)/page.tsx +56 -0
  27. package/dist/templates/code/src/app/admin/quotations/[id]/preview/page.tsx +17 -0
  28. package/dist/templates/code/src/app/admin/quotations/_blocks/commands.tsx +24 -0
  29. package/dist/templates/code/src/app/admin/quotations/_blocks/hero/config.ts +14 -0
  30. package/dist/templates/code/src/app/admin/quotations/_blocks/hero/create.tsx +152 -0
  31. package/dist/templates/code/src/app/admin/quotations/_blocks/hero/inline.tsx +90 -0
  32. package/dist/templates/code/src/app/admin/quotations/_blocks/hero/preview.tsx +90 -0
  33. package/dist/templates/code/src/app/admin/quotations/_blocks/registry.ts +13 -0
  34. package/dist/templates/code/src/app/admin/quotations/_blocks/render.tsx +190 -0
  35. package/dist/templates/code/src/app/admin/quotations/_blocks/styles.ts +24 -0
  36. package/dist/templates/code/src/app/admin/quotations/_blocks/summary/config.ts +13 -0
  37. package/dist/templates/code/src/app/admin/quotations/_blocks/summary/create.tsx +175 -0
  38. package/dist/templates/code/src/app/admin/quotations/_blocks/summary/inline.tsx +93 -0
  39. package/dist/templates/code/src/app/admin/quotations/_blocks/summary/preview.tsx +25 -0
  40. package/dist/templates/code/src/app/admin/quotations/_blocks/totals/config.ts +18 -0
  41. package/dist/templates/code/src/app/admin/quotations/_blocks/totals/create.tsx +177 -0
  42. package/dist/templates/code/src/app/admin/quotations/_blocks/totals/inline.tsx +96 -0
  43. package/dist/templates/code/src/app/admin/quotations/_blocks/totals/preview.tsx +32 -0
  44. package/dist/templates/code/src/app/admin/quotations/_components/create-command.tsx +97 -0
  45. package/dist/templates/code/src/app/admin/quotations/_components/tables.tsx +68 -0
  46. package/dist/templates/code/src/app/admin/quotations/_components/toolbars.tsx +18 -0
  47. package/dist/templates/code/src/app/admin/quotations/_config/columns-data.tsx +35 -0
  48. package/dist/templates/code/src/app/admin/quotations/_config/commands.tsx +71 -0
  49. package/dist/templates/code/src/app/admin/quotations/_config/filters.ts +22 -0
  50. package/dist/templates/code/src/app/admin/quotations/_config/sorting.ts +23 -0
  51. package/dist/templates/code/src/app/admin/quotations/_lib/router.ts +260 -0
  52. package/dist/templates/code/src/app/admin/quotations/_lib/schema.ts +42 -0
  53. package/dist/templates/code/src/app/admin/quotations/_lib/validators.ts +20 -0
  54. package/dist/templates/code/src/app/admin/tasks/[id]/(default)/@dashboard/page.tsx +6 -4
  55. package/dist/templates/code/src/lib/config/editor.ts +54 -0
  56. package/dist/templates/code/src/lib/config/paths.tsx +8 -0
  57. package/dist/templates/code/src/lib/utils/fonts.ts +102 -0
  58. package/dist/templates/code/src/server/auth/permissions.ts +4 -0
  59. package/dist/templates/code/src/server/db/schema.ts +1 -0
  60. package/dist/templates/code/src/server/router/router.ts +10 -0
  61. package/dist/templates/spark/package.json +30 -13
  62. package/dist/templates/spark/src/app/admin/assignments/[id]/(default)/@dashboard/page.tsx +2 -0
  63. package/dist/templates/spark/src/app/admin/companies/[id]/(default)/@dashboard/page.tsx +2 -0
  64. package/dist/templates/spark/src/app/admin/customers/[id]/(default)/@dashboard/page.tsx +2 -0
  65. package/dist/templates/spark/src/app/admin/emails/_components/send-email.tsx +5 -6
  66. package/dist/templates/spark/src/app/admin/executions/[id]/@dashboard/page.tsx +2 -0
  67. package/dist/templates/spark/src/app/admin/products/[id]/(preview)/page.tsx +2 -2
  68. package/dist/templates/spark/src/app/admin/products/[id]/dashboard/@dashboard/page.tsx +2 -0
  69. package/dist/templates/spark/src/app/admin/products/[id]/dashboard/editor/page.tsx +6 -8
  70. package/dist/templates/spark/src/app/admin/projects/[id]/(dashboard)/page.tsx +2 -0
  71. package/dist/templates/spark/src/app/admin/projects/[id]/messages/page.tsx +7 -1
  72. package/dist/templates/spark/src/app/admin/purchase-orders/[id]/(default)/@dashboard/page.tsx +2 -0
  73. package/dist/templates/spark/src/app/admin/purchase-orders/_config/columns-data.tsx +7 -1
  74. package/dist/templates/spark/src/app/admin/quotation-items/_config/columns-data.tsx +7 -1
  75. package/dist/templates/spark/src/app/admin/quotation-items/_config/columns-inline.tsx +2 -0
  76. package/dist/templates/spark/src/app/admin/quotations/[id]/(default)/@dashboard/page.tsx +2 -0
  77. package/dist/templates/spark/src/app/admin/run-tasks/_config/columns-data.tsx +7 -1
  78. package/dist/templates/spark/src/app/admin/runs/[id]/@dashboard/page.tsx +2 -0
  79. package/dist/templates/spark/src/app/admin/runs/[id]/reports/page.tsx +2 -0
  80. package/dist/templates/spark/src/app/admin/sales-invoice-items/_config/columns-data.tsx +7 -1
  81. package/dist/templates/spark/src/app/admin/sales-invoice-items/_config/columns-inline.tsx +2 -0
  82. package/dist/templates/spark/src/app/admin/sales-order-items/_config/columns-data.tsx +7 -1
  83. package/dist/templates/spark/src/app/admin/sales-order-items/_config/columns-inline.tsx +2 -0
  84. package/dist/templates/spark/src/app/admin/sales-orders/[id]/(default)/@dashboard/page.tsx +2 -0
  85. package/dist/templates/spark/src/app/admin/suppliers/[id]/@dashboard/page.tsx +2 -0
  86. package/dist/templates/spark/src/app/admin/tasks/_config/columns-data.tsx +7 -1
  87. package/dist/templates/spark/src/app/admin/tasks/_config/columns-inline.tsx +2 -0
  88. package/dist/templates/spark/src/app/admin/technical-sheets/[id]/page.tsx +2 -0
  89. package/dist/templates/spark/src/app/admin/technical-sheets/_config/columns-data.tsx +13 -2
  90. package/dist/templates/spark/src/app/admin/template-tasks/_config/columns-data.tsx +7 -1
  91. package/dist/templates/spark/src/app/admin/template-tasks/_config/columns-inline.tsx +2 -0
  92. package/dist/templates/spark/src/app/admin/templates/[id]/@dashboard/page.tsx +2 -0
  93. package/dist/templates/spark/src/lib/config/editor.ts +54 -0
  94. 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.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",
28
+ "@tulip-systems/app": "0.12.4",
29
+ "@tulip-systems/auth": "0.12.4",
30
+ "@tulip-systems/commands": "0.12.4",
31
+ "@tulip-systems/core": "0.12.4",
32
+ "@tulip-systems/ui": "0.12.4",
33
33
  "@vercel/functions": "^3.9.0",
34
34
  "better-auth": "^1.6.26",
35
35
  "class-variance-authority": "^0.7.1",
@@ -30,6 +30,7 @@
30
30
  "@dnd-kit/helpers": "^0.5.0",
31
31
  "@dnd-kit/modifiers": "^9.0.0",
32
32
  "@dnd-kit/react": "^0.5.0",
33
+ "@dnd-kit/sortable": "^10.0.0",
33
34
  "@hookform/resolvers": "^5.7.1",
34
35
  "@js-temporal/polyfill": "^0.5.1",
35
36
  "@modelcontextprotocol/server": "^2.0.0",
@@ -49,20 +50,38 @@
49
50
  "@tanstack/react-query": "^5.101.4",
50
51
  "@tanstack/react-table": "^8.21.3",
51
52
  "@tanstack/react-virtual": "^3.14.9",
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",
53
+ "@tiptap/core": "^3.29.2",
54
+ "@tiptap/extension-blockquote": "^3.29.2",
55
+ "@tiptap/extension-bold": "^3.29.2",
56
+ "@tiptap/extension-document": "^3.29.2",
57
+ "@tiptap/extension-hard-break": "^3.29.2",
58
+ "@tiptap/extension-heading": "^3.29.2",
59
+ "@tiptap/extension-highlight": "^3.29.2",
60
+ "@tiptap/extension-horizontal-rule": "^3.29.2",
61
+ "@tiptap/extension-italic": "^3.29.2",
62
+ "@tiptap/extension-link": "^3.29.2",
63
+ "@tiptap/extension-list": "^3.29.2",
64
+ "@tiptap/extension-paragraph": "^3.29.2",
65
+ "@tiptap/extension-strike": "^3.29.2",
66
+ "@tiptap/extension-text": "^3.29.2",
67
+ "@tiptap/extension-text-style": "^3.29.2",
68
+ "@tiptap/extension-underline": "^3.29.2",
69
+ "@tiptap/extensions": "^3.29.2",
70
+ "@tulip-systems/ai": "0.12.4",
71
+ "@tulip-systems/app": "0.12.4",
72
+ "@tulip-systems/auth": "0.12.4",
73
+ "@tulip-systems/blocks": "0.12.4",
74
+ "@tulip-systems/commands": "0.12.4",
75
+ "@tulip-systems/core": "0.12.4",
76
+ "@tulip-systems/data-tables": "0.12.4",
77
+ "@tulip-systems/drive": "0.12.4",
78
+ "@tulip-systems/drive-google": "0.12.4",
79
+ "@tulip-systems/editor": "0.12.4",
80
+ "@tulip-systems/editor-storage": "0.12.4",
81
+ "@tulip-systems/inline": "0.12.4",
82
+ "@tulip-systems/query": "0.12.4",
83
+ "@tulip-systems/storage": "0.12.4",
84
+ "@tulip-systems/ui": "0.12.4",
66
85
  "@uidotdev/usehooks": "^2.4.1",
67
86
  "@vercel/functions": "^3.9.0",
68
87
  "@vercel/otel": "^2.1.3",
@@ -11,6 +11,7 @@ import {
11
11
  import { DataList, DataListItem, DataListTitle, DataListValue } from "@tulip-systems/ui/data-list";
12
12
  import { use } from "react";
13
13
  import { z } from "zod";
14
+ import { richTextEditorPreset } from "@/lib/config/editor";
14
15
  import { orpc } from "@/server/router/client";
15
16
 
16
17
  const customerMutation = orpc.customers.update.mutationOptions();
@@ -130,6 +131,7 @@ export default function CustomerDetailPage(props: { params: Promise<{ id: string
130
131
  mutation={customerMutation}
131
132
  strategy={{ mutate: { on: "blur" }, validate: { blur: z.any().nullable() } }}
132
133
  className="min-h-40"
134
+ extensions={richTextEditorPreset}
133
135
  >
134
136
  <EditorMenuBubble />
135
137
  <EditorContent />
@@ -26,6 +26,7 @@ import { use } from "react";
26
26
  import { z } from "zod";
27
27
  import { InitiativeStatusField } from "@/app/admin/initiatives/_config/status";
28
28
  import { Icons } from "@/components/icons";
29
+ import { richTextEditorPreset } from "@/lib/config/editor";
29
30
  import { orpc } from "@/server/router/client";
30
31
  import { projectKeySchema } from "../../_lib/validators";
31
32
 
@@ -144,6 +145,7 @@ export default function ProjectDetailPage(props: PageProps<"/admin/projects/[id]
144
145
  mutation={projectMutation}
145
146
  strategy={{ mutate: { on: "change", debounceMs: 700 }, validate: { change: z.any() } }}
146
147
  className="min-h-80"
148
+ extensions={richTextEditorPreset}
147
149
  >
148
150
  <EditorMenuFixed />
149
151
  <EditorContent />
@@ -11,6 +11,7 @@ import {
11
11
  import { DataList, DataListItem, DataListTitle, DataListValue } from "@tulip-systems/ui/data-list";
12
12
  import { notFound } from "next/navigation";
13
13
  import { use } from "react";
14
+ import { richTextEditorPreset } from "@/lib/config/editor";
14
15
  import { orpc } from "@/server/router/client";
15
16
  import { updateProspectSchema } from "../../../_lib/validators";
16
17
 
@@ -164,6 +165,7 @@ export default function ProspectDashboardPage(props: { params: Promise<{ id: str
164
165
  validate: { change: updateProspectSchema.shape.notes },
165
166
  }}
166
167
  className="min-h-40"
168
+ extensions={richTextEditorPreset}
167
169
  >
168
170
  <EditorMenuBubble />
169
171
  <EditorContent />
@@ -0,0 +1,24 @@
1
+ import { Header, Topbar, TopbarTitle, TopbarTools } from "@tulip-systems/app/components/client";
2
+ import { InlineCommandMenu, InlineCommandMenuLoading } from "@tulip-systems/commands/client";
3
+ import { type PropsWithChildren, Suspense } from "react";
4
+ import { quotationCreateCommand } from "../_config/commands";
5
+
6
+ export default function QuotationsLayout({ children }: PropsWithChildren) {
7
+ return (
8
+ <>
9
+ <Header>
10
+ <Topbar>
11
+ <TopbarTitle breadcrumbs={[{ label: "Offertes" }]} />
12
+
13
+ <TopbarTools>
14
+ <Suspense fallback={<InlineCommandMenuLoading />}>
15
+ <InlineCommandMenu data={{}} commands={[quotationCreateCommand]} />
16
+ </Suspense>
17
+ </TopbarTools>
18
+ </Topbar>
19
+ </Header>
20
+
21
+ {children}
22
+ </>
23
+ );
24
+ }
@@ -0,0 +1,23 @@
1
+ import { Allowed } from "@tulip-systems/auth/permissions/client";
2
+ import { TableLayout } from "@tulip-systems/data-tables";
3
+ import { DataTable } from "@tulip-systems/data-tables/client";
4
+ import { QuotationPaginationTableProvider } from "../_components/tables";
5
+ import { QuotationTableToolbar } from "../_components/toolbars";
6
+ import { quotationColumns } from "../_config/columns-data";
7
+ import { quotationDeleteCommand } from "../_config/commands";
8
+
9
+ export default function QuotationsPage() {
10
+ return (
11
+ <Allowed permission={{ quotation: ["view"] }}>
12
+ <TableLayout className="p-content">
13
+ <QuotationPaginationTableProvider
14
+ columns={quotationColumns}
15
+ commands={[quotationDeleteCommand]}
16
+ >
17
+ <QuotationTableToolbar />
18
+ <DataTable />
19
+ </QuotationPaginationTableProvider>
20
+ </TableLayout>
21
+ </Allowed>
22
+ );
23
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { useSuspenseQuery } from "@tanstack/react-query";
4
+ import { Header, Topbar, TopbarTitle, TopbarTools } from "@tulip-systems/app/components/client";
5
+ import { InlineCommandMenu, InlineCommandMenuLoading } from "@tulip-systems/commands/client";
6
+ import { notFound } from "next/navigation";
7
+ import { Suspense, use } from "react";
8
+ import { orpc } from "@/server/router/client";
9
+ import { quotationDeleteCommand, quotationPreviewCommand } from "../../_config/commands";
10
+
11
+ export default function QuotationsLayout(props: LayoutProps<"/admin/quotations/[id]">) {
12
+ const params = use(props.params);
13
+ const id = params.id?.toString();
14
+
15
+ const { data } = useSuspenseQuery(orpc.quotations.getById.queryOptions({ input: { id } }));
16
+ if (!data) notFound();
17
+
18
+ return (
19
+ <>
20
+ <Header>
21
+ <Topbar>
22
+ <TopbarTitle
23
+ breadcrumbs={[{ label: "Offertes", href: "/admin/quotations" }, { label: data.title }]}
24
+ />
25
+
26
+ <TopbarTools>
27
+ <Suspense fallback={<InlineCommandMenuLoading />}>
28
+ <InlineCommandMenu
29
+ data={data}
30
+ commands={[quotationPreviewCommand, quotationDeleteCommand]}
31
+ />
32
+ </Suspense>
33
+ </TopbarTools>
34
+ </Topbar>
35
+ </Header>
36
+
37
+ {props.children}
38
+ </>
39
+ );
40
+ }
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { useSuspenseQuery } from "@tanstack/react-query";
4
+ import { Allowed } from "@tulip-systems/auth/permissions/client";
5
+ import {
6
+ InlineField,
7
+ InlineFieldError,
8
+ InlineFieldLabel,
9
+ InlineStringInput,
10
+ } from "@tulip-systems/inline/client";
11
+ import { DataStackLayout } from "@tulip-systems/ui/data-stack";
12
+ import { notFound } from "next/navigation";
13
+ import { use } from "react";
14
+ import { z } from "zod";
15
+ import { orpc } from "@/server/router/client";
16
+ import { CreateQuotationBlock, RenderQuotationInlineBlocks } from "../../_blocks/render";
17
+
18
+ const quotationMutation = orpc.quotations.update.mutationOptions({
19
+ meta: { invalidates: [orpc.quotations.key()] },
20
+ });
21
+
22
+ export default function QuotationDetailPage(props: PageProps<"/admin/quotations/[id]">) {
23
+ const params = use(props.params);
24
+ const id = params.id?.toString();
25
+
26
+ const { data } = useSuspenseQuery(orpc.quotations.getById.queryOptions({ input: { id } }));
27
+ if (!data) notFound();
28
+
29
+ return (
30
+ <Allowed permission={{ quotation: ["view"] }}>
31
+ <DataStackLayout className="p-content">
32
+ <InlineField>
33
+ <InlineFieldLabel>Titel</InlineFieldLabel>
34
+ <InlineStringInput
35
+ initialValue={data.title}
36
+ variables={(value) => ({ id, data: { title: value ?? "" } })}
37
+ mutation={quotationMutation}
38
+ strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
39
+ />
40
+ <InlineFieldError />
41
+ </InlineField>
42
+
43
+ <InlineField>
44
+ <InlineFieldLabel>Blokken</InlineFieldLabel>
45
+
46
+ <div className="flex flex-col gap-2">
47
+ <RenderQuotationInlineBlocks quotationId={id} />
48
+ <CreateQuotationBlock quotationId={id} />
49
+ </div>
50
+
51
+ <InlineFieldError />
52
+ </InlineField>
53
+ </DataStackLayout>
54
+ </Allowed>
55
+ );
56
+ }
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import { useSuspenseQuery } from "@tanstack/react-query";
4
+ import { notFound } from "next/navigation";
5
+ import { use } from "react";
6
+ import { orpc } from "@/server/router/client";
7
+ import { RenderQuotationPreview } from "../../_blocks/render";
8
+
9
+ export default function QuotationPreviewPage(props: PageProps<"/admin/quotations/[id]/preview">) {
10
+ const params = use(props.params);
11
+ const id = params.id?.toString();
12
+
13
+ const { data } = useSuspenseQuery(orpc.quotations.getById.queryOptions({ input: { id } }));
14
+ if (!data) notFound();
15
+
16
+ return <RenderQuotationPreview quotationId={id} />;
17
+ }
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import { commandBuilder } from "@tulip-systems/commands";
4
+ import { DeleteCommand } from "@tulip-systems/commands/client";
5
+ import { toast } from "@tulip-systems/ui/sonner";
6
+ import { orpc } from "@/server/router/client";
7
+ import type { QuotationBlock } from "./registry";
8
+
9
+ type QuotationBlockCommandItem = Pick<QuotationBlock, "id">;
10
+
11
+ export const quotationBlockDeleteCommand = commandBuilder
12
+ .$type<QuotationBlockCommandItem>()
13
+ .permission({ quotation: ["update"] })
14
+ .render(({ data }) => (
15
+ <DeleteCommand
16
+ variables={{ id: data.id }}
17
+ mutation={orpc.quotationBlocks.delete.mutationOptions({
18
+ meta: { invalidates: [orpc.quotationBlocks.key()] },
19
+ onSuccess: () => toast.success("Blok succesvol verwijderd"),
20
+ })}
21
+ />
22
+ ));
23
+
24
+ export const quotationInlineBlockCommands = [quotationBlockDeleteCommand];
@@ -0,0 +1,14 @@
1
+ import type { BlockDefinition } from "@tulip-systems/blocks";
2
+ import { z } from "zod";
3
+
4
+ export const quotationHeroBlock = {
5
+ slug: "hero",
6
+ label: "Hero",
7
+ description: "Homepage hero title, subtitle, and supporting copy.",
8
+ schema: z.object({
9
+ media: z.unknown().optional(),
10
+ titlePart1: z.string().min(1),
11
+ titlePart2: z.string().optional(),
12
+ description: z.string().optional(),
13
+ }),
14
+ } as const satisfies BlockDefinition;
@@ -0,0 +1,152 @@
1
+ "use client";
2
+
3
+ import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
4
+ import type { DefaultError } from "@tanstack/react-query";
5
+ import type { BlockCreateComponentProps } from "@tulip-systems/blocks";
6
+ import {
7
+ CreateBlock,
8
+ CreateBlockCancel,
9
+ CreateBlockContent,
10
+ CreateBlockDescription,
11
+ CreateBlockFields,
12
+ CreateBlockFooter,
13
+ CreateBlockHeader,
14
+ CreateBlockSubmit,
15
+ CreateBlockTitle,
16
+ CreateBlockTrigger,
17
+ CreateBlockTriggerContent,
18
+ CreateBlockTriggerDescription,
19
+ CreateBlockTriggerIcon,
20
+ CreateBlockTriggerTitle,
21
+ } from "@tulip-systems/blocks/client";
22
+ import { Input } from "@tulip-systems/ui/input";
23
+ import {
24
+ Form,
25
+ FormControl,
26
+ FormField,
27
+ FormItem,
28
+ FormLabel,
29
+ FormMessage,
30
+ } from "@tulip-systems/ui/react-hook-form";
31
+ import { Textarea } from "@tulip-systems/ui/textarea";
32
+ import { BlocksIcon } from "lucide-react";
33
+ import { useForm } from "react-hook-form";
34
+ import type { z } from "zod";
35
+ import { quotationHeroBlock } from "./config";
36
+
37
+ export type QuotationHeroCreateValues = z.input<typeof quotationHeroBlock.schema>;
38
+
39
+ type QuotationHeroCreateBlockInput = {
40
+ slug: typeof quotationHeroBlock.slug;
41
+ data: QuotationHeroCreateValues;
42
+ };
43
+
44
+ type QuotationHeroCreateBlockProps<
45
+ TData = unknown,
46
+ TError = DefaultError,
47
+ TVariables = undefined,
48
+ TOnMutateResult = unknown,
49
+ > = BlockCreateComponentProps<
50
+ QuotationHeroCreateBlockInput,
51
+ TData,
52
+ TError,
53
+ TVariables,
54
+ TOnMutateResult
55
+ >;
56
+
57
+ export function QuotationHeroCreateBlock<
58
+ TData = unknown,
59
+ TError = DefaultError,
60
+ TVariables = undefined,
61
+ TOnMutateResult = unknown,
62
+ >({
63
+ variables,
64
+ mutation,
65
+ }: QuotationHeroCreateBlockProps<TData, TError, TVariables, TOnMutateResult>) {
66
+ const form = useForm<QuotationHeroCreateValues>({
67
+ resolver: standardSchemaResolver(quotationHeroBlock.schema),
68
+ });
69
+
70
+ return (
71
+ <CreateBlock>
72
+ <CreateBlockTrigger>
73
+ <CreateBlockTriggerIcon>
74
+ <BlocksIcon />
75
+ </CreateBlockTriggerIcon>
76
+
77
+ <CreateBlockTriggerContent>
78
+ <CreateBlockTriggerTitle>{quotationHeroBlock.label}</CreateBlockTriggerTitle>
79
+ <CreateBlockTriggerDescription>
80
+ {quotationHeroBlock.description}
81
+ </CreateBlockTriggerDescription>
82
+ </CreateBlockTriggerContent>
83
+ </CreateBlockTrigger>
84
+
85
+ <Form {...form}>
86
+ <CreateBlockContent
87
+ className="sm:max-w-xl"
88
+ variables={(data: QuotationHeroCreateValues) =>
89
+ variables({ slug: quotationHeroBlock.slug, data })
90
+ }
91
+ mutation={mutation}
92
+ >
93
+ <CreateBlockHeader>
94
+ <CreateBlockTitle>{quotationHeroBlock.label} toevoegen</CreateBlockTitle>
95
+ <CreateBlockDescription>
96
+ Vul de verplichte velden in om het blok toe te voegen.
97
+ </CreateBlockDescription>
98
+ </CreateBlockHeader>
99
+
100
+ <CreateBlockFields className="grid gap-4 md:grid-cols-2">
101
+ <FormField
102
+ control={form.control}
103
+ name="titlePart1"
104
+ render={({ field }) => (
105
+ <FormItem>
106
+ <FormLabel>Titel deel 1</FormLabel>
107
+ <FormControl>
108
+ <Input {...field} />
109
+ </FormControl>
110
+ <FormMessage />
111
+ </FormItem>
112
+ )}
113
+ />
114
+
115
+ <FormField
116
+ control={form.control}
117
+ name="titlePart2"
118
+ render={({ field }) => (
119
+ <FormItem>
120
+ <FormLabel>Titel deel 2</FormLabel>
121
+ <FormControl>
122
+ <Input {...field} />
123
+ </FormControl>
124
+ <FormMessage />
125
+ </FormItem>
126
+ )}
127
+ />
128
+
129
+ <FormField
130
+ control={form.control}
131
+ name="description"
132
+ render={({ field }) => (
133
+ <FormItem className="md:col-span-2">
134
+ <FormLabel>Beschrijving</FormLabel>
135
+ <FormControl>
136
+ <Textarea {...field} />
137
+ </FormControl>
138
+ <FormMessage />
139
+ </FormItem>
140
+ )}
141
+ />
142
+ </CreateBlockFields>
143
+
144
+ <CreateBlockFooter>
145
+ <CreateBlockCancel>Annuleren</CreateBlockCancel>
146
+ <CreateBlockSubmit>Blok toevoegen</CreateBlockSubmit>
147
+ </CreateBlockFooter>
148
+ </CreateBlockContent>
149
+ </Form>
150
+ </CreateBlock>
151
+ );
152
+ }
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import type { BlockComponentProps } from "@tulip-systems/blocks";
4
+ import {
5
+ InlineBlock,
6
+ InlineBlockContent,
7
+ InlineBlockDragHandle,
8
+ InlineBlockHeader,
9
+ InlineBlockSortable,
10
+ InlineBlockTrigger,
11
+ } from "@tulip-systems/blocks/client";
12
+ import { DropdownCommandMenu } from "@tulip-systems/commands/client";
13
+ import {
14
+ InlineField,
15
+ InlineFieldError,
16
+ InlineFieldLabel,
17
+ InlineTextarea,
18
+ } from "@tulip-systems/inline/client";
19
+ import { DataStackLayout, DataStackRow } from "@tulip-systems/ui/data-stack";
20
+ import { GripIcon } from "lucide-react";
21
+ import { z } from "zod";
22
+ import { orpc } from "@/server/router/client";
23
+ import { quotationInlineBlockCommands } from "../commands";
24
+ import { quotationHeroBlock } from "./config";
25
+
26
+ const heroMutation = orpc.quotationBlocks.update.mutationOptions({
27
+ meta: { invalidates: [orpc.quotationBlocks.key()] },
28
+ });
29
+
30
+ export function QuotationHeroInlineBlock({
31
+ block,
32
+ }: BlockComponentProps<typeof quotationHeroBlock>) {
33
+ return (
34
+ <InlineBlockSortable id={block.id}>
35
+ <InlineBlock>
36
+ <InlineBlockHeader>
37
+ <InlineBlockDragHandle>
38
+ <GripIcon className="h-4 w-4" />
39
+ <span className="sr-only">Blok verplaatsen</span>
40
+ </InlineBlockDragHandle>
41
+ <InlineBlockTrigger>{quotationHeroBlock.label}</InlineBlockTrigger>
42
+ <DropdownCommandMenu
43
+ data={{ id: block.id }}
44
+ commands={quotationInlineBlockCommands}
45
+ className="ml-2 size-7"
46
+ />
47
+ </InlineBlockHeader>
48
+
49
+ <InlineBlockContent>
50
+ <DataStackLayout>
51
+ <DataStackRow>
52
+ <InlineField>
53
+ <InlineFieldLabel>Titel deel 1</InlineFieldLabel>
54
+ <InlineTextarea
55
+ initialValue={block.data.titlePart1}
56
+ variables={(value) => ({ id: block.id, data: { titlePart1: value ?? "" } })}
57
+ mutation={heroMutation}
58
+ strategy={{ mutate: { on: "blur" }, validate: { blur: z.string().min(1) } }}
59
+ />
60
+ <InlineFieldError />
61
+ </InlineField>
62
+
63
+ <InlineField>
64
+ <InlineFieldLabel>Titel deel 2</InlineFieldLabel>
65
+ <InlineTextarea
66
+ initialValue={block.data.titlePart2 ?? ""}
67
+ variables={(value) => ({ id: block.id, data: { titlePart2: value ?? "" } })}
68
+ mutation={heroMutation}
69
+ strategy={{ mutate: { on: "blur" }, validate: { blur: z.string() } }}
70
+ />
71
+ <InlineFieldError />
72
+ </InlineField>
73
+ </DataStackRow>
74
+
75
+ <InlineField className="md:col-span-2">
76
+ <InlineFieldLabel>Beschrijving</InlineFieldLabel>
77
+ <InlineTextarea
78
+ initialValue={block.data.description ?? ""}
79
+ variables={(value) => ({ id: block.id, data: { description: value ?? "" } })}
80
+ mutation={heroMutation}
81
+ strategy={{ mutate: { on: "blur" }, validate: { blur: z.string() } }}
82
+ />
83
+ <InlineFieldError />
84
+ </InlineField>
85
+ </DataStackLayout>
86
+ </InlineBlockContent>
87
+ </InlineBlock>
88
+ </InlineBlockSortable>
89
+ );
90
+ }