@flamingo-stack/openframe-frontend-core 0.0.398 → 0.0.399-snapshot.20260710120252
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/dist/{chunk-GKRJOS6S.js → chunk-463X3AW2.js} +3 -3
- package/dist/{chunk-MNQVBME4.js → chunk-4HAVKWTJ.js} +4 -4
- package/dist/{chunk-PE37UDPF.cjs → chunk-556BG7SX.cjs} +40 -40
- package/dist/{chunk-PE37UDPF.cjs.map → chunk-556BG7SX.cjs.map} +1 -1
- package/dist/{chunk-N4R7TRF3.js → chunk-56XCRZ7I.js} +3 -3
- package/dist/{chunk-VDDNJ6XC.js → chunk-6VR7BJF6.js} +6 -6
- package/dist/{chunk-FZIMFLJG.cjs → chunk-6XQJSN7G.cjs} +19 -19
- package/dist/{chunk-FZIMFLJG.cjs.map → chunk-6XQJSN7G.cjs.map} +1 -1
- package/dist/{chunk-HMTOQGYU.cjs → chunk-ABRDOCVN.cjs} +12 -12
- package/dist/{chunk-HMTOQGYU.cjs.map → chunk-ABRDOCVN.cjs.map} +1 -1
- package/dist/{chunk-ZSZRJWY3.js → chunk-DH6HTYOV.js} +153 -134
- package/dist/chunk-DH6HTYOV.js.map +1 -0
- package/dist/{chunk-XYT5PIKZ.cjs → chunk-EFTKJFEF.cjs} +5 -5
- package/dist/{chunk-XYT5PIKZ.cjs.map → chunk-EFTKJFEF.cjs.map} +1 -1
- package/dist/{chunk-MGJZQ3EE.js → chunk-FHEIG7QO.js} +3 -3
- package/dist/{chunk-NYIZVI4I.cjs → chunk-GA5AN24T.cjs} +10 -3
- package/dist/chunk-GA5AN24T.cjs.map +1 -0
- package/dist/{chunk-X2FPWS3P.js → chunk-HGKWTKW7.js} +10 -3
- package/dist/chunk-HGKWTKW7.js.map +1 -0
- package/dist/{chunk-G42EXM23.js → chunk-I4XG5PJA.js} +2 -2
- package/dist/{chunk-YEWGFWNM.cjs → chunk-ILWSJCWD.cjs} +33 -33
- package/dist/{chunk-YEWGFWNM.cjs.map → chunk-ILWSJCWD.cjs.map} +1 -1
- package/dist/{chunk-JWBRDNYH.cjs → chunk-J4XR6YYZ.cjs} +23 -4
- package/dist/chunk-J4XR6YYZ.cjs.map +1 -0
- package/dist/{chunk-VVO7AZKZ.cjs → chunk-J6IQQJJQ.cjs} +1011 -986
- package/dist/chunk-J6IQQJJQ.cjs.map +1 -0
- package/dist/{chunk-4BVRLCCA.cjs → chunk-KIC4ZEZ7.cjs} +93 -93
- package/dist/{chunk-4BVRLCCA.cjs.map → chunk-KIC4ZEZ7.cjs.map} +1 -1
- package/dist/{chunk-Y3FREU4B.js → chunk-LAL63W5J.js} +6 -6
- package/dist/{chunk-7Q2DZEUA.cjs → chunk-MFRP2UDK.cjs} +32 -32
- package/dist/{chunk-7Q2DZEUA.cjs.map → chunk-MFRP2UDK.cjs.map} +1 -1
- package/dist/{chunk-URODM26E.js → chunk-MQRTT3DO.js} +6 -6
- package/dist/{chunk-4KWIQEZT.js → chunk-MTBFE3JF.js} +2 -2
- package/dist/{chunk-2DGA5LHT.cjs → chunk-OPD5V57R.cjs} +19 -19
- package/dist/{chunk-2DGA5LHT.cjs.map → chunk-OPD5V57R.cjs.map} +1 -1
- package/dist/{chunk-WXOIEYXW.js → chunk-PD5MXNTJ.js} +5 -5
- package/dist/{chunk-WBLRUO3F.cjs → chunk-Q3AO2QWK.cjs} +3 -3
- package/dist/{chunk-WBLRUO3F.cjs.map → chunk-Q3AO2QWK.cjs.map} +1 -1
- package/dist/{chunk-C4DHYDXB.cjs → chunk-QGQ76SSR.cjs} +12 -12
- package/dist/{chunk-C4DHYDXB.cjs.map → chunk-QGQ76SSR.cjs.map} +1 -1
- package/dist/{chunk-WJSUUOPU.cjs → chunk-RSFHAFZN.cjs} +40 -40
- package/dist/{chunk-WJSUUOPU.cjs.map → chunk-RSFHAFZN.cjs.map} +1 -1
- package/dist/{chunk-MQXUIYYN.js → chunk-RYPCNUXA.js} +4 -4
- package/dist/{chunk-2TBXIW7L.js → chunk-UFP45JLE.js} +3 -3
- package/dist/{chunk-B2K5COVE.js → chunk-UYBNOHTC.js} +170 -145
- package/dist/{chunk-B2K5COVE.js.map → chunk-UYBNOHTC.js.map} +1 -1
- package/dist/{chunk-LBDKAMOH.cjs → chunk-VTSEL7CJ.cjs} +73 -73
- package/dist/{chunk-LBDKAMOH.cjs.map → chunk-VTSEL7CJ.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/auth/auth-benefits-panel.d.ts.map +1 -1
- package/dist/components/features/auth/complete-account-form.d.ts.map +1 -1
- package/dist/components/features/auth/create-organization-form.d.ts.map +1 -1
- package/dist/components/features/auth/login-form.d.ts.map +1 -1
- package/dist/components/features/auth/openframe-sso-login-form.d.ts.map +1 -1
- package/dist/components/features/auth/openframe-sso-signup-form.d.ts.map +1 -1
- package/dist/components/features/auth/password-reset-form.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +149 -149
- package/dist/components/index.js +12 -12
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/field-wrapper.d.ts +1 -1
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +4 -4
- package/dist/components/ui/index.js +3 -3
- package/dist/hooks/index.cjs +4 -2
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +3 -1
- package/dist/hooks/ui/index.d.ts +1 -0
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/use-deferred-error.d.ts +16 -0
- package/dist/hooks/ui/use-deferred-error.d.ts.map +1 -0
- package/dist/index.cjs +6 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/src/components/features/auth/auth-benefits-panel.tsx +1 -1
- package/src/components/features/auth/complete-account-form.tsx +48 -51
- package/src/components/features/auth/create-organization-form.tsx +33 -31
- package/src/components/features/auth/login-form.tsx +6 -1
- package/src/components/features/auth/openframe-sso-login-form.tsx +9 -2
- package/src/components/features/auth/openframe-sso-signup-form.tsx +51 -52
- package/src/components/features/auth/password-reset-form.tsx +9 -2
- package/src/components/ui/field-wrapper.tsx +6 -3
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/use-deferred-error.ts +38 -0
- package/src/stories/Input.stories.tsx +44 -0
- package/src/stories/auth/CreateOrganization.stories.tsx +20 -0
- package/dist/chunk-JWBRDNYH.cjs.map +0 -1
- package/dist/chunk-NYIZVI4I.cjs.map +0 -1
- package/dist/chunk-VVO7AZKZ.cjs.map +0 -1
- package/dist/chunk-X2FPWS3P.js.map +0 -1
- package/dist/chunk-ZSZRJWY3.js.map +0 -1
- /package/dist/{chunk-GKRJOS6S.js.map → chunk-463X3AW2.js.map} +0 -0
- /package/dist/{chunk-MNQVBME4.js.map → chunk-4HAVKWTJ.js.map} +0 -0
- /package/dist/{chunk-N4R7TRF3.js.map → chunk-56XCRZ7I.js.map} +0 -0
- /package/dist/{chunk-VDDNJ6XC.js.map → chunk-6VR7BJF6.js.map} +0 -0
- /package/dist/{chunk-MGJZQ3EE.js.map → chunk-FHEIG7QO.js.map} +0 -0
- /package/dist/{chunk-G42EXM23.js.map → chunk-I4XG5PJA.js.map} +0 -0
- /package/dist/{chunk-Y3FREU4B.js.map → chunk-LAL63W5J.js.map} +0 -0
- /package/dist/{chunk-URODM26E.js.map → chunk-MQRTT3DO.js.map} +0 -0
- /package/dist/{chunk-4KWIQEZT.js.map → chunk-MTBFE3JF.js.map} +0 -0
- /package/dist/{chunk-WXOIEYXW.js.map → chunk-PD5MXNTJ.js.map} +0 -0
- /package/dist/{chunk-MQXUIYYN.js.map → chunk-RYPCNUXA.js.map} +0 -0
- /package/dist/{chunk-2TBXIW7L.js.map → chunk-UFP45JLE.js.map} +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type * as React from 'react'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useDeferredError } from '../../../hooks/ui/use-deferred-error'
|
|
5
6
|
import { Button } from '../../ui/button'
|
|
6
7
|
import { Input } from '../../ui/input'
|
|
7
8
|
import { PasswordInput } from '../../ui/password-input'
|
|
@@ -75,6 +76,12 @@ export function CompleteAccountForm({
|
|
|
75
76
|
}: CompleteAccountFormProps) {
|
|
76
77
|
const fieldsDisabled = disabled || loading
|
|
77
78
|
|
|
79
|
+
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
80
|
+
const firstNameErr = useDeferredError(errors?.firstName, firstName)
|
|
81
|
+
const lastNameErr = useDeferredError(errors?.lastName, lastName)
|
|
82
|
+
const passwordErr = useDeferredError(errors?.password, password)
|
|
83
|
+
const confirmErr = useDeferredError(errors?.confirmPassword, confirmPassword)
|
|
84
|
+
|
|
78
85
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
79
86
|
if (event.key === 'Enter' && !fieldsDisabled && !submitDisabled) {
|
|
80
87
|
onSubmit()
|
|
@@ -111,57 +118,47 @@ export function CompleteAccountForm({
|
|
|
111
118
|
</>
|
|
112
119
|
)}
|
|
113
120
|
|
|
114
|
-
{/*
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
label="Confirm Password"
|
|
156
|
-
placeholder="Confirm Password"
|
|
157
|
-
value={confirmPassword}
|
|
158
|
-
error={errors?.confirmPassword}
|
|
159
|
-
disabled={fieldsDisabled}
|
|
160
|
-
onChange={(event) => onConfirmPasswordChange(event.target.value)}
|
|
161
|
-
onKeyDown={handleKeyDown}
|
|
162
|
-
/>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
121
|
+
{/* Name + password fields — single column on every breakpoint */}
|
|
122
|
+
<Input
|
|
123
|
+
label="First Name"
|
|
124
|
+
placeholder="Enter First Name"
|
|
125
|
+
value={firstName}
|
|
126
|
+
error={firstNameErr.error}
|
|
127
|
+
disabled={fieldsDisabled}
|
|
128
|
+
onBlur={firstNameErr.onBlur}
|
|
129
|
+
onChange={(event) => onFirstNameChange(event.target.value)}
|
|
130
|
+
onKeyDown={handleKeyDown}
|
|
131
|
+
/>
|
|
132
|
+
<Input
|
|
133
|
+
label="Last Name"
|
|
134
|
+
placeholder="Enter Last Name"
|
|
135
|
+
value={lastName}
|
|
136
|
+
error={lastNameErr.error}
|
|
137
|
+
disabled={fieldsDisabled}
|
|
138
|
+
onBlur={lastNameErr.onBlur}
|
|
139
|
+
onChange={(event) => onLastNameChange(event.target.value)}
|
|
140
|
+
onKeyDown={handleKeyDown}
|
|
141
|
+
/>
|
|
142
|
+
<PasswordInput
|
|
143
|
+
label="Password"
|
|
144
|
+
placeholder="Enter Password"
|
|
145
|
+
value={password}
|
|
146
|
+
error={passwordErr.error}
|
|
147
|
+
disabled={fieldsDisabled}
|
|
148
|
+
onBlur={passwordErr.onBlur}
|
|
149
|
+
onChange={(event) => onPasswordChange(event.target.value)}
|
|
150
|
+
onKeyDown={handleKeyDown}
|
|
151
|
+
/>
|
|
152
|
+
<PasswordInput
|
|
153
|
+
label="Confirm Password"
|
|
154
|
+
placeholder="Confirm Password"
|
|
155
|
+
value={confirmPassword}
|
|
156
|
+
error={confirmErr.error}
|
|
157
|
+
disabled={fieldsDisabled}
|
|
158
|
+
onBlur={confirmErr.onBlur}
|
|
159
|
+
onChange={(event) => onConfirmPasswordChange(event.target.value)}
|
|
160
|
+
onKeyDown={handleKeyDown}
|
|
161
|
+
/>
|
|
165
162
|
|
|
166
163
|
{/* Actions — optional back + submit */}
|
|
167
164
|
<div className="flex items-center gap-[var(--spacing-system-l)]">
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useDeferredError } from '../../../hooks/ui/use-deferred-error'
|
|
5
6
|
import { Button } from '../../ui/button'
|
|
6
7
|
import { CheckboxBlock } from '../../ui/checkbox-block'
|
|
7
8
|
import { Input } from '../../ui/input'
|
|
@@ -91,6 +92,11 @@ export function CreateOrganizationForm({
|
|
|
91
92
|
const isSsoMode = !!ssoProviders && ssoProviders.length > 0
|
|
92
93
|
const fieldsDisabled = disabled || loading || isSsoMode
|
|
93
94
|
|
|
95
|
+
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
96
|
+
const emailErr = useDeferredError(errors?.email, email)
|
|
97
|
+
const orgNameErr = useDeferredError(errors?.organizationName, organizationName)
|
|
98
|
+
const domainErr = useDeferredError(errors?.domain, domain)
|
|
99
|
+
|
|
94
100
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
95
101
|
if (event.key === 'Enter' && !fieldsDisabled) {
|
|
96
102
|
onSubmit()
|
|
@@ -110,33 +116,29 @@ export function CreateOrganizationForm({
|
|
|
110
116
|
<p className="text-h4 text-ods-text-secondary">Start your journey with OpenFrame.</p>
|
|
111
117
|
</div>
|
|
112
118
|
|
|
113
|
-
{/* Email + Organization Name —
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
onKeyDown={handleKeyDown}
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
119
|
+
{/* Email + Organization Name — single column on every breakpoint */}
|
|
120
|
+
<Input
|
|
121
|
+
type="email"
|
|
122
|
+
label="Email"
|
|
123
|
+
placeholder="username@mail.com"
|
|
124
|
+
value={email}
|
|
125
|
+
error={emailErr.error ?? emailStatus?.message}
|
|
126
|
+
errorVariant={emailErr.error ? 'error' : emailStatus?.variant}
|
|
127
|
+
disabled={fieldsDisabled}
|
|
128
|
+
onBlur={emailErr.onBlur}
|
|
129
|
+
onChange={(event) => onEmailChange(event.target.value)}
|
|
130
|
+
onKeyDown={handleKeyDown}
|
|
131
|
+
/>
|
|
132
|
+
<Input
|
|
133
|
+
label="Organization Name"
|
|
134
|
+
placeholder="Your Company Name"
|
|
135
|
+
value={organizationName}
|
|
136
|
+
error={orgNameErr.error}
|
|
137
|
+
disabled={fieldsDisabled}
|
|
138
|
+
onBlur={orgNameErr.onBlur}
|
|
139
|
+
onChange={(event) => onOrganizationNameChange(event.target.value)}
|
|
140
|
+
onKeyDown={handleKeyDown}
|
|
141
|
+
/>
|
|
140
142
|
|
|
141
143
|
{/* Domain */}
|
|
142
144
|
<div className="flex flex-col">
|
|
@@ -144,15 +146,15 @@ export function CreateOrganizationForm({
|
|
|
144
146
|
label="Domain"
|
|
145
147
|
placeholder={domainPlaceholder}
|
|
146
148
|
value={domain}
|
|
147
|
-
error={
|
|
148
|
-
errorVariant={
|
|
149
|
+
error={domainErr.error ?? domainStatus?.message}
|
|
150
|
+
errorVariant={domainErr.error ? 'error' : domainStatus?.variant}
|
|
149
151
|
disabled={fieldsDisabled}
|
|
152
|
+
onBlur={domainErr.onBlur}
|
|
150
153
|
endAdornment={domainSuffix ? <span className="whitespace-nowrap">{domainSuffix}</span> : undefined}
|
|
151
154
|
onChange={(event) => onDomainChange(event.target.value)}
|
|
152
155
|
onKeyDown={handleKeyDown}
|
|
153
156
|
/>
|
|
154
|
-
{
|
|
155
|
-
{domainSlot && <div className="pt-[var(--spacing-system-l)]">{domainSlot}</div>}
|
|
157
|
+
{domainSlot && <div className="pt-[var(--spacing-system-s)]">{domainSlot}</div>}
|
|
156
158
|
</div>
|
|
157
159
|
|
|
158
160
|
{/* Terms & Privacy */}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type * as React from 'react'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useDeferredError } from '../../../hooks/ui/use-deferred-error'
|
|
5
6
|
import { Button } from '../../ui/button'
|
|
6
7
|
import { Input } from '../../ui/input'
|
|
7
8
|
import type { AuthSsoProvider } from './sso-providers'
|
|
@@ -61,6 +62,9 @@ export function LoginForm({
|
|
|
61
62
|
const isSsoMode = !!ssoProviders && ssoProviders.length > 0
|
|
62
63
|
const fieldDisabled = disabled || loading || isSsoMode
|
|
63
64
|
|
|
65
|
+
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
66
|
+
const emailErr = useDeferredError(errors?.email, email)
|
|
67
|
+
|
|
64
68
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
65
69
|
if (event.key === 'Enter' && !fieldDisabled) {
|
|
66
70
|
onSubmit()
|
|
@@ -86,8 +90,9 @@ export function LoginForm({
|
|
|
86
90
|
label="Email"
|
|
87
91
|
placeholder={emailPlaceholder}
|
|
88
92
|
value={email}
|
|
89
|
-
error={
|
|
93
|
+
error={emailErr.error}
|
|
90
94
|
disabled={fieldDisabled}
|
|
95
|
+
onBlur={emailErr.onBlur}
|
|
91
96
|
onChange={(event) => onEmailChange(event.target.value)}
|
|
92
97
|
onKeyDown={handleKeyDown}
|
|
93
98
|
/>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type * as React from 'react'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useDeferredError } from '../../../hooks/ui/use-deferred-error'
|
|
5
6
|
import { Button } from '../../ui/button'
|
|
6
7
|
import { Input } from '../../ui/input'
|
|
7
8
|
import { PasswordInput } from '../../ui/password-input'
|
|
@@ -60,6 +61,10 @@ export function OpenFrameSsoLoginForm({
|
|
|
60
61
|
}: OpenFrameSsoLoginFormProps) {
|
|
61
62
|
const fieldsDisabled = disabled || loading
|
|
62
63
|
|
|
64
|
+
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
65
|
+
const emailErr = useDeferredError(errors?.email, email)
|
|
66
|
+
const passwordErr = useDeferredError(errors?.password, password)
|
|
67
|
+
|
|
63
68
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
64
69
|
if (event.key === 'Enter' && !fieldsDisabled && !submitDisabled) {
|
|
65
70
|
onSubmit()
|
|
@@ -84,8 +89,9 @@ export function OpenFrameSsoLoginForm({
|
|
|
84
89
|
label={emailLabel}
|
|
85
90
|
placeholder={emailPlaceholder}
|
|
86
91
|
value={email}
|
|
87
|
-
error={
|
|
92
|
+
error={emailErr.error}
|
|
88
93
|
disabled={fieldsDisabled}
|
|
94
|
+
onBlur={emailErr.onBlur}
|
|
89
95
|
onChange={(event) => onEmailChange(event.target.value)}
|
|
90
96
|
onKeyDown={handleKeyDown}
|
|
91
97
|
/>
|
|
@@ -94,8 +100,9 @@ export function OpenFrameSsoLoginForm({
|
|
|
94
100
|
label={passwordLabel}
|
|
95
101
|
placeholder={passwordPlaceholder}
|
|
96
102
|
value={password}
|
|
97
|
-
error={
|
|
103
|
+
error={passwordErr.error}
|
|
98
104
|
disabled={fieldsDisabled}
|
|
105
|
+
onBlur={passwordErr.onBlur}
|
|
99
106
|
onChange={(event) => onPasswordChange(event.target.value)}
|
|
100
107
|
onKeyDown={handleKeyDown}
|
|
101
108
|
/>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type * as React from 'react'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useDeferredError } from '../../../hooks/ui/use-deferred-error'
|
|
5
6
|
import { Button } from '../../ui/button'
|
|
6
7
|
import { Input } from '../../ui/input'
|
|
7
8
|
import { PasswordInput } from '../../ui/password-input'
|
|
@@ -72,6 +73,13 @@ export function OpenFrameSsoSignUpForm({
|
|
|
72
73
|
}: OpenFrameSsoSignUpFormProps) {
|
|
73
74
|
const fieldsDisabled = disabled || loading
|
|
74
75
|
|
|
76
|
+
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
77
|
+
const emailErr = useDeferredError(errors?.email, email)
|
|
78
|
+
const firstNameErr = useDeferredError(errors?.firstName, firstName)
|
|
79
|
+
const lastNameErr = useDeferredError(errors?.lastName, lastName)
|
|
80
|
+
const passwordErr = useDeferredError(errors?.password, password)
|
|
81
|
+
const confirmErr = useDeferredError(errors?.confirmPassword, confirmPassword)
|
|
82
|
+
|
|
75
83
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
76
84
|
if (event.key === 'Enter' && !fieldsDisabled && !submitDisabled) {
|
|
77
85
|
onSubmit()
|
|
@@ -96,63 +104,54 @@ export function OpenFrameSsoSignUpForm({
|
|
|
96
104
|
label={emailLabel}
|
|
97
105
|
placeholder="username@mail.com"
|
|
98
106
|
value={email}
|
|
99
|
-
error={
|
|
107
|
+
error={emailErr.error}
|
|
100
108
|
disabled={fieldsDisabled || emailReadOnly}
|
|
109
|
+
onBlur={emailErr.onBlur}
|
|
101
110
|
onChange={(event) => onEmailChange(event.target.value)}
|
|
102
111
|
onKeyDown={handleKeyDown}
|
|
103
112
|
/>
|
|
104
113
|
|
|
105
|
-
{/*
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
label="Confirm Password"
|
|
147
|
-
placeholder="Confirm Password"
|
|
148
|
-
value={confirmPassword}
|
|
149
|
-
error={errors?.confirmPassword}
|
|
150
|
-
disabled={fieldsDisabled}
|
|
151
|
-
onChange={(event) => onConfirmPasswordChange(event.target.value)}
|
|
152
|
-
onKeyDown={handleKeyDown}
|
|
153
|
-
/>
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
114
|
+
{/* Name + password fields — single column on every breakpoint */}
|
|
115
|
+
<Input
|
|
116
|
+
label="First Name"
|
|
117
|
+
placeholder="Enter First Name"
|
|
118
|
+
value={firstName}
|
|
119
|
+
error={firstNameErr.error}
|
|
120
|
+
disabled={fieldsDisabled}
|
|
121
|
+
onBlur={firstNameErr.onBlur}
|
|
122
|
+
onChange={(event) => onFirstNameChange(event.target.value)}
|
|
123
|
+
onKeyDown={handleKeyDown}
|
|
124
|
+
/>
|
|
125
|
+
<Input
|
|
126
|
+
label="Last Name"
|
|
127
|
+
placeholder="Enter Last Name"
|
|
128
|
+
value={lastName}
|
|
129
|
+
error={lastNameErr.error}
|
|
130
|
+
disabled={fieldsDisabled}
|
|
131
|
+
onBlur={lastNameErr.onBlur}
|
|
132
|
+
onChange={(event) => onLastNameChange(event.target.value)}
|
|
133
|
+
onKeyDown={handleKeyDown}
|
|
134
|
+
/>
|
|
135
|
+
<PasswordInput
|
|
136
|
+
label="Password"
|
|
137
|
+
placeholder="Enter Password"
|
|
138
|
+
value={password}
|
|
139
|
+
error={passwordErr.error}
|
|
140
|
+
disabled={fieldsDisabled}
|
|
141
|
+
onBlur={passwordErr.onBlur}
|
|
142
|
+
onChange={(event) => onPasswordChange(event.target.value)}
|
|
143
|
+
onKeyDown={handleKeyDown}
|
|
144
|
+
/>
|
|
145
|
+
<PasswordInput
|
|
146
|
+
label="Confirm Password"
|
|
147
|
+
placeholder="Confirm Password"
|
|
148
|
+
value={confirmPassword}
|
|
149
|
+
error={confirmErr.error}
|
|
150
|
+
disabled={fieldsDisabled}
|
|
151
|
+
onBlur={confirmErr.onBlur}
|
|
152
|
+
onChange={(event) => onConfirmPasswordChange(event.target.value)}
|
|
153
|
+
onKeyDown={handleKeyDown}
|
|
154
|
+
/>
|
|
156
155
|
|
|
157
156
|
{/* Forgot password + Continue */}
|
|
158
157
|
<div className="flex items-center gap-[var(--spacing-system-l)]">
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type * as React from 'react'
|
|
4
4
|
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { useDeferredError } from '../../../hooks/ui/use-deferred-error'
|
|
5
6
|
import { Button } from '../../ui/button'
|
|
6
7
|
import { Input } from '../../ui/input'
|
|
7
8
|
import { BackToLoginLink } from './back-to-login-link'
|
|
@@ -66,6 +67,10 @@ export function PasswordResetForm({
|
|
|
66
67
|
}: PasswordResetFormProps) {
|
|
67
68
|
const fieldsDisabled = disabled || loading
|
|
68
69
|
|
|
70
|
+
// Validation messages are deferred while the user is typing (shown on blur or after a pause).
|
|
71
|
+
const passwordErr = useDeferredError(errors?.password, password)
|
|
72
|
+
const confirmErr = useDeferredError(errors?.confirmPassword, confirmPassword)
|
|
73
|
+
|
|
69
74
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
70
75
|
if (event.key === 'Enter' && !fieldsDisabled && !submitDisabled) {
|
|
71
76
|
onSubmit()
|
|
@@ -91,8 +96,9 @@ export function PasswordResetForm({
|
|
|
91
96
|
label={newPasswordLabel}
|
|
92
97
|
placeholder={newPasswordPlaceholder}
|
|
93
98
|
value={password}
|
|
94
|
-
error={
|
|
99
|
+
error={passwordErr.error}
|
|
95
100
|
disabled={fieldsDisabled}
|
|
101
|
+
onBlur={passwordErr.onBlur}
|
|
96
102
|
onChange={(event) => onPasswordChange(event.target.value)}
|
|
97
103
|
onKeyDown={handleKeyDown}
|
|
98
104
|
/>
|
|
@@ -103,8 +109,9 @@ export function PasswordResetForm({
|
|
|
103
109
|
label={confirmPasswordLabel}
|
|
104
110
|
placeholder={confirmPasswordPlaceholder}
|
|
105
111
|
value={confirmPassword}
|
|
106
|
-
error={
|
|
112
|
+
error={confirmErr.error}
|
|
107
113
|
disabled={fieldsDisabled}
|
|
114
|
+
onBlur={confirmErr.onBlur}
|
|
108
115
|
onChange={(event) => onConfirmPasswordChange(event.target.value)}
|
|
109
116
|
onKeyDown={handleKeyDown}
|
|
110
117
|
/>
|
|
@@ -6,7 +6,7 @@ import { cn } from "../../utils/cn"
|
|
|
6
6
|
export interface FieldWrapperProps {
|
|
7
7
|
/** Label text displayed above the field */
|
|
8
8
|
label?: string
|
|
9
|
-
/** Status message displayed below the field.
|
|
9
|
+
/** Status message displayed below the field. Wraps up to two lines, then ellipsizes. */
|
|
10
10
|
error?: string
|
|
11
11
|
/** Color variant for the message: "error" (red), "warning" (yellow), "success" (green) or "muted" (grey) */
|
|
12
12
|
errorVariant?: "error" | "warning" | "success" | "muted"
|
|
@@ -27,7 +27,7 @@ const FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(
|
|
|
27
27
|
const hasChrome = label != null || error != null
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div ref={ref} className={cn(hasChrome ? "
|
|
30
|
+
<div ref={ref} className={cn(hasChrome ? "flex w-full flex-col" : "contents", className)}>
|
|
31
31
|
{label && (
|
|
32
32
|
<label className="text-h4 text-ods-text-primary mb-1">
|
|
33
33
|
{label}
|
|
@@ -35,7 +35,10 @@ const FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(
|
|
|
35
35
|
)}
|
|
36
36
|
{children}
|
|
37
37
|
{error && (
|
|
38
|
-
<p
|
|
38
|
+
<p
|
|
39
|
+
className={cn("mt-1 text-h6 break-words line-clamp-2", errorVariantClasses[errorVariant])}
|
|
40
|
+
title={error}
|
|
41
|
+
>
|
|
39
42
|
{error}
|
|
40
43
|
</p>
|
|
41
44
|
)}
|
package/src/hooks/ui/index.ts
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
|
|
5
|
+
export interface UseDeferredErrorResult {
|
|
6
|
+
/** The message to render (undefined while the reveal is deferred or the field is valid). */
|
|
7
|
+
error: string | undefined
|
|
8
|
+
/** Attach to the field's onBlur — reveals validation immediately and keeps it live from then on. */
|
|
9
|
+
onBlur: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Defers showing a field validation message while the user is still typing.
|
|
14
|
+
*
|
|
15
|
+
* The message is revealed when the field loses focus (touched) or when the
|
|
16
|
+
* value hasn't changed for `delay` ms; every keystroke restarts the timer.
|
|
17
|
+
* Once touched, validation stays live. A message that clears (the value became
|
|
18
|
+
* valid) disappears immediately.
|
|
19
|
+
*/
|
|
20
|
+
export function useDeferredError(error: string | undefined, value: string, delay = 1500): UseDeferredErrorResult {
|
|
21
|
+
const [touched, setTouched] = React.useState(false)
|
|
22
|
+
const [paused, setPaused] = React.useState(false)
|
|
23
|
+
|
|
24
|
+
// Typing hides the message and restarts the pause timer.
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (touched) return
|
|
27
|
+
setPaused(false)
|
|
28
|
+
const timer = setTimeout(() => setPaused(true), delay)
|
|
29
|
+
return () => clearTimeout(timer)
|
|
30
|
+
}, [value, delay, touched])
|
|
31
|
+
|
|
32
|
+
const onBlur = React.useCallback(() => setTouched(true), [])
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
error: error && (touched || paused) ? error : undefined,
|
|
36
|
+
onBlur,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -219,3 +219,47 @@ export const AllVariants: Story = {
|
|
|
219
219
|
</div>
|
|
220
220
|
),
|
|
221
221
|
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Error / status states. Long messages wrap up to two lines, then ellipsize.
|
|
225
|
+
*/
|
|
226
|
+
export const ErrorState: Story = {
|
|
227
|
+
args: {
|
|
228
|
+
placeholder: 'Enter text...',
|
|
229
|
+
},
|
|
230
|
+
render: () => (
|
|
231
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2.5rem', width: '480px' }}>
|
|
232
|
+
<Input label="Short error (no interaction)" placeholder="username@mail.com" error="Required field" />
|
|
233
|
+
<Input
|
|
234
|
+
label="Long error — wraps to two lines"
|
|
235
|
+
defaultValue="ada@example.com"
|
|
236
|
+
error="This email is already registered. Sign in instead or use a different email address to create the organization."
|
|
237
|
+
/>
|
|
238
|
+
{/* Half-width pair, as on the Create Organization screen */}
|
|
239
|
+
<div style={{ display: 'flex', gap: '1.5rem' }}>
|
|
240
|
+
<Input
|
|
241
|
+
label="Email"
|
|
242
|
+
defaultValue="ada@example.com"
|
|
243
|
+
error="This email is already registered. Sign in instead."
|
|
244
|
+
/>
|
|
245
|
+
<Input
|
|
246
|
+
label="Organization Name"
|
|
247
|
+
defaultValue="A"
|
|
248
|
+
error="Organization Name must be 2-100 characters"
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
<Input
|
|
252
|
+
label="Success status"
|
|
253
|
+
defaultValue="ada@example.com"
|
|
254
|
+
error="Email is available"
|
|
255
|
+
errorVariant="success"
|
|
256
|
+
/>
|
|
257
|
+
<Input
|
|
258
|
+
label="Muted status (long, truncating)"
|
|
259
|
+
defaultValue="ada-lovelace-computing"
|
|
260
|
+
error="Checking availability against every registered organization domain, this may take a moment…"
|
|
261
|
+
errorVariant="muted"
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
),
|
|
265
|
+
};
|
|
@@ -97,6 +97,26 @@ export const Filled: Story = {
|
|
|
97
97
|
),
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Error state: long messages under the fields wrap up to two lines.
|
|
102
|
+
* Validation is deferred — messages appear after a 1.5s typing pause or on blur.
|
|
103
|
+
*/
|
|
104
|
+
export const ErrorState: Story = {
|
|
105
|
+
render: () => (
|
|
106
|
+
<CreateOrganizationPage
|
|
107
|
+
email="ada@example.com"
|
|
108
|
+
organizationName="A"
|
|
109
|
+
domainSuffix=".openframe.ai"
|
|
110
|
+
termsUrl="#terms"
|
|
111
|
+
privacyPolicyUrl="#privacy"
|
|
112
|
+
emailStatus={{ message: 'This email is already registered. Sign in instead.', variant: 'error' }}
|
|
113
|
+
errors={{
|
|
114
|
+
organizationName: 'Organization Name must be 2-100 characters',
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
),
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
const SSO_PROVIDERS: AuthSsoProvider[] = ['openframe', 'google', 'microsoft']
|
|
101
121
|
|
|
102
122
|
/** SSO configured: fields disabled, submit replaced by provider buttons. */
|