@homefile/components-v2 2.52.3 → 2.53.1

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 (53) hide show
  1. package/dist/assets/locales/en/index.json +11 -6
  2. package/dist/components/appBar/HomefileLogo.js +4 -4
  3. package/dist/components/brand/BrandLogo.d.ts +1 -0
  4. package/dist/components/brand/BrandLogo.js +13 -0
  5. package/dist/components/brand/index.d.ts +1 -0
  6. package/dist/components/brand/index.js +1 -0
  7. package/dist/components/button/Button.d.ts +33 -0
  8. package/dist/components/button/Button.js +30 -0
  9. package/dist/components/forms/dynamicForm/fields/LabeledField.js +54 -1
  10. package/dist/components/icons/AppleIcon.d.ts +4 -0
  11. package/dist/components/icons/AppleIcon.js +5 -0
  12. package/dist/components/icons/GoogleIcon.d.ts +3 -0
  13. package/dist/components/icons/GoogleIcon.js +4 -0
  14. package/dist/components/icons/index.d.ts +2 -0
  15. package/dist/components/icons/index.js +2 -0
  16. package/dist/components/index.d.ts +1 -0
  17. package/dist/components/index.js +1 -0
  18. package/dist/components/inputs/PasswordStrengthMeter.d.ts +5 -0
  19. package/dist/components/inputs/PasswordStrengthMeter.js +35 -0
  20. package/dist/components/inputs/TextInput.js +1 -1
  21. package/dist/components/inputs/index.d.ts +1 -0
  22. package/dist/components/inputs/index.js +1 -0
  23. package/dist/components/loaders/SkeletonBox.js +1 -1
  24. package/dist/components/myHomes/HomeDetailsContent.js +1 -1
  25. package/dist/components/onboarding/CardFooter.d.ts +1 -1
  26. package/dist/components/onboarding/CardFooter.js +9 -3
  27. package/dist/components/onboarding/Footer.js +3 -4
  28. package/dist/components/onboarding/Logo.js +3 -3
  29. package/dist/components/onboarding/pages/SignUp.d.ts +1 -1
  30. package/dist/components/onboarding/pages/SignUp.js +16 -12
  31. package/dist/components/propertyTiles/charts/lineCharts/trendingLineChart/TrendingLineChart.js +6 -5
  32. package/dist/helpers/SignUp.helper.d.ts +1 -0
  33. package/dist/helpers/SignUp.helper.js +1 -0
  34. package/dist/hooks/onboarding/useSignUp.js +14 -7
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +1 -0
  37. package/dist/interfaces/onboarding/CardFooter.interface.d.ts +2 -0
  38. package/dist/interfaces/pages/SignUp.interface.d.ts +3 -0
  39. package/dist/stories/foundations/ButtonAtom.stories.d.ts +11 -0
  40. package/dist/stories/foundations/ButtonAtom.stories.js +15 -0
  41. package/dist/stories/foundations/DesignTokens.stories.d.ts +13 -0
  42. package/dist/stories/foundations/DesignTokens.stories.js +85 -0
  43. package/dist/theme/colors.d.ts +2 -0
  44. package/dist/theme/colors.js +2 -0
  45. package/dist/theme/components/ButtonStyles.d.ts +82 -0
  46. package/dist/theme/components/ButtonStyles.js +64 -8
  47. package/dist/theme/components/InputStyles.js +3 -3
  48. package/dist/theme/components/SelectStyles.js +1 -1
  49. package/dist/theme/components/TextArea.js +1 -1
  50. package/dist/theme/components/TextStyles.js +1 -1
  51. package/dist/theme/index.js +70 -0
  52. package/dist/utils/Validations.utils.js +1 -1
  53. package/package.json +1 -1
@@ -307,9 +307,9 @@
307
307
  "footer": {
308
308
  "copyright": "HMFC, Inc. All Rights Reserved.",
309
309
  "privacyLink": "Privacy Policy",
310
- "privacyUrl": "https://homefile.com/privacy-policy",
310
+ "privacyUrl": "https://privacy.homefile.com",
311
311
  "termsLink": "Terms of Service",
312
- "termsUrl": "https://homefile.com/terms-of-service"
312
+ "termsUrl": "https://terms.homefile.com"
313
313
  },
314
314
  "forms": {
315
315
  "addAll": "Add All",
@@ -328,9 +328,10 @@
328
328
  "errorImageSize": "Please select a file less than 25MB",
329
329
  "errorImageType": "File type must be .png, .jpeg, .jpg or .heic",
330
330
  "errorLogin": "Invalid email or password",
331
- "errorPasswordLength": "Password must be at least 6 characters",
331
+ "errorPasswordLength": "Password must be at least 8 characters",
332
332
  "errorPasswordMatch": "Passwords do not match",
333
333
  "firstName": "First name",
334
+ "fullName": "Full name",
334
335
  "invalid": "Please type a valid",
335
336
  "invalid2": "Invalid field",
336
337
  "itemDetails": "Add Item Details",
@@ -1132,12 +1133,16 @@
1132
1133
  "signupPartner": "Don’t have a Homefile Partner account?"
1133
1134
  },
1134
1135
  "signup": {
1135
- "signin": "Already have a Homefile account?",
1136
+ "signin": "Already have an account?",
1136
1137
  "signinPartner": "Already have a Homefile Partner account?",
1137
1138
  "signinBt": "Log in",
1138
- "signupBt": "Create Account",
1139
+ "signupBt": "Create account",
1140
+ "continueApple": "Continue with Apple",
1141
+ "continueGoogle": "Continue with Google",
1142
+ "or": "or",
1143
+ "partnerCodePrompt": "Have a partner code?",
1139
1144
  "subtitle": "Create a strong password",
1140
- "terms": "By creating an account you agree to the Homefile ",
1145
+ "terms": "By creating an account you agree to the",
1141
1146
  "title": "Create your Homefile Account",
1142
1147
  "titlePartner": "Create your Homefile Partner Account"
1143
1148
  },
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Button, Flex, Image, Text } from '@chakra-ui/react';
3
- import { LogoSmall, HomefileUpdate } from '../../assets/images';
2
+ import { Box, Button, Flex, Text } from '@chakra-ui/react';
3
+ import { BrandLogo } from '../../components/brand/BrandLogo';
4
4
  import { useWindowDimensions } from '../../hooks';
5
5
  import { MOBILE_WIDTH } from '../../utils';
6
6
  import { t } from 'i18next';
@@ -8,7 +8,7 @@ export const HomefileLogo = ({ onClick, isPartner = false }) => {
8
8
  const { windowDimensions: { width }, } = useWindowDimensions();
9
9
  const isMobile = width < MOBILE_WIDTH;
10
10
  if (!isPartner) {
11
- return (_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(Image, { src: isMobile ? LogoSmall : HomefileUpdate, alt: "Homefile", h: "image.logo" }) }));
11
+ return (_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(BrandLogo, {}) }));
12
12
  }
13
- return (_jsxs(Flex, { align: "center", gap: ['2', 'base'], children: [_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(Image, { src: isMobile ? LogoSmall : HomefileUpdate, alt: "Homefile", h: "image.logo" }) }), !isMobile && (_jsx(Box, { pl: "base", borderLeft: "1px solid", borderColor: "gray.200", children: _jsx(Text, { fontSize: "18px", fontWeight: "medium", children: t('partner.admin') }) }))] }));
13
+ return (_jsxs(Flex, { align: "center", gap: ['2', 'base'], children: [_jsx(Button, { variant: "menuIcon", maxW: "fit-content", onClick: onClick, "data-testid": "homefile-logo", children: _jsx(BrandLogo, {}) }), !isMobile && (_jsx(Box, { pl: "base", borderLeft: "1px solid", borderColor: "border.subtle", children: _jsx(Text, { fontSize: "lg", fontWeight: "medium", children: t('partner.admin') }) }))] }));
14
14
  };
@@ -0,0 +1 @@
1
+ export declare const BrandLogo: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Flex, Text, chakra } from '@chakra-ui/react';
3
+ /**
4
+ * BrandLogo — the ONE Homefile logo, as code (not an image).
5
+ *
6
+ * Green house mark + "Homefile" wordmark in Poppins Medium + registered mark.
7
+ * Self-contained and themed via Chakra, so every surface that shows the logo
8
+ * (app bar, onboarding, partner, etc.) renders the exact same mark. Consolidates
9
+ * the previously fragmented logo images/components into a single source.
10
+ */
11
+ // House icon path (viewBox 0 0 32.877 32.877), Homefile green #6ab528.
12
+ const HOUSE_PATH = 'M912.815,312.316H885.7a2.882,2.882,0,0,0-2.882,2.882V342.31a2.882,2.882,0,0,0,2.882,2.882h8.473V334.22a4.936,4.936,0,0,1,9.872,0v8.58H901.77v-8.58a2.658,2.658,0,0,0-5.316,0v10.973h10.661V333.169a7.827,7.827,0,1,0-15.654,0v7.764h-2.278v-7.764c0-.287.013-.582.04-.881h-2.086a1.279,1.279,0,0,1-1.163-.692,1.073,1.073,0,0,1,.249-1.265l9.8-9.631,3.083-2.977.153-.147.153.147L902.5,320.7l9.8,9.626a1.076,1.076,0,0,1,.253,1.269,1.279,1.279,0,0,1-1.163.692h-2.028c.027.3.04.6.04.881v12.024h3.422a2.882,2.882,0,0,0,2.882-2.882V315.2A2.882,2.882,0,0,0,912.815,312.316Z';
13
+ export const BrandLogo = () => (_jsxs(Flex, { align: "center", gap: "2.5", children: [_jsx(chakra.svg, { width: "33", height: "33", viewBox: "882.821 312.316 32.877 32.877", role: "img", "aria-label": "Homefile", xmlns: "http://www.w3.org/2000/svg", display: "block", children: _jsx(chakra.path, { d: HOUSE_PATH, fill: "brand.logo" }) }), _jsxs(Text, { fontFamily: "primary", fontWeight: "medium", fontSize: "2xl", lineHeight: "none", letterSpacing: "-0.01em", color: "text.heading", children: ["Homefile", _jsx(Box, { as: "sup", fontSize: "0.72em", verticalAlign: "-0.08em", ml: "-1px", children: "\u00AE" })] })] }));
@@ -0,0 +1 @@
1
+ export * from './BrandLogo';
@@ -0,0 +1 @@
1
+ export * from './BrandLogo';
@@ -0,0 +1,33 @@
1
+ import type { ButtonProps as ChakraButtonProps } from '@chakra-ui/react';
2
+ /**
3
+ * Button — the canonical button atom.
4
+ *
5
+ * 3 templates (variant) × 2 real sizes × width/shape/colorScheme, with
6
+ * default/hover/active/focus/disabled states from the theme. This is the
7
+ * target set; the old 20 theme variants remain as a compat shim during
8
+ * migration. Width is a style-prop axis (w / isFullWidth), NOT baked in.
9
+ */
10
+ export type ButtonTemplate = 'filled' | 'outline' | 'ghost';
11
+ export type ButtonSize = 'standard' | 'compact';
12
+ export type ButtonShape = 'square' | 'pillLeft' | 'pillRight';
13
+ export type ButtonColorScheme = 'brand' | 'success';
14
+ export interface ButtonProps extends Omit<ChakraButtonProps, 'variant' | 'size' | 'colorScheme'> {
15
+ /** fill treatment — the one real archetype axis */
16
+ variant?: ButtonTemplate;
17
+ /** real height: standard 3.4rem · compact 2.5rem */
18
+ size?: ButtonSize;
19
+ colorScheme?: ButtonColorScheme;
20
+ /** corner treatment — square (brand default) or a pill half for segmented pairs */
21
+ shape?: ButtonShape;
22
+ /** square icon-only button (sized to the height, no h-padding) */
23
+ isIcon?: boolean;
24
+ }
25
+ export declare const Button: import("@chakra-ui/react").ComponentWithAs<"button", ButtonProps>;
26
+ /**
27
+ * IconButton — the most-used derivative (~60 real usages). A square,
28
+ * icon-only Button. `aria-label` is required (accessibility).
29
+ */
30
+ export interface IconButtonProps extends Omit<ButtonProps, 'isIcon'> {
31
+ 'aria-label': string;
32
+ }
33
+ export declare const IconButton: import("@chakra-ui/react").ComponentWithAs<"button", IconButtonProps>;
@@ -0,0 +1,30 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { Button as ChakraButton, forwardRef } from '@chakra-ui/react';
14
+ const shapeStyles = (shape) => {
15
+ if (shape === 'pillLeft')
16
+ return { borderLeftRadius: 'full', borderRightRadius: 'none' };
17
+ if (shape === 'pillRight')
18
+ return { borderRightRadius: 'full', borderLeftRadius: 'none' };
19
+ return {}; // square → the variant's own radius (none)
20
+ };
21
+ export const Button = forwardRef((_a, ref) => {
22
+ var { variant = 'filled', size = 'standard', colorScheme = 'brand', shape = 'square', isIcon = false } = _a, rest = __rest(_a, ["variant", "size", "colorScheme", "shape", "isIcon"]);
23
+ const iconProps = isIcon
24
+ ? { boxSize: size === 'compact' ? '2.5rem' : '3rem', minW: 0, px: 0 }
25
+ : {};
26
+ return (_jsx(ChakraButton, Object.assign({ ref: ref, variant: variant, size: size, colorScheme: colorScheme }, shapeStyles(shape), iconProps, rest)));
27
+ });
28
+ Button.displayName = 'Button';
29
+ export const IconButton = forwardRef((props, ref) => (_jsx(Button, Object.assign({ ref: ref, isIcon: true, variant: "ghost" }, props))));
30
+ IconButton.displayName = 'IconButton';
@@ -1,8 +1,61 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
2
3
  import { Box, Text, Flex } from '@chakra-ui/react';
3
4
  export const LabeledField = ({ label, isFilled = false, children, }) => {
5
+ const wrapRef = useRef(null);
6
+ const [autofilled, setAutofilled] = useState(false);
7
+ // Chrome fills the DOM input on load without firing React's onChange, so the
8
+ // parent's `isFilled` (derived from React state) stays false and the label
9
+ // never floats. Detect the browser autofill directly via the `:-webkit-autofill`
10
+ // pseudo: on mount + a couple of short delays (autofill lands just after mount),
11
+ // and on Chrome's autofill animation event (covers a login picked from the
12
+ // saved-credentials dropdown, which also fires no onChange).
13
+ useEffect(() => {
14
+ const node = wrapRef.current;
15
+ if (!node)
16
+ return;
17
+ const check = () => {
18
+ const input = node.querySelector('input');
19
+ try {
20
+ setAutofilled(!!input && input.matches(':-webkit-autofill'));
21
+ }
22
+ catch (_a) {
23
+ // pseudo unsupported (non-Chromium) — fall back to React state only
24
+ }
25
+ };
26
+ check();
27
+ const timers = [
28
+ window.setTimeout(check, 80),
29
+ window.setTimeout(check, 400),
30
+ ];
31
+ const onAnim = (e) => {
32
+ var _a;
33
+ if ((_a = e.animationName) === null || _a === void 0 ? void 0 : _a.startsWith('hfAutofill'))
34
+ check();
35
+ };
36
+ node.addEventListener('animationstart', onAnim, true);
37
+ return () => {
38
+ timers.forEach(clearTimeout);
39
+ node.removeEventListener('animationstart', onAnim, true);
40
+ };
41
+ }, []);
4
42
  if (!label) {
5
43
  return _jsx(_Fragment, { children: children });
6
44
  }
7
- return (_jsxs(Box, { w: "100%", children: [isFilled && (_jsx(Flex, { gap: "1", align: "start", children: _jsx(Text, { fontSize: "xs", fontFamily: "secondary", color: "gray.2", mb: "0.5", children: label }) })), children] }));
45
+ const showLabel = isFilled || autofilled;
46
+ return (_jsxs(Box, { ref: wrapRef, w: "100%", sx: {
47
+ // Emit a no-op animation on autofill state changes so `animationstart`
48
+ // fires and re-checks the pseudo. This is how autofill is observable
49
+ // from JS (the pseudo alone gives no event).
50
+ '& input:-webkit-autofill': {
51
+ animationName: 'hfAutofillStart',
52
+ animationDuration: '0.001s',
53
+ },
54
+ '& input:not(:-webkit-autofill)': {
55
+ animationName: 'hfAutofillCancel',
56
+ animationDuration: '0.001s',
57
+ },
58
+ '@keyframes hfAutofillStart': { from: {}, to: {} },
59
+ '@keyframes hfAutofillCancel': { from: {}, to: {} },
60
+ }, children: [showLabel && (_jsx(Flex, { gap: "1", align: "start", children: _jsx(Text, { fontSize: "xs", fontFamily: "secondary", color: "gray.2", mb: "0.5", children: label }) })), children] }));
8
61
  };
@@ -0,0 +1,4 @@
1
+ export declare const AppleIcon: ({ size, fill }: {
2
+ size?: number | undefined;
3
+ fill?: string | undefined;
4
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { colors } from '../../theme/colors';
3
+ export const AppleIcon = ({ size = 20, fill = colors.gray[4] }) => {
4
+ return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 384 512", fill: fill, "aria-hidden": "true", children: _jsx("path", { d: "M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" }) }));
5
+ };
@@ -0,0 +1,3 @@
1
+ export declare const GoogleIcon: ({ size }: {
2
+ size?: number | undefined;
3
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const GoogleIcon = ({ size = 20 }) => {
3
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 48 48", "aria-hidden": "true", children: [_jsx("path", { fill: "#EA4335", d: "M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" }), _jsx("path", { fill: "#4285F4", d: "M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" }), _jsx("path", { fill: "#FBBC05", d: "M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" }), _jsx("path", { fill: "#34A853", d: "M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" })] }));
4
+ };
@@ -1,4 +1,6 @@
1
+ export * from './AppleIcon';
1
2
  export * from './ArrowLeft';
3
+ export * from './GoogleIcon';
2
4
  export * from './Calendar';
3
5
  export * from './Camera';
4
6
  export * from './ChevronDown';
@@ -1,4 +1,6 @@
1
+ export * from './AppleIcon';
1
2
  export * from './ArrowLeft';
3
+ export * from './GoogleIcon';
2
4
  export * from './Calendar';
3
5
  export * from './Camera';
4
6
  export * from './ChevronDown';
@@ -3,6 +3,7 @@ export * from './animations';
3
3
  export * from './appliances';
4
4
  export * from './appBar';
5
5
  export * from './badge';
6
+ export * from './brand';
6
7
  export * from './buttons';
7
8
  export * from './contacts';
8
9
  export * from './customToast';
@@ -3,6 +3,7 @@ export * from './animations';
3
3
  export * from './appliances';
4
4
  export * from './appBar';
5
5
  export * from './badge';
6
+ export * from './brand';
6
7
  export * from './buttons';
7
8
  export * from './contacts';
8
9
  export * from './customToast';
@@ -0,0 +1,5 @@
1
+ interface PasswordStrengthMeterProps {
2
+ password?: string;
3
+ }
4
+ export declare const PasswordStrengthMeter: ({ password, }: PasswordStrengthMeterProps) => import("react/jsx-runtime").JSX.Element | null;
5
+ export {};
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Flex, Text } from '@chakra-ui/react';
3
+ /**
4
+ * PasswordStrengthMeter — lightweight, dependency-free strength hint.
5
+ *
6
+ * This is UX GUIDANCE ONLY (client-side, bypassable). Real strength/breach
7
+ * enforcement belongs server-side — tracked separately (breach-check ticket).
8
+ */
9
+ const LEVELS = [
10
+ { label: '', color: 'lightGray.1' }, // 0 — unused (empty returns null)
11
+ { label: 'Weak', color: 'red.1' },
12
+ { label: 'Fair', color: 'orange.2' },
13
+ { label: 'Good', color: 'blue.2' },
14
+ { label: 'Strong', color: 'status.success' },
15
+ ];
16
+ const scorePassword = (pw) => {
17
+ if (!pw)
18
+ return 0;
19
+ let score = 1; // any input is at least "Weak"
20
+ if (pw.length >= 8)
21
+ score++;
22
+ if (pw.length >= 12)
23
+ score++;
24
+ const variety = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^a-zA-Z0-9]/].filter((re) => re.test(pw)).length;
25
+ if (variety >= 3)
26
+ score++;
27
+ return Math.min(score, 4);
28
+ };
29
+ export const PasswordStrengthMeter = ({ password = '', }) => {
30
+ if (!password)
31
+ return null;
32
+ const score = scorePassword(password);
33
+ const level = LEVELS[score];
34
+ return (_jsxs(Box, { mt: "1.5", "aria-live": "polite", children: [_jsx(Flex, { gap: "1", mb: "1", children: [1, 2, 3, 4].map((i) => (_jsx(Box, { flex: "1", h: "4px", borderRadius: "full", bg: i <= score ? level.color : 'lightGray.1', transition: "background-color 0.2s ease" }, i))) }), _jsx(Text, { fontSize: "xxs", fontFamily: "secondary", color: level.color, textAlign: "right", children: level.label })] }));
35
+ };
@@ -18,5 +18,5 @@ export const TextInput = (_a) => {
18
18
  const error = hasError && !isDisabled;
19
19
  const isFilled = String(value).trim().length > 0 && showLabel;
20
20
  const placeholderToUse = isFilled ? '' : ((_b = placeholder !== null && placeholder !== void 0 ? placeholder : label) !== null && _b !== void 0 ? _b : '');
21
- return (_jsxs(FormControl, { isInvalid: error, children: [_jsx(LabeledField, { label: label !== null && label !== void 0 ? label : placeholder, isFilled: isFilled, children: _jsx(Input, Object.assign({}, props, { autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, id: id, placeholder: placeholderToUse, value: value, type: type, onChange: handleChange, isInvalid: error, isDisabled: isDisabled, _placeholder: { color: 'gray.2' } })) }), error && (_jsx(Text, { variant: "error", my: "1", children: errorMessage })), hint && !error && (_jsx(Text, { variant: "hint", my: "1", children: hint }))] }));
21
+ return (_jsxs(FormControl, { isInvalid: error, children: [_jsx(LabeledField, { label: label !== null && label !== void 0 ? label : placeholder, isFilled: isFilled, children: _jsx(Input, Object.assign({}, props, { autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, id: id, placeholder: placeholderToUse, value: value, type: type, onChange: handleChange, isInvalid: error, isDisabled: isDisabled })) }), error && (_jsx(Text, { variant: "error", my: "1", children: errorMessage })), hint && !error && (_jsx(Text, { variant: "hint", my: "1", children: hint }))] }));
22
22
  };
@@ -5,6 +5,7 @@ export * from './EditFileUploader';
5
5
  export * from './ItemSubTypeSelect';
6
6
  export * from './MobileFileUploader';
7
7
  export * from './PasswordInput';
8
+ export * from './PasswordStrengthMeter';
8
9
  export * from './RadioCard';
9
10
  export * from './SearchInput';
10
11
  export * from './SelectButton';
@@ -5,6 +5,7 @@ export * from './EditFileUploader';
5
5
  export * from './ItemSubTypeSelect';
6
6
  export * from './MobileFileUploader';
7
7
  export * from './PasswordInput';
8
+ export * from './PasswordStrengthMeter';
8
9
  export * from './RadioCard';
9
10
  export * from './SearchInput';
10
11
  export * from './SelectButton';
@@ -9,4 +9,4 @@ const shine = keyframes `
9
9
  background-position: 200% 0;
10
10
  }
11
11
  `;
12
- export const SkeletonBox = ({ height = '20px', width = '100%', borderRadius = 'md', }) => (_jsx(Box, { bg: "#F4F7F8", w: width, h: height, borderRadius: borderRadius, backgroundImage: "linear-gradient(90deg, #F4F7F8 0%, #e6ecee 50%, #F4F7F8 100%)", backgroundSize: "200% 100%", animation: `${shine} 1.5s infinite linear` }));
12
+ export const SkeletonBox = ({ height = '20px', width = '100%', borderRadius = 'md', }) => (_jsx(Box, { bg: "bg.surface", w: width, h: height, borderRadius: borderRadius, backgroundImage: "linear-gradient(90deg, #F4F7F8 0%, #e6ecee 50%, #F4F7F8 100%)", backgroundSize: "200% 100%", animation: `${shine} 1.5s infinite linear` }));
@@ -17,5 +17,5 @@ import { House } from '../../assets/images';
17
17
  export const HomeDetailsContent = (_a) => {
18
18
  var props = __rest(_a, []);
19
19
  const { handleClose, isLoading, children, showFooter, footerButtons } = props;
20
- return (_jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { p: "0", children: _jsx(PanelHeader, { icon: House, handleCloseButton: handleClose, title: t("myHomes.details.header") }) }), _jsx(DrawerBody, { padding: "0 0 6rem 0", bg: "#F4F7F8", children: _jsx(Box, { children: isLoading ? (_jsx(Loading, {})) : (_jsxs(_Fragment, { children: [_jsx(HomeCard, Object.assign({}, props)), children] })) }) }), _jsx(DrawerFooter, { children: _jsx(FooterDrawer, { isOpen: showFooter, children: footerButtons }) })] }));
20
+ return (_jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { p: "0", children: _jsx(PanelHeader, { icon: House, handleCloseButton: handleClose, title: t("myHomes.details.header") }) }), _jsx(DrawerBody, { padding: "0 0 6rem 0", bg: "bg.surface", children: _jsx(Box, { children: isLoading ? (_jsx(Loading, {})) : (_jsxs(_Fragment, { children: [_jsx(HomeCard, Object.assign({}, props)), children] })) }) }), _jsx(DrawerFooter, { children: _jsx(FooterDrawer, { isOpen: showFooter, children: footerButtons }) })] }));
21
21
  };
@@ -1,2 +1,2 @@
1
1
  import { CardFooterI } from '../../interfaces';
2
- export declare const CardFooter: ({ title, buttonLabel, handleButtonClick, }: CardFooterI) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const CardFooter: ({ title, buttonLabel, handleButtonClick, children, }: CardFooterI) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Container, Text, Button } from '@chakra-ui/react';
3
- export const CardFooter = ({ title = '', buttonLabel, handleButtonClick, }) => {
4
- return (_jsx(Container, { variant: "secondary", centerContent: true, children: _jsxs(Box, { w: "full", px: ['container.sm', 'container.md', 'container.lg'], my: "auto", py: "2rem", children: [title !== '' && (_jsx(Text, { mb: 4, textAlign: "center", variant: "body", children: title })), _jsx(Button, { mb: 2, variant: "secondary", onClick: handleButtonClick, children: buttonLabel })] }) }));
2
+ import { Box, Container, Text } from '@chakra-ui/react';
3
+ export const CardFooter = ({ title = '', buttonLabel, handleButtonClick, children, }) => {
4
+ return (_jsx(Container, { variant: "secondary", centerContent: true, children: _jsxs(Box, { w: "full", px: ['container.sm', 'container.md', 'container.lg'], my: "auto", py: "2rem", children: [_jsxs(Text, { textAlign: "center", variant: "body", children: [title, ' ', _jsx(Box, { as: "button", type: "button", display: "inline", color: "link.default", fontWeight: "medium", onClick: handleButtonClick, _hover: { textDecoration: 'underline' }, _focusVisible: {
5
+ textDecoration: 'underline',
6
+ outline: '2px solid',
7
+ outlineColor: 'border.focus',
8
+ outlineOffset: '2px',
9
+ borderRadius: 'sm',
10
+ }, children: buttonLabel })] }), children && _jsx(Box, { mt: 6, children: children })] }) }));
5
11
  };
@@ -1,8 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { t } from 'i18next';
3
3
  import { Flex, Text } from '@chakra-ui/react';
4
4
  export const Footer = () => {
5
- const currentDate = new Date();
6
- const currentYear = currentDate.getFullYear();
7
- return (_jsxs(Flex, { w: ['container.full', 'container.sm', 'container.md'], direction: ['column', 'row'], mx: "auto", my: 4, justifyContent: ['center', 'space-between'], alignItems: "center", children: [_jsx(Text, { variant: "label", children: `©${currentYear} ${t('footer.copyright')}` }), _jsxs(Flex, { gap: 2, children: [_jsx(Text, { variant: "label", children: _jsx("a", { target: "_blank", href: t('footer.termsUrl'), children: t('footer.termsLink') }) }), _jsx(Text, { variant: "label", children: _jsx("a", { target: "_blank", href: t('footer.privacyUrl'), children: t('footer.privacyLink') }) })] })] }));
5
+ const currentYear = new Date().getFullYear();
6
+ return (_jsx(Flex, { justifyContent: "center", px: 4, my: 4, children: _jsx(Text, { variant: "label", textAlign: "center", children: `©${currentYear} ${t('footer.copyright')}` }) }));
8
7
  };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Center, Container, Image } from '@chakra-ui/react';
3
- import { HomefileUpdate } from '../../assets/images';
2
+ import { Center, Container } from '@chakra-ui/react';
3
+ import { BrandLogo } from '../../components/brand/BrandLogo';
4
4
  export const Logo = () => {
5
- return (_jsx(Container, { variant: "ghost", size: "full", children: _jsx(Center, { h: "container.xxs", children: _jsx(Image, { src: HomefileUpdate, alt: "Homefile", h: "image.logo" }) }) }));
5
+ return (_jsx(Container, { variant: "ghost", size: "full", children: _jsx(Center, { h: "container.xxs", children: _jsx(BrandLogo, {}) }) }));
6
6
  };
@@ -1,2 +1,2 @@
1
1
  import { SignUpI } from '../../../interfaces';
2
- export declare const SignUp: ({ isLoading, isPartner, handleCreateAccount, handleSignIn, showPromoCode, signupErrorMessage, values, }: SignUpI) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const SignUp: ({ isLoading, isPartner, handleCreateAccount, handleSignIn, handleGoogleSignIn, handleAppleSignIn, signupErrorMessage, values, }: SignUpI) => import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { t } from 'i18next';
3
- import { Box, Button, Center, Container, Stack, Text } from '@chakra-ui/react';
4
- import { Footer, CardFooter, PageTitle, PasswordInput, TextInput, ButtonLoader, BasePageWrapper, AuthHeader, } from '../../../components';
5
- import { isEmptyField, isValidEmail, isValidPassword, passwordMatch, } from '../../../utils';
4
+ import { Box, Button, Center, Container, Divider, Flex, Stack, Text, } from '@chakra-ui/react';
5
+ import { Footer, CardFooter, PageTitle, PasswordInput, PasswordStrengthMeter, TextInput, ButtonLoader, BasePageWrapper, AuthHeader, GoogleIcon, AppleIcon, } from '../../../components';
6
+ import { isEmptyField, isValidEmail, isValidPassword } from '../../../utils';
6
7
  import { useSignUp } from '../../../hooks';
7
- export const SignUp = ({ isLoading, isPartner, handleCreateAccount, handleSignIn, showPromoCode, signupErrorMessage, values = {
8
+ export const SignUp = ({ isLoading, isPartner, handleCreateAccount, handleSignIn, handleGoogleSignIn, handleAppleSignIn, signupErrorMessage, values = {
8
9
  email: '',
9
10
  firstName: '',
10
11
  lastName: '',
@@ -12,15 +13,18 @@ export const SignUp = ({ isLoading, isPartner, handleCreateAccount, handleSignIn
12
13
  password: '',
13
14
  partnerCode: '',
14
15
  }, }) => {
15
- var _a, _b, _c;
16
+ var _a, _b, _c, _d;
16
17
  const { inputs, handleInputChange, handleSubmit, isValidated, passwordErrorMessage, } = useSignUp({
17
18
  handleCreateAccount,
18
19
  values,
19
20
  });
20
- return (_jsxs(BasePageWrapper, { children: [_jsxs(Container, { size: "onboarding", boxShadow: "lg", children: [_jsx(AuthHeader, { isPartner: isPartner }), _jsx(PageTitle, { title: isPartner ? t('signup.titlePartner') : t('signup.title') }), _jsx(Box, { px: ['container.sm', 'container.md', 'container.lg'], mb: 6, children: _jsxs(Stack, { spacing: "base", as: "form", children: [Boolean(signupErrorMessage) && (_jsx(Text, { variant: "error", children: signupErrorMessage })), _jsx(TextInput, { errorMessage: t('forms.firstName') + ' ' + t('forms.required'), hasError: isValidated && isEmptyField(inputs.firstName), id: "firstName", placeholder: t('forms.firstName'), value: inputs.firstName, handleChange: handleInputChange, autoComplete: "given-name" }), _jsx(TextInput, { errorMessage: t('forms.lastName') + ' ' + t('forms.required'), hasError: isValidated && isEmptyField(inputs.lastName), id: "lastName", placeholder: t('forms.lastName'), value: inputs.lastName, handleChange: handleInputChange, autoComplete: "family-name" }), _jsx(TextInput, { autoCapitalize: "none", autoCorrect: "off", errorMessage: t('forms.email') + ' ' + t('forms.required'), hasError: isValidated &&
21
- (isEmptyField(inputs.email) || !isValidEmail(inputs.email)), id: "email", placeholder: t('forms.email'), value: inputs.email, handleChange: handleInputChange, autoComplete: "email" }), showPromoCode && (_jsx(TextInput, { autoCapitalize: "none", autoCorrect: "off", id: "partnerCode", placeholder: t('forms.promoCode'), value: (_a = inputs.partnerCode) !== null && _a !== void 0 ? _a : '', handleChange: handleInputChange, autoComplete: "off" })), _jsx(Text, { textAlign: "center", variant: "caption", children: t('signup.subtitle') }), _jsx(PasswordInput, { errorMessage: passwordErrorMessage(values.password), hasError: isValidated &&
22
- (!isValidPassword(inputs.password) ||
23
- !passwordMatch(inputs.password, inputs.confirmPassword)), id: "password", placeholder: t('forms.password'), value: (_b = inputs.password) !== null && _b !== void 0 ? _b : '', handleChange: (event) => handleInputChange(event), autoComplete: "new-password" }), _jsx(PasswordInput, { errorMessage: passwordErrorMessage(inputs.confirmPassword), hasError: isValidated &&
24
- (!isValidPassword(inputs.confirmPassword) ||
25
- !passwordMatch(inputs.password, inputs.confirmPassword)), id: "confirmPassword", placeholder: t('forms.passwordConfirm'), value: (_c = inputs.confirmPassword) !== null && _c !== void 0 ? _c : '', handleChange: (event) => handleInputChange(event), autoComplete: "new-password" }), _jsx(Center, { px: ['container.sm', 'container.md', 'container.lg'], h: "4rem", children: _jsxs(Text, { textAlign: "center", variant: "info", w: "100%", children: [t('signup.terms'), _jsx("a", { target: "_blank", href: t('footer.termsUrl'), children: t('footer.termsLink') })] }) }), _jsx(Button, { isLoading: isLoading, spinner: _jsx(ButtonLoader, {}), onClick: handleSubmit, children: t('signup.signupBt') })] }) }), _jsx(CardFooter, { title: isPartner ? t('signup.signinPartner') : t('signup.signin'), buttonLabel: t('signup.signinBt'), handleButtonClick: handleSignIn })] }), _jsx(Footer, {})] }));
21
+ // Partner code is hidden behind a "Have a partner code?" link, closed by default.
22
+ const [showPartnerCode, setShowPartnerCode] = useState(false);
23
+ // Social sign-in (and the "or" divider) appear only once OAuth is wired —
24
+ // i.e. a handler is actually provided by the app.
25
+ const showGoogle = Boolean(handleGoogleSignIn);
26
+ const showApple = Boolean(handleAppleSignIn);
27
+ const showSocial = showGoogle || showApple;
28
+ return (_jsxs(BasePageWrapper, { children: [_jsxs(Container, { size: "onboarding", boxShadow: "lg", children: [_jsx(AuthHeader, { isPartner: isPartner }), _jsx(PageTitle, { title: isPartner ? t('signup.titlePartner') : t('signup.title') }), _jsx(Box, { px: ['container.sm', 'container.md', 'container.lg'], mb: 6, children: _jsxs(Stack, { spacing: "base", as: "form", children: [Boolean(signupErrorMessage) && (_jsx(Text, { variant: "error", children: signupErrorMessage })), _jsx(TextInput, { errorMessage: t('forms.fullName') + ' ' + t('forms.required'), hasError: isValidated && isEmptyField(inputs.name), id: "name", placeholder: t('forms.fullName'), value: (_a = inputs.name) !== null && _a !== void 0 ? _a : '', handleChange: handleInputChange, autoComplete: "name" }), _jsx(TextInput, { autoCapitalize: "none", autoCorrect: "off", errorMessage: t('forms.email') + ' ' + t('forms.required'), hasError: isValidated &&
29
+ (isEmptyField(inputs.email) || !isValidEmail(inputs.email)), id: "email", placeholder: t('forms.email'), value: inputs.email, handleChange: handleInputChange, autoComplete: "email" }), _jsxs(Box, { children: [_jsx(PasswordInput, { errorMessage: passwordErrorMessage(values.password), hasError: isValidated && !isValidPassword(inputs.password), id: "password", placeholder: t('forms.password'), value: (_b = inputs.password) !== null && _b !== void 0 ? _b : '', handleChange: (event) => handleInputChange(event), autoComplete: "new-password" }), _jsx(PasswordStrengthMeter, { password: (_c = inputs.password) !== null && _c !== void 0 ? _c : '' })] }), _jsx(Center, { children: _jsx(Button, { variant: "text", leftIcon: _jsx(Text, { as: "span", fontSize: "md", lineHeight: "1", children: "+" }), onClick: () => setShowPartnerCode((prev) => !prev), children: t('signup.partnerCodePrompt') }) }), showPartnerCode && (_jsx(TextInput, { autoCapitalize: "none", autoCorrect: "off", id: "partnerCode", placeholder: t('forms.promoCode'), value: (_d = inputs.partnerCode) !== null && _d !== void 0 ? _d : '', handleChange: handleInputChange, autoComplete: "off" })), _jsx(Button, { isLoading: isLoading, spinner: _jsx(ButtonLoader, {}), onClick: handleSubmit, children: t('signup.signupBt') }), showSocial && (_jsxs(_Fragment, { children: [_jsxs(Flex, { align: "center", gap: "3", py: "1", children: [_jsx(Divider, { flex: "1", borderColor: "border.default" }), _jsx(Text, { variant: "caption", children: t('signup.or') }), _jsx(Divider, { flex: "1", borderColor: "border.default" })] }), showGoogle && (_jsx(Button, { variant: "secondary", leftIcon: _jsx(GoogleIcon, { size: 20 }), onClick: handleGoogleSignIn, children: t('signup.continueGoogle') })), showApple && (_jsx(Button, { variant: "secondary", leftIcon: _jsx(AppleIcon, { size: 20 }), onClick: handleAppleSignIn, children: t('signup.continueApple') }))] }))] }) }), _jsx(CardFooter, { title: isPartner ? t('signup.signinPartner') : t('signup.signin'), buttonLabel: t('signup.signinBt'), handleButtonClick: handleSignIn, children: _jsxs(Text, { textAlign: "center", variant: "info", w: "100%", children: [t('signup.terms'), ' ', _jsxs(Text, { as: "span", whiteSpace: "nowrap", children: ["Homefile", ' ', _jsx("a", { target: "_blank", href: t('footer.termsUrl'), children: t('footer.termsLink') }), "."] })] }) })] }), _jsx(Footer, {})] }));
26
30
  };
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Stack } from '@chakra-ui/react';
3
- import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
3
+ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts';
4
4
  import { contentStyle, cursorStyle, labelStyle, itemStyle, yAxisStyle, TrendingLineChartHeader, } from '../../../../../components';
5
5
  import { colors } from '../../../../../theme/colors';
6
6
  export const TrendingLineChart = ({ data = [] }) => {
7
- return (_jsxs(Stack, { spacing: "base", bg: "neutral.white", p: "base", children: [_jsx(TrendingLineChartHeader, {}), _jsxs(LineChart, { width: 300, height: 200, data: data, margin: {
8
- left: -25,
9
- bottom: -10,
10
- }, children: [_jsx(CartesianGrid, {}), _jsx(XAxis, { dataKey: "year", style: yAxisStyle }), _jsx(YAxis, { style: yAxisStyle }), _jsx(Tooltip, { contentStyle: contentStyle, cursor: cursorStyle, labelStyle: labelStyle, itemStyle: itemStyle }), _jsx(Line, { type: "monotone", dataKey: "value", stroke: colors.green[1], strokeWidth: 3, dot: _jsx(Box, {}) }), _jsx(Line, { type: "monotone", dataKey: "invested", stroke: colors.yellow[1], strokeWidth: 3, dot: _jsx(Box, {}) }), _jsx(Line, { type: "monotone", strokeDasharray: "5 5", dataKey: "balance", stroke: colors.blue[8], strokeWidth: 3, dot: _jsx(Box, {}) })] })] }));
7
+ return (_jsxs(Stack, { spacing: "base", bg: "neutral.white", p: "base", children: [_jsx(TrendingLineChartHeader, {}), _jsx(ResponsiveContainer, { width: "100%", height: 200, children: _jsxs(LineChart, { data: data, margin: {
8
+ left: -25,
9
+ right: 8,
10
+ bottom: -10,
11
+ }, children: [_jsx(CartesianGrid, {}), _jsx(XAxis, { dataKey: "year", style: yAxisStyle }), _jsx(YAxis, { style: yAxisStyle }), _jsx(Tooltip, { contentStyle: contentStyle, cursor: cursorStyle, labelStyle: labelStyle, itemStyle: itemStyle }), _jsx(Line, { type: "monotone", dataKey: "value", stroke: colors.green[1], strokeWidth: 3, dot: _jsx(Box, {}) }), _jsx(Line, { type: "monotone", dataKey: "invested", stroke: colors.yellow[1], strokeWidth: 3, dot: _jsx(Box, {}) }), _jsx(Line, { type: "monotone", strokeDasharray: "5 5", dataKey: "balance", stroke: colors.blue[8], strokeWidth: 3, dot: _jsx(Box, {}) })] }) })] }));
11
12
  };
@@ -1,4 +1,5 @@
1
1
  export declare const SignUpForm: {
2
+ name: string;
2
3
  firstName: string;
3
4
  lastName: string;
4
5
  email: string;
@@ -1,4 +1,5 @@
1
1
  export const SignUpForm = {
2
+ name: '',
2
3
  firstName: '',
3
4
  lastName: '',
4
5
  email: '',
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { t } from 'i18next';
3
3
  import { SignUpForm } from '../../helpers';
4
- import { isEmptyField, isValidEmail, isValidPassword, passwordMatch, } from '../../utils';
4
+ import { isEmptyField, isValidEmail, isValidPassword } from '../../utils';
5
5
  export const useSignUp = ({ handleCreateAccount, values }) => {
6
6
  const [inputs, setInputs] = useState(SignUpForm);
7
7
  const [isValidated, setIsValidated] = useState(false);
@@ -13,16 +13,23 @@ export const useSignUp = ({ handleCreateAccount, values }) => {
13
13
  setInputs(Object.assign(Object.assign({}, inputs), { [event.target.id]: event.target.value }));
14
14
  };
15
15
  const handleSubmit = () => {
16
+ var _a;
16
17
  setIsValidated(true);
17
- if (!isEmptyField(inputs.firstName) &&
18
- !isEmptyField(inputs.lastName) &&
18
+ // One "Name" field in the UI; split into first/last for the backend
19
+ // (contract unchanged — firstName is what greetings/emails/Homi use).
20
+ const name = ((_a = inputs.name) !== null && _a !== void 0 ? _a : '').trim();
21
+ const [firstName = '', ...rest] = name.split(/\s+/);
22
+ const lastName = rest.join(' ');
23
+ if (!isEmptyField(name) &&
19
24
  isValidEmail(inputs.email) &&
20
- isValidPassword(inputs.password) &&
21
- passwordMatch(inputs.password, inputs.confirmPassword))
22
- handleCreateAccount(inputs);
25
+ isValidPassword(inputs.password))
26
+ handleCreateAccount(Object.assign(Object.assign({}, inputs), { firstName, lastName }));
23
27
  };
24
28
  useEffect(() => {
25
- setInputs(values);
29
+ var _a, _b;
30
+ // Prefill the single Name field from any first/last that arrives via props.
31
+ const name = values.name || `${(_a = values.firstName) !== null && _a !== void 0 ? _a : ''} ${(_b = values.lastName) !== null && _b !== void 0 ? _b : ''}`.trim();
32
+ setInputs(Object.assign(Object.assign({}, values), { name }));
26
33
  }, [values]);
27
34
  return {
28
35
  inputs,
package/dist/index.d.ts CHANGED
@@ -7,3 +7,5 @@ export { homeAssistantWizardSteps, partnerWizardSteps } from './helpers';
7
7
  export { billingProxy, dynamicFormProxy, homeCardProxy, partnerContentProxy, partnerDetailsProxy, paymentMethodProxy, recordsInputsProxy, recordsInputsToDBProxy, userDetailsProxy, confirmAddressProxy, addressProxy, } from './proxies';
8
8
  import theme from './theme';
9
9
  export { theme };
10
+ export { Button, IconButton } from './components/button/Button';
11
+ export type { ButtonProps, IconButtonProps } from './components/button/Button';
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { homeAssistantWizardSteps, partnerWizardSteps } from './helpers';
7
7
  export { billingProxy, dynamicFormProxy, homeCardProxy, partnerContentProxy, partnerDetailsProxy, paymentMethodProxy, recordsInputsProxy, recordsInputsToDBProxy, userDetailsProxy, confirmAddressProxy, addressProxy, } from './proxies';
8
8
  import theme from './theme';
9
9
  export { theme };
10
+ export { Button, IconButton } from './components/button/Button';
@@ -1,5 +1,7 @@
1
+ import { ReactNode } from 'react';
1
2
  export interface CardFooterI {
2
3
  title?: string;
3
4
  buttonLabel: string;
4
5
  handleButtonClick: () => void;
6
+ children?: ReactNode;
5
7
  }
@@ -1,6 +1,7 @@
1
1
  export interface SignUpFormI {
2
2
  confirmPassword?: string;
3
3
  email: string;
4
+ name?: string;
4
5
  firstName: string;
5
6
  lastName: string;
6
7
  password?: string;
@@ -11,6 +12,8 @@ export interface SignUpI {
11
12
  isPartner?: boolean;
12
13
  handleCreateAccount: (form: SignUpFormI) => void;
13
14
  handleSignIn: () => void;
15
+ handleGoogleSignIn?: () => void;
16
+ handleAppleSignIn?: () => void;
14
17
  showPromoCode?: boolean;
15
18
  signupErrorMessage?: string;
16
19
  values?: SignUpFormI;
@@ -0,0 +1,11 @@
1
+ import { Meta } from '@storybook/react';
2
+ /**
3
+ * Button (atom) — the 3 canonical templates × 2 real sizes, plus shape and
4
+ * the IconButton derivative. Rendered through the real wrapper component.
5
+ * The old 20 theme variants remain untouched (compat shim) during migration.
6
+ */
7
+ declare const _default: Meta;
8
+ export default _default;
9
+ export declare const Templates: () => import("react/jsx-runtime").JSX.Element;
10
+ export declare const Shapes: () => import("react/jsx-runtime").JSX.Element;
11
+ export declare const Icons: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HStack, Text, VStack } from '@chakra-ui/react';
3
+ import { Button, IconButton } from '../../components/button/Button';
4
+ /**
5
+ * Button (atom) — the 3 canonical templates × 2 real sizes, plus shape and
6
+ * the IconButton derivative. Rendered through the real wrapper component.
7
+ * The old 20 theme variants remain untouched (compat shim) during migration.
8
+ */
9
+ export default {
10
+ title: 'Foundations/Button (atom)',
11
+ };
12
+ const Row = ({ label, variant, }) => (_jsxs(HStack, { spacing: "4", align: "center", children: [_jsx(Text, { w: "80px", fontSize: "xs", fontFamily: "secondary", color: "text.secondary", children: label }), _jsx(Button, { variant: variant, size: "standard", children: "Continue" }), _jsx(Button, { variant: variant, size: "compact", children: "Continue" }), _jsx(Button, { variant: variant, isDisabled: true, children: "Disabled" })] }));
13
+ export const Templates = () => (_jsxs(VStack, { align: "stretch", spacing: "6", p: "8", bg: "bg.canvas", maxW: "640px", children: [_jsx(Text, { fontFamily: "primary", fontSize: "lg", color: "text.heading", children: "3 templates \u00B7 standard (3.4rem) + compact (2.5rem)" }), _jsx(Row, { label: "filled", variant: "filled" }), _jsx(Row, { label: "outline", variant: "outline" }), _jsx(Row, { label: "ghost", variant: "ghost" })] }));
14
+ export const Shapes = () => (_jsxs(HStack, { spacing: "4", p: "8", bg: "bg.canvas", align: "center", children: [_jsx(Button, { shape: "square", children: "Square" }), _jsx(Button, { shape: "pillLeft", children: "Back" }), _jsx(Button, { shape: "pillRight", children: "Next" }), _jsx(Button, { width: "12rem", children: "Fixed width" })] }));
15
+ export const Icons = () => (_jsxs(HStack, { spacing: "4", p: "8", bg: "bg.canvas", align: "center", children: [_jsx(IconButton, { "aria-label": "Add", variant: "outline", children: "\uFF0B" }), _jsx(IconButton, { "aria-label": "Menu", children: "\u2630" }), _jsx(IconButton, { "aria-label": "Close", size: "compact", children: "\u2715" })] }));
@@ -0,0 +1,13 @@
1
+ import { Meta } from '@storybook/react';
2
+ /**
3
+ * Design Tokens — visual reference for the Step 1 semantic token layer.
4
+ *
5
+ * Every value is read from the live Chakra theme via `useTheme()`, so this
6
+ * doc CANNOT drift from `src/theme/index.ts`. Nothing here is hardcoded.
7
+ */
8
+ declare const _default: Meta;
9
+ export default _default;
10
+ export declare const Colors: () => import("react/jsx-runtime").JSX.Element;
11
+ export declare const Radii: () => import("react/jsx-runtime").JSX.Element;
12
+ export declare const Shadows: () => import("react/jsx-runtime").JSX.Element;
13
+ export declare const Usage: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Center, Code, Divider, Grid, HStack, Text, VStack, useTheme, } from '@chakra-ui/react';
3
+ /**
4
+ * Design Tokens — visual reference for the Step 1 semantic token layer.
5
+ *
6
+ * Every value is read from the live Chakra theme via `useTheme()`, so this
7
+ * doc CANNOT drift from `src/theme/index.ts`. Nothing here is hardcoded.
8
+ */
9
+ export default {
10
+ title: 'Foundations/Design Tokens',
11
+ };
12
+ // ── helpers ────────────────────────────────────────────────────────
13
+ // Resolve a palette token ref (e.g. 'blue.2', 'overlay') to its raw hex.
14
+ const resolveHex = (theme, token) => {
15
+ const parts = String(token).split('.');
16
+ let cur = theme.colors;
17
+ for (const p of parts)
18
+ cur = cur === null || cur === void 0 ? void 0 : cur[p];
19
+ return typeof cur === 'string' ? cur : token;
20
+ };
21
+ // Convert a radius value ('0.5rem' | '2px' | '9999px') to a px label.
22
+ const toPx = (v) => {
23
+ if (typeof v !== 'string')
24
+ return '';
25
+ if (v.endsWith('rem'))
26
+ return `${parseFloat(v) * 16}px`;
27
+ return v;
28
+ };
29
+ // The 7 semantic groups WE authored, in display order. Explicit list keeps
30
+ // Chakra's built-in semanticTokens (chakra-body-text, …) out of the doc.
31
+ const COLOR_GROUPS = [
32
+ 'brand',
33
+ 'bg',
34
+ 'text',
35
+ 'border',
36
+ 'link',
37
+ 'status',
38
+ 'accent',
39
+ ];
40
+ const RADII_KEYS = ['none', 'sm', 'base', 'md', 'lg', 'xl', '2xl', 'full'];
41
+ const SHADOW_KEYS = ['focusRing', 'cardHover'];
42
+ // ── shared bits ────────────────────────────────────────────────────
43
+ const SectionTitle = ({ children }) => (_jsx(Text, { fontFamily: "primary", fontSize: "lg", fontWeight: "semibold", color: "text.heading", textTransform: "capitalize", mb: "3", children: children }));
44
+ const ColorSwatch = ({ role, alias, hex, }) => (_jsxs(Box, { borderWidth: "1px", borderColor: "border.subtle", borderRadius: "base", overflow: "hidden", bg: "bg.elevated", children: [_jsx(Box, { bg: role, h: "56px", borderBottomWidth: "1px", borderColor: "blackAlpha.100" }), _jsxs(Box, { p: "2", children: [_jsx(Text, { fontFamily: "secondary", fontSize: "xs", fontWeight: "bold", color: "text.heading", children: role }), _jsxs(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.secondary", children: ["alias: ", alias] }), _jsx(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.muted", textTransform: "uppercase", children: hex })] })] }));
45
+ // ── 1. COLORS ──────────────────────────────────────────────────────
46
+ export const Colors = () => {
47
+ var _a, _b;
48
+ const theme = useTheme();
49
+ const sem = (_b = (_a = theme.semanticTokens) === null || _a === void 0 ? void 0 : _a.colors) !== null && _b !== void 0 ? _b : {};
50
+ return (_jsx(VStack, { align: "stretch", spacing: "8", p: "base", maxW: "960px", children: COLOR_GROUPS.map((group) => {
51
+ var _a;
52
+ const roles = (_a = sem[group]) !== null && _a !== void 0 ? _a : {};
53
+ return (_jsxs(Box, { children: [_jsx(SectionTitle, { children: group }), _jsx(Grid, { templateColumns: "repeat(auto-fill, minmax(170px, 1fr))", gap: "4", children: Object.entries(roles).map(([role, val]) => {
54
+ const alias = val === null || val === void 0 ? void 0 : val.default;
55
+ if (!alias)
56
+ return null;
57
+ const fullRole = `${group}.${role}`;
58
+ return (_jsx(ColorSwatch, { role: fullRole, alias: String(alias), hex: resolveHex(theme, alias) }, fullRole));
59
+ }) })] }, group));
60
+ }) }));
61
+ };
62
+ // ── 2. RADII ───────────────────────────────────────────────────────
63
+ export const Radii = () => {
64
+ const theme = useTheme();
65
+ return (_jsxs(Box, { p: "base", maxW: "960px", children: [_jsx(SectionTitle, { children: "Radii" }), _jsx(Grid, { templateColumns: "repeat(auto-fill, minmax(120px, 1fr))", gap: "6", children: RADII_KEYS.map((key) => {
66
+ var _a;
67
+ const value = (_a = theme.radii) === null || _a === void 0 ? void 0 : _a[key];
68
+ if (value === undefined)
69
+ return null;
70
+ return (_jsxs(Center, { flexDirection: "column", children: [_jsx(Box, { boxSize: "80px", bg: "brand.primary", borderRadius: key, mb: "2" }), _jsx(Text, { fontFamily: "secondary", fontSize: "xs", fontWeight: "bold", color: "text.heading", children: key }), _jsxs(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.muted", children: [value, " \u00B7 ", toPx(value)] })] }, key));
71
+ }) })] }));
72
+ };
73
+ // ── 3. SHADOWS ─────────────────────────────────────────────────────
74
+ export const Shadows = () => {
75
+ const theme = useTheme();
76
+ return (_jsxs(Box, { p: "base", maxW: "960px", children: [_jsx(SectionTitle, { children: "Shadows" }), _jsx(HStack, { spacing: "10", flexWrap: "wrap", align: "flex-start", children: SHADOW_KEYS.map((key) => {
77
+ var _a;
78
+ const value = (_a = theme.shadows) === null || _a === void 0 ? void 0 : _a[key];
79
+ if (value === undefined)
80
+ return null;
81
+ return (_jsxs(Center, { flexDirection: "column", children: [_jsx(Box, { boxSize: "96px", bg: "bg.elevated", borderRadius: "base", boxShadow: key, mb: "3" }), _jsx(Text, { fontFamily: "secondary", fontSize: "xs", fontWeight: "bold", color: "text.heading", children: key }), _jsx(Text, { fontFamily: "secondary", fontSize: "0.625rem", color: "text.muted", maxW: "140px", textAlign: "center", children: value })] }, key));
82
+ }) })] }));
83
+ };
84
+ // ── 4. USAGE / INTRO ───────────────────────────────────────────────
85
+ export const Usage = () => (_jsxs(Box, { p: "base", maxW: "720px", children: [_jsx(Text, { fontFamily: "primary", fontSize: "3xl", color: "text.heading", mb: "4", children: "Design Tokens" }), _jsxs(Text, { fontFamily: "secondary", fontSize: "md", color: "text.body", mb: "4", children: ["Semantic ", _jsx("b", { children: "roles" }), " that alias our existing palette. Instead of remembering that the brand blue is ", _jsx(Code, { children: "blue.2" }), " or a card background is ", _jsx(Code, { children: "lightBlue.1" }), ", you name the job:"] }), _jsxs(VStack, { align: "stretch", spacing: "1", mb: "5", children: [_jsx(Code, { children: "bg=\"bg.canvas\"" }), _jsx(Code, { children: "bg=\"bg.surface\"" }), _jsx(Code, { children: "color=\"text.heading\"" }), _jsx(Code, { children: "color=\"brand.primary\"" }), _jsx(Code, { children: "borderColor=\"border.default\"" })] }), _jsx(Divider, { mb: "5", borderColor: "border.subtle" }), _jsxs(Text, { fontFamily: "secondary", fontSize: "md", color: "text.body", mb: "3", children: [_jsx("b", { children: "One source of truth." }), " Every role points at one palette token, defined once in ", _jsx(Code, { children: "src/theme/index.ts" }), ". Change", ' ', _jsx(Code, { children: "brand.primary" }), " in that one place and every card, button, link and border that uses the role updates together."] }), _jsxs(Text, { fontFamily: "secondary", fontSize: "md", color: "text.body", children: [_jsx("b", { children: "Additive & opt-in." }), " These tokens are new and change nothing on screen yet \u2014 existing components still use raw palette tokens. Repointing components to these roles happens in a later step (Step 3). Use the roles in ", _jsx("i", { children: "new" }), " work now; nothing breaks."] })] }));
@@ -89,6 +89,7 @@ export declare const colors: {
89
89
  9: string;
90
90
  10: string;
91
91
  11: string;
92
+ brand: string;
92
93
  };
93
94
  teal: {
94
95
  1: string;
@@ -113,6 +114,7 @@ export declare const colors: {
113
114
  error: {
114
115
  1: string;
115
116
  2: string;
117
+ 3: string;
116
118
  };
117
119
  dayGradient: string;
118
120
  morningGradient: string;
@@ -89,6 +89,7 @@ export const colors = {
89
89
  9: '#726E60',
90
90
  10: '#5D675B',
91
91
  11: '#4C8827',
92
+ brand: '#6ab528', // Homefile logo house mark
92
93
  },
93
94
  teal: {
94
95
  1: '#F2EFF9',
@@ -113,6 +114,7 @@ export const colors = {
113
114
  error: {
114
115
  1: '#f5ddf4',
115
116
  2: '#F544DA',
117
+ 3: '#D53F8C', // canonical validation-error color (was rendering via Chakra-default pink.500)
116
118
  },
117
119
  dayGradient: 'linear(to-b, #cff1fc, #F9FEFF)',
118
120
  morningGradient: 'linear(to-b, #d2abf1, #f7dcac)',
@@ -22,8 +22,90 @@ export declare const Button: {
22
22
  onboarding: {
23
23
  w: string[];
24
24
  };
25
+ standard: {
26
+ fontSize: string;
27
+ h: string;
28
+ px: string;
29
+ fontWeight: string;
30
+ };
31
+ compact: {
32
+ fontSize: string;
33
+ h: string;
34
+ px: string;
35
+ fontWeight: string;
36
+ };
25
37
  };
26
38
  variants: {
39
+ filled: ({ colorScheme: cs }: {
40
+ colorScheme?: string;
41
+ }) => {
42
+ color: string;
43
+ bg: string;
44
+ borderRadius: string;
45
+ boxShadow: string;
46
+ textTransform: string;
47
+ _hover: {
48
+ boxShadow: string;
49
+ };
50
+ _active: {
51
+ bg: string;
52
+ boxShadow: string;
53
+ };
54
+ _focusVisible: {
55
+ boxShadow: string;
56
+ };
57
+ _disabled: {
58
+ filter: string;
59
+ opacity: number;
60
+ pointerEvents: string;
61
+ };
62
+ };
63
+ outline: ({ colorScheme: cs }: {
64
+ colorScheme?: string;
65
+ }) => {
66
+ color: string;
67
+ bg: string;
68
+ border: string;
69
+ borderColor: string;
70
+ borderRadius: string;
71
+ textTransform: string;
72
+ _hover: {
73
+ bg: string;
74
+ };
75
+ _active: {
76
+ bg: string;
77
+ };
78
+ _focusVisible: {
79
+ boxShadow: string;
80
+ };
81
+ _disabled: {
82
+ filter: string;
83
+ opacity: number;
84
+ pointerEvents: string;
85
+ };
86
+ };
87
+ ghost: ({ colorScheme: cs }: {
88
+ colorScheme?: string;
89
+ }) => {
90
+ color: string;
91
+ bg: string;
92
+ boxShadow: string;
93
+ textTransform: string;
94
+ _hover: {
95
+ color: string;
96
+ bg: string;
97
+ };
98
+ _active: {
99
+ color: string;
100
+ };
101
+ _focusVisible: {
102
+ boxShadow: string;
103
+ };
104
+ _disabled: {
105
+ filter: string;
106
+ pointerEvents: string;
107
+ };
108
+ };
27
109
  primary: {
28
110
  fontSize: string;
29
111
  textTransform: string;
@@ -22,13 +22,69 @@ export const Button = {
22
22
  onboarding: {
23
23
  w: ['button.full', 'button.md'],
24
24
  },
25
+ // ── Canonical sizes (the atom): 2 real heights, type-only. Width is a
26
+ // separate axis set via style props (w / isFullWidth), never baked here.
27
+ standard: { fontSize: 'md', h: 'button.xs', px: '1.4rem', fontWeight: 'medium' }, // 3.4rem
28
+ compact: { fontSize: 'sm', h: 'button.xxs', px: '1rem', fontWeight: 'medium' }, // 2.5rem
25
29
  },
26
30
  variants: {
31
+ // ── Canonical templates (the atom) ──────────────────────────────
32
+ // 3 templates × colorScheme (brand default; success on demand). Old
33
+ // variants below are deprecated aliases kept for the compat shim —
34
+ // migrate internal usages to these + props, then delete each.
35
+ filled: ({ colorScheme: cs }) => {
36
+ const c = cs === 'success'
37
+ ? { base: 'status.success', active: 'green.5' }
38
+ : { base: 'brand.primary', active: 'brand.primaryHover' };
39
+ return {
40
+ color: 'text.inverse',
41
+ bg: c.base,
42
+ borderRadius: 'none',
43
+ boxShadow: 'base',
44
+ textTransform: 'none',
45
+ _hover: { boxShadow: 'xl' },
46
+ _active: { bg: c.active, boxShadow: 'none' },
47
+ _focusVisible: { boxShadow: 'focusRing' },
48
+ _disabled: { filter: 'grayscale(100%)', opacity: 1, pointerEvents: 'none' },
49
+ };
50
+ },
51
+ outline: ({ colorScheme: cs }) => {
52
+ const c = cs === 'success'
53
+ ? { edge: 'status.success', tint: 'lightGreen.3', press: 'lightGreen.4' }
54
+ : { edge: 'brand.primary', tint: 'lightBlue.3', press: 'lightBlue.5' };
55
+ return {
56
+ color: c.edge,
57
+ bg: 'transparent',
58
+ border: '1px solid',
59
+ borderColor: c.edge,
60
+ borderRadius: 'none',
61
+ textTransform: 'none',
62
+ _hover: { bg: c.tint },
63
+ _active: { bg: c.press },
64
+ _focusVisible: { boxShadow: 'focusRing' },
65
+ _disabled: { filter: 'grayscale(100%)', opacity: 1, pointerEvents: 'none' },
66
+ };
67
+ },
68
+ ghost: ({ colorScheme: cs }) => {
69
+ const c = cs === 'success'
70
+ ? { text: 'status.success', hover: 'green.5' }
71
+ : { text: 'brand.primary', hover: 'brand.primaryHover' };
72
+ return {
73
+ color: c.text,
74
+ bg: 'transparent',
75
+ boxShadow: 'none',
76
+ textTransform: 'none',
77
+ _hover: { color: c.hover, bg: 'transparent' },
78
+ _active: { color: c.hover },
79
+ _focusVisible: { boxShadow: 'focusRing' },
80
+ _disabled: { filter: 'grayscale(100%)', pointerEvents: 'none' },
81
+ };
82
+ },
27
83
  primary: {
28
84
  fontSize: 'md',
29
- textTransform: 'uppercase',
85
+ textTransform: 'none',
30
86
  h: 'button.xs',
31
- fontWeight: 'regular',
87
+ fontWeight: 'medium',
32
88
  borderRadius: 'none',
33
89
  boxShadow: 'base',
34
90
  color: 'neutral.white',
@@ -49,10 +105,10 @@ export const Button = {
49
105
  },
50
106
  secondary: {
51
107
  fontSize: 'md',
52
- textTransform: 'uppercase',
108
+ textTransform: 'none',
53
109
  h: 'button.xs',
54
110
  px: '1rem',
55
- fontWeight: 'regular',
111
+ fontWeight: 'medium',
56
112
  borderRadius: 'none',
57
113
  boxShadow: 'base',
58
114
  backgroundColor: 'transparent',
@@ -66,7 +122,7 @@ export const Button = {
66
122
  boxShadow: 'none',
67
123
  },
68
124
  _hover: {
69
- backgroundColor: 'lightGreen.3',
125
+ backgroundColor: 'lightBlue.3',
70
126
  boxShadow: 'lg',
71
127
  },
72
128
  _disabled: {
@@ -236,7 +292,7 @@ export const Button = {
236
292
  },
237
293
  rightRounded: {
238
294
  fontSize: 'md',
239
- textTransform: 'uppercase',
295
+ textTransform: 'none',
240
296
  h: 'button.xs',
241
297
  pr: '3rem',
242
298
  fontWeight: 'semibold',
@@ -259,7 +315,7 @@ export const Button = {
259
315
  },
260
316
  menu: {
261
317
  fontSize: 'md',
262
- textTransform: 'uppercase',
318
+ textTransform: 'none',
263
319
  h: 'button.xs',
264
320
  fontWeight: 'regular',
265
321
  borderRadius: 'none',
@@ -285,7 +341,7 @@ export const Button = {
285
341
  maxW: 'fit-content',
286
342
  px: '1rem',
287
343
  fontSize: 'md',
288
- textTransform: 'uppercase',
344
+ textTransform: 'none',
289
345
  h: 'button.xxs',
290
346
  fontWeight: 'semibold',
291
347
  borderRadius: 'none',
@@ -25,7 +25,7 @@ export const Input = {
25
25
  borderColor: 'lightBlue.6',
26
26
  borderRadius: 'sm',
27
27
  _placeholder: {
28
- color: 'gray.3',
28
+ color: 'text.secondary', // #818181 — single source; consistent across all inputs
29
29
  },
30
30
  _hover: {
31
31
  borderColor: 'blue.1',
@@ -44,7 +44,7 @@ export const Input = {
44
44
  },
45
45
  },
46
46
  _invalid: {
47
- borderColor: 'pink.500',
47
+ borderColor: 'status.error',
48
48
  },
49
49
  },
50
50
  },
@@ -72,7 +72,7 @@ export const Input = {
72
72
  },
73
73
  },
74
74
  _invalid: {
75
- borderColor: 'pink.500',
75
+ borderColor: 'status.error',
76
76
  },
77
77
  },
78
78
  },
@@ -44,7 +44,7 @@ export const Select = {
44
44
  },
45
45
  },
46
46
  _invalid: {
47
- borderColor: 'pink.500',
47
+ borderColor: 'status.error',
48
48
  },
49
49
  },
50
50
  },
@@ -28,7 +28,7 @@ export const Textarea = {
28
28
  },
29
29
  },
30
30
  _invalid: {
31
- borderColor: 'pink.500',
31
+ borderColor: 'status.error',
32
32
  },
33
33
  },
34
34
  },
@@ -53,7 +53,7 @@ export const Text = {
53
53
  error: {
54
54
  fontSize: 'xs',
55
55
  fontFamily: 'secondary',
56
- color: 'pink.500',
56
+ color: 'status.error',
57
57
  lineHeight: '13px',
58
58
  },
59
59
  hint: {
@@ -37,6 +37,58 @@ const theme = extendTheme({
37
37
  Textarea,
38
38
  },
39
39
  colors,
40
+ // Semantic token layer: purpose-named roles that ALIAS existing palette
41
+ // tokens (no new colors). Light-only ({ default }); dark can bolt on later.
42
+ // Consume as color props, e.g. bg="bg.canvas", color="text.heading".
43
+ semanticTokens: {
44
+ colors: {
45
+ brand: {
46
+ primary: { default: 'blue.2' }, // #2488C6
47
+ primaryHover: { default: 'blue.3' }, // #1D7AB4
48
+ primaryActive: { default: 'blue.8' }, // #1E6388
49
+ logo: { default: 'green.brand' }, // #6ab528 — logo house mark
50
+ },
51
+ bg: {
52
+ canvas: { default: 'lightBlue.4' }, // #DCE8EC — app body (matches global)
53
+ surface: { default: 'lightBlue.1' }, // #F4F7F8 — cards / tiles
54
+ elevated: { default: 'neutral.white' }, // #FFFFFF
55
+ subtle: { default: 'lightBlue.2' }, // #E9EDEF
56
+ hover: { default: 'lightGreen.1' }, // #F0F8F0 — row hover
57
+ overlay: { default: 'overlay' }, // #00000040 — modal scrim
58
+ },
59
+ text: {
60
+ heading: { default: 'gray.4' }, // #37323E
61
+ body: { default: 'gray.3' }, // #464646
62
+ secondary: { default: 'gray.2' }, // #818181
63
+ muted: { default: 'gray.1' }, // #ADB6BA
64
+ inverse: { default: 'neutral.white' }, // on dark/brand fills
65
+ },
66
+ border: {
67
+ default: { default: 'lightBlue.6' }, // #BCD1D8
68
+ subtle: { default: 'lightGray.1' }, // #E2EBEF
69
+ focus: { default: 'blue.2' }, // #2488C6
70
+ },
71
+ link: {
72
+ default: { default: 'blue.2' }, // #2488C6 — matches global `a`
73
+ hover: { default: 'blue.3' }, // #1D7AB4
74
+ },
75
+ status: {
76
+ success: { default: 'green.1' }, // #519F0E
77
+ successBg: { default: 'lightGreen.1' }, // #F0F8F0
78
+ error: { default: 'error.3' }, // #D53F8C — deliberately magenta, NOT red (red is used elsewhere in-app)
79
+ errorBg: { default: 'error.1' }, // #f5ddf4
80
+ warning: { default: 'orange.2' }, // #EA6808
81
+ warningBg: { default: 'orange.1' }, // #FDEDE2
82
+ info: { default: 'blue.2' }, // #2488C6
83
+ infoBg: { default: 'lightBlue.3' }, // #DFEFF8
84
+ caution: { default: 'yellow.1' }, // #DEB841
85
+ },
86
+ accent: {
87
+ homi: { default: 'teal.3' }, // #13C0B7 — Homi voice / special moments
88
+ violet: { default: 'violet.1' }, // #6041B7 — insight/accent
89
+ },
90
+ },
91
+ },
40
92
  fonts: {
41
93
  primary: 'Poppins',
42
94
  secondary: 'Assistant',
@@ -134,5 +186,23 @@ const theme = extendTheme({
134
186
  wMd: '22rem',
135
187
  },
136
188
  },
189
+ // Corner-radius scale — declared at Chakra defaults so existing
190
+ // borderRadius="sm|base|md|lg|xl" render identically; makes the scale ours (SSOT).
191
+ radii: {
192
+ none: '0',
193
+ sm: '0.125rem', // 2px
194
+ base: '0.25rem', // 4px
195
+ md: '0.375rem', // 6px
196
+ lg: '0.5rem', // 8px
197
+ xl: '0.75rem', // 12px
198
+ '2xl': '1rem', // 16px
199
+ full: '9999px',
200
+ },
201
+ // NEW, non-colliding shadow keys only — does NOT redefine Chakra's numeric
202
+ // keys, so existing boxShadow="base|lg|2xl" keep their current values.
203
+ shadows: {
204
+ focusRing: '0 0 0 2px #2488C6', // = blue.2 (v2 shadow strings can't ref color tokens)
205
+ cardHover: '0 4px 12px rgba(36,136,198,0.15)', // brand-tinted
206
+ },
137
207
  });
138
208
  export default theme;
@@ -22,7 +22,7 @@ export const isValidEmail = (email = '') => {
22
22
  return true;
23
23
  };
24
24
  export const isValidPassword = (password = '') => {
25
- return password.trim().length >= 6;
25
+ return password.trim().length >= 8;
26
26
  };
27
27
  export const passwordMatch = (password = '', confirmPassword = '') => {
28
28
  return password.trim() === confirmPassword.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homefile/components-v2",
3
- "version": "2.52.3",
3
+ "version": "2.53.1",
4
4
  "author": "Homefile",
5
5
  "license": "UNLICENSED",
6
6
  "typings": "dist/index.d.ts",