create-m5kdev 0.33.0 → 0.34.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 (147) hide show
  1. package/dist/src/catalog.cjs +21 -1
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +3 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs.map +1 -1
  7. package/dist/src/fs.cjs +10 -5
  8. package/dist/src/fs.cjs.map +1 -1
  9. package/dist/src/prompts.cjs +32 -0
  10. package/dist/src/prompts.cjs.map +1 -1
  11. package/dist/src/prompts.d.cts +7 -1
  12. package/dist/src/template.cjs +23 -4
  13. package/dist/src/template.cjs.map +1 -1
  14. package/dist/src/template.d.cts +17 -3
  15. package/dist/src/types.d.cts +11 -4
  16. package/package.json +4 -1
  17. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  18. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  19. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  20. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  21. package/templates/minimal-app/.dockerignore.tpl +31 -0
  22. package/templates/minimal-app/.gitignore.tpl +1 -0
  23. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  24. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  25. package/templates/minimal-app/README.md.tpl +50 -19
  26. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  27. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  28. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  29. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  30. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  31. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  32. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  33. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  34. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  35. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  36. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  37. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  38. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  39. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  40. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  41. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  42. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  43. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  44. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  45. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  46. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  47. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  48. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  49. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  50. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  51. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  52. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  53. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  54. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  55. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  56. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  57. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  58. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  59. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  60. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  61. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  62. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  63. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  64. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  65. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  67. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  68. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  69. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  70. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  71. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  72. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  73. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
  74. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  75. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  76. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  77. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  78. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  79. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  80. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  81. package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
  82. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  83. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  84. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  85. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  86. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  87. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
  88. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  89. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
  90. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  91. package/templates/minimal-app/apps/server/package.json.tpl +5 -3
  92. package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
  93. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  94. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
  95. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
  96. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  97. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  98. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
  99. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  100. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  103. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  104. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  105. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
  106. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  107. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  108. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  109. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  110. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  111. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  112. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  113. package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
  114. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  115. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  116. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  117. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  118. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  119. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  120. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  121. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  122. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  123. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  124. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  125. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  126. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  127. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  128. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  129. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  130. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  131. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  132. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  133. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  134. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  135. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  136. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  137. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  138. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  139. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  140. package/templates/minimal-app/biome.json.tpl +17 -12
  141. package/templates/minimal-app/package.json.tpl +6 -2
  142. package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
  143. package/templates/minimal-app/template.manifest.json +92 -2
  144. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  145. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  146. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  147. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
@@ -0,0 +1,88 @@
1
+ import { Button, Card, Chip, EmptyState, Skeleton } from "@heroui/react";
2
+ import { ImageIcon, UploadIcon } from "lucide-react";
3
+ import { useRef } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useFilesRoute } from "./hooks/useFilesRoute";
6
+
7
+ export function FilesRoute() {
8
+ const { t } = useTranslation("{{APP_SLUG}}");
9
+ const files = useFilesRoute();
10
+ const inputRef = useRef<HTMLInputElement>(null);
11
+
12
+ return (
13
+ <div className="p-4 grid gap-6" data-testid="files-route">
14
+ <Card>
15
+ <Card.Header className="flex flex-col gap-1">
16
+ <p className="text-xs font-medium uppercase tracking-wide text-muted">
17
+ {t("files.hero.eyebrow")}
18
+ </p>
19
+ <Card.Title>{t("files.hero.title")}</Card.Title>
20
+ <Card.Description>{t("files.hero.body")}</Card.Description>
21
+ </Card.Header>
22
+ <Card.Content className="grid gap-4">
23
+ <div className="flex flex-wrap items-center gap-3">
24
+ <input
25
+ ref={inputRef}
26
+ type="file"
27
+ accept="image/jpeg,image/png,image/webp"
28
+ className="sr-only"
29
+ data-testid="file-upload-input"
30
+ onChange={(event) => {
31
+ const file = event.target.files?.[0];
32
+ if (file) files.upload(file);
33
+ event.target.value = "";
34
+ }}
35
+ />
36
+ <Button
37
+ variant="primary"
38
+ isDisabled={files.isUploading}
39
+ data-testid="file-upload"
40
+ onPress={() => inputRef.current?.click()}
41
+ >
42
+ <span className="inline-flex items-center gap-2">
43
+ <UploadIcon className="h-4 w-4" />
44
+ {t("files.hero.upload")}
45
+ </span>
46
+ </Button>
47
+ <Chip variant="soft" color="default">
48
+ {files.isFetching ? t("files.hero.syncing") : t("files.hero.synced")}
49
+ </Chip>
50
+ </div>
51
+ </Card.Content>
52
+ </Card>
53
+
54
+ {files.isLoading ? (
55
+ <div className="grid gap-3">
56
+ <Skeleton className="h-24 rounded-xl" />
57
+ <Skeleton className="h-24 rounded-xl" />
58
+ </div>
59
+ ) : files.rows.length === 0 ? (
60
+ <EmptyState className="flex flex-col items-center gap-4 py-10 text-center">
61
+ <ImageIcon className="size-6 text-muted" />
62
+ <div className="grid gap-2">
63
+ <p className="text-lg font-semibold">{t("files.empty.title")}</p>
64
+ <p className="max-w-xl text-sm text-muted">{t("files.empty.body")}</p>
65
+ </div>
66
+ </EmptyState>
67
+ ) : (
68
+ <div className="grid gap-3">
69
+ {files.rows.map((row) => (
70
+ <Card key={row.id} data-testid="file-row">
71
+ <Card.Header className="flex flex-wrap items-start justify-between gap-3">
72
+ <div className="grid gap-1">
73
+ <Card.Title>{row.originalName}</Card.Title>
74
+ <Card.Description>{row.contentType}</Card.Description>
75
+ </div>
76
+ {row.memberId ? (
77
+ <Chip size="sm" variant="soft" color="default">
78
+ {t("files.row.organization")}
79
+ </Chip>
80
+ ) : null}
81
+ </Card.Header>
82
+ </Card>
83
+ ))}
84
+ </div>
85
+ )}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,50 @@
1
+ import { useFileUpload } from "@m5kdev/frontend/modules/file/hooks/useUpload";
2
+ import type { AppRouter } from "{{PACKAGE_SCOPE}}/server/types";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import type { inferRouterOutputs } from "@trpc/server";
5
+ import { useTranslation } from "react-i18next";
6
+ import { toast } from "sonner";
7
+ import { useTRPC } from "@/utils/trpc";
8
+
9
+ type FileListOutput = inferRouterOutputs<AppRouter>["file"]["list"];
10
+ type FileRow = FileListOutput["rows"][number];
11
+
12
+ interface FilesRouteState {
13
+ readonly rows: FileRow[];
14
+ readonly isLoading: boolean;
15
+ readonly isFetching: boolean;
16
+ readonly isUploading: boolean;
17
+ readonly upload: (file: File) => void;
18
+ }
19
+
20
+ export function useFilesRoute(): FilesRouteState {
21
+ const { t } = useTranslation("{{APP_SLUG}}");
22
+ const trpc = useTRPC();
23
+ const queryClient = useQueryClient();
24
+ const fileUpload = useFileUpload();
25
+
26
+ const listQuery = useQuery(trpc.file.list.queryOptions({}));
27
+
28
+ const uploadMutation = useMutation({
29
+ mutationFn: async (file: File) => {
30
+ await fileUpload.upload("image", file);
31
+ },
32
+ onSuccess: async () => {
33
+ toast.success(t("files.toast.uploaded"));
34
+ await queryClient.invalidateQueries(trpc.file.list.queryFilter());
35
+ },
36
+ onError: (error) => {
37
+ toast.error(error instanceof Error ? error.message : t("files.toast.failed"));
38
+ },
39
+ });
40
+
41
+ return {
42
+ rows: listQuery.data?.rows ?? [],
43
+ isLoading: listQuery.isLoading,
44
+ isFetching: listQuery.isFetching,
45
+ isUploading: uploadMutation.isPending,
46
+ upload: (file: File) => {
47
+ void uploadMutation.mutateAsync(file).catch(() => undefined);
48
+ },
49
+ };
50
+ }
@@ -0,0 +1,97 @@
1
+ import { Button, Card, Chip, EmptyState, Skeleton } from "@heroui/react";
2
+ import type { WorkflowStatus } from "@m5kdev/commons/modules/workflow/workflow.constants";
3
+ import { PlayIcon, WorkflowIcon } from "lucide-react";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useWorkflowsRoute } from "./hooks/useWorkflowsRoute";
6
+
7
+ function statusColor(status: WorkflowStatus): "success" | "danger" | "warning" | "default" {
8
+ if (status === "completed") return "success";
9
+ if (status === "failed") return "danger";
10
+ if (status === "running") return "warning";
11
+ return "default";
12
+ }
13
+
14
+ function formatTimestamp(value: Date | string): string {
15
+ return new Intl.DateTimeFormat(undefined, {
16
+ dateStyle: "medium",
17
+ timeStyle: "short",
18
+ }).format(new Date(value));
19
+ }
20
+
21
+ export function WorkflowsRoute() {
22
+ const { t } = useTranslation("{{APP_SLUG}}");
23
+ const workflows = useWorkflowsRoute();
24
+
25
+ return (
26
+ <div className="p-4 grid gap-6" data-testid="workflows-route">
27
+ <Card>
28
+ <Card.Header className="flex flex-col gap-1">
29
+ <p className="text-xs font-medium uppercase tracking-wide text-muted">
30
+ {t("workflows.hero.eyebrow")}
31
+ </p>
32
+ <Card.Title>{t("workflows.hero.title")}</Card.Title>
33
+ <Card.Description>{t("workflows.hero.body")}</Card.Description>
34
+ </Card.Header>
35
+ <Card.Content className="grid gap-4">
36
+ <div className="flex flex-wrap items-center gap-3">
37
+ <Button
38
+ variant="primary"
39
+ isDisabled={workflows.isTriggering}
40
+ data-testid="workflow-trigger"
41
+ onPress={() => workflows.trigger()}
42
+ >
43
+ <span className="inline-flex items-center gap-2">
44
+ <PlayIcon className="h-4 w-4" />
45
+ {t("workflows.hero.run")}
46
+ </span>
47
+ </Button>
48
+ <Chip variant="soft" color="default">
49
+ {workflows.isFetching ? t("workflows.hero.syncing") : t("workflows.hero.synced")}
50
+ </Chip>
51
+ </div>
52
+ </Card.Content>
53
+ </Card>
54
+
55
+ {workflows.isLoading ? (
56
+ <div className="grid gap-3">
57
+ <Skeleton className="h-24 rounded-xl" />
58
+ <Skeleton className="h-24 rounded-xl" />
59
+ </div>
60
+ ) : workflows.rows.length === 0 ? (
61
+ <EmptyState className="flex flex-col items-center gap-4 py-10 text-center">
62
+ <WorkflowIcon className="size-6 text-muted" />
63
+ <div className="grid gap-2">
64
+ <p className="text-lg font-semibold">{t("workflows.empty.title")}</p>
65
+ <p className="max-w-xl text-sm text-muted">{t("workflows.empty.body")}</p>
66
+ </div>
67
+ </EmptyState>
68
+ ) : (
69
+ <div className="grid gap-3">
70
+ {workflows.rows.map((row) => (
71
+ <Card key={row.jobId} data-testid="workflow-run">
72
+ <Card.Header className="flex flex-wrap items-start justify-between gap-3">
73
+ <div className="grid gap-1">
74
+ <Card.Title>{row.jobName}</Card.Title>
75
+ <Card.Description>
76
+ {t("workflows.columns.id")}: {row.jobId}
77
+ </Card.Description>
78
+ </div>
79
+ <Chip
80
+ size="sm"
81
+ variant="soft"
82
+ color={statusColor(row.status)}
83
+ data-testid="workflow-run-status"
84
+ >
85
+ {t(`workflows.status.${row.status}`)}
86
+ </Chip>
87
+ </Card.Header>
88
+ <Card.Footer className="text-sm text-muted">
89
+ {t("workflows.columns.updated")}: {formatTimestamp(row.updatedAt)}
90
+ </Card.Footer>
91
+ </Card>
92
+ ))}
93
+ </div>
94
+ )}
95
+ </div>
96
+ );
97
+ }
@@ -0,0 +1,64 @@
1
+ import type { WorkflowStatus } from "@m5kdev/commons/modules/workflow/workflow.constants";
2
+ import type { AppRouter } from "{{PACKAGE_SCOPE}}/server/types";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import type { inferRouterOutputs } from "@trpc/server";
5
+ import { useTranslation } from "react-i18next";
6
+ import { toast } from "sonner";
7
+ import { useTRPC } from "@/utils/trpc";
8
+
9
+ type WorkflowListOutput = inferRouterOutputs<AppRouter>["workflow"]["list"];
10
+ type WorkflowRunRow = WorkflowListOutput[number];
11
+
12
+ const OPEN_STATUSES: ReadonlySet<WorkflowStatus> = new Set(["queued", "running"]);
13
+
14
+ interface WorkflowsRouteState {
15
+ readonly rows: WorkflowRunRow[];
16
+ readonly isLoading: boolean;
17
+ readonly isFetching: boolean;
18
+ readonly isTriggering: boolean;
19
+ readonly trigger: () => void;
20
+ }
21
+
22
+ export function useWorkflowsRoute(): WorkflowsRouteState {
23
+ const { t } = useTranslation("{{APP_SLUG}}");
24
+ const trpc = useTRPC();
25
+ const queryClient = useQueryClient();
26
+
27
+ const listQuery = useQuery(
28
+ trpc.workflow.list.queryOptions(
29
+ {},
30
+ {
31
+ refetchInterval: (query) => {
32
+ const rows = query.state.data ?? [];
33
+ return rows.some((row) => OPEN_STATUSES.has(row.status)) ? 750 : false;
34
+ },
35
+ }
36
+ )
37
+ );
38
+
39
+ const invalidateList = async (): Promise<void> => {
40
+ await queryClient.invalidateQueries(trpc.workflow.list.queryFilter());
41
+ };
42
+
43
+ const runMutation = useMutation(
44
+ trpc.demoWorkflow.run.mutationOptions({
45
+ onSuccess: async () => {
46
+ toast.success(t("workflows.toast.triggered"));
47
+ await invalidateList();
48
+ },
49
+ onError: (error) => {
50
+ toast.error(error instanceof Error ? error.message : String(error));
51
+ },
52
+ })
53
+ );
54
+
55
+ return {
56
+ rows: listQuery.data ?? [],
57
+ isLoading: listQuery.isLoading,
58
+ isFetching: listQuery.isFetching,
59
+ isTriggering: runMutation.isPending,
60
+ trigger: () => {
61
+ void runMutation.mutateAsync().catch(() => undefined);
62
+ },
63
+ };
64
+ }
@@ -1,13 +1,13 @@
1
- // @ts-expect-error-next-line: virtual module
2
- import resources from "virtual:i18next-loader";
3
- import i18n from "i18next";
4
- import { initReactI18next } from "react-i18next";
5
-
6
- i18n.use(initReactI18next).init({
7
- fallbackLng: "en",
8
- debug: import.meta.env.MODE === "development",
9
- interpolation: {
10
- escapeValue: false,
11
- },
12
- resources,
13
- });
1
+ // @ts-expect-error-next-line: virtual module
2
+ import resources from "virtual:i18next-loader";
3
+ import i18n from "i18next";
4
+ import { initReactI18next } from "react-i18next";
5
+
6
+ i18n.use(initReactI18next).init({
7
+ fallbackLng: "en",
8
+ debug: import.meta.env.MODE === "development",
9
+ interpolation: {
10
+ escapeValue: false,
11
+ },
12
+ resources,
13
+ });
@@ -1,10 +1,10 @@
1
- import { useAppTRPC, useAppTRPCClient } from "@m5kdev/frontend/modules/app/hooks/useAppTrpc";
2
- import type { AppRouter } from "{{PACKAGE_SCOPE}}/server/types";
3
-
4
- export function useTRPC() {
5
- return useAppTRPC<AppRouter>();
6
- }
7
-
8
- export function useTRPCClient() {
9
- return useAppTRPCClient<AppRouter>();
10
- }
1
+ import { useAppTRPC, useAppTRPCClient } from "@m5kdev/frontend/modules/app/hooks/useAppTrpc";
2
+ import type { AppRouter } from "{{PACKAGE_SCOPE}}/server/types";
3
+
4
+ export function useTRPC() {
5
+ return useAppTRPC<AppRouter>();
6
+ }
7
+
8
+ export function useTRPCClient() {
9
+ return useAppTRPCClient<AppRouter>();
10
+ }
@@ -1 +1 @@
1
- /// <reference types="vite/client" />
1
+ /// <reference types="vite/client" />
@@ -1,7 +1,7 @@
1
- {
2
- "user.role.user": "User",
3
- "user.role.admin": "Admin",
4
- "organization.role.member": "Member",
5
- "organization.role.admin": "Admin",
6
- "organization.role.owner": "Owner"
7
- }
1
+ {
2
+ "user.role.user": "User",
3
+ "user.role.admin": "Admin",
4
+ "organization.role.member": "Member",
5
+ "organization.role.admin": "Admin",
6
+ "organization.role.owner": "Owner"
7
+ }