torch-glare 2.4.0 → 2.4.2
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.
- package/README.md +8 -0
- package/apps/lib/components/ActionButton.tsx +7 -12
- package/apps/lib/components/ActionsGroup.tsx +4 -15
- package/apps/lib/components/AlertDialog.tsx +173 -191
- package/apps/lib/components/Avatar.tsx +13 -16
- package/apps/lib/components/Badge.tsx +1 -3
- package/apps/lib/components/BadgeField.tsx +76 -71
- package/apps/lib/components/Breadcrumb.tsx +49 -85
- package/apps/lib/components/Button.tsx +21 -20
- package/apps/lib/components/ButtonGroup.tsx +31 -41
- package/apps/lib/components/Calendar.tsx +51 -29
- package/apps/lib/components/Card.tsx +40 -24
- package/apps/lib/components/Checkbox.tsx +9 -16
- package/apps/lib/components/ColorPicker.tsx +441 -0
- package/apps/lib/components/ConclusionHeader.tsx +148 -0
- package/apps/lib/components/ContextMenu.tsx +97 -96
- package/apps/lib/components/CountBadge.tsx +4 -6
- package/apps/lib/components/DataTable.tsx +196 -216
- package/apps/lib/components/DataViews/DataViewRadio.tsx +1 -6
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +13 -47
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
- package/apps/lib/components/DataViews/FilterPanel.tsx +155 -149
- package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
- package/apps/lib/components/DataViews/InboxView.tsx +22 -68
- package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
- package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
- package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
- package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
- package/apps/lib/components/DataViews/TableView.tsx +8 -28
- package/apps/lib/components/DataViews/TreeView.tsx +12 -40
- package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
- package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
- package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +30 -23
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
- package/apps/lib/components/DataViews/index.ts +26 -26
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
- package/apps/lib/components/DataViews/types.ts +104 -115
- package/apps/lib/components/DatePicker.tsx +200 -196
- package/apps/lib/components/Dialog.tsx +92 -105
- package/apps/lib/components/Divider.tsx +21 -24
- package/apps/lib/components/Drawer.tsx +124 -142
- package/apps/lib/components/DropdownMenu.tsx +131 -171
- package/apps/lib/components/FieldHint.tsx +4 -11
- package/apps/lib/components/Form.tsx +146 -155
- package/apps/lib/components/FormBuilder/DisplayField.tsx +40 -0
- package/apps/lib/components/FormBuilder/context.ts +71 -0
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +65 -0
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +76 -0
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +16 -0
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +34 -0
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +79 -0
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +174 -0
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +40 -0
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +139 -0
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +32 -0
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +82 -0
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +37 -0
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +101 -0
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +175 -0
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +72 -0
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +42 -0
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +312 -0
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +222 -0
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +49 -0
- package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
- package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
- package/apps/lib/components/FormBuilder/form-builder.tsx +292 -0
- package/apps/lib/components/FormBuilder/header.tsx +105 -0
- package/apps/lib/components/FormBuilder/index.ts +37 -0
- package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
- package/apps/lib/components/FormBuilder/stepper.tsx +217 -0
- package/apps/lib/components/FormBuilder/submit.tsx +41 -0
- package/apps/lib/components/FormBuilder/types.ts +264 -0
- package/apps/lib/components/FormBuilder/viewFormat.tsx +137 -0
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +121 -0
- package/apps/lib/components/FormRenderer/form-renderer.tsx +113 -0
- package/apps/lib/components/FormRenderer/index.ts +9 -0
- package/apps/lib/components/FormRenderer/types.ts +79 -0
- package/apps/lib/components/FormStepper.tsx +9 -39
- package/apps/lib/components/FormSummary.tsx +282 -0
- package/apps/lib/components/HeaderBar.tsx +1 -9
- package/apps/lib/components/ImageAttachment.tsx +106 -93
- package/apps/lib/components/InnerLabelField.tsx +20 -16
- package/apps/lib/components/Input.tsx +171 -172
- package/apps/lib/components/InputField.tsx +44 -45
- package/apps/lib/components/InputOTP.tsx +68 -69
- package/apps/lib/components/Label.tsx +61 -58
- package/apps/lib/components/LabelField.tsx +4 -5
- package/apps/lib/components/LabeledCheckBox.tsx +8 -12
- package/apps/lib/components/LabeledRadio.tsx +7 -5
- package/apps/lib/components/LinkButton.tsx +2 -9
- package/apps/lib/components/LoginButton.tsx +4 -9
- package/apps/lib/components/PasswordLevel.tsx +5 -5
- package/apps/lib/components/Popover.tsx +15 -33
- package/apps/lib/components/ProfileMenu.tsx +67 -72
- package/apps/lib/components/Radio.tsx +1 -5
- package/apps/lib/components/RadioCard.tsx +11 -28
- package/apps/lib/components/ScrollArea.tsx +45 -43
- package/apps/lib/components/SearchField.tsx +49 -39
- package/apps/lib/components/SearchableSelect.tsx +16 -16
- package/apps/lib/components/SearchableTable.tsx +108 -118
- package/apps/lib/components/SearchableTree.tsx +469 -0
- package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
- package/apps/lib/components/SectionBlock.tsx +19 -23
- package/apps/lib/components/Select.tsx +80 -186
- package/apps/lib/components/SimpleSelect.tsx +196 -187
- package/apps/lib/components/Skeleton.tsx +9 -12
- package/apps/lib/components/SlideDatePicker.tsx +215 -166
- package/apps/lib/components/SpinLoading.tsx +33 -49
- package/apps/lib/components/Stepper.tsx +13 -56
- package/apps/lib/components/Switch.tsx +8 -10
- package/apps/lib/components/TabFormItem.tsx +3 -12
- package/apps/lib/components/TabSwitch.tsx +7 -34
- package/apps/lib/components/Table.tsx +60 -75
- package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +17 -52
- package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
- package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +15 -48
- package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +156 -113
- package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
- package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
- package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
- package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
- package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
- package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
- package/apps/lib/components/TextEditor/index.ts +7 -0
- package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +2 -7
- package/apps/lib/components/Textarea.tsx +9 -13
- package/apps/lib/components/Timeline.tsx +21 -41
- package/apps/lib/components/Toast.tsx +83 -83
- package/apps/lib/components/Toggle.tsx +112 -114
- package/apps/lib/components/ToggleButton.tsx +1 -1
- package/apps/lib/components/Tooltip.tsx +24 -19
- package/apps/lib/components/TransparentLabel.tsx +61 -62
- package/apps/lib/components/TreeDropDown.tsx +86 -52
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
- package/apps/lib/components/TreeFolder/icons.tsx +14 -17
- package/apps/lib/components/TreeFolder/index.ts +15 -8
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
- package/apps/lib/components/TreeFolder/types.ts +32 -32
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
- package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
- package/apps/lib/hooks/useClickOutside.ts +21 -20
- package/apps/lib/hooks/useDataViewsState.ts +86 -80
- package/apps/lib/hooks/useIsMobile.ts +12 -12
- package/apps/lib/hooks/useResize.ts +54 -65
- package/apps/lib/hooks/useTagSelection.ts +201 -201
- package/apps/lib/layouts/CNLayout.tsx +181 -140
- package/apps/lib/layouts/DataViewCard.tsx +4 -18
- package/apps/lib/layouts/FieldSection.tsx +2 -6
- package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
- package/apps/lib/providers/ThemeProvider.tsx +8 -23
- package/apps/lib/registry.json +997 -0
- package/apps/lib/tsconfig.json +9 -0
- package/apps/lib/tsconfig.tsbuildinfo +1 -0
- package/apps/lib/utils/cn.ts +1 -1
- package/apps/lib/utils/color.ts +175 -0
- package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
- package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
- package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
- package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
- package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
- package/apps/lib/utils/dateFormat.ts +64 -54
- package/apps/lib/utils/resize.ts +21 -25
- package/apps/lib/utils/types.ts +13 -13
- package/dist/bin/index.js +8 -2
- package/dist/bin/index.js.map +1 -1
- package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
- package/dist/src/shared/getAvailableFiles.js +6 -1
- package/dist/src/shared/getAvailableFiles.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +7 -0
- package/dist/src/shared/loadRegistry.d.ts.map +1 -0
- package/dist/src/shared/loadRegistry.js +31 -0
- package/dist/src/shared/loadRegistry.js.map +1 -0
- package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
- package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
- package/dist/src/shared/resolveInstallPlan.js +39 -0
- package/dist/src/shared/resolveInstallPlan.js.map +1 -0
- package/dist/src/types/main.d.ts +24 -0
- package/dist/src/types/main.d.ts.map +1 -1
- package/docs/README.md +10 -2
- package/docs/components/action-button.md +30 -24
- package/docs/components/actions-group.md +26 -20
- package/docs/components/alert-dialog.md +45 -45
- package/docs/components/avatar.md +6 -2
- package/docs/components/badge-field.md +6 -2
- package/docs/components/badge.md +7 -1
- package/docs/components/breadcrumb.md +15 -40
- package/docs/components/button-group.md +13 -10
- package/docs/components/button.md +58 -33
- package/docs/components/calendar.md +26 -19
- package/docs/components/card.md +23 -18
- package/docs/components/chart-block-tool.md +129 -0
- package/docs/components/checkbox.md +16 -8
- package/docs/components/cn-layout.md +7 -1
- package/docs/components/color-picker.md +101 -0
- package/docs/components/conclusion-header.md +80 -0
- package/docs/components/context-menu.md +18 -25
- package/docs/components/count-badge.md +7 -1
- package/docs/components/data-table.md +45 -33
- package/docs/components/data-views-config-panel.md +15 -11
- package/docs/components/data-views-layout.md +13 -16
- package/docs/components/date-picker.md +30 -23
- package/docs/components/dialog.md +43 -42
- package/docs/components/divider.md +10 -4
- package/docs/components/drawer.md +161 -135
- package/docs/components/dropdown-menu.md +25 -34
- package/docs/components/field-hint.md +37 -25
- package/docs/components/field-section.md +9 -3
- package/docs/components/form-builder.md +270 -0
- package/docs/components/form-renderer.md +228 -0
- package/docs/components/form-stepper.md +6 -0
- package/docs/components/form-summary.md +123 -0
- package/docs/components/form.md +17 -20
- package/docs/components/header-bar.md +6 -0
- package/docs/components/image-attachment.md +43 -45
- package/docs/components/inbox-view.md +16 -5
- package/docs/components/inner-label-field.md +34 -24
- package/docs/components/input-field.md +18 -10
- package/docs/components/input-otp.md +14 -11
- package/docs/components/input.md +22 -16
- package/docs/components/kanban-view.md +15 -5
- package/docs/components/label-field.md +32 -22
- package/docs/components/label.md +43 -32
- package/docs/components/labeled-check-box.md +16 -8
- package/docs/components/labeled-radio.md +18 -11
- package/docs/components/link-button.md +7 -1
- package/docs/components/login-button.md +8 -2
- package/docs/components/password-level.md +37 -24
- package/docs/components/popover.md +26 -23
- package/docs/components/profile-menu.md +38 -27
- package/docs/components/radio-card.md +16 -9
- package/docs/components/radio.md +16 -8
- package/docs/components/scroll-area.md +6 -2
- package/docs/components/search-field.md +14 -6
- package/docs/components/searchable-select.md +19 -14
- package/docs/components/searchable-table.md +18 -13
- package/docs/components/searchable-tree-dialog.md +190 -0
- package/docs/components/searchable-tree.md +200 -0
- package/docs/components/section-block.md +12 -4
- package/docs/components/select.md +23 -18
- package/docs/components/simple-select.md +14 -10
- package/docs/components/skeleton.md +43 -33
- package/docs/components/slide-date-picker.md +37 -29
- package/docs/components/spin-loading.md +32 -23
- package/docs/components/stepper.md +6 -0
- package/docs/components/switch.md +14 -6
- package/docs/components/tab-form-item.md +7 -1
- package/docs/components/tab-switch.md +13 -6
- package/docs/components/table-dnd-wrapper.md +107 -0
- package/docs/components/table-view.md +15 -5
- package/docs/components/table.md +28 -28
- package/docs/components/text-editor.md +37 -13
- package/docs/components/textarea.md +15 -7
- package/docs/components/timeline.md +6 -0
- package/docs/components/toast.md +35 -26
- package/docs/components/toggle-button.md +24 -21
- package/docs/components/toggle.md +23 -15
- package/docs/components/tooltip.md +42 -30
- package/docs/components/transparent-label.md +29 -20
- package/docs/components/tree-drop-down.md +31 -23
- package/docs/components/tree-sub-layout.md +6 -2
- package/docs/components/tree-view.md +16 -5
- package/docs/explanation/architecture.md +54 -0
- package/docs/explanation/design-system.md +52 -0
- package/docs/how-to/data-views-from-backend-response.md +6 -4
- package/docs/how-to/form-and-list-recipes.md +26 -19
- package/docs/how-to/forms-with-form-builder.md +408 -0
- package/docs/how-to/guides.md +177 -197
- package/docs/migration/changelog.md +38 -0
- package/docs/reference/cli.md +87 -0
- package/docs/reference/components.md +11 -12
- package/docs/reference/hooks.md +8 -8
- package/docs/reference/providers.md +22 -22
- package/docs/reference/tailwind-plugins.md +2 -2
- package/docs/reference/theme.md +85 -0
- package/docs/reference/types.md +4 -4
- package/docs/reference/utilities.md +19 -26
- package/docs/tutorials/building-first-form.md +170 -172
- package/docs/tutorials/component-composition.md +34 -50
- package/docs/tutorials/getting-started.md +2 -2
- package/docs/tutorials/theming-basics.md +13 -10
- package/package.json +31 -3
- package/dist/src/commands/block.d.ts +0 -5
- package/dist/src/commands/block.d.ts.map +0 -1
- package/dist/src/commands/block.js +0 -255
- package/dist/src/commands/block.js.map +0 -1
- package/dist/src/shared/configureFonts.d.ts +0 -6
- package/dist/src/shared/configureFonts.d.ts.map +0 -1
- package/dist/src/shared/configureFonts.js +0 -106
- package/dist/src/shared/configureFonts.js.map +0 -1
- package/dist/src/shared/configureGlobalCss.d.ts +0 -6
- package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
- package/dist/src/shared/configureGlobalCss.js +0 -154
- package/dist/src/shared/configureGlobalCss.js.map +0 -1
- package/dist/src/shared/configureTailwind.d.ts +0 -7
- package/dist/src/shared/configureTailwind.d.ts.map +0 -1
- package/dist/src/shared/configureTailwind.js +0 -163
- package/dist/src/shared/configureTailwind.js.map +0 -1
- package/dist/src/shared/detectFramework.d.ts +0 -23
- package/dist/src/shared/detectFramework.d.ts.map +0 -1
- package/dist/src/shared/detectFramework.js +0 -119
- package/dist/src/shared/detectFramework.js.map +0 -1
- package/dist/src/shared/installBaseUtils.d.ts +0 -5
- package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
- package/dist/src/shared/installBaseUtils.js +0 -79
- package/dist/src/shared/installBaseUtils.js.map +0 -1
- package/dist/src/shared/resolveAliases.d.ts +0 -24
- package/dist/src/shared/resolveAliases.d.ts.map +0 -1
- package/dist/src/shared/resolveAliases.js +0 -98
- package/dist/src/shared/resolveAliases.js.map +0 -1
|
@@ -16,9 +16,19 @@ related:
|
|
|
16
16
|
|
|
17
17
|
Learn how to build complete, production-ready forms with TORCH Glare. This tutorial covers everything from basic inputs to validation and error handling.
|
|
18
18
|
|
|
19
|
+
> **Build real forms with `FormBuilder`.** In an actual app, do **not** hand-wire fields with
|
|
20
|
+
> `useState` + `LabelField`/`InputField` rows as this tutorial does — that boilerplate is exactly
|
|
21
|
+
> what [`FormBuilder`](../components/form-builder.md) removes. With `FormBuilder` each field is one
|
|
22
|
+
> JSX child (`<FormBuilder.Text name="…" label="…" required />`) and validation comes from a
|
|
23
|
+
> react-hook-form resolver. Add [`FormRenderer`](../components/form-renderer.md) for page/drawer
|
|
24
|
+
> chrome + an `actions` slot for the Save, and [`FormSummary`](../components/form-summary.md) for live totals.
|
|
25
|
+
> See the **[Forms with FormBuilder](../how-to/forms-with-form-builder.md)** guide. The manual
|
|
26
|
+
> approach below is kept only to show what `FormBuilder` does under the hood.
|
|
27
|
+
|
|
19
28
|
## What You'll Build
|
|
20
29
|
|
|
21
30
|
A user registration form with:
|
|
31
|
+
|
|
22
32
|
- Multiple input types (text, email, password)
|
|
23
33
|
- Form validation
|
|
24
34
|
- Error handling and display
|
|
@@ -40,29 +50,27 @@ Let's start with a basic form structure:
|
|
|
40
50
|
|
|
41
51
|
```tsx
|
|
42
52
|
// components/RegistrationForm.tsx
|
|
43
|
-
|
|
53
|
+
"use client";
|
|
44
54
|
|
|
45
|
-
import { useState } from
|
|
46
|
-
import { Button } from
|
|
55
|
+
import { useState } from "react";
|
|
56
|
+
import { Button } from "@/components/Button";
|
|
47
57
|
|
|
48
58
|
export default function RegistrationForm() {
|
|
49
59
|
const [formData, setFormData] = useState({
|
|
50
|
-
fullName:
|
|
51
|
-
email:
|
|
52
|
-
password:
|
|
53
|
-
confirmPassword:
|
|
60
|
+
fullName: "",
|
|
61
|
+
email: "",
|
|
62
|
+
password: "",
|
|
63
|
+
confirmPassword: "",
|
|
54
64
|
});
|
|
55
65
|
|
|
56
66
|
const handleSubmit = (e: React.FormEvent) => {
|
|
57
67
|
e.preventDefault();
|
|
58
|
-
console.log(
|
|
68
|
+
console.log("Form submitted:", formData);
|
|
59
69
|
};
|
|
60
70
|
|
|
61
71
|
return (
|
|
62
72
|
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6">
|
|
63
|
-
<h2 className="typography-display-medium-bold mb-6">
|
|
64
|
-
Create Account
|
|
65
|
-
</h2>
|
|
73
|
+
<h2 className="typography-display-medium-bold mb-6">Create Account</h2>
|
|
66
74
|
|
|
67
75
|
{/* We'll add fields here */}
|
|
68
76
|
|
|
@@ -83,36 +91,35 @@ export default function RegistrationForm() {
|
|
|
83
91
|
`LabelField` combines a label and input field with built-in styling:
|
|
84
92
|
|
|
85
93
|
```tsx
|
|
86
|
-
import { useState } from
|
|
87
|
-
import {
|
|
94
|
+
import { useState } from "react";
|
|
95
|
+
import { Button } from "@/components/Button";
|
|
96
|
+
import { LabelField } from "@/components/LabelField";
|
|
88
97
|
|
|
89
98
|
export default function RegistrationForm() {
|
|
90
99
|
const [formData, setFormData] = useState({
|
|
91
|
-
fullName:
|
|
92
|
-
email:
|
|
93
|
-
password:
|
|
94
|
-
confirmPassword:
|
|
100
|
+
fullName: "",
|
|
101
|
+
email: "",
|
|
102
|
+
password: "",
|
|
103
|
+
confirmPassword: "",
|
|
95
104
|
});
|
|
96
105
|
|
|
97
106
|
const handleChange = (field: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
98
|
-
setFormData(prev => ({
|
|
107
|
+
setFormData((prev) => ({
|
|
99
108
|
...prev,
|
|
100
|
-
[field]: e.target.value
|
|
109
|
+
[field]: e.target.value,
|
|
101
110
|
}));
|
|
102
111
|
};
|
|
103
112
|
|
|
104
113
|
return (
|
|
105
114
|
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
|
|
106
|
-
<h2 className="typography-display-medium-bold mb-6">
|
|
107
|
-
Create Account
|
|
108
|
-
</h2>
|
|
115
|
+
<h2 className="typography-display-medium-bold mb-6">Create Account</h2>
|
|
109
116
|
|
|
110
117
|
<LabelField
|
|
111
118
|
theme="light"
|
|
112
119
|
label="Full Name"
|
|
113
120
|
requiredLabel="*"
|
|
114
121
|
value={formData.fullName}
|
|
115
|
-
onChange={handleChange(
|
|
122
|
+
onChange={handleChange("fullName")}
|
|
116
123
|
placeholder="John Doe"
|
|
117
124
|
/>
|
|
118
125
|
|
|
@@ -122,7 +129,7 @@ export default function RegistrationForm() {
|
|
|
122
129
|
requiredLabel="*"
|
|
123
130
|
type="email"
|
|
124
131
|
value={formData.email}
|
|
125
|
-
onChange={handleChange(
|
|
132
|
+
onChange={handleChange("email")}
|
|
126
133
|
placeholder="you@example.com"
|
|
127
134
|
/>
|
|
128
135
|
|
|
@@ -132,7 +139,7 @@ export default function RegistrationForm() {
|
|
|
132
139
|
requiredLabel="*"
|
|
133
140
|
type="password"
|
|
134
141
|
value={formData.password}
|
|
135
|
-
onChange={handleChange(
|
|
142
|
+
onChange={handleChange("password")}
|
|
136
143
|
placeholder="••••••••"
|
|
137
144
|
/>
|
|
138
145
|
|
|
@@ -142,7 +149,7 @@ export default function RegistrationForm() {
|
|
|
142
149
|
requiredLabel="*"
|
|
143
150
|
type="password"
|
|
144
151
|
value={formData.confirmPassword}
|
|
145
|
-
onChange={handleChange(
|
|
152
|
+
onChange={handleChange("confirmPassword")}
|
|
146
153
|
placeholder="••••••••"
|
|
147
154
|
/>
|
|
148
155
|
|
|
@@ -161,10 +168,12 @@ export default function RegistrationForm() {
|
|
|
161
168
|
Let's add comprehensive validation:
|
|
162
169
|
|
|
163
170
|
```tsx
|
|
164
|
-
|
|
171
|
+
"use client";
|
|
165
172
|
|
|
166
|
-
import { useState, FormEvent } from
|
|
167
|
-
import {
|
|
173
|
+
import { useState, FormEvent } from "react";
|
|
174
|
+
import { Button } from "@/components/Button";
|
|
175
|
+
import { FieldHint } from "@/components/FieldHint";
|
|
176
|
+
import { LabelField } from "@/components/LabelField";
|
|
168
177
|
|
|
169
178
|
interface FormErrors {
|
|
170
179
|
fullName?: string;
|
|
@@ -175,10 +184,10 @@ interface FormErrors {
|
|
|
175
184
|
|
|
176
185
|
export default function RegistrationForm() {
|
|
177
186
|
const [formData, setFormData] = useState({
|
|
178
|
-
fullName:
|
|
179
|
-
email:
|
|
180
|
-
password:
|
|
181
|
-
confirmPassword:
|
|
187
|
+
fullName: "",
|
|
188
|
+
email: "",
|
|
189
|
+
password: "",
|
|
190
|
+
confirmPassword: "",
|
|
182
191
|
});
|
|
183
192
|
|
|
184
193
|
const [errors, setErrors] = useState<FormErrors>({});
|
|
@@ -186,49 +195,48 @@ export default function RegistrationForm() {
|
|
|
186
195
|
|
|
187
196
|
// Validation functions
|
|
188
197
|
const validateFullName = (name: string): string | undefined => {
|
|
189
|
-
if (!name.trim()) return
|
|
190
|
-
if (name.trim().length < 2) return
|
|
198
|
+
if (!name.trim()) return "Full name is required";
|
|
199
|
+
if (name.trim().length < 2) return "Name must be at least 2 characters";
|
|
191
200
|
return undefined;
|
|
192
201
|
};
|
|
193
202
|
|
|
194
203
|
const validateEmail = (email: string): string | undefined => {
|
|
195
|
-
if (!email) return
|
|
204
|
+
if (!email) return "Email is required";
|
|
196
205
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
197
|
-
if (!emailRegex.test(email)) return
|
|
206
|
+
if (!emailRegex.test(email)) return "Please enter a valid email";
|
|
198
207
|
return undefined;
|
|
199
208
|
};
|
|
200
209
|
|
|
201
210
|
const validatePassword = (password: string): string | undefined => {
|
|
202
|
-
if (!password) return
|
|
203
|
-
if (password.length < 8) return
|
|
204
|
-
if (!/[A-Z]/.test(password)) return
|
|
205
|
-
if (!/[a-z]/.test(password)) return
|
|
206
|
-
if (!/[0-9]/.test(password)) return
|
|
211
|
+
if (!password) return "Password is required";
|
|
212
|
+
if (password.length < 8) return "Password must be at least 8 characters";
|
|
213
|
+
if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
|
|
214
|
+
if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
|
|
215
|
+
if (!/[0-9]/.test(password)) return "Password must contain a number";
|
|
207
216
|
return undefined;
|
|
208
217
|
};
|
|
209
218
|
|
|
210
219
|
const validateConfirmPassword = (confirmPassword: string): string | undefined => {
|
|
211
|
-
if (!confirmPassword) return
|
|
212
|
-
if (confirmPassword !== formData.password) return
|
|
220
|
+
if (!confirmPassword) return "Please confirm your password";
|
|
221
|
+
if (confirmPassword !== formData.password) return "Passwords do not match";
|
|
213
222
|
return undefined;
|
|
214
223
|
};
|
|
215
224
|
|
|
216
225
|
// Handle field changes
|
|
217
|
-
const handleChange =
|
|
218
|
-
e: React.ChangeEvent<HTMLInputElement>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
};
|
|
226
|
+
const handleChange =
|
|
227
|
+
(field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
228
|
+
const value = e.target.value;
|
|
229
|
+
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
230
|
+
|
|
231
|
+
// Validate on change if field has been touched
|
|
232
|
+
if (touched[field]) {
|
|
233
|
+
validateField(field, value);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
228
236
|
|
|
229
237
|
// Handle blur events
|
|
230
238
|
const handleBlur = (field: keyof typeof formData) => () => {
|
|
231
|
-
setTouched(prev => ({ ...prev, [field]: true }));
|
|
239
|
+
setTouched((prev) => ({ ...prev, [field]: true }));
|
|
232
240
|
validateField(field, formData[field]);
|
|
233
241
|
};
|
|
234
242
|
|
|
@@ -237,21 +245,21 @@ export default function RegistrationForm() {
|
|
|
237
245
|
let error: string | undefined;
|
|
238
246
|
|
|
239
247
|
switch (field) {
|
|
240
|
-
case
|
|
248
|
+
case "fullName":
|
|
241
249
|
error = validateFullName(value);
|
|
242
250
|
break;
|
|
243
|
-
case
|
|
251
|
+
case "email":
|
|
244
252
|
error = validateEmail(value);
|
|
245
253
|
break;
|
|
246
|
-
case
|
|
254
|
+
case "password":
|
|
247
255
|
error = validatePassword(value);
|
|
248
256
|
break;
|
|
249
|
-
case
|
|
257
|
+
case "confirmPassword":
|
|
250
258
|
error = validateConfirmPassword(value);
|
|
251
259
|
break;
|
|
252
260
|
}
|
|
253
261
|
|
|
254
|
-
setErrors(prev => ({ ...prev, [field]: error }));
|
|
262
|
+
setErrors((prev) => ({ ...prev, [field]: error }));
|
|
255
263
|
};
|
|
256
264
|
|
|
257
265
|
// Validate all fields
|
|
@@ -271,7 +279,7 @@ export default function RegistrationForm() {
|
|
|
271
279
|
confirmPassword: true,
|
|
272
280
|
});
|
|
273
281
|
|
|
274
|
-
return !Object.values(newErrors).some(error => error !== undefined);
|
|
282
|
+
return !Object.values(newErrors).some((error) => error !== undefined);
|
|
275
283
|
};
|
|
276
284
|
|
|
277
285
|
// Handle form submission
|
|
@@ -279,16 +287,14 @@ export default function RegistrationForm() {
|
|
|
279
287
|
e.preventDefault();
|
|
280
288
|
|
|
281
289
|
if (validateForm()) {
|
|
282
|
-
console.log(
|
|
290
|
+
console.log("Form is valid:", formData);
|
|
283
291
|
// Submit form data
|
|
284
292
|
}
|
|
285
293
|
};
|
|
286
294
|
|
|
287
295
|
return (
|
|
288
296
|
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 space-y-4">
|
|
289
|
-
<h2 className="typography-display-medium-bold mb-6">
|
|
290
|
-
Create Account
|
|
291
|
-
</h2>
|
|
297
|
+
<h2 className="typography-display-medium-bold mb-6">Create Account</h2>
|
|
292
298
|
|
|
293
299
|
<div>
|
|
294
300
|
<LabelField
|
|
@@ -296,8 +302,8 @@ export default function RegistrationForm() {
|
|
|
296
302
|
label="Full Name"
|
|
297
303
|
requiredLabel="*"
|
|
298
304
|
value={formData.fullName}
|
|
299
|
-
onChange={handleChange(
|
|
300
|
-
onBlur={handleBlur(
|
|
305
|
+
onChange={handleChange("fullName")}
|
|
306
|
+
onBlur={handleBlur("fullName")}
|
|
301
307
|
placeholder="John Doe"
|
|
302
308
|
errorMessage={touched.fullName ? errors.fullName : undefined}
|
|
303
309
|
/>
|
|
@@ -310,8 +316,8 @@ export default function RegistrationForm() {
|
|
|
310
316
|
requiredLabel="*"
|
|
311
317
|
type="email"
|
|
312
318
|
value={formData.email}
|
|
313
|
-
onChange={handleChange(
|
|
314
|
-
onBlur={handleBlur(
|
|
319
|
+
onChange={handleChange("email")}
|
|
320
|
+
onBlur={handleBlur("email")}
|
|
315
321
|
placeholder="you@example.com"
|
|
316
322
|
errorMessage={touched.email ? errors.email : undefined}
|
|
317
323
|
/>
|
|
@@ -324,8 +330,8 @@ export default function RegistrationForm() {
|
|
|
324
330
|
requiredLabel="*"
|
|
325
331
|
type="password"
|
|
326
332
|
value={formData.password}
|
|
327
|
-
onChange={handleChange(
|
|
328
|
-
onBlur={handleBlur(
|
|
333
|
+
onChange={handleChange("password")}
|
|
334
|
+
onBlur={handleBlur("password")}
|
|
329
335
|
placeholder="••••••••"
|
|
330
336
|
errorMessage={touched.password ? errors.password : undefined}
|
|
331
337
|
/>
|
|
@@ -338,8 +344,8 @@ export default function RegistrationForm() {
|
|
|
338
344
|
requiredLabel="*"
|
|
339
345
|
type="password"
|
|
340
346
|
value={formData.confirmPassword}
|
|
341
|
-
onChange={handleChange(
|
|
342
|
-
onBlur={handleBlur(
|
|
347
|
+
onChange={handleChange("confirmPassword")}
|
|
348
|
+
onBlur={handleBlur("confirmPassword")}
|
|
343
349
|
placeholder="••••••••"
|
|
344
350
|
errorMessage={touched.confirmPassword ? errors.confirmPassword : undefined}
|
|
345
351
|
/>
|
|
@@ -360,7 +366,9 @@ export default function RegistrationForm() {
|
|
|
360
366
|
Use the `PasswordLevel` component to show password strength:
|
|
361
367
|
|
|
362
368
|
```tsx
|
|
363
|
-
import {
|
|
369
|
+
import { Button } from "@/components/Button";
|
|
370
|
+
import { LabelField } from "@/components/LabelField";
|
|
371
|
+
import { PasswordLevel } from "@/components/PasswordLevel";
|
|
364
372
|
|
|
365
373
|
// Inside your form component, add this after the password field:
|
|
366
374
|
|
|
@@ -371,8 +379,8 @@ import { LabelField, PasswordLevel, Button } from '@torch-ai/torch-glare';
|
|
|
371
379
|
requiredLabel="*"
|
|
372
380
|
type="password"
|
|
373
381
|
value={formData.password}
|
|
374
|
-
onChange={handleChange(
|
|
375
|
-
onBlur={handleBlur(
|
|
382
|
+
onChange={handleChange("password")}
|
|
383
|
+
onBlur={handleBlur("password")}
|
|
376
384
|
placeholder="••••••••"
|
|
377
385
|
errorMessage={touched.password ? errors.password : undefined}
|
|
378
386
|
/>
|
|
@@ -383,7 +391,7 @@ import { LabelField, PasswordLevel, Button } from '@torch-ai/torch-glare';
|
|
|
383
391
|
<PasswordLevel theme="light" value={formData.password} />
|
|
384
392
|
</div>
|
|
385
393
|
)}
|
|
386
|
-
</div
|
|
394
|
+
</div>;
|
|
387
395
|
```
|
|
388
396
|
|
|
389
397
|
---
|
|
@@ -393,7 +401,9 @@ import { LabelField, PasswordLevel, Button } from '@torch-ai/torch-glare';
|
|
|
393
401
|
Use `FieldHint` to provide helpful guidance:
|
|
394
402
|
|
|
395
403
|
```tsx
|
|
396
|
-
import {
|
|
404
|
+
import { Button } from "@/components/Button";
|
|
405
|
+
import { FieldHint } from "@/components/FieldHint";
|
|
406
|
+
import { LabelField } from "@/components/LabelField";
|
|
397
407
|
|
|
398
408
|
<div>
|
|
399
409
|
<LabelField
|
|
@@ -402,8 +412,8 @@ import { LabelField, FieldHint, Button } from '@torch-ai/torch-glare';
|
|
|
402
412
|
requiredLabel="*"
|
|
403
413
|
type="email"
|
|
404
414
|
value={formData.email}
|
|
405
|
-
onChange={handleChange(
|
|
406
|
-
onBlur={handleBlur(
|
|
415
|
+
onChange={handleChange("email")}
|
|
416
|
+
onBlur={handleBlur("email")}
|
|
407
417
|
placeholder="you@example.com"
|
|
408
418
|
errorMessage={touched.email ? errors.email : undefined}
|
|
409
419
|
/>
|
|
@@ -416,7 +426,7 @@ import { LabelField, FieldHint, Button } from '@torch-ai/torch-glare';
|
|
|
416
426
|
className="mt-2"
|
|
417
427
|
/>
|
|
418
428
|
)}
|
|
419
|
-
</div
|
|
429
|
+
</div>;
|
|
420
430
|
```
|
|
421
431
|
|
|
422
432
|
---
|
|
@@ -437,12 +447,12 @@ const handleSubmit = async (e: FormEvent) => {
|
|
|
437
447
|
|
|
438
448
|
try {
|
|
439
449
|
// Simulate API call
|
|
440
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
450
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
441
451
|
|
|
442
|
-
console.log(
|
|
452
|
+
console.log("Registration successful:", formData);
|
|
443
453
|
// Handle success (e.g., redirect, show success message)
|
|
444
454
|
} catch (error) {
|
|
445
|
-
console.error(
|
|
455
|
+
console.error("Registration failed:", error);
|
|
446
456
|
// Handle error
|
|
447
457
|
} finally {
|
|
448
458
|
setIsSubmitting(false);
|
|
@@ -450,15 +460,9 @@ const handleSubmit = async (e: FormEvent) => {
|
|
|
450
460
|
};
|
|
451
461
|
|
|
452
462
|
// Update button
|
|
453
|
-
<Button
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
type="submit"
|
|
457
|
-
className="w-full"
|
|
458
|
-
disabled={isSubmitting}
|
|
459
|
-
>
|
|
460
|
-
{isSubmitting ? 'Registering...' : 'Register'}
|
|
461
|
-
</Button>
|
|
463
|
+
<Button theme="light" variant="PrimeStyle" type="submit" className="w-full" disabled={isSubmitting}>
|
|
464
|
+
{isSubmitting ? "Registering..." : "Register"}
|
|
465
|
+
</Button>;
|
|
462
466
|
```
|
|
463
467
|
|
|
464
468
|
---
|
|
@@ -468,13 +472,15 @@ const handleSubmit = async (e: FormEvent) => {
|
|
|
468
472
|
Use the `toast` function for success notifications:
|
|
469
473
|
|
|
470
474
|
```tsx
|
|
471
|
-
import {
|
|
475
|
+
import { Button } from "@/components/Button";
|
|
476
|
+
import { LabelField } from "@/components/LabelField";
|
|
477
|
+
import { toast } from "@/components/Toast";
|
|
472
478
|
|
|
473
479
|
const handleSubmit = async (e: FormEvent) => {
|
|
474
480
|
e.preventDefault();
|
|
475
481
|
|
|
476
482
|
if (!validateForm()) {
|
|
477
|
-
toast.error(
|
|
483
|
+
toast.error("Please fix the errors in the form");
|
|
478
484
|
return;
|
|
479
485
|
}
|
|
480
486
|
|
|
@@ -482,21 +488,21 @@ const handleSubmit = async (e: FormEvent) => {
|
|
|
482
488
|
|
|
483
489
|
try {
|
|
484
490
|
// Simulate API call
|
|
485
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
491
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
486
492
|
|
|
487
|
-
toast.success(
|
|
493
|
+
toast.success("Account created successfully!");
|
|
488
494
|
|
|
489
495
|
// Reset form
|
|
490
496
|
setFormData({
|
|
491
|
-
fullName:
|
|
492
|
-
email:
|
|
493
|
-
password:
|
|
494
|
-
confirmPassword:
|
|
497
|
+
fullName: "",
|
|
498
|
+
email: "",
|
|
499
|
+
password: "",
|
|
500
|
+
confirmPassword: "",
|
|
495
501
|
});
|
|
496
502
|
setErrors({});
|
|
497
503
|
setTouched({});
|
|
498
504
|
} catch (error) {
|
|
499
|
-
toast.error(
|
|
505
|
+
toast.error("Registration failed. Please try again.");
|
|
500
506
|
} finally {
|
|
501
507
|
setIsSubmitting(false);
|
|
502
508
|
}
|
|
@@ -510,16 +516,14 @@ const handleSubmit = async (e: FormEvent) => {
|
|
|
510
516
|
Here's the complete, production-ready form:
|
|
511
517
|
|
|
512
518
|
```tsx
|
|
513
|
-
|
|
519
|
+
"use client";
|
|
514
520
|
|
|
515
|
-
import { useState, FormEvent } from
|
|
516
|
-
import {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
toast,
|
|
522
|
-
} from '@torch-ai/torch-glare';
|
|
521
|
+
import { useState, FormEvent } from "react";
|
|
522
|
+
import { Button } from "@/components/Button";
|
|
523
|
+
import { FieldHint } from "@/components/FieldHint";
|
|
524
|
+
import { LabelField } from "@/components/LabelField";
|
|
525
|
+
import { PasswordLevel } from "@/components/PasswordLevel";
|
|
526
|
+
import { toast } from "@/components/Toast";
|
|
523
527
|
|
|
524
528
|
interface FormErrors {
|
|
525
529
|
fullName?: string;
|
|
@@ -530,10 +534,10 @@ interface FormErrors {
|
|
|
530
534
|
|
|
531
535
|
export default function RegistrationForm() {
|
|
532
536
|
const [formData, setFormData] = useState({
|
|
533
|
-
fullName:
|
|
534
|
-
email:
|
|
535
|
-
password:
|
|
536
|
-
confirmPassword:
|
|
537
|
+
fullName: "",
|
|
538
|
+
email: "",
|
|
539
|
+
password: "",
|
|
540
|
+
confirmPassword: "",
|
|
537
541
|
});
|
|
538
542
|
|
|
539
543
|
const [errors, setErrors] = useState<FormErrors>({});
|
|
@@ -542,46 +546,45 @@ export default function RegistrationForm() {
|
|
|
542
546
|
|
|
543
547
|
// Validation functions
|
|
544
548
|
const validateFullName = (name: string): string | undefined => {
|
|
545
|
-
if (!name.trim()) return
|
|
546
|
-
if (name.trim().length < 2) return
|
|
549
|
+
if (!name.trim()) return "Full name is required";
|
|
550
|
+
if (name.trim().length < 2) return "Name must be at least 2 characters";
|
|
547
551
|
return undefined;
|
|
548
552
|
};
|
|
549
553
|
|
|
550
554
|
const validateEmail = (email: string): string | undefined => {
|
|
551
|
-
if (!email) return
|
|
555
|
+
if (!email) return "Email is required";
|
|
552
556
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
553
|
-
if (!emailRegex.test(email)) return
|
|
557
|
+
if (!emailRegex.test(email)) return "Please enter a valid email";
|
|
554
558
|
return undefined;
|
|
555
559
|
};
|
|
556
560
|
|
|
557
561
|
const validatePassword = (password: string): string | undefined => {
|
|
558
|
-
if (!password) return
|
|
559
|
-
if (password.length < 8) return
|
|
560
|
-
if (!/[A-Z]/.test(password)) return
|
|
561
|
-
if (!/[a-z]/.test(password)) return
|
|
562
|
-
if (!/[0-9]/.test(password)) return
|
|
562
|
+
if (!password) return "Password is required";
|
|
563
|
+
if (password.length < 8) return "Password must be at least 8 characters";
|
|
564
|
+
if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
|
|
565
|
+
if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
|
|
566
|
+
if (!/[0-9]/.test(password)) return "Password must contain a number";
|
|
563
567
|
return undefined;
|
|
564
568
|
};
|
|
565
569
|
|
|
566
570
|
const validateConfirmPassword = (confirmPassword: string): string | undefined => {
|
|
567
|
-
if (!confirmPassword) return
|
|
568
|
-
if (confirmPassword !== formData.password) return
|
|
571
|
+
if (!confirmPassword) return "Please confirm your password";
|
|
572
|
+
if (confirmPassword !== formData.password) return "Passwords do not match";
|
|
569
573
|
return undefined;
|
|
570
574
|
};
|
|
571
575
|
|
|
572
|
-
const handleChange =
|
|
573
|
-
e: React.ChangeEvent<HTMLInputElement>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
setFormData(prev => ({ ...prev, [field]: value }));
|
|
576
|
+
const handleChange =
|
|
577
|
+
(field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
578
|
+
const value = e.target.value;
|
|
579
|
+
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
577
580
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
581
|
+
if (touched[field]) {
|
|
582
|
+
validateField(field, value);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
582
585
|
|
|
583
586
|
const handleBlur = (field: keyof typeof formData) => () => {
|
|
584
|
-
setTouched(prev => ({ ...prev, [field]: true }));
|
|
587
|
+
setTouched((prev) => ({ ...prev, [field]: true }));
|
|
585
588
|
validateField(field, formData[field]);
|
|
586
589
|
};
|
|
587
590
|
|
|
@@ -589,21 +592,21 @@ export default function RegistrationForm() {
|
|
|
589
592
|
let error: string | undefined;
|
|
590
593
|
|
|
591
594
|
switch (field) {
|
|
592
|
-
case
|
|
595
|
+
case "fullName":
|
|
593
596
|
error = validateFullName(value);
|
|
594
597
|
break;
|
|
595
|
-
case
|
|
598
|
+
case "email":
|
|
596
599
|
error = validateEmail(value);
|
|
597
600
|
break;
|
|
598
|
-
case
|
|
601
|
+
case "password":
|
|
599
602
|
error = validatePassword(value);
|
|
600
603
|
break;
|
|
601
|
-
case
|
|
604
|
+
case "confirmPassword":
|
|
602
605
|
error = validateConfirmPassword(value);
|
|
603
606
|
break;
|
|
604
607
|
}
|
|
605
608
|
|
|
606
|
-
setErrors(prev => ({ ...prev, [field]: error }));
|
|
609
|
+
setErrors((prev) => ({ ...prev, [field]: error }));
|
|
607
610
|
};
|
|
608
611
|
|
|
609
612
|
const validateForm = (): boolean => {
|
|
@@ -622,14 +625,14 @@ export default function RegistrationForm() {
|
|
|
622
625
|
confirmPassword: true,
|
|
623
626
|
});
|
|
624
627
|
|
|
625
|
-
return !Object.values(newErrors).some(error => error !== undefined);
|
|
628
|
+
return !Object.values(newErrors).some((error) => error !== undefined);
|
|
626
629
|
};
|
|
627
630
|
|
|
628
631
|
const handleSubmit = async (e: FormEvent) => {
|
|
629
632
|
e.preventDefault();
|
|
630
633
|
|
|
631
634
|
if (!validateForm()) {
|
|
632
|
-
toast.error(
|
|
635
|
+
toast.error("Please fix the errors in the form");
|
|
633
636
|
return;
|
|
634
637
|
}
|
|
635
638
|
|
|
@@ -637,21 +640,21 @@ export default function RegistrationForm() {
|
|
|
637
640
|
|
|
638
641
|
try {
|
|
639
642
|
// Simulate API call
|
|
640
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
643
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
641
644
|
|
|
642
|
-
toast.success(
|
|
645
|
+
toast.success("Account created successfully!");
|
|
643
646
|
|
|
644
647
|
// Reset form
|
|
645
648
|
setFormData({
|
|
646
|
-
fullName:
|
|
647
|
-
email:
|
|
648
|
-
password:
|
|
649
|
-
confirmPassword:
|
|
649
|
+
fullName: "",
|
|
650
|
+
email: "",
|
|
651
|
+
password: "",
|
|
652
|
+
confirmPassword: "",
|
|
650
653
|
});
|
|
651
654
|
setErrors({});
|
|
652
655
|
setTouched({});
|
|
653
656
|
} catch (error) {
|
|
654
|
-
toast.error(
|
|
657
|
+
toast.error("Registration failed. Please try again.");
|
|
655
658
|
} finally {
|
|
656
659
|
setIsSubmitting(false);
|
|
657
660
|
}
|
|
@@ -669,9 +672,7 @@ export default function RegistrationForm() {
|
|
|
669
672
|
"
|
|
670
673
|
>
|
|
671
674
|
<div className="mb-6">
|
|
672
|
-
<h2 className="typography-display-medium-bold mb-2">
|
|
673
|
-
Create Account
|
|
674
|
-
</h2>
|
|
675
|
+
<h2 className="typography-display-medium-bold mb-2">Create Account</h2>
|
|
675
676
|
<p className="typography-body-medium-regular text-content-presentation-global-secondary">
|
|
676
677
|
Join us and get started today
|
|
677
678
|
</p>
|
|
@@ -683,8 +684,8 @@ export default function RegistrationForm() {
|
|
|
683
684
|
label="Full Name"
|
|
684
685
|
requiredLabel="*"
|
|
685
686
|
value={formData.fullName}
|
|
686
|
-
onChange={handleChange(
|
|
687
|
-
onBlur={handleBlur(
|
|
687
|
+
onChange={handleChange("fullName")}
|
|
688
|
+
onBlur={handleBlur("fullName")}
|
|
688
689
|
placeholder="John Doe"
|
|
689
690
|
errorMessage={touched.fullName ? errors.fullName : undefined}
|
|
690
691
|
/>
|
|
@@ -697,8 +698,8 @@ export default function RegistrationForm() {
|
|
|
697
698
|
requiredLabel="*"
|
|
698
699
|
type="email"
|
|
699
700
|
value={formData.email}
|
|
700
|
-
onChange={handleChange(
|
|
701
|
-
onBlur={handleBlur(
|
|
701
|
+
onChange={handleChange("email")}
|
|
702
|
+
onBlur={handleBlur("email")}
|
|
702
703
|
placeholder="you@example.com"
|
|
703
704
|
errorMessage={touched.email ? errors.email : undefined}
|
|
704
705
|
/>
|
|
@@ -719,8 +720,8 @@ export default function RegistrationForm() {
|
|
|
719
720
|
requiredLabel="*"
|
|
720
721
|
type="password"
|
|
721
722
|
value={formData.password}
|
|
722
|
-
onChange={handleChange(
|
|
723
|
-
onBlur={handleBlur(
|
|
723
|
+
onChange={handleChange("password")}
|
|
724
|
+
onBlur={handleBlur("password")}
|
|
724
725
|
placeholder="••••••••"
|
|
725
726
|
errorMessage={touched.password ? errors.password : undefined}
|
|
726
727
|
/>
|
|
@@ -738,8 +739,8 @@ export default function RegistrationForm() {
|
|
|
738
739
|
requiredLabel="*"
|
|
739
740
|
type="password"
|
|
740
741
|
value={formData.confirmPassword}
|
|
741
|
-
onChange={handleChange(
|
|
742
|
-
onBlur={handleBlur(
|
|
742
|
+
onChange={handleChange("confirmPassword")}
|
|
743
|
+
onBlur={handleBlur("confirmPassword")}
|
|
743
744
|
placeholder="••••••••"
|
|
744
745
|
errorMessage={touched.confirmPassword ? errors.confirmPassword : undefined}
|
|
745
746
|
/>
|
|
@@ -752,11 +753,11 @@ export default function RegistrationForm() {
|
|
|
752
753
|
className="w-full"
|
|
753
754
|
disabled={isSubmitting}
|
|
754
755
|
>
|
|
755
|
-
{isSubmitting ?
|
|
756
|
+
{isSubmitting ? "Registering..." : "Register"}
|
|
756
757
|
</Button>
|
|
757
758
|
|
|
758
759
|
<p className="typography-body-small-regular text-content-presentation-global-secondary text-center">
|
|
759
|
-
Already have an account?{
|
|
760
|
+
Already have an account?{" "}
|
|
760
761
|
<a href="/login" className="text-content-presentation-action-light-primary">
|
|
761
762
|
Sign in
|
|
762
763
|
</a>
|
|
@@ -799,11 +800,8 @@ errorMessage={touched.email ? errors.email : undefined}
|
|
|
799
800
|
### 3. Disable Submit During Submission
|
|
800
801
|
|
|
801
802
|
```tsx
|
|
802
|
-
<Button
|
|
803
|
-
|
|
804
|
-
disabled={isSubmitting || Object.keys(errors).length > 0}
|
|
805
|
-
>
|
|
806
|
-
{isSubmitting ? 'Submitting...' : 'Submit'}
|
|
803
|
+
<Button type="submit" disabled={isSubmitting || Object.keys(errors).length > 0}>
|
|
804
|
+
{isSubmitting ? "Submitting..." : "Submit"}
|
|
807
805
|
</Button>
|
|
808
806
|
```
|
|
809
807
|
|
|
@@ -811,10 +809,10 @@ errorMessage={touched.email ? errors.email : undefined}
|
|
|
811
809
|
|
|
812
810
|
```tsx
|
|
813
811
|
// ✓ Good - Specific and actionable
|
|
814
|
-
|
|
812
|
+
"Password must contain at least one uppercase letter";
|
|
815
813
|
|
|
816
814
|
// ✗ Bad - Vague
|
|
817
|
-
|
|
815
|
+
"Invalid password";
|
|
818
816
|
```
|
|
819
817
|
|
|
820
818
|
### 5. Reset Form After Success
|
|
@@ -824,7 +822,7 @@ const handleSuccess = () => {
|
|
|
824
822
|
setFormData(initialState);
|
|
825
823
|
setErrors({});
|
|
826
824
|
setTouched({});
|
|
827
|
-
toast.success(
|
|
825
|
+
toast.success("Success!");
|
|
828
826
|
};
|
|
829
827
|
```
|
|
830
828
|
|