create-m5kdev 0.25.2 → 0.26.1

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 (143) hide show
  1. package/dist/src/create.cjs +18 -1
  2. package/dist/src/create.cjs.map +1 -1
  3. package/dist/src/fs.cjs +65 -5
  4. package/dist/src/fs.cjs.map +1 -1
  5. package/dist/src/fs.d.cts +13 -2
  6. package/dist/src/index.cjs +0 -0
  7. package/dist/src/paths.cjs +2 -2
  8. package/dist/src/prompts.cjs +22 -0
  9. package/dist/src/prompts.cjs.map +1 -1
  10. package/dist/src/runCli.cjs +13 -2
  11. package/dist/src/runCli.cjs.map +1 -1
  12. package/dist/src/types.d.cts +9 -1
  13. package/package.json +8 -6
  14. package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +46 -3
  15. package/templates/minimal-app/.cursor/rules/module-db-guide.mdc +15 -1
  16. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +60 -0
  17. package/templates/minimal-app/.cursor/rules/module-repository-guide.mdc +98 -193
  18. package/templates/minimal-app/.cursor/rules/module-schema-guide.mdc +53 -173
  19. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +133 -240
  20. package/templates/minimal-app/.cursor/rules/module-trpc-guide.mdc +71 -113
  21. package/templates/minimal-app/README.md.tpl +3 -3
  22. package/templates/minimal-app/apps/.gitignore.tpl +13 -0
  23. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -0
  24. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +39 -0
  25. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -0
  26. package/templates/minimal-app/apps/e2e/servers.ts.tpl +100 -0
  27. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -0
  28. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -0
  29. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -0
  30. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -0
  31. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -0
  32. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -0
  33. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -0
  34. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -0
  35. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  36. package/templates/minimal-app/apps/email/src/emails/accountDeletionEmail.tsx.tpl +35 -34
  37. package/templates/minimal-app/apps/email/src/emails/organizationInviteEmail.tsx.tpl +34 -33
  38. package/templates/minimal-app/apps/email/src/emails/passwordResetEmail.tsx.tpl +35 -34
  39. package/templates/minimal-app/apps/email/src/emails/verificationEmail.tsx.tpl +37 -36
  40. package/templates/minimal-app/apps/email/src/emails/waitlistEmails.tsx.tpl +87 -0
  41. package/templates/minimal-app/apps/email/src/index.ts.tpl +72 -32
  42. package/templates/minimal-app/apps/email/src/resources.ts.tpl +65 -36
  43. package/templates/minimal-app/apps/email/tsconfig.json.tpl +16 -11
  44. package/templates/minimal-app/apps/expo/.gitignore.tpl +44 -0
  45. package/templates/minimal-app/apps/expo/.prettierignore +13 -0
  46. package/templates/minimal-app/apps/expo/.prettierrc.json.tpl +9 -0
  47. package/templates/minimal-app/apps/expo/CHANGELOG.md.tpl +156 -0
  48. package/templates/minimal-app/apps/expo/README.md.tpl +44 -0
  49. package/templates/minimal-app/apps/expo/app.json.tpl +36 -0
  50. package/templates/minimal-app/apps/expo/assets/images/adaptive-icon.png +0 -0
  51. package/templates/minimal-app/apps/expo/assets/images/favicon.png +0 -0
  52. package/templates/minimal-app/apps/expo/assets/images/icon.png +0 -0
  53. package/templates/minimal-app/apps/expo/assets/images/splash-icon.png +0 -0
  54. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -0
  55. package/templates/minimal-app/apps/expo/expo-env.d.ts.tpl +3 -0
  56. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -0
  57. package/templates/minimal-app/apps/expo/package.json.tpl +59 -0
  58. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -0
  59. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -0
  60. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -0
  61. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -0
  62. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -0
  63. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -0
  64. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -0
  65. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -0
  66. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -0
  67. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -0
  68. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -0
  69. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -0
  70. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -0
  71. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -0
  72. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -0
  73. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -0
  74. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -0
  75. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -0
  76. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -0
  77. package/templates/minimal-app/apps/expo/uniwind-types.d.ts.tpl +10 -0
  78. package/templates/minimal-app/apps/server/AGENTS.md.tpl +39 -39
  79. package/templates/minimal-app/apps/server/drizzle/0000_wealthy_absorbing_man.sql +268 -0
  80. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +26 -0
  81. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +1890 -0
  82. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +13 -0
  83. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +33 -0
  84. package/templates/minimal-app/apps/server/drizzle/seed.e2e.ts.tpl +244 -0
  85. package/templates/minimal-app/apps/server/drizzle/seed.ts.tpl +181 -180
  86. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +14 -21
  87. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +33 -35
  88. package/templates/minimal-app/apps/server/package.json.tpl +56 -52
  89. package/templates/minimal-app/apps/server/src/app.ts.tpl +142 -126
  90. package/templates/minimal-app/apps/server/src/index.ts.tpl +58 -58
  91. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +12 -12
  92. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +25 -25
  93. package/templates/minimal-app/apps/server/src/modules/posts/posts.dto.ts.tpl +23 -0
  94. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +30 -40
  95. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  96. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -112
  97. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +104 -170
  98. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -48
  99. package/templates/minimal-app/apps/server/src/modules/posts/posts.utils.ts.tpl +19 -0
  100. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -0
  101. package/templates/minimal-app/apps/server/src/schema.ts.tpl +20 -0
  102. package/templates/minimal-app/apps/server/src/types.ts.tpl +1 -1
  103. package/templates/minimal-app/apps/server/tsconfig.json.tpl +20 -14
  104. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +9 -9
  105. package/templates/minimal-app/apps/shared/package.json.tpl +24 -24
  106. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -2
  107. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  108. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  109. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -76
  110. package/templates/minimal-app/apps/shared/tsconfig.json.tpl +16 -12
  111. package/templates/minimal-app/apps/webapp/index.html.tpl +22 -22
  112. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -52
  113. package/templates/minimal-app/apps/webapp/public/{push-sw.js → push-sw.js.tpl} +34 -34
  114. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +44 -13
  115. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +18 -12
  116. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +33 -40
  117. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -75
  118. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  119. package/templates/minimal-app/apps/webapp/src/modules/posts/PostsRoute.tsx.tpl +278 -307
  120. package/templates/minimal-app/apps/webapp/src/modules/posts/components/PostCard.tsx.tpl +96 -138
  121. package/templates/minimal-app/apps/webapp/src/modules/posts/components/PostEditorModal.tsx.tpl +118 -124
  122. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostActions.ts.tpl +108 -116
  123. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostsList.ts.tpl +15 -9
  124. package/templates/minimal-app/apps/webapp/src/modules/posts/hooks/usePostsRoute.ts.tpl +13 -13
  125. package/templates/minimal-app/apps/webapp/src/modules/posts/posts.utils.ts.tpl +16 -16
  126. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  127. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -6
  128. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  129. package/templates/minimal-app/apps/webapp/translations/{app.json.tpl → en/app.json.tpl} +7 -7
  130. package/templates/minimal-app/apps/webapp/translations/en/{blog-app.json.tpl → {{APP_SLUG}}.json.tpl} +135 -135
  131. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  132. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  133. package/templates/minimal-app/template.manifest.json +21 -0
  134. package/templates/minimal-app/apps/server/drizzle/generate-schema.ts.tpl +0 -31
  135. package/templates/minimal-app/apps/server/src/db.ts.tpl +0 -7
  136. package/templates/minimal-app/apps/server/src/generated/schema.ts.tpl +0 -13
  137. package/templates/minimal-app/apps/server/src/utils/trpc.ts.tpl +0 -8
  138. package/templates/minimal-app/apps/shared/src/modules/app/locale.constants.ts.tpl +0 -7
  139. package/templates/minimal-app/apps/shared/src/modules/app/roles.constants.ts.tpl +0 -17
  140. package/templates/minimal-app/apps/webapp/src/Providers.tsx.tpl +0 -36
  141. package/templates/minimal-app/apps/webapp/src/modules/notification/PushNotificationsPanel.tsx.tpl +0 -89
  142. package/templates/minimal-app/apps/webapp/src/navigation/navigation.config.tsx.tpl +0 -85
  143. /package/templates/minimal-app/apps/webapp/public/{logo.svg → logo.svg.tpl} +0 -0
@@ -1,116 +1,108 @@
1
- import { useDialog } from "@m5kdev/web-ui/components/DialogProvider";
2
- import type {
3
- PostCreateInputSchema,
4
- PostPublishInputSchema,
5
- PostSoftDeleteInputSchema,
6
- PostUpdateInputSchema,
7
- } from "{{PACKAGE_SCOPE}}/shared/modules/posts/posts.schema";
8
- import { useMutation, useQueryClient } from "@tanstack/react-query";
9
- import { useTranslation } from "react-i18next";
10
- import { toast } from "sonner";
11
- import { useTRPC } from "@/utils/trpc";
12
-
13
- /**
14
- * Reusable post mutations: save (create/update), publish, and delete with a
15
- * confirmation dialog. Toasts and list invalidation live here so any component
16
- * can trigger the same action with the same behavior.
17
- */
18
- export function usePostActions() {
19
- const { t } = useTranslation("blog{{PACKAGE_SCOPE}}");
20
- const trpc = useTRPC();
21
- const queryClient = useQueryClient();
22
- const showDialog = useDialog();
23
-
24
- const invalidateList = async () => {
25
- await queryClient.invalidateQueries(trpc.posts.list.queryFilter());
26
- };
27
-
28
- const onError = (error: unknown) => {
29
- toast.error(error instanceof Error ? error.message : String(error));
30
- };
31
-
32
- const createMutation = useMutation(
33
- trpc.posts.create.mutationOptions({
34
- onSuccess: async () => {
35
- toast.success(t("posts.toast.created"));
36
- await invalidateList();
37
- },
38
- onError,
39
- })
40
- );
41
-
42
- const updateMutation = useMutation(
43
- trpc.posts.update.mutationOptions({
44
- onSuccess: async () => {
45
- toast.success(t("posts.toast.updated"));
46
- await invalidateList();
47
- },
48
- onError,
49
- })
50
- );
51
-
52
- const publishMutation = useMutation(
53
- trpc.posts.publish.mutationOptions({
54
- onSuccess: async () => {
55
- toast.success(t("posts.toast.published"));
56
- await invalidateList();
57
- },
58
- onError,
59
- })
60
- );
61
-
62
- const deleteMutation = useMutation(
63
- trpc.posts.softDelete.mutationOptions({
64
- onSuccess: async () => {
65
- toast.success(t("posts.toast.deleted"));
66
- await invalidateList();
67
- },
68
- onError,
69
- })
70
- );
71
-
72
- /** Create when `id` is missing, update otherwise. Resolves false on failure (toast already shown). */
73
- const savePost = async (payload: PostCreateInputSchema, id?: string): Promise<boolean> => {
74
- try {
75
- if (id) {
76
- await updateMutation.mutateAsync({ id, ...payload } as PostUpdateInputSchema);
77
- } else {
78
- await createMutation.mutateAsync(payload);
79
- }
80
- return true;
81
- } catch {
82
- return false;
83
- }
84
- };
85
-
86
- const publishPost = (id: string) => {
87
- void publishMutation
88
- .mutateAsync({ id } satisfies PostPublishInputSchema)
89
- .catch(() => undefined);
90
- };
91
-
92
- const deletePost = (id: string) => {
93
- showDialog({
94
- title: t("posts.deleteDialog.title"),
95
- description: t("posts.deleteDialog.body"),
96
- intent: "danger",
97
- cancelable: true,
98
- confirmLabel: t("posts.deleteDialog.confirm"),
99
- cancelLabel: t("posts.deleteDialog.cancel"),
100
- onConfirm: () => {
101
- void deleteMutation
102
- .mutateAsync({ id } satisfies PostSoftDeleteInputSchema)
103
- .catch(() => undefined);
104
- },
105
- });
106
- };
107
-
108
- return {
109
- savePost,
110
- publishPost,
111
- deletePost,
112
- isSaving: createMutation.isPending || updateMutation.isPending,
113
- publishingId: publishMutation.isPending ? publishMutation.variables?.id : undefined,
114
- deletingId: deleteMutation.isPending ? deleteMutation.variables?.id : undefined,
115
- };
116
- }
1
+ import { useDialog } from "@m5kdev/web-ui/components/DialogProvider";
2
+ import type { PostCreateInputSchema } from "{{PACKAGE_SCOPE}}/shared/modules/posts/posts.schema";
3
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
4
+ import { useTranslation } from "react-i18next";
5
+ import { toast } from "sonner";
6
+ import { useTRPC } from "@/utils/trpc";
7
+
8
+ /**
9
+ * Reusable post mutations: save (create/update), publish, and delete with a
10
+ * confirmation dialog. Toasts and list invalidation live here so any component
11
+ * can trigger the same action with the same behavior.
12
+ */
13
+ export function usePostActions() {
14
+ const { t } = useTranslation("{{APP_SLUG}}");
15
+ const trpc = useTRPC();
16
+ const queryClient = useQueryClient();
17
+ const showDialog = useDialog();
18
+
19
+ const invalidateList = async () => {
20
+ await queryClient.invalidateQueries(trpc.posts.list.queryFilter());
21
+ };
22
+
23
+ const onError = (error: unknown) => {
24
+ toast.error(error instanceof Error ? error.message : String(error));
25
+ };
26
+
27
+ const createMutation = useMutation(
28
+ trpc.posts.create.mutationOptions({
29
+ onSuccess: async () => {
30
+ toast.success(t("posts.toast.created"));
31
+ await invalidateList();
32
+ },
33
+ onError,
34
+ })
35
+ );
36
+
37
+ const updateMutation = useMutation(
38
+ trpc.posts.update.mutationOptions({
39
+ onSuccess: async () => {
40
+ toast.success(t("posts.toast.updated"));
41
+ await invalidateList();
42
+ },
43
+ onError,
44
+ })
45
+ );
46
+
47
+ const publishMutation = useMutation(
48
+ trpc.posts.publish.mutationOptions({
49
+ onSuccess: async () => {
50
+ toast.success(t("posts.toast.published"));
51
+ await invalidateList();
52
+ },
53
+ onError,
54
+ })
55
+ );
56
+
57
+ const deleteMutation = useMutation(
58
+ trpc.posts.softDelete.mutationOptions({
59
+ onSuccess: async () => {
60
+ toast.success(t("posts.toast.deleted"));
61
+ await invalidateList();
62
+ },
63
+ onError,
64
+ })
65
+ );
66
+
67
+ /** Create when `id` is missing, update otherwise. Resolves false on failure (toast already shown). */
68
+ const savePost = async (payload: PostCreateInputSchema, id?: string): Promise<boolean> => {
69
+ try {
70
+ if (id) {
71
+ await updateMutation.mutateAsync({ id, ...payload });
72
+ } else {
73
+ await createMutation.mutateAsync(payload);
74
+ }
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ };
80
+
81
+ const publishPost = (id: string) => {
82
+ // input shape is enforced by the router's inferred mutation type
83
+ void publishMutation.mutateAsync({ id }).catch(() => undefined);
84
+ };
85
+
86
+ const deletePost = (id: string) => {
87
+ showDialog({
88
+ title: t("posts.deleteDialog.title"),
89
+ description: t("posts.deleteDialog.body"),
90
+ intent: "danger",
91
+ cancelable: true,
92
+ confirmLabel: t("posts.deleteDialog.confirm"),
93
+ cancelLabel: t("posts.deleteDialog.cancel"),
94
+ onConfirm: () => {
95
+ void deleteMutation.mutateAsync({ id }).catch(() => undefined);
96
+ },
97
+ });
98
+ };
99
+
100
+ return {
101
+ savePost,
102
+ publishPost,
103
+ deletePost,
104
+ isSaving: createMutation.isPending || updateMutation.isPending,
105
+ publishingId: publishMutation.isPending ? publishMutation.variables?.id : undefined,
106
+ deletingId: deleteMutation.isPending ? deleteMutation.variables?.id : undefined,
107
+ };
108
+ }
@@ -1,18 +1,20 @@
1
+ import type { QueryInput } from "@m5kdev/commons/modules/schemas/query.schema";
1
2
  import {
2
3
  POST_FILTER_VALUES,
3
4
  POSTS_PAGE_SIZE,
4
5
  } from "{{PACKAGE_SCOPE}}/shared/modules/posts/posts.constants";
5
- import type {
6
- PostsListInputSchema,
7
- PostsListOutputSchema,
8
- } from "{{PACKAGE_SCOPE}}/shared/modules/posts/posts.schema";
6
+ import type { AppRouter } from "{{PACKAGE_SCOPE}}/server/types";
9
7
  import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
8
+ import type { inferRouterOutputs } from "@trpc/server";
10
9
  import { parseAsInteger, parseAsString, parseAsStringLiteral, useQueryState } from "nuqs";
11
10
  import { startTransition, useDeferredValue, useMemo } from "react";
12
11
  import { useTRPC } from "@/utils/trpc";
13
12
 
14
13
  export type PostStatusFilter = (typeof POST_FILTER_VALUES)[number];
15
- export type PostRow = PostsListOutputSchema["rows"][number];
14
+
15
+ // row/list types are inferred from the server router — no hand-written mirrors
16
+ type PostsListOutput = inferRouterOutputs<AppRouter>["posts"]["list"];
17
+ export type PostRow = PostsListOutput["rows"][number];
16
18
 
17
19
  const STATUS_PARSER = parseAsStringLiteral(POST_FILTER_VALUES).withDefault("all");
18
20
 
@@ -26,12 +28,16 @@ export function usePostsList() {
26
28
 
27
29
  const deferredSearch = useDeferredValue(search);
28
30
 
29
- const listInput = useMemo<PostsListInputSchema>(
31
+ // the generic list contract: q for global search, filters for column filters
32
+ const listInput = useMemo<QueryInput>(
30
33
  () => ({
31
34
  page,
32
35
  limit: POSTS_PAGE_SIZE,
33
- search: deferredSearch || undefined,
34
- status: status === "all" ? undefined : status,
36
+ q: deferredSearch || undefined,
37
+ filters:
38
+ status === "all"
39
+ ? undefined
40
+ : [{ columnId: "status", type: "enum", method: "equals", value: status }],
35
41
  sort: "updatedAt",
36
42
  order: "desc",
37
43
  }),
@@ -39,7 +45,7 @@ export function usePostsList() {
39
45
  );
40
46
 
41
47
  const { data, isLoading, isFetching } = useQuery(
42
- trpc.posts.list.queryOptions(listInput) as unknown as UseQueryOptions<PostsListOutputSchema>
48
+ trpc.posts.list.queryOptions(listInput) as unknown as UseQueryOptions<PostsListOutput>
43
49
  );
44
50
 
45
51
  const rows = data?.rows ?? [];
@@ -1,13 +1,13 @@
1
- import { usePostActions } from "./usePostActions";
2
- import { usePostsList } from "./usePostsList";
3
-
4
- /**
5
- * Screen-level hook for the posts route: composes the list query/URL state and
6
- * the reusable post actions so the route component makes a single call.
7
- */
8
- export function usePostsRoute() {
9
- const list = usePostsList();
10
- const actions = usePostActions();
11
-
12
- return { ...list, ...actions };
13
- }
1
+ import { usePostActions } from "./usePostActions";
2
+ import { usePostsList } from "./usePostsList";
3
+
4
+ /**
5
+ * Screen-level hook for the posts route: composes the list query/URL state and
6
+ * the reusable post actions so the route component makes a single call.
7
+ */
8
+ export function usePostsRoute() {
9
+ const list = usePostsList();
10
+ const actions = usePostActions();
11
+
12
+ return { ...list, ...actions };
13
+ }
@@ -1,16 +1,16 @@
1
- export function formatDate(value: Date | null | undefined): string {
2
- if (!value) {
3
- return "Not scheduled";
4
- }
5
-
6
- return new Intl.DateTimeFormat(undefined, {
7
- dateStyle: "medium",
8
- timeStyle: "short",
9
- }).format(new Date(value));
10
- }
11
-
12
- export function getReadingTime(content: string): string {
13
- const words = content.trim().split(/\s+/).filter(Boolean).length;
14
- const minutes = Math.max(1, Math.ceil(words / 180));
15
- return `${minutes} min read`;
16
- }
1
+ export function formatDate(value: Date | null | undefined): string {
2
+ if (!value) {
3
+ return "Not scheduled";
4
+ }
5
+
6
+ return new Intl.DateTimeFormat(undefined, {
7
+ dateStyle: "medium",
8
+ timeStyle: "short",
9
+ }).format(new Date(value));
10
+ }
11
+
12
+ export function getReadingTime(content: string): string {
13
+ const words = content.trim().split(/\s+/).filter(Boolean).length;
14
+ const minutes = Math.max(1, Math.ceil(words / 180));
15
+ return `${minutes} min read`;
16
+ }
@@ -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,6 +1,10 @@
1
- import { useAppTRPC } from "@m5kdev/frontend/modules/app/hooks/useAppTrpc";
2
- import type { AppRouter } from "{{PACKAGE_SCOPE}}/server/types";
3
- import type { TRPCOptionsProxy } from "@trpc/tanstack-react-query";
4
- export function useTRPC(): TRPCOptionsProxy<AppRouter> {
5
- return useAppTRPC<AppRouter>();
6
- }
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
+ }