@leo-h/create-nodejs-app 1.0.68 → 1.0.69

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 (35) hide show
  1. package/dist/src/validations/front-end-framework.validation.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/react-vite-tanstack-router/.env.development +5 -0
  4. package/templates/react-vite-tanstack-router/.env.example +5 -0
  5. package/templates/react-vite-tanstack-router/.vscode/settings.json +12 -0
  6. package/templates/react-vite-tanstack-router/README.md +73 -0
  7. package/templates/react-vite-tanstack-router/biome.json +16 -0
  8. package/templates/react-vite-tanstack-router/gitignore +24 -0
  9. package/templates/react-vite-tanstack-router/index.html +12 -0
  10. package/templates/react-vite-tanstack-router/kubb.config.ts +43 -0
  11. package/templates/react-vite-tanstack-router/npmrc +1 -0
  12. package/templates/react-vite-tanstack-router/package.json +47 -0
  13. package/templates/react-vite-tanstack-router/pnpm-lock.yaml +3773 -0
  14. package/templates/react-vite-tanstack-router/public/vite.svg +1 -0
  15. package/templates/react-vite-tanstack-router/src/api/errors.ts +17 -0
  16. package/templates/react-vite-tanstack-router/src/api/swr-fetcher.ts +100 -0
  17. package/templates/react-vite-tanstack-router/src/env.ts +16 -0
  18. package/templates/react-vite-tanstack-router/src/index.css +29 -0
  19. package/templates/react-vite-tanstack-router/src/lib/swr-config.ts +8 -0
  20. package/templates/react-vite-tanstack-router/src/lib/tanstack-router-instance.ts +16 -0
  21. package/templates/react-vite-tanstack-router/src/lib/zod-config.ts +97 -0
  22. package/templates/react-vite-tanstack-router/src/lib/zod-custom-error-map.ts +259 -0
  23. package/templates/react-vite-tanstack-router/src/main.tsx +20 -0
  24. package/templates/react-vite-tanstack-router/src/pages/__root.tsx +12 -0
  25. package/templates/react-vite-tanstack-router/src/pages/_app/index.tsx +24 -0
  26. package/templates/react-vite-tanstack-router/src/pages/_app/layout.tsx +15 -0
  27. package/templates/react-vite-tanstack-router/src/pages/_app/styles.css +40 -0
  28. package/templates/react-vite-tanstack-router/src/pages/_auth/layout.tsx +15 -0
  29. package/templates/react-vite-tanstack-router/src/pages/_auth/sign-in/index.tsx +94 -0
  30. package/templates/react-vite-tanstack-router/src/pages/_auth/sign-in/styles.css +92 -0
  31. package/templates/react-vite-tanstack-router/src/route-tree.gen.ts +127 -0
  32. package/templates/react-vite-tanstack-router/tsconfig.app.json +34 -0
  33. package/templates/react-vite-tanstack-router/tsconfig.json +7 -0
  34. package/templates/react-vite-tanstack-router/tsconfig.node.json +32 -0
  35. package/templates/react-vite-tanstack-router/vite.config.ts +23 -0
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1,17 @@
1
+ export class ApiError {
2
+ public readonly name: string;
3
+ public readonly statusCode: number;
4
+ public readonly message: string;
5
+
6
+ constructor(data: { name: string; statusCode: number; message: string }) {
7
+ this.name = data.name;
8
+ this.statusCode = data.statusCode;
9
+ this.message = data.message;
10
+ }
11
+ }
12
+
13
+ export class ApiUnknownError {
14
+ static readonly message =
15
+ "Houve uma falha inesperada ao se comunicar com nosso servidor. Tente novamente em alguns segundos ou nos contate se o problema persistir.";
16
+ public message = ApiUnknownError.message;
17
+ }
@@ -0,0 +1,100 @@
1
+ import { env } from "@/env";
2
+ import { ApiError, ApiUnknownError } from "./errors";
3
+
4
+ export type RequestConfig<Input = unknown> = {
5
+ url: string;
6
+ method: "GET" | "PUT" | "PATCH" | "POST" | "DELETE";
7
+ params?: object;
8
+ data?: Input | FormData;
9
+ responseType?:
10
+ | "arraybuffer"
11
+ | "blob"
12
+ | "document"
13
+ | "json"
14
+ | "text"
15
+ | "stream";
16
+ signal?: AbortSignal;
17
+ headers?: HeadersInit;
18
+ };
19
+
20
+ export type ResponseConfig<Output = unknown> = {
21
+ body: Output;
22
+ response: Response;
23
+ };
24
+
25
+ export type ResponseErrorConfig<Error> = Error;
26
+
27
+ async function swrFetcher<Output, _Error = unknown, Input = unknown>(
28
+ config: RequestConfig<Input>,
29
+ ): Promise<ResponseConfig<Output>> {
30
+ const url = getFullApiUrlByEndpointUrl(config.url, config.params);
31
+ let response: Response;
32
+
33
+ try {
34
+ response = await fetch(url, {
35
+ credentials: "include",
36
+ method: config.method,
37
+ headers: config.headers,
38
+ signal: config.signal,
39
+ body: config.data as FormData,
40
+ ...(!(config.data instanceof FormData) && {
41
+ body: JSON.stringify(config.data),
42
+ headers: {
43
+ ...config.headers,
44
+ "content-type": "application/json",
45
+ },
46
+ }),
47
+ });
48
+ } catch {
49
+ throw new ApiUnknownError();
50
+ }
51
+ let body: unknown = null;
52
+
53
+ if (
54
+ response.headers.get("Content-Type")?.includes("application/json") &&
55
+ response.status !== 204
56
+ ) {
57
+ try {
58
+ body = await response.json();
59
+ } catch {
60
+ body = null;
61
+ }
62
+ }
63
+
64
+ if (!response.ok) {
65
+ const error = body as ApiError | null;
66
+
67
+ if (error) throw new ApiError(error);
68
+
69
+ throw new ApiUnknownError();
70
+ }
71
+
72
+ return {
73
+ body: body as Output,
74
+ response,
75
+ };
76
+ }
77
+
78
+ function getFullApiUrlByEndpointUrl(endpointUrl: string, queryParams?: object) {
79
+ const url = new URL(env.PUBLIC_API_BASE_URL);
80
+
81
+ if (url.pathname.endsWith("/")) {
82
+ url.pathname = endpointUrl;
83
+ } else {
84
+ url.pathname += endpointUrl;
85
+ }
86
+
87
+ if (queryParams) {
88
+ for (const queryParamKey of Object.keys(queryParams)) {
89
+ const searchParamValue =
90
+ queryParams[queryParamKey as keyof typeof queryParams];
91
+
92
+ if (searchParamValue)
93
+ url.searchParams.set(queryParamKey, searchParamValue);
94
+ }
95
+ }
96
+
97
+ return url;
98
+ }
99
+
100
+ export default swrFetcher;
@@ -0,0 +1,16 @@
1
+ import { treeifyError, z } from "zod";
2
+
3
+ const schema = z.object({
4
+ PUBLIC_APP_NAME: z.string(),
5
+ PUBLIC_API_BASE_URL: z.url().transform((url) => new URL(url)),
6
+ });
7
+
8
+ const parsedEnv = schema.safeParse(import.meta.env);
9
+
10
+ if (!parsedEnv.success) {
11
+ console.error(treeifyError(parsedEnv.error));
12
+
13
+ throw new Error("Invalid environment variables.");
14
+ }
15
+
16
+ export const env = parsedEnv.data;
@@ -0,0 +1,29 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ font-size: 1.6rem;
6
+ line-height: 1.5;
7
+
8
+ @media (max-width: 640px) {
9
+ font-size: 1.5rem;
10
+ }
11
+ }
12
+
13
+ :root {
14
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
15
+ font-size: 62.5%;
16
+ font-synthesis: none;
17
+ text-rendering: optimizeLegibility;
18
+ -webkit-font-smoothing: antialiased;
19
+ -moz-osx-font-smoothing: grayscale;
20
+ }
21
+
22
+ body {
23
+ background-color: #0d0d0d;
24
+ color: #d4d4d8;
25
+ }
26
+
27
+ a {
28
+ text-decoration: none;
29
+ }
@@ -0,0 +1,8 @@
1
+ import type { SWRConfiguration } from "swr";
2
+
3
+ export const swrConfig: SWRConfiguration = {
4
+ errorRetryCount: 3,
5
+ errorRetryInterval: 3500,
6
+ shouldRetryOnError: false,
7
+ revalidateOnFocus: false,
8
+ };
@@ -0,0 +1,16 @@
1
+ import { routeTree } from "@/route-tree.gen";
2
+ import { createRouter } from "@tanstack/react-router";
3
+
4
+ declare module "@tanstack/react-router" {
5
+ interface Register {
6
+ router: typeof tanstackRouterInstance;
7
+ }
8
+ }
9
+
10
+ export const tanstackRouterInstance = createRouter({
11
+ routeTree,
12
+ defaultPreload: "intent",
13
+ scrollRestoration: true,
14
+ defaultStructuralSharing: true,
15
+ defaultPreloadStaleTime: 0,
16
+ });
@@ -0,0 +1,97 @@
1
+ import prettyBytes from "pretty-bytes";
2
+ import { z } from "zod";
3
+ import { ZodCustomErrorMap } from "./zod-custom-error-map";
4
+
5
+ const zodUserFriendlyErrorsPtBr = new ZodCustomErrorMap({
6
+ required: `O campo é obrigatório.`,
7
+ types: {
8
+ default: {
9
+ invalid: ({ expected }) =>
10
+ `O tipo do campo é inválido. É esperado que seja: "${expected}".`,
11
+ too_small: "O campo é inválido.",
12
+ too_big: "O campo é inválido.",
13
+ },
14
+ string: {
15
+ invalid: "O campo deve ser um texto.",
16
+ greater_than_or_equal: ({ minimum }) =>
17
+ `O campo deve conter no mínimo ${minimum} caractere(s).`,
18
+ less_than_or_equal: ({ maximum }) =>
19
+ `O campo deve conter no máximo ${maximum} caractere(s).`,
20
+ exact: ({ minimum, maximum }) =>
21
+ `O campo deve conter exatamente ${minimum ?? maximum} caractere(s).`,
22
+ },
23
+ number: {
24
+ invalid: "O campo deve ser um número.",
25
+ greater_than: ({ minimum }) => `O número deve ser maior que ${minimum}.`,
26
+ greater_than_or_equal: ({ minimum }) =>
27
+ `O número deve ser maior ou igual a ${minimum}.`,
28
+ less_than: ({ maximum }) => `O número deve ser menor que ${maximum}.`,
29
+ less_than_or_equal: ({ maximum }) =>
30
+ `O número deve ser menor ou igual a ${maximum}.`,
31
+ not_multiple_of: ({ divisor }) =>
32
+ `O número deve ser múltiplo de ${divisor}.`,
33
+ },
34
+ date: {
35
+ invalid: "O campo deve ser uma data.",
36
+ greater_than_or_equal: ({ minimum }) =>
37
+ `A data deve ser maior ou igual a ${minimum.toLocaleString("pt-BR")}.`,
38
+ less_than_or_equal: ({ maximum }) =>
39
+ `A data deve ser menor ou igual a ${maximum.toLocaleString("pt-BR")}.`,
40
+ },
41
+ array: {
42
+ invalid: "O campo deve ser uma lista.",
43
+ greater_than: ({ minimum }) =>
44
+ `Adicione no mínimo ${minimum} ${minimum > 1 ? "itens" : "item"}.`,
45
+ less_than: ({ maximum }) =>
46
+ `Adicione no máximo ${maximum} ${maximum > 1 ? "itens" : "item"}.`,
47
+ exact: ({ minimum, maximum }) =>
48
+ `Adicione exatamente ${minimum ?? maximum} ${((minimum ?? maximum) as number) > 1 ? "itens" : "item"}.`,
49
+ },
50
+ file: {
51
+ invalid: "O campo deve ser um arquivo.",
52
+ greater_than_or_equal: ({ minimum }) =>
53
+ `O arquivo deve possuir no mínimo ${prettyBytes(minimum, { locale: "pt" })}.`,
54
+ less_than_or_equal: ({ maximum }) =>
55
+ `O arquivo deve possuir no máximo ${prettyBytes(maximum, { locale: "pt" })}.`,
56
+ },
57
+ boolean: { invalid: "O campo deve ser verdadeiro ou falso." },
58
+ undefined: { invalid: "O campo deve estar vazio." },
59
+ null: { invalid: "O campo deve estar vazio." },
60
+ },
61
+ formats: {
62
+ default: `O formato do campo é inválido.`,
63
+
64
+ // common
65
+ email: "O e-mail é inválido.",
66
+ url: "A URL é inválida.",
67
+ regex: "O campo é inválido.",
68
+ date: "A data é inválida.",
69
+ time: "A hora é inválida.",
70
+ datetime: "A data e a hora são inválidos.",
71
+ emoji: "Emoji inválido.",
72
+ starts_with: ({ prefix }) => `O campo deve iniciar com "${prefix}".`,
73
+ ends_with: ({ suffix }) => `O campo deve terminar com "${suffix}".`,
74
+ includes: ({ includes }) => `O campo deve incluir "${includes}".`,
75
+
76
+ // programming
77
+ uuid: "O UUID é inválido.",
78
+ cuid: "O CUID é inválido.",
79
+ nanoid: "O Nano ID é inválido.",
80
+ base64: "Base64 inválido.",
81
+ base64url: "Base64Url inválido.",
82
+ jwt: "JSON Web Token inválido.",
83
+ ipv4: "Endereço IPv4 inválido.",
84
+ ipv6: "Endereço IPv6 inválido.",
85
+ json_string: "JSON inválido.",
86
+ },
87
+ others: {
88
+ default: "O campo é inválido.",
89
+ custom: "O campo é inválido.",
90
+ invalid_value: ({ values }) => `O campo deve ser exatamente "${values}".`,
91
+ invalid_union: "O tipo do campo é inválido.",
92
+ },
93
+ });
94
+
95
+ z.config({
96
+ customError: (issue) => zodUserFriendlyErrorsPtBr.map(issue),
97
+ });
@@ -0,0 +1,259 @@
1
+ import type { OverrideProperties } from "type-fest";
2
+ import type {
3
+ $ZodIssueCode,
4
+ $ZodIssueCustom,
5
+ $ZodIssueInvalidElement,
6
+ $ZodIssueInvalidKey,
7
+ $ZodIssueInvalidStringFormat,
8
+ $ZodIssueInvalidType,
9
+ $ZodIssueInvalidUnion,
10
+ $ZodIssueInvalidValue,
11
+ $ZodIssueNotMultipleOf,
12
+ $ZodIssueStringEndsWith,
13
+ $ZodIssueStringIncludes,
14
+ $ZodIssueStringInvalidJWT,
15
+ $ZodIssueStringInvalidRegex,
16
+ $ZodIssueStringStartsWith,
17
+ $ZodIssueTooBig,
18
+ $ZodIssueTooSmall,
19
+ $ZodIssueUnrecognizedKeys,
20
+ $ZodRawIssue,
21
+ $ZodStringFormats,
22
+ $ZodType,
23
+ } from "zod/v4/core";
24
+
25
+ type ZodIssueTooSmallOrTooBigExact = ($ZodIssueTooSmall | $ZodIssueTooBig) & {
26
+ minimum?: number | bigint;
27
+ maximum?: number | bigint;
28
+ };
29
+
30
+ type ZodCustomErrorMapData = {
31
+ required: string | ((issue: $ZodIssueInvalidType) => string);
32
+ types: OverrideProperties<
33
+ Partial<
34
+ Record<
35
+ $ZodType["_zod"]["def"]["type"],
36
+ {
37
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
38
+ }
39
+ >
40
+ >,
41
+ {
42
+ default: {
43
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
44
+ too_small: string | ((issue: $ZodIssueTooSmall) => string);
45
+ too_big: string | ((issue: $ZodIssueTooBig) => string);
46
+ };
47
+ string: {
48
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
49
+ greater_than_or_equal: string | ((issue: $ZodIssueTooSmall) => string);
50
+ less_than_or_equal: string | ((issue: $ZodIssueTooBig) => string);
51
+ exact: string | ((issue: ZodIssueTooSmallOrTooBigExact) => string);
52
+ };
53
+ number: {
54
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
55
+ greater_than: string | ((issue: $ZodIssueTooSmall) => string);
56
+ greater_than_or_equal: string | ((issue: $ZodIssueTooSmall) => string);
57
+ less_than: string | ((issue: $ZodIssueTooBig) => string);
58
+ less_than_or_equal: string | ((issue: $ZodIssueTooBig) => string);
59
+ not_multiple_of: string | ((issue: $ZodIssueNotMultipleOf) => string);
60
+ };
61
+ date: {
62
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
63
+ greater_than_or_equal:
64
+ | string
65
+ | ((
66
+ issue: OverrideProperties<$ZodIssueTooSmall, { minimum: Date }>,
67
+ ) => string);
68
+ less_than_or_equal:
69
+ | string
70
+ | ((
71
+ issue: OverrideProperties<$ZodIssueTooBig, { maximum: Date }>,
72
+ ) => string);
73
+ };
74
+ array: {
75
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
76
+ greater_than: string | ((issue: $ZodIssueTooSmall) => string);
77
+ less_than: string | ((issue: $ZodIssueTooBig) => string);
78
+ exact: string | ((issue: ZodIssueTooSmallOrTooBigExact) => string);
79
+ };
80
+ file: {
81
+ invalid: string | ((issue: $ZodIssueInvalidType) => string);
82
+ greater_than_or_equal: string | ((issue: $ZodIssueTooSmall) => string);
83
+ less_than_or_equal: string | ((issue: $ZodIssueTooBig) => string);
84
+ };
85
+ }
86
+ >;
87
+ formats: OverrideProperties<
88
+ Partial<
89
+ Record<
90
+ $ZodStringFormats,
91
+ string | ((issue: $ZodIssueInvalidStringFormat) => string)
92
+ >
93
+ >,
94
+ {
95
+ regex?: string | ((issue: $ZodIssueStringInvalidRegex) => string);
96
+ jwt?: string | ((issue: $ZodIssueStringInvalidJWT) => string);
97
+ includes?: string | ((issue: $ZodIssueStringIncludes) => string);
98
+ starts_with?: string | ((issue: $ZodIssueStringStartsWith) => string);
99
+ ends_with?: string | ((issue: $ZodIssueStringEndsWith) => string);
100
+ }
101
+ > & {
102
+ default: string | ((issue: $ZodIssueInvalidStringFormat) => string);
103
+ };
104
+ others: {
105
+ custom?: string | ((issue: $ZodIssueCustom) => string);
106
+ invalid_value?: string | ((issue: $ZodIssueInvalidValue) => string);
107
+ invalid_union?: string | ((issue: $ZodIssueInvalidUnion) => string);
108
+ invalid_element?: string | ((issue: $ZodIssueInvalidElement) => string);
109
+ invalid_key?: string | ((issue: $ZodIssueInvalidKey) => string);
110
+ unrecognized_keys?: string | ((issue: $ZodIssueUnrecognizedKeys) => string);
111
+ } & {
112
+ default:
113
+ | string
114
+ | ((
115
+ issue: Exclude<
116
+ $ZodIssueCode,
117
+ | "invalid_type"
118
+ | "too_small"
119
+ | "too_big"
120
+ | "invalid_format"
121
+ | "not_multiple_of"
122
+ >,
123
+ ) => string);
124
+ };
125
+ };
126
+
127
+ export class ZodCustomErrorMap {
128
+ private data: ZodCustomErrorMapData;
129
+
130
+ public constructor(data: ZodCustomErrorMapData) {
131
+ this.data = data;
132
+ }
133
+
134
+ private getCustomMessage<
135
+ // biome-ignore lint/suspicious/noExplicitAny: The issue here is specific.
136
+ Value extends string | ((issue: any) => string),
137
+ Issue,
138
+ >(value: Value, issue: Issue): string {
139
+ if (typeof value === "function") return value(issue);
140
+
141
+ return value;
142
+ }
143
+
144
+ public map(issue: $ZodRawIssue) {
145
+ switch (issue.code) {
146
+ case "invalid_type": {
147
+ if (issue.input === undefined || issue.input === null) {
148
+ return this.getCustomMessage(this.data.required, issue);
149
+ }
150
+
151
+ const messageDefinition =
152
+ this.data.types[issue.expected] ?? this.data.types.default;
153
+
154
+ return this.getCustomMessage(messageDefinition.invalid, issue);
155
+ }
156
+
157
+ case "too_small": {
158
+ const { types } = this.data;
159
+
160
+ if (issue.origin === "string") {
161
+ if (issue.exact)
162
+ return this.getCustomMessage(types.string.exact, issue);
163
+
164
+ return this.getCustomMessage(
165
+ types.string.greater_than_or_equal,
166
+ issue,
167
+ );
168
+ }
169
+
170
+ if (["number", "int", "bigint"].includes(issue.origin)) {
171
+ if (issue.inclusive)
172
+ return this.getCustomMessage(
173
+ types.number.greater_than_or_equal,
174
+ issue,
175
+ );
176
+
177
+ return this.getCustomMessage(types.number.greater_than, issue);
178
+ }
179
+
180
+ if (issue.origin === "array") {
181
+ if (issue.exact)
182
+ return this.getCustomMessage(types.array.exact, issue);
183
+
184
+ return this.getCustomMessage(types.array.greater_than, issue);
185
+ }
186
+
187
+ if (issue.origin === "date") {
188
+ return this.getCustomMessage(types.date.greater_than_or_equal, issue);
189
+ }
190
+
191
+ if (issue.origin === "file") {
192
+ return this.getCustomMessage(types.file.greater_than_or_equal, issue);
193
+ }
194
+
195
+ return this.getCustomMessage(types.default.too_small, issue);
196
+ }
197
+
198
+ case "too_big": {
199
+ const { types } = this.data;
200
+
201
+ if (issue.origin === "string") {
202
+ if (issue.exact)
203
+ return this.getCustomMessage(types.string.exact, issue);
204
+
205
+ return this.getCustomMessage(types.string.less_than_or_equal, issue);
206
+ }
207
+
208
+ if (["number", "int", "bigint"].includes(issue.origin)) {
209
+ if (issue.inclusive)
210
+ return this.getCustomMessage(
211
+ types.number.less_than_or_equal,
212
+ issue,
213
+ );
214
+
215
+ return this.getCustomMessage(types.number.less_than, issue);
216
+ }
217
+
218
+ if (issue.origin === "array") {
219
+ if (issue.exact)
220
+ return this.getCustomMessage(types.array.exact, issue);
221
+
222
+ return this.getCustomMessage(types.array.less_than, issue);
223
+ }
224
+
225
+ if (issue.origin === "date") {
226
+ return this.getCustomMessage(types.date.less_than_or_equal, issue);
227
+ }
228
+
229
+ if (issue.origin === "file") {
230
+ return this.getCustomMessage(types.file.less_than_or_equal, issue);
231
+ }
232
+
233
+ return this.getCustomMessage(types.default.too_big, issue);
234
+ }
235
+
236
+ case "invalid_format": {
237
+ const definitionMessage =
238
+ this.data.formats[
239
+ issue.format as keyof ZodCustomErrorMapData["formats"]
240
+ ] ?? this.data.formats.default;
241
+
242
+ return this.getCustomMessage(definitionMessage, issue);
243
+ }
244
+
245
+ case "not_multiple_of": {
246
+ return this.getCustomMessage(
247
+ this.data.types.number.not_multiple_of,
248
+ issue,
249
+ );
250
+ }
251
+ }
252
+
253
+ const definitionMessage =
254
+ this.data.others[issue.code as keyof typeof this.data.others] ??
255
+ this.data.others.default;
256
+
257
+ return this.getCustomMessage(definitionMessage, issue);
258
+ }
259
+ }
@@ -0,0 +1,20 @@
1
+ import { RouterProvider } from "@tanstack/react-router";
2
+ import { StrictMode } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { SWRConfig } from "swr";
5
+ import "./index.css";
6
+ import { swrConfig } from "./lib/swr-config";
7
+ import { tanstackRouterInstance } from "./lib/tanstack-router-instance";
8
+ import "./lib/zod-config";
9
+
10
+ const rootElement = document.getElementById("root");
11
+
12
+ if (!rootElement) throw new Error("Root element is not defined.");
13
+
14
+ createRoot(rootElement).render(
15
+ <StrictMode>
16
+ <SWRConfig value={swrConfig}>
17
+ <RouterProvider router={tanstackRouterInstance} />
18
+ </SWRConfig>
19
+ </StrictMode>,
20
+ );
@@ -0,0 +1,12 @@
1
+ import { createRootRoute, HeadContent, Outlet } from "@tanstack/react-router";
2
+
3
+ export const Route = createRootRoute({ component: RootLayout });
4
+
5
+ function RootLayout() {
6
+ return (
7
+ <>
8
+ <HeadContent />
9
+ <Outlet />
10
+ </>
11
+ );
12
+ }
@@ -0,0 +1,24 @@
1
+ import { env } from "@/env";
2
+ import { createFileRoute, Link } from "@tanstack/react-router";
3
+ import "./styles.css";
4
+
5
+ export const Route = createFileRoute("/_app/")({
6
+ component: RouteComponent,
7
+ head: () => ({
8
+ meta: [
9
+ {
10
+ title: `Dashboard | ${env.PUBLIC_APP_NAME}`,
11
+ },
12
+ ],
13
+ }),
14
+ });
15
+
16
+ function RouteComponent() {
17
+ return (
18
+ <div className="dashboard-wrapper">
19
+ <h1>Hello world!</h1>
20
+
21
+ <Link to="/sign-in">Sign out</Link>
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,15 @@
1
+ import { createFileRoute, Outlet } from "@tanstack/react-router";
2
+
3
+ export const Route = createFileRoute("/_app")({
4
+ component: AppLayout,
5
+ });
6
+
7
+ function AppLayout() {
8
+ return (
9
+ <div>
10
+ <h1>App layout</h1>
11
+
12
+ <Outlet />
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,40 @@
1
+ .dashboard-wrapper {
2
+ height: 100%;
3
+ display: flex;
4
+ flex-direction: column;
5
+ justify-content: center;
6
+ align-items: center;
7
+ padding-left: 15px;
8
+ padding-right: 15px;
9
+ }
10
+
11
+ .dashboard-wrapper > h1 {
12
+ font-size: 4.2rem;
13
+ line-height: 1.2;
14
+ letter-spacing: 0.8px;
15
+ font-weight: 800;
16
+ color: #fff;
17
+ margin-bottom: 4rem;
18
+
19
+ @media (max-width: 640px) {
20
+ font-size: 3.6rem;
21
+ line-height: 1.3;
22
+ }
23
+ }
24
+
25
+ .dashboard-wrapper > a {
26
+ display: inline-block;
27
+ padding: 1rem 1.6rem;
28
+ font-weight: 800;
29
+ text-align: center;
30
+ border: 1px solid transparent;
31
+ border-radius: 6px;
32
+ background-color: #fff;
33
+ color: #000;
34
+ cursor: pointer;
35
+ transition: opacity 150ms ease-in-out;
36
+ }
37
+
38
+ .dashboard-wrapper > a:hover {
39
+ opacity: 0.8;
40
+ }
@@ -0,0 +1,15 @@
1
+ import { createFileRoute, Outlet } from "@tanstack/react-router";
2
+
3
+ export const Route = createFileRoute("/_auth")({
4
+ component: AuthLayout,
5
+ });
6
+
7
+ function AuthLayout() {
8
+ return (
9
+ <div>
10
+ <h1>Auth layout</h1>
11
+
12
+ <Outlet />
13
+ </div>
14
+ );
15
+ }