keystone-design-bootstrap 1.0.74 → 1.0.76
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 +160 -1
- package/dist/contexts/index.js +3 -1
- package/dist/contexts/index.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/themes/index.d.ts +1 -0
- package/dist/themes/index.js +3 -1
- package/dist/themes/index.js.map +1 -1
- package/package.json +1 -1
- package/src/design_system/portal/LoginForm.tsx +9 -24
- package/src/next/layouts/root-layout.tsx +16 -12
- package/src/themes/index.ts +1 -0
- package/src/utils/gradient-placeholder.ts +0 -2
package/dist/themes/index.d.ts
CHANGED
package/dist/themes/index.js
CHANGED
|
@@ -6,8 +6,10 @@ var THEME_CONFIG = {
|
|
|
6
6
|
// Aman Hotels variant files (hero-home.aman.tsx)
|
|
7
7
|
barelux: ".barelux",
|
|
8
8
|
// Bare Lux Studio variant files (hero-home.barelux.tsx)
|
|
9
|
-
balance: ".balance"
|
|
9
|
+
balance: ".balance",
|
|
10
10
|
// Balance Aesthetics variant files (hero-home.balance.tsx)
|
|
11
|
+
custom: ""
|
|
12
|
+
// Fully custom sites — no design-system CSS loaded, all styling built in the site itself
|
|
11
13
|
};
|
|
12
14
|
function getAvailableThemes() {
|
|
13
15
|
return Object.keys(THEME_CONFIG);
|
package/dist/themes/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/themes/index.ts"],"sourcesContent":["/**\n * Theme Configuration\n * Single source of truth for all themes\n */\n\nexport const THEME_CONFIG = {\n classic: '', // Base files with no suffix (hero-home.tsx)\n aman: '.aman', // Aman Hotels variant files (hero-home.aman.tsx)\n barelux: '.barelux', // Bare Lux Studio variant files (hero-home.barelux.tsx)\n balance: '.balance', // Balance Aesthetics variant files (hero-home.balance.tsx)\n} as const;\n\nexport type Theme = keyof typeof THEME_CONFIG;\n\nexport function getAvailableThemes(): Theme[] {\n return Object.keys(THEME_CONFIG) as Theme[];\n}\n\nexport function getThemeSuffix(theme: Theme): string {\n return THEME_CONFIG[theme] || '';\n}\n\nexport function isValidTheme(theme: string): theme is Theme {\n return theme in THEME_CONFIG;\n}\n"],"mappings":";AAKO,IAAM,eAAe;AAAA,EAC1B,SAAS;AAAA;AAAA,EACT,MAAM;AAAA;AAAA,EACN,SAAS;AAAA;AAAA,EACT,SAAS;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../src/themes/index.ts"],"sourcesContent":["/**\n * Theme Configuration\n * Single source of truth for all themes\n */\n\nexport const THEME_CONFIG = {\n classic: '', // Base files with no suffix (hero-home.tsx)\n aman: '.aman', // Aman Hotels variant files (hero-home.aman.tsx)\n barelux: '.barelux', // Bare Lux Studio variant files (hero-home.barelux.tsx)\n balance: '.balance', // Balance Aesthetics variant files (hero-home.balance.tsx)\n custom: '', // Fully custom sites — no design-system CSS loaded, all styling built in the site itself\n} as const;\n\nexport type Theme = keyof typeof THEME_CONFIG;\n\nexport function getAvailableThemes(): Theme[] {\n return Object.keys(THEME_CONFIG) as Theme[];\n}\n\nexport function getThemeSuffix(theme: Theme): string {\n return THEME_CONFIG[theme] || '';\n}\n\nexport function isValidTheme(theme: string): theme is Theme {\n return theme in THEME_CONFIG;\n}\n"],"mappings":";AAKO,IAAM,eAAe;AAAA,EAC1B,SAAS;AAAA;AAAA,EACT,MAAM;AAAA;AAAA,EACN,SAAS;AAAA;AAAA,EACT,SAAS;AAAA;AAAA,EACT,QAAQ;AAAA;AACV;AAIO,SAAS,qBAA8B;AAC5C,SAAO,OAAO,KAAK,YAAY;AACjC;AAEO,SAAS,eAAe,OAAsB;AACnD,SAAO,aAAa,KAAK,KAAK;AAChC;AAEO,SAAS,aAAa,OAA+B;AAC1D,SAAO,SAAS;AAClB;","names":[]}
|
package/package.json
CHANGED
|
@@ -18,10 +18,6 @@ const inputClass =
|
|
|
18
18
|
'block w-full rounded-input border border-primary bg-primary px-3 py-2.5 text-base text-primary placeholder-quaternary focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand transition-colors';
|
|
19
19
|
const labelClass = 'block text-sm text-secondary mb-1';
|
|
20
20
|
|
|
21
|
-
function isValidEmail(value: string): boolean {
|
|
22
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
26
22
|
const router = useRouter();
|
|
27
23
|
const [step, setStep] = useState<Step>('identifier');
|
|
@@ -31,7 +27,6 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
31
27
|
}, []);
|
|
32
28
|
|
|
33
29
|
// Identifier step state
|
|
34
|
-
const [email, setEmail] = useState('');
|
|
35
30
|
const [phoneValue, setPhoneValue] = useState(''); // formatted national number
|
|
36
31
|
const [selectedCountry, setSelectedCountry] = useState('US');
|
|
37
32
|
|
|
@@ -64,7 +59,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
64
59
|
// Computed API values
|
|
65
60
|
const phoneDigits = phoneValue.replace(/\D/g, '');
|
|
66
61
|
const fullPhone = phoneDigits.length > 0 ? `${phoneCode}${phoneDigits}` : null;
|
|
67
|
-
const emailVal =
|
|
62
|
+
const emailVal = null;
|
|
68
63
|
|
|
69
64
|
const emailInput = (value: string, onChange: (v: string) => void, required = false) => (
|
|
70
65
|
<input
|
|
@@ -107,8 +102,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
107
102
|
|
|
108
103
|
const handleContinue = async (e: React.FormEvent) => {
|
|
109
104
|
e.preventDefault();
|
|
110
|
-
if (!
|
|
111
|
-
if (emailVal && !isValidEmail(emailVal)) { setError('Enter a valid email address.'); return; }
|
|
105
|
+
if (!fullPhone) { setError('Enter your phone number to continue.'); return; }
|
|
112
106
|
setError(null);
|
|
113
107
|
setLoading(true);
|
|
114
108
|
try {
|
|
@@ -118,14 +112,14 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
118
112
|
body: JSON.stringify({ email: emailVal, phone: fullPhone }),
|
|
119
113
|
});
|
|
120
114
|
const result = await res.json().catch(() => ({ exists: null }));
|
|
121
|
-
const method =
|
|
115
|
+
const method = 'phone';
|
|
122
116
|
if (result.exists === true) {
|
|
123
117
|
await setPixelUserData({ email: emailVal, phone: fullPhone });
|
|
124
118
|
firePixelEvent('Lead');
|
|
125
119
|
captureEvent('portal_login_identified', { method, user_exists: true });
|
|
126
120
|
setWelcomeName(result.firstName ?? null);
|
|
127
121
|
if (result.hasPassword === false) {
|
|
128
|
-
setIdentifiedWith(
|
|
122
|
+
setIdentifiedWith('phone');
|
|
129
123
|
setStep('new');
|
|
130
124
|
} else {
|
|
131
125
|
setStep('returning');
|
|
@@ -134,7 +128,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
134
128
|
await setPixelUserData({ email: emailVal, phone: fullPhone });
|
|
135
129
|
firePixelEvent('Lead');
|
|
136
130
|
captureEvent('portal_login_identified', { method, user_exists: false });
|
|
137
|
-
setIdentifiedWith(
|
|
131
|
+
setIdentifiedWith('phone');
|
|
138
132
|
setStep('new');
|
|
139
133
|
} else {
|
|
140
134
|
captureEvent('portal_login_failed', { step: 'identifier', reason: 'unknown_error' });
|
|
@@ -229,7 +223,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
229
223
|
setError(null);
|
|
230
224
|
};
|
|
231
225
|
|
|
232
|
-
const identifierSummary =
|
|
226
|
+
const identifierSummary = fullPhone ?? '';
|
|
233
227
|
|
|
234
228
|
const headerTitle =
|
|
235
229
|
step === 'identifier' ? 'Pricing & Booking Portal' :
|
|
@@ -237,7 +231,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
237
231
|
(welcomeName ? `Welcome back, ${welcomeName}!` : 'Welcome!');
|
|
238
232
|
|
|
239
233
|
const headerSubtitle =
|
|
240
|
-
step === 'identifier' ? 'Enter your
|
|
234
|
+
step === 'identifier' ? 'Enter your phone number to get started.' :
|
|
241
235
|
step === 'returning' ? 'Enter your password to sign in.' :
|
|
242
236
|
(welcomeName ? 'Create a password to access your account.' : "Let's get you set up.");
|
|
243
237
|
|
|
@@ -272,18 +266,9 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
272
266
|
{/* Step: identifier */}
|
|
273
267
|
{step === 'identifier' && (
|
|
274
268
|
<form onSubmit={handleContinue} className="space-y-3">
|
|
275
|
-
<div>
|
|
276
|
-
<label className={labelClass}>Email address</label>
|
|
277
|
-
{emailInput(email, setEmail)}
|
|
278
|
-
</div>
|
|
279
|
-
<div className="flex items-center gap-3">
|
|
280
|
-
<hr className="flex-1 border-secondary" />
|
|
281
|
-
<span className="text-xs text-quaternary">or</span>
|
|
282
|
-
<hr className="flex-1 border-secondary" />
|
|
283
|
-
</div>
|
|
284
269
|
<div>
|
|
285
270
|
<label className={labelClass}>Phone number</label>
|
|
286
|
-
{phoneInput(phoneValue, setPhoneValue)}
|
|
271
|
+
{phoneInput(phoneValue, setPhoneValue, true)}
|
|
287
272
|
</div>
|
|
288
273
|
<div className="pt-1">
|
|
289
274
|
<button
|
|
@@ -326,7 +311,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
326
311
|
onClick={goBack}
|
|
327
312
|
className="w-full text-center text-sm text-tertiary hover:text-primary transition-colors"
|
|
328
313
|
>
|
|
329
|
-
← {identifierSummary ? `Not ${identifierSummary}?` : 'Change
|
|
314
|
+
← {identifierSummary ? `Not ${identifierSummary}?` : 'Change phone number'}
|
|
330
315
|
</button>
|
|
331
316
|
</form>
|
|
332
317
|
)}
|
|
@@ -194,19 +194,23 @@ export async function KeystoneRootLayout(props: {
|
|
|
194
194
|
jobApplicationFormDefinition={jobApplicationFormDefinition ?? null}
|
|
195
195
|
marketingListSignupFormDefinition={marketingListSignupFormDefinition ?? null}
|
|
196
196
|
>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
{theme !== 'custom' && (
|
|
198
|
+
<HeaderNavigation
|
|
199
|
+
config={dynamicConfig}
|
|
200
|
+
companyInformation={companyInformation}
|
|
201
|
+
websitePhotos={websitePhotos}
|
|
202
|
+
props={headerProps}
|
|
203
|
+
logoText={headerOverrides?.logoText}
|
|
204
|
+
/>
|
|
205
|
+
)}
|
|
204
206
|
{children}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
{theme !== 'custom' && (
|
|
208
|
+
<FooterHome
|
|
209
|
+
config={dynamicConfig}
|
|
210
|
+
companyInformation={companyInformation}
|
|
211
|
+
websitePhotos={websitePhotos}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
210
214
|
{chatEnabled ? (
|
|
211
215
|
<ChatWidget
|
|
212
216
|
position={options?.chatPosition || 'bottom-right'}
|
package/src/themes/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export const THEME_CONFIG = {
|
|
|
8
8
|
aman: '.aman', // Aman Hotels variant files (hero-home.aman.tsx)
|
|
9
9
|
barelux: '.barelux', // Bare Lux Studio variant files (hero-home.barelux.tsx)
|
|
10
10
|
balance: '.balance', // Balance Aesthetics variant files (hero-home.balance.tsx)
|
|
11
|
+
custom: '', // Fully custom sites — no design-system CSS loaded, all styling built in the site itself
|
|
11
12
|
} as const;
|
|
12
13
|
|
|
13
14
|
export type Theme = keyof typeof THEME_CONFIG;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates a beautiful gradient SVG data URL using the full color palette
|
|
3
|
-
* Based on gradients used in corporate marketing site animations
|
|
4
3
|
* Each seed (post ID or index) will consistently return the same gradient
|
|
5
4
|
*/
|
|
6
5
|
export const getGradientUrl = (seed: string | number): string => {
|
|
7
|
-
// Beautiful gradient combinations using the full palette from corporate marketing site
|
|
8
6
|
// Colors match Tailwind's default palette (purple, pink, blue, green, orange, indigo)
|
|
9
7
|
const gradients = [
|
|
10
8
|
// Purple gradients (like from-purple-200 to-purple-300)
|