create-warlock 5.34.2 → 5.35.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 (106) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +8 -0
  3. package/esm/commands/create-new-app/index.mjs +4 -3
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-warlock-app/index.mjs +7 -1
  6. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  7. package/esm/features/features-map.mjs +8 -2
  8. package/esm/features/features-map.mjs.map +1 -1
  9. package/esm/helpers/app.mjs +1 -0
  10. package/esm/helpers/app.mjs.map +1 -1
  11. package/esm/prompts/ask-stack.mjs +1 -1
  12. package/esm/prompts/ask-stack.mjs.map +1 -1
  13. package/llms-full.txt +64 -28
  14. package/llms.txt +1 -1
  15. package/package.json +2 -2
  16. package/skills/{create-a-warlock-project/SKILL.md → SKILL.md} +63 -27
  17. package/templates/design-system/SOURCES.md +23 -0
  18. package/templates/design-system/package.json +14 -0
  19. package/templates/design-system/src/web/components/ui/LICENSE.md +21 -0
  20. package/templates/design-system/src/web/components/ui/button.tsx +61 -0
  21. package/templates/design-system/src/web/components/ui/calendar.tsx +178 -0
  22. package/templates/design-system/src/web/components/ui/checkbox.tsx +28 -0
  23. package/templates/design-system/src/web/components/ui/command.tsx +161 -0
  24. package/templates/design-system/src/web/components/ui/dialog.tsx +144 -0
  25. package/templates/design-system/src/web/components/ui/input.tsx +20 -0
  26. package/templates/design-system/src/web/components/ui/label.tsx +20 -0
  27. package/templates/design-system/src/web/components/ui/popover.tsx +73 -0
  28. package/templates/design-system/src/web/components/ui/radio-group.tsx +44 -0
  29. package/templates/design-system/src/web/components/ui/switch.tsx +34 -0
  30. package/templates/design-system/src/web/components/ui/textarea.tsx +17 -0
  31. package/templates/design-system/src/web/design-system/components/checkbox-input.tsx +19 -0
  32. package/templates/design-system/src/web/design-system/components/date-picker-input.tsx +59 -0
  33. package/templates/design-system/src/web/design-system/components/date-time-picker-input.tsx +67 -0
  34. package/templates/design-system/src/web/design-system/components/email-input.tsx +22 -0
  35. package/templates/design-system/src/web/design-system/components/field-frame.tsx +36 -0
  36. package/templates/design-system/src/web/design-system/components/file-input.tsx +24 -0
  37. package/templates/design-system/src/web/design-system/components/multi-select-input.tsx +94 -0
  38. package/templates/design-system/src/web/design-system/components/password-input.tsx +22 -0
  39. package/templates/design-system/src/web/design-system/components/radio-input.tsx +41 -0
  40. package/templates/design-system/src/web/design-system/components/select-input.tsx +94 -0
  41. package/templates/design-system/src/web/design-system/components/submit-button.tsx +20 -0
  42. package/templates/design-system/src/web/design-system/components/switch-input.tsx +19 -0
  43. package/templates/design-system/src/web/design-system/components/text-input.tsx +22 -0
  44. package/templates/design-system/src/web/design-system/components/textarea-input.tsx +22 -0
  45. package/templates/design-system/src/web/design-system/components/time-picker-input.tsx +22 -0
  46. package/templates/design-system/src/web/design-system/hooks/use-calendar-day-button.ts +9 -0
  47. package/templates/design-system/src/web/design-system/hooks/use-control.ts +29 -0
  48. package/templates/design-system/src/web/design-system/hooks/use-date-control.ts +42 -0
  49. package/templates/design-system/src/web/design-system/hooks/use-file-control.ts +24 -0
  50. package/templates/design-system/src/web/design-system/hooks/use-select-control.ts +78 -0
  51. package/templates/design-system/src/web/design-system/hooks/use-submit-control.ts +13 -0
  52. package/templates/design-system/src/web/design-system/types/control-props.ts +46 -0
  53. package/templates/toolchains/oxc/.oxlintrc.json +3 -1
  54. package/templates/warlock/.oxlintrc.json +3 -1
  55. package/templates/warlock/AGENTS.md +10 -10
  56. package/templates/warlock/e2e/home.e2e.ts +19 -0
  57. package/templates/warlock/package.json +1 -1
  58. package/templates/warlock/skills/api-design/SKILL.md +180 -57
  59. package/templates/warlock/skills/code-standards/SKILL.md +48 -39
  60. package/templates/warlock/skills/data-and-persistence/SKILL.md +72 -57
  61. package/templates/warlock/skills/git-workflow/SKILL.md +9 -9
  62. package/templates/warlock/skills/module-boundaries/SKILL.md +2 -2
  63. package/templates/warlock/skills/observability-and-resilience/SKILL.md +8 -6
  64. package/templates/warlock/skills/security-baseline/SKILL.md +23 -22
  65. package/templates/warlock/skills/testing-strategy/SKILL.md +53 -12
  66. package/templates/warlock/skills/web-code-standards/SKILL.md +195 -60
  67. package/templates/warlock/src/app/auth/controllers/forgot-password.controller.ts +6 -0
  68. package/templates/warlock/src/app/auth/controllers/login.controller.ts +17 -0
  69. package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +6 -0
  70. package/templates/warlock/src/app/auth/controllers/logout.controller.ts +6 -0
  71. package/templates/warlock/src/app/auth/controllers/me.controller.ts +7 -0
  72. package/templates/warlock/src/app/auth/controllers/refresh-token.controller.ts +12 -0
  73. package/templates/warlock/src/app/auth/controllers/reset-password.controller.ts +6 -0
  74. package/templates/warlock/src/app/auth/models/otp/otp.model.ts +9 -3
  75. package/templates/warlock/src/app/auth/services/otp.service.ts +18 -6
  76. package/templates/warlock/src/app/contact/controllers/contact.controller.ts +6 -0
  77. package/templates/warlock/src/app/home/controllers/home-page.controller.ts +4 -0
  78. package/templates/warlock/src/app/locale/controllers/locale.controller.ts +6 -0
  79. package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +7 -0
  80. package/templates/warlock/src/app/posts/controllers/update-post.controller.ts +15 -9
  81. package/templates/warlock/src/app/posts/models/post/migrations/09-01-2026_02-07-51-post.migration.ts +1 -0
  82. package/templates/warlock/src/app/posts/models/post/post.model.ts +6 -0
  83. package/templates/warlock/src/app/posts/resources/post.resource.ts +1 -1
  84. package/templates/warlock/src/app/posts/schema/update-post.schema.ts +1 -0
  85. package/templates/warlock/src/app/shared/utils/format-http-error-response.ts +53 -0
  86. package/templates/warlock/src/app/users/commands/hello-world.command.ts +2 -1
  87. package/templates/warlock/src/app/users/controllers/create-new-user.controller.ts +13 -1
  88. package/templates/warlock/src/app/users/controllers/list-users.controller.ts +24 -1
  89. package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts +1 -0
  90. package/templates/warlock/src/app/users/models/user/user.model.ts +5 -0
  91. package/templates/warlock/src/app/users/repositories/users.repository.ts +2 -2
  92. package/templates/warlock/src/app/users/resources/user.resource.ts +1 -1
  93. package/templates/warlock/src/app/users/schema/list-users.schema.ts +1 -1
  94. package/templates/warlock/src/config/http.ts +6 -0
  95. package/templates/warlock/src/web/home/components/contact-field.tsx +18 -0
  96. package/templates/warlock/src/web/home/components/contact-section.tsx +5 -29
  97. package/templates/warlock/src/web/home/components/contact-submit-button.tsx +16 -0
  98. package/templates/warlock/src/web/home/hooks/use-contact-field.ts +9 -0
  99. package/templates/warlock/src/web/home/hooks/use-contact-form.ts +5 -8
  100. package/templates/warlock/src/web/home/hooks/use-contact-locale.ts +34 -0
  101. package/templates/warlock/src/web/home/hooks/use-contact-submit-button.ts +6 -0
  102. package/templates/warlock/src/web/home/index.page.tsx +1 -1
  103. package/templates/warlock/src/web/home/types/contact-controls.type.ts +13 -0
  104. package/templates/warlock/src/web/shared/utils/set-form-errors.ts +3 -1
  105. package/templates/warlock/warlock.config.ts +1 -0
  106. package/templates/warlock/src/web/home/components/contact-form-controls.tsx +0 -42
@@ -0,0 +1,53 @@
1
+ import type { HttpErrorResponseContext } from "@warlock.js/core";
2
+
3
+ const statusCodes: Record<number, string> = {
4
+ 400: "badRequest",
5
+ 401: "unauthorized",
6
+ 403: "forbidden",
7
+ 404: "notFound",
8
+ 409: "conflict",
9
+ 422: "validationFailed",
10
+ 429: "tooManyRequests",
11
+ };
12
+
13
+ /** Convert framework JSON errors to the project's public error contract. */
14
+ export function formatHttpErrorResponse({
15
+ status,
16
+ body,
17
+ requestId,
18
+ appError,
19
+ }: HttpErrorResponseContext) {
20
+ if (appError) {
21
+ return {
22
+ error: { code: appError.code, message: appError.publicMessage },
23
+ ...(appError.details === undefined ? {} : { details: appError.details }),
24
+ requestId,
25
+ };
26
+ }
27
+ const source = isRecord(body) ? body : {};
28
+ const error = isRecord(source.error) ? source.error : {};
29
+ const serverFault = status >= 500;
30
+ const code = serverFault
31
+ ? "serverError"
32
+ : (text(error.code) ?? text(source.errorCode) ?? statusCodes[status] ?? "requestFailed");
33
+ const message = serverFault
34
+ ? "Internal server error."
35
+ : (text(error.message) ?? text(source.message) ?? text(source.error) ?? "Request failed.");
36
+ // Only deliberately public details belong here. Never include a 5xx payload
37
+ // or the development-only stack attached by the framework error funnel.
38
+ const details = serverFault ? undefined : (source.details ?? source.errors ?? source.payload);
39
+
40
+ return {
41
+ error: { code, message },
42
+ ...(details === undefined ? {} : { details }),
43
+ requestId,
44
+ };
45
+ }
46
+
47
+ function isRecord(value: unknown): value is Record<string, unknown> {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49
+ }
50
+
51
+ function text(value: unknown): string | undefined {
52
+ return typeof value === "string" && value.length > 0 ? value : undefined;
53
+ }
@@ -1,8 +1,9 @@
1
1
  import { command } from "@warlock.js/core";
2
+ import { log } from "@warlock.js/logger";
2
3
 
3
4
  export default command({
4
5
  name: "hello.world",
5
6
  action: () => {
6
- console.log("Hello World Guys!");
7
+ log.info("users", "hello.world", "Hello World!");
7
8
  },
8
9
  });
@@ -1,4 +1,5 @@
1
1
  import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
2
+ import { UserResource } from "app/users/resources/user.resource";
2
3
  import { User } from "../models/user";
3
4
  import { createUserSchema, type CreateUserSchema } from "../schema/create-user.schema";
4
5
 
@@ -6,7 +7,11 @@ export const createNewUserController: GuardedRequestHandler<CreateUserSchema> =
6
7
  request,
7
8
  response,
8
9
  }) => {
9
- const file = request.file("image")!;
10
+ const file = request.file("image");
11
+
12
+ if (!file) {
13
+ return response.badRequest({ error: { code: "imageRequired", message: "Image is required." } });
14
+ }
10
15
 
11
16
  const output = await file.save("images");
12
17
 
@@ -25,3 +30,10 @@ export const createNewUserController: GuardedRequestHandler<CreateUserSchema> =
25
30
  createNewUserController.validation = {
26
31
  schema: createUserSchema,
27
32
  };
33
+
34
+ createNewUserController.responseSchema = {
35
+ 201: { body: { message: "string", user: UserResource } },
36
+ 422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
37
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
38
+ 400: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
39
+ };
@@ -1,4 +1,5 @@
1
1
  import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
2
+ import { UserResource } from "app/users/resources/user.resource";
2
3
  import { usersRepository } from "../repositories/users.repository";
3
4
  import { listUsersSchema, type ListUsersSchema } from "../schema/list-users.schema";
4
5
 
@@ -6,10 +7,15 @@ export const listUsersController: GuardedRequestHandler<ListUsersSchema> = async
6
7
  request,
7
8
  response,
8
9
  }) => {
9
- const users = await usersRepository.listCached(request.validated());
10
+ const { id, ...filters } = request.validated();
11
+ const { data: users, pagination } = await usersRepository.listCached({
12
+ ...filters,
13
+ publicId: id,
14
+ });
10
15
 
11
16
  return response.success({
12
17
  users,
18
+ pagination,
13
19
  });
14
20
  };
15
21
 
@@ -18,3 +24,20 @@ listUsersController.description = "List Users Controller";
18
24
  listUsersController.validation = {
19
25
  schema: listUsersSchema,
20
26
  };
27
+
28
+ listUsersController.responseSchema = {
29
+ 200: {
30
+ body: {
31
+ users: [UserResource],
32
+ pagination: {
33
+ limit: "number",
34
+ result: "number",
35
+ page: "number",
36
+ total: "number",
37
+ pages: "number",
38
+ },
39
+ },
40
+ },
41
+ 422: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
42
+ 500: { body: { error: { code: "string", message: "string" }, requestId: "string" } },
43
+ };
@@ -2,6 +2,7 @@ import { json, Migration, string, timestamp } from "@warlock.js/cascade";
2
2
  import { User } from "../user.model";
3
3
 
4
4
  export default Migration.create(User, {
5
+ publicId: string(36).unique(),
5
6
  name: string(255),
6
7
  email: string(255).unique(),
7
8
  password: string(255),
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { Auth } from "@warlock.js/auth";
2
3
  import { RegisterModel } from "@warlock.js/cascade";
3
4
  import { useHashedPassword } from "@warlock.js/core";
@@ -5,6 +6,10 @@ import { v, type Infer } from "@warlock.js/seal";
5
6
  import { UserResource } from "app/users/resources/user.resource";
6
7
 
7
8
  export const userSchema = v.object({
9
+ publicId: v
10
+ .string()
11
+ .uuid()
12
+ .default(() => randomUUID()),
8
13
  name: v.string(),
9
14
  email: v.email().requiredIfEmpty("id"),
10
15
  // Required at the controller layer (`create-user.schema.ts`) on
@@ -5,7 +5,7 @@ import { User } from "../models/user";
5
5
  export type UserFilter = {
6
6
  email?: string;
7
7
  name?: string;
8
- id?: string;
8
+ publicId?: string;
9
9
  };
10
10
 
11
11
  export type UsersListOptions = RepositoryOptions & UserFilter;
@@ -14,7 +14,7 @@ class UsersRepository extends RepositoryManager<User, UserFilter> {
14
14
  public source = User;
15
15
 
16
16
  public filterBy: FilterRules = {
17
- id: "=",
17
+ publicId: "=",
18
18
  name: "like",
19
19
  email: "=",
20
20
  };
@@ -2,7 +2,7 @@ import { defineResource } from "@warlock.js/core";
2
2
 
3
3
  export const UserResource = defineResource({
4
4
  schema: {
5
- id: "number",
5
+ id: ["publicId", "string"],
6
6
  name: "string",
7
7
  email: "string",
8
8
  image: "storageUrl",
@@ -5,7 +5,7 @@ import { v, type Infer } from "@warlock.js/seal";
5
5
  * smuggled operator (`?email[$ne]=`) never reaches the repository filters.
6
6
  */
7
7
  export const listUsersSchema = v.object({
8
- id: v.string().optional(),
8
+ id: v.string().uuid().optional(),
9
9
  name: v.string().max(100).optional(),
10
10
  email: v.string().max(255).optional(),
11
11
  page: v.numeric().min(1).optional(),
@@ -1,10 +1,16 @@
1
1
  import type { HttpConfigurations } from "@warlock.js/core";
2
2
  import { env } from "@warlock.js/core";
3
+ import { formatHttpErrorResponse } from "../app/shared/utils/format-http-error-response";
3
4
 
4
5
  const httpConfigurations: HttpConfigurations = {
5
6
  port: env("HTTP_PORT", 3000),
6
7
  host: env("HTTP_HOST", "localhost"),
7
8
  log: true,
9
+ // errorMapper classifies thrown non-HttpErrors; it cannot shape returned
10
+ // validation, not-found, or rate-limit responses. Format all JSON errors at
11
+ // the transport boundary, including Fastify's early rejections.
12
+ errorMapper: () => ({ status: 500 }),
13
+ errorResponse: formatHttpErrorResponse,
8
14
  fileUploadLimit: 12 * 1024 * 1024 * 1024,
9
15
  rateLimit: {
10
16
  max: 260,
@@ -0,0 +1,18 @@
1
+ import { useContactField } from "../hooks/use-contact-field";
2
+ import type { ContactFieldProps } from "../types/contact-controls.type";
3
+
4
+ export default function ContactField({ label, multiline, rows, ...props }: ContactFieldProps) {
5
+ const { error, getErrorProps, inputProps, id } = useContactField(props, rows);
6
+
7
+ return (
8
+ <label htmlFor={id}>
9
+ <span>{label}</span>
10
+ {multiline ? <textarea {...inputProps} /> : <input {...inputProps} />}
11
+ {error ? (
12
+ <small className="warlock-field-error" {...getErrorProps()}>
13
+ {error}
14
+ </small>
15
+ ) : null}
16
+ </label>
17
+ );
18
+ }
@@ -1,38 +1,14 @@
1
1
  import { Form } from "@mongez/react-form";
2
- import { changeLocaleCode, useLocale, useTextDirection, useTrans } from "@warlock.js/web";
3
- import { useState } from "react";
4
2
  import { contactSchema } from "../../../shared/contact.schema";
5
3
  import { useContactForm } from "../hooks/use-contact-form";
6
- import { ContactField, ContactSubmitButton } from "./contact-form-controls";
4
+ import { useContactLocale } from "../hooks/use-contact-locale";
5
+ import ContactField from "./contact-field";
6
+ import ContactSubmitButton from "./contact-submit-button";
7
7
 
8
- export function ContactSection() {
8
+ export default function ContactSection() {
9
9
  const { status: contactStatus, submitContact } = useContactForm();
10
- const locale = useLocale();
11
- const direction = useTextDirection();
12
- const translate = useTrans();
13
- const [isSwitchingLocale, setIsSwitchingLocale] = useState(false);
14
- const [localeError, setLocaleError] = useState("");
10
+ const { direction, translate, isSwitchingLocale, localeError, toggleLocale } = useContactLocale();
15
11
 
16
- /**
17
- * Switch locale via the framework's `changeLocaleCode` — no reload, no
18
- * manual cookie write. The control is disabled while the request is in
19
- * flight, and a rejection leaves the current locale in place while
20
- * surfacing an error instead of throwing.
21
- */
22
- async function toggleLocale() {
23
- const nextLocale = locale === "en" ? "ar" : "en";
24
-
25
- setIsSwitchingLocale(true);
26
- setLocaleError("");
27
-
28
- try {
29
- await changeLocaleCode(nextLocale);
30
- } catch (error) {
31
- setLocaleError(error instanceof Error ? error.message : "Could not update the locale.");
32
- } finally {
33
- setIsSwitchingLocale(false);
34
- }
35
- }
36
12
  return (
37
13
  <section className="warlock-section warlock-contact" id="contact" dir={direction}>
38
14
  <div className="warlock-contact-copy">
@@ -0,0 +1,16 @@
1
+ import { useContactSubmitButton } from "../hooks/use-contact-submit-button";
2
+ import type { ContactSubmitButtonProps } from "../types/contact-controls.type";
3
+
4
+ export default function ContactSubmitButton({
5
+ idleLabel,
6
+ submittingLabel,
7
+ }: ContactSubmitButtonProps) {
8
+ const { disabled, isSubmitting } = useContactSubmitButton();
9
+
10
+ return (
11
+ <button className="warlock-contact-submit" type="submit" disabled={disabled}>
12
+ {isSubmitting ? submittingLabel : idleLabel}
13
+ <span aria-hidden="true">→</span>
14
+ </button>
15
+ );
16
+ }
@@ -0,0 +1,9 @@
1
+ import { useFormControl, type FormControlProps } from "@mongez/react-form";
2
+
3
+ /** Keep control identity, validation and DOM wiring in one hook. */
4
+ export function useContactField(props: FormControlProps, rows?: number) {
5
+ const { error, getErrorProps, getInputProps, id } = useFormControl(props);
6
+ const inputProps = getInputProps(rows ? { rows } : undefined);
7
+
8
+ return { error, getErrorProps, id, inputProps };
9
+ }
@@ -7,15 +7,12 @@ type ContactValues = InferFormValues<typeof contactSchema>;
7
7
 
8
8
  type ContactResponse = {
9
9
  message: string;
10
- received: {
11
- email: string;
12
- characters: number;
13
- };
14
10
  };
15
11
 
16
12
  type ContactErrorBody = {
17
- message?: string;
18
- errors?: unknown;
13
+ error?: { code: string; message: string };
14
+ details?: unknown;
15
+ requestId?: string;
19
16
  };
20
17
 
21
18
  type ContactStatus = {
@@ -44,12 +41,12 @@ export function useContactForm() {
44
41
 
45
42
  if (!response.ok) {
46
43
  if (response.status === 422) {
47
- setFormErrors({ body }, form);
44
+ setFormErrors({ body: { errors: body.details } }, form);
48
45
  }
49
46
 
50
47
  setStatus({
51
48
  state: "error",
52
- message: body.message || "The request was not accepted.",
49
+ message: body.error?.message || "The request was not accepted.",
53
50
  });
54
51
  return;
55
52
  }
@@ -0,0 +1,34 @@
1
+ import { changeLocaleCode, useLocale, useTextDirection, useTrans } from "@warlock.js/web";
2
+ import { useState } from "react";
3
+
4
+ /** Own the contact section's locale switch and recoverable error state. */
5
+ export function useContactLocale() {
6
+ const locale = useLocale();
7
+ const direction = useTextDirection();
8
+ const translate = useTrans();
9
+ const [isSwitchingLocale, setIsSwitchingLocale] = useState(false);
10
+ const [localeError, setLocaleError] = useState("");
11
+
12
+ /**
13
+ * Switch locale via the framework's `changeLocaleCode` — no reload, no
14
+ * manual cookie write. The control is disabled while the request is in
15
+ * flight, and a rejection leaves the current locale in place while
16
+ * surfacing an error instead of throwing.
17
+ */
18
+ async function toggleLocale() {
19
+ const nextLocale = locale === "en" ? "ar" : "en";
20
+
21
+ setIsSwitchingLocale(true);
22
+ setLocaleError("");
23
+
24
+ try {
25
+ await changeLocaleCode(nextLocale);
26
+ } catch (error) {
27
+ setLocaleError(error instanceof Error ? error.message : "Could not update the locale.");
28
+ } finally {
29
+ setIsSwitchingLocale(false);
30
+ }
31
+ }
32
+
33
+ return { direction, translate, isSwitchingLocale, localeError, toggleLocale };
34
+ }
@@ -0,0 +1,6 @@
1
+ import { useSubmitButton } from "@mongez/react-form";
2
+
3
+ /** Read submission state from the owning Mongez form. */
4
+ export function useContactSubmitButton() {
5
+ return useSubmitButton();
6
+ }
@@ -1,5 +1,5 @@
1
1
  import type { PageProps } from "@warlock.js/web";
2
- import { ContactSection } from "./components/contact-section";
2
+ import ContactSection from "./components/contact-section";
3
3
  import { ContentSections } from "./components/content-sections";
4
4
  import { HeroSection } from "./components/hero-section";
5
5
  import { HomeFooter } from "./components/home-footer";
@@ -0,0 +1,13 @@
1
+ import type { FormControlProps } from "@mongez/react-form";
2
+ import type { ReactNode } from "react";
3
+
4
+ export type ContactFieldProps = FormControlProps & {
5
+ label: ReactNode;
6
+ multiline?: boolean;
7
+ rows?: number;
8
+ };
9
+
10
+ export type ContactSubmitButtonProps = {
11
+ idleLabel: ReactNode;
12
+ submittingLabel: ReactNode;
13
+ };
@@ -31,7 +31,9 @@ export function setFormErrors(
31
31
 
32
32
  if (Array.isArray(errors)) {
33
33
  for (const issue of errors) {
34
- if (!isRecord(issue)) continue;
34
+ if (!isRecord(issue)) {
35
+ continue;
36
+ }
35
37
 
36
38
  const input = issue[inputKey];
37
39
  const message = issue[errorKey];
@@ -6,6 +6,7 @@ import {
6
6
  import { defineConfig } from "@warlock.js/core";
7
7
 
8
8
  export default defineConfig({
9
+ build: { budgets: { page: { js: "150kb", css: "50kb" }, mode: "error" } },
9
10
  cli: {
10
11
  commands: [registerJWTSecretGeneratorCommand(), registerAuthCleanupCommand()],
11
12
  },
@@ -1,42 +0,0 @@
1
- import { useFormControl, useSubmitButton, type FormControlProps } from "@mongez/react-form";
2
- import { type ReactNode } from "react";
3
-
4
- type ContactFieldProps = FormControlProps & {
5
- label: ReactNode;
6
- multiline?: boolean;
7
- rows?: number;
8
- };
9
-
10
- export function ContactField({ label, multiline, rows, ...props }: ContactFieldProps) {
11
- const { error, getErrorProps, getInputProps, id } = useFormControl(props);
12
- const inputProps = getInputProps(rows ? { rows } : undefined);
13
-
14
- return (
15
- <label htmlFor={id}>
16
- <span>{label}</span>
17
- {multiline ? <textarea {...inputProps} /> : <input {...inputProps} />}
18
- {error ? (
19
- <small className="warlock-field-error" {...getErrorProps()}>
20
- {error}
21
- </small>
22
- ) : null}
23
- </label>
24
- );
25
- }
26
-
27
- export function ContactSubmitButton({
28
- idleLabel,
29
- submittingLabel,
30
- }: {
31
- idleLabel: ReactNode;
32
- submittingLabel: ReactNode;
33
- }) {
34
- const { disabled, isSubmitting } = useSubmitButton();
35
-
36
- return (
37
- <button className="warlock-contact-submit" type="submit" disabled={disabled}>
38
- {isSubmitting ? submittingLabel : idleLabel}
39
- <span aria-hidden="true">→</span>
40
- </button>
41
- );
42
- }