@truworth/twc-auth 2.0.0 → 3.0.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 (38) hide show
  1. package/build/src/components/ConfirmationModal/index.js +2 -2
  2. package/build/src/constants/password-requirements.js +7 -0
  3. package/build/src/index.js +4 -0
  4. package/build/src/screens/EnterMobile/components/ExistingAccountsSheet/index.js +1 -1
  5. package/build/src/screens/Profile/components/EditMobileNumber/index.js +31 -0
  6. package/build/src/screens/Profile/components/EditMobileNumber/index.native.js +8 -0
  7. package/build/src/screens/Profile/components/EditPassword/index.js +29 -0
  8. package/build/src/screens/Profile/components/EditPassword/index.native.js +8 -0
  9. package/build/src/screens/Profile/components/EditProfile/index.js +89 -0
  10. package/build/src/screens/Profile/components/EditProfile/index.native.js +8 -0
  11. package/build/src/screens/Profile/components/OtpVerificationModal/index.js +61 -0
  12. package/build/src/screens/Profile/components/OtpVerificationModal/index.native.js +7 -0
  13. package/build/src/screens/Profile/components/ProfileWebComponent/index.js +80 -0
  14. package/build/src/screens/Profile/components/ProfileWebComponent/index.native.js +9 -0
  15. package/build/src/screens/Profile/hooks/internal/useProfile.js +185 -0
  16. package/build/src/screens/Profile/index.js +6 -0
  17. package/build/src/screens/Profile/index.native.js +6 -0
  18. package/build/src/screens/Profile/types.js +1 -0
  19. package/build/src/screens/Welcome/index.js +1 -1
  20. package/build/types/components/ConfirmationModal/index.d.ts +1 -1
  21. package/build/types/components/ConfirmationModal/types.d.ts +3 -0
  22. package/build/types/constants/password-requirements.d.ts +4 -0
  23. package/build/types/index.d.ts +2 -0
  24. package/build/types/screens/Profile/components/EditMobileNumber/index.d.ts +6 -0
  25. package/build/types/screens/Profile/components/EditMobileNumber/index.native.d.ts +3 -0
  26. package/build/types/screens/Profile/components/EditPassword/index.d.ts +6 -0
  27. package/build/types/screens/Profile/components/EditPassword/index.native.d.ts +3 -0
  28. package/build/types/screens/Profile/components/EditProfile/index.d.ts +6 -0
  29. package/build/types/screens/Profile/components/EditProfile/index.native.d.ts +3 -0
  30. package/build/types/screens/Profile/components/OtpVerificationModal/index.d.ts +4 -0
  31. package/build/types/screens/Profile/components/OtpVerificationModal/index.native.d.ts +3 -0
  32. package/build/types/screens/Profile/components/ProfileWebComponent/index.d.ts +4 -0
  33. package/build/types/screens/Profile/components/ProfileWebComponent/index.native.d.ts +3 -0
  34. package/build/types/screens/Profile/hooks/internal/useProfile.d.ts +26 -0
  35. package/build/types/screens/Profile/index.d.ts +7 -0
  36. package/build/types/screens/Profile/index.native.d.ts +7 -0
  37. package/build/types/screens/Profile/types.d.ts +53 -0
  38. package/package.json +2 -2
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IonIcon } from '../../components/IonIcon';
3
3
  import { Button, Flex, ResponsiveModal, Typography } from '@truworth/twc-web-design';
4
- const ConfirmationModal = ({ title, visible, onClose, onProceed, onDismiss, primaryLabel, secondaryLabel, iconName = "information-circle-outline", iconColor = "hsl(var(--tw-ui-utility---warning--main))", description }) => {
5
- return (_jsx(ResponsiveModal, { title: "", open: visible, onClose: onClose, footer: null, showCloseButton: false, className: 'py-0', children: _jsxs(Flex, { direction: "column", align: "center", justify: "center", children: [_jsxs(Flex, { direction: "column", align: "center", justify: "center", children: [_jsx(IonIcon, { name: iconName, style: { color: iconColor, fontSize: 96 } }), title &&
4
+ const ConfirmationModal = ({ title, visible, onClose, onProceed, onDismiss, primaryLabel, secondaryLabel, iconName = "information-circle-outline", iconClassName = "text-utility-warning-main", description }) => {
5
+ return (_jsx(ResponsiveModal, { title: "", open: visible, onClose: onClose, footer: null, showCloseButton: false, className: 'py-0', children: _jsxs(Flex, { direction: "column", align: "center", justify: "center", children: [_jsxs(Flex, { direction: "column", align: "center", justify: "center", children: [_jsx(IonIcon, { name: iconName, className: `text-[96px] ${iconClassName}` }), title &&
6
6
  _jsx(Typography, { type: "heading", size: "h6", className: "mt-2 text-center", children: title }), description &&
7
7
  _jsx(Typography, { type: "utility", size: "medium", color: 'gray-600', className: "mt-2 mb-6 text-center", children: description })] }), primaryLabel &&
8
8
  _jsx(Button, { onClick: () => onProceed?.(), label: primaryLabel, className: 'w-full my-3' }), secondaryLabel &&
@@ -0,0 +1,7 @@
1
+ export const strongPasswordRequirements = [
2
+ { regex: /.{6,}/, text: "At least 6 characters" },
3
+ { regex: /[0-9]/, text: "At least 1 number" },
4
+ { regex: /[a-z]/, text: "At least 1 lowercase letter" },
5
+ { regex: /[A-Z]/, text: "At least 1 uppercase letter" },
6
+ { regex: /[!@#$%^&*()_+]/, text: "At least 1 special character" }
7
+ ];
@@ -14,3 +14,7 @@ export * from './navigator';
14
14
  export * from './screens/Login/components/LoginWebComponent/index';
15
15
  export * from './screens/SignUp/components/SignUpWebComponent/index';
16
16
  export * from './screens/SSOLogin/SSOCallback/components/SSOCallbackComponent/index';
17
+ // export profile components
18
+ export * from './screens/Profile';
19
+ // export constants
20
+ export { strongPasswordRequirements } from './constants/password-requirements';
@@ -23,7 +23,7 @@ const ExistingAccountsSheet = ({ visible, hide, phone, existingAccounts, onIniti
23
23
  };
24
24
  const displayPhone = phone ? `+91${phone.replace(/^.{8}/, 'XXXXXXXX')}` : '+91**********';
25
25
  return (_jsxs(ResponsiveModal, { open: visible, onClose: hide, onOpenChange: hide, maskClosable: false, title: _jsxs(_Fragment, { children: [_jsx(Typography, { type: "heading", size: "h6", children: "Multiple Accounts Found" }), _jsxs(Typography, { type: "utility", size: "medium", color: "gray-600", className: "mt-2 mb-3", children: ["The phone number ", displayPhone, " is linked to the following accounts."] }), _jsx("hr", {})] }), centered: true, showCloseButton: false, className: "px-0", footer: _jsxs(Flex, { direction: "column", className: 'pt-1', children: [_jsx(Flex, { className: "flex-1 border-t border-gray-300 mb-2" }), selectedAccount &&
26
- _jsxs(Flex, { align: 'center', className: 'bg-utility-warning-bg p-2 rounded-lg gap-1', children: [_jsx(IonIcon, { name: "information-circle-outline", style: { color: "hsl(var(--tw-ui-utility---warning--main))", fontSize: 24 } }), _jsxs(Typography, { type: "utility", size: "small", children: [_jsx("span", { className: "text-gray-500 text-[12px]", children: "Once you proceed, this phone number will be removed" }), " from other accounts."] })] }), _jsx(Button, { isFullWidth: true, loading: loading, variant: "primary", label: "Link and Continue", className: 'mt-4 mb-[-16px]', onClick: onContinue, disabled: !selectedAccount }), _jsx(SupportDetails, {})] }), children: [_jsx(Typography, { type: "utility", size: "medium", className: "mb-4", children: "Select account you want to continue with." }), existingAccounts.map((item) => {
26
+ _jsxs(Flex, { align: 'center', className: 'bg-utility-warning-bg p-2 rounded-lg gap-1', children: [_jsx(IonIcon, { name: "information-circle-outline", className: "text-utility-warning-main text-2xl" }), _jsxs(Typography, { type: "utility", size: "small", children: [_jsx("span", { className: "text-gray-500 text-[12px]", children: "Once you proceed, this phone number will be removed" }), " from other accounts."] })] }), _jsx(Button, { isFullWidth: true, loading: loading, variant: "primary", label: "Link and Continue", className: 'mt-4 mb-[-16px]', onClick: onContinue, disabled: !selectedAccount }), _jsx(SupportDetails, {})] }), children: [_jsx(Typography, { type: "utility", size: "medium", className: "mb-4", children: "Select account you want to continue with." }), existingAccounts.map((item) => {
27
27
  return (_jsx(OptionCard, { item: item, onClick: () => setSelectedAccount(item), selectedItem: selectedAccount }, item.memberId));
28
28
  })] }));
29
29
  };
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useState } from 'react';
3
+ import { Button, TextInput, Typography } from '@truworth/twc-web-design';
4
+ import { Phone, Shield } from 'lucide-react';
5
+ import { useProfile } from '../../hooks/internal/useProfile';
6
+ import { OtpVerificationModal } from '../OtpVerificationModal';
7
+ const EditMobileNumber = ({ onSuccess }) => {
8
+ const { loading, profileData, updateMobileNumber } = useProfile();
9
+ const [showOtpModal, setShowOtpModal] = useState(false);
10
+ const [newPhone, setNewPhone] = useState('');
11
+ const handleSubmit = (e) => {
12
+ e.preventDefault();
13
+ const countryCode = profileData?.countryCode || '91';
14
+ updateMobileNumber({ countryCode, mobileNo: newPhone }, () => setShowOtpModal(true));
15
+ };
16
+ const handlePhoneChange = (e) => {
17
+ const value = e.target.value.replace(/\s+|[^\d]/g, '').trim().slice(0, 10);
18
+ setNewPhone(value);
19
+ };
20
+ const handleOtpSuccess = () => {
21
+ setShowOtpModal(false);
22
+ setNewPhone('');
23
+ onSuccess?.();
24
+ };
25
+ const currentPhone = profileData?.phone || '';
26
+ const maskedCurrentPhone = currentPhone
27
+ ? `+91 ${currentPhone.slice(0, 2)}******${currentPhone.slice(-2)}`
28
+ : 'Not set';
29
+ return (_jsxs(_Fragment, { children: [_jsxs("form", { onSubmit: handleSubmit, children: [_jsxs("div", { className: "mb-8 pb-8 border-b border-gray-100", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Current Mobile Number" }), _jsxs("div", { className: "flex items-center gap-4 p-4 bg-gray-50 rounded-xl border border-gray-100", children: [_jsx("div", { className: "w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center", children: _jsx(Phone, { className: "w-6 h-6 text-primary" }) }), _jsxs("div", { children: [_jsx(Typography, { type: "body", size: "large", className: "text-gray-900 font-medium", children: maskedCurrentPhone }), _jsx(Typography, { type: "body", size: "small", className: "text-gray-500", children: "Verified and active" })] }), _jsxs("div", { className: "ml-auto flex items-center gap-1 text-green-600", children: [_jsx(Shield, { className: "w-4 h-4" }), _jsx("span", { className: "text-sm font-medium", children: "Verified" })] })] })] }), _jsxs("div", { className: "mb-8", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Update Mobile Number" }), _jsx(Typography, { type: "body", size: "medium", className: "text-gray-500 mb-4", children: "Enter your new mobile number. We'll send an OTP to verify it." }), _jsxs("div", { className: "flex gap-4", children: [_jsxs("div", { className: "w-24", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Country" }), _jsx(TextInput, { disabled: true, value: "+91", className: "bg-gray-50 rounded-xl h-12 text-center font-medium" })] }), _jsxs("div", { className: "flex-1", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "New Mobile Number" }), _jsx(TextInput, { value: newPhone, onChange: handlePhoneChange, placeholder: "Enter 10-digit mobile number", className: "rounded-xl h-12", maxLength: 10 })] })] }), newPhone.length > 0 && newPhone.length < 10 && (_jsx(Typography, { type: "body", size: "small", className: "text-amber-600 mt-2", children: "Please enter a valid 10-digit mobile number" }))] }), _jsx("div", { className: "mb-8 p-4 bg-blue-50 rounded-xl border border-blue-100", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center flex-shrink-0", children: _jsx(Shield, { className: "w-4 h-4 text-blue-600" }) }), _jsxs("div", { children: [_jsx(Typography, { type: "body", size: "medium", className: "text-blue-900 font-medium mb-1", children: "Verification Required" }), _jsx(Typography, { type: "body", size: "small", className: "text-blue-700", children: "For your security, we'll send a one-time password (OTP) to your new mobile number to verify the change." })] })] }) }), _jsx("div", { className: "flex justify-end pt-4 border-t border-gray-100", children: _jsx(Button, { type: "submit", loading: loading, disabled: newPhone.length !== 10, className: "px-8", size: "large", children: "Verify & Update" }) })] }), _jsx(OtpVerificationModal, { open: showOtpModal, onOpenChange: setShowOtpModal, phone: newPhone, onSuccess: handleOtpSuccess })] }));
30
+ };
31
+ export { EditMobileNumber };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { View, Text } from 'react-native';
4
+ // Native implementation placeholder
5
+ const EditMobileNumber = () => {
6
+ return (_jsx(View, { children: _jsx(Text, { children: "Edit Mobile Number - Native Implementation" }) }));
7
+ };
8
+ export { EditMobileNumber };
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useState } from 'react';
3
+ import { Button, Col, Form, Row, PasswordInput, Typography, Flex, useForm } from '@truworth/twc-web-design';
4
+ import { Shield, AlertCircle } from 'lucide-react';
5
+ import { useProfile } from '../../hooks/internal/useProfile';
6
+ import { strongPasswordRequirements } from '../../../../constants/password-requirements';
7
+ const EditPassword = ({ onSuccess }) => {
8
+ const { loading, changePassword } = useProfile();
9
+ const [currentValidate, setCurrentValidate] = useState(false);
10
+ const [strongPassword, setStrongPassword] = useState(false);
11
+ const editPasswordForm = useForm();
12
+ const handleSubmit = ({ currentPassword, password, newPassword }) => {
13
+ setCurrentValidate(false);
14
+ changePassword({ currentPassword, newPassword }, () => {
15
+ editPasswordForm.reset();
16
+ setCurrentValidate(false);
17
+ onSuccess?.();
18
+ }, () => {
19
+ setCurrentValidate(true);
20
+ });
21
+ };
22
+ const currentPassword = editPasswordForm.watch('currentPassword');
23
+ const password = editPasswordForm.watch('password');
24
+ const newPassword = editPasswordForm.watch('newPassword');
25
+ const passwordsMatch = password && newPassword && password === newPassword;
26
+ const disabled = currentPassword && strongPassword && passwordsMatch;
27
+ return (_jsxs(Form, { form: editPasswordForm, onSubmit: handleSubmit, children: [_jsxs("div", { className: "mb-8 pb-8 border-b border-gray-100", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Current Password" }), _jsx(Typography, { type: "body", size: "medium", className: "text-gray-500 mb-4", children: "Enter your current password to verify it's you." }), _jsx(Row, { children: _jsx(Col, { md: 16, xs: 24, children: _jsx(Form.Item, { name: "currentPassword", normalize: (value) => value.replace(/\s/g, ''), children: _jsx(PasswordInput, { showStrengthIndicator: false, placeholder: "Enter your current password", minLength: 3, className: "rounded-xl" }) }) }) }), currentValidate && (_jsxs(Flex, { align: "center", className: "gap-2 p-3 mt-2 bg-red-50 rounded-xl border border-red-100", children: [_jsx(AlertCircle, { size: 16, className: "text-red-600 flex-shrink-0" }), _jsx(Typography, { type: "body", size: "small", className: "text-red-700", children: "The password you entered doesn't match your current password." })] }))] }), _jsxs("div", { className: "mb-8 pb-8 border-b border-gray-100", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Create New Password" }), _jsx(Typography, { type: "body", size: "medium", className: "text-gray-500 mb-4", children: "Choose a strong password that you don't use for other accounts." }), _jsxs(Row, { gutter: 20, children: [_jsx(Col, { md: 12, xs: 24, children: _jsx(Form.Item, { name: "password", label: "New Password", normalize: (value) => value.replace(/\s/g, ''), children: _jsx(PasswordInput, { requirements: strongPasswordRequirements, showStrengthIndicator: true, placeholder: "Create a new password", minLength: 3, className: "rounded-xl", onAllRequirementsMet: (value) => setStrongPassword(value) }) }) }), _jsxs(Col, { md: 12, xs: 24, children: [_jsx(Form.Item, { name: "newPassword", label: "Confirm New Password", normalize: (value) => value.replace(/\s/g, ''), children: _jsx(PasswordInput, { minLength: 3, placeholder: "Re-enter your new password", showStrengthIndicator: false, className: "rounded-xl" }) }), password && newPassword && !passwordsMatch && (_jsx(Typography, { type: "body", size: "small", className: "text-red-600 mt-1", children: "Passwords don't match" })), passwordsMatch && (_jsx(Typography, { type: "body", size: "small", className: "text-green-600 mt-1", children: "\u2713 Passwords match" }))] })] })] }), _jsx("div", { className: "mb-8 p-4 bg-primary/5 rounded-xl border border-primary/10", children: _jsxs(Flex, { align: "start", className: "gap-3", children: [_jsx("div", { className: "w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0", children: _jsx(Shield, { className: "w-4 h-4 text-primary" }) }), _jsxs("div", { children: [_jsx(Typography, { type: "body", size: "medium", className: "text-gray-900 font-medium mb-1", children: "Password Security Tips" }), _jsxs("ul", { className: "text-sm text-gray-600 space-y-1", children: [_jsx("li", { children: "\u2022 Use a mix of letters, numbers, and symbols" }), _jsx("li", { children: "\u2022 Avoid using personal information" }), _jsx("li", { children: "\u2022 Don't reuse passwords from other accounts" })] })] })] }) }), _jsx(Flex, { justify: "end", className: "pt-4 border-t border-gray-100", children: _jsx(Button, { loading: loading, type: "submit", disabled: !disabled, className: "px-8", size: "large", children: "Update Password" }) })] }));
28
+ };
29
+ export { EditPassword };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { View, Text } from 'react-native';
4
+ // Native implementation placeholder
5
+ const EditPassword = () => {
6
+ return (_jsx(View, { children: _jsx(Text, { children: "Edit Password - Native Implementation" }) }));
7
+ };
8
+ export { EditPassword };
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useState } from 'react';
3
+ import { Button, DatePicker, TextInput, TextArea, UploadButton, Col, Form, Row, RadioGroup, Flex, Typography, showNotification, useForm } from '@truworth/twc-web-design';
4
+ import { Camera, Info, UserRound } from 'lucide-react';
5
+ import { useProfile } from '../../hooks/internal/useProfile';
6
+ import { MEMBER_IMAGES_URL } from '../../../../constants/cdn-url';
7
+ const EditProfile = ({ onSuccess }) => {
8
+ const { loading, profileData, imageUrl, setImageUrl, updateProfile, uploadProfileImage } = useProfile();
9
+ const [image, setImage] = useState(null);
10
+ const editProfileForm = useForm({
11
+ values: {
12
+ firstName: profileData?.firstName || '',
13
+ lastName: profileData?.lastName || '',
14
+ bio: profileData?.bio || '',
15
+ gender: profileData?.gender || '',
16
+ dateOfBirth: profileData?.dateOfBirth instanceof Date ? profileData.dateOfBirth : undefined
17
+ },
18
+ liveValidation: true
19
+ });
20
+ const handleSubmit = async ({ firstName, lastName, bio }) => {
21
+ const payload = {
22
+ firstName,
23
+ lastName
24
+ };
25
+ if (bio)
26
+ payload.bio = bio;
27
+ if (image) {
28
+ const uploadedFileName = await uploadProfileImage(image);
29
+ if (uploadedFileName) {
30
+ payload.image = uploadedFileName;
31
+ }
32
+ }
33
+ await updateProfile(payload, () => {
34
+ onSuccess?.();
35
+ localStorage.setItem('profileData', JSON.stringify({
36
+ firstName: payload.firstName,
37
+ lastName: payload.lastName,
38
+ image: payload.image ? `${MEMBER_IMAGES_URL}${payload.image}` : imageUrl
39
+ }));
40
+ });
41
+ };
42
+ const beforeUpload = async (files) => {
43
+ const file = files[0];
44
+ if (!file)
45
+ return false;
46
+ const validTypes = ['image/png', 'image/jpg', 'image/jpeg'];
47
+ const isValidType = validTypes.includes(file.type);
48
+ const isLt5M = file.size / 1024 / 1024 < 5;
49
+ if (!isValidType) {
50
+ showNotification({
51
+ type: 'error',
52
+ description: `${file.name} is not a valid image file`,
53
+ showIcon: false
54
+ });
55
+ return false;
56
+ }
57
+ if (!isLt5M) {
58
+ showNotification({
59
+ type: 'error',
60
+ description: 'Image must be smaller than 5 MB',
61
+ showIcon: false
62
+ });
63
+ return false;
64
+ }
65
+ const reader = new FileReader();
66
+ reader.onload = () => setImageUrl(reader.result);
67
+ reader.readAsDataURL(file);
68
+ setImage(file);
69
+ return false;
70
+ };
71
+ useEffect(() => {
72
+ window.scrollTo({ top: 0 });
73
+ }, []);
74
+ const firstName = editProfileForm.watch('firstName');
75
+ const lastName = editProfileForm.watch('lastName');
76
+ const bio = editProfileForm.watch('bio');
77
+ const isDisabled = firstName?.length > 2 && lastName?.length > 2;
78
+ return (_jsxs(Form, { form: editProfileForm, onSubmit: handleSubmit, children: [_jsxs("div", { className: "mb-8 pb-8 border-b border-gray-100", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Profile Picture" }), _jsxs(Flex, { align: "center", className: "gap-6", children: [_jsxs("div", { className: "relative group", children: [_jsx("div", { className: "w-24 h-24 rounded-2xl bg-gray-100 flex items-center justify-center overflow-hidden border-2 border-gray-200", children: !imageUrl ? (_jsx(UserRound, { size: 40, className: "text-gray-400" })) : (_jsx("img", { src: imageUrl, alt: "Profile", className: "w-full h-full object-cover" })) }), _jsx("div", { className: "absolute inset-0 rounded-2xl bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center", children: _jsx(UploadButton, { files: [], accept: ".png,.jpg,.jpeg", leftIcon: _jsx(Camera, { size: 24, className: "text-white" }), size: "small", buttonStyle: "border-0 p-0 bg-transparent hover:bg-transparent", onUpload: (files) => beforeUpload(files), showAttachments: false }) })] }), _jsxs("div", { children: [_jsx(Typography, { type: "body", size: "medium", className: "text-gray-900 font-medium", children: "Upload a new photo" }), _jsx(Typography, { type: "body", size: "small", className: "text-gray-500 mt-1", children: "JPG, PNG or JPEG. Max size 5MB" }), _jsx(UploadButton, { files: [], accept: ".png,.jpg,.jpeg", label: "Choose File", size: "small", buttonStyle: "mt-3 border-gray-300 text-gray-700 hover:bg-gray-50", onUpload: (files) => beforeUpload(files), showAttachments: false })] })] })] }), _jsxs("div", { className: "mb-8 pb-8 border-b border-gray-100", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Personal Information" }), _jsxs(Row, { gutter: 20, children: [_jsx(Col, { md: 12, xs: 24, children: _jsx(Form.Item, { label: "First Name", name: "firstName", rules: [{
79
+ required: true,
80
+ message: 'Enter at least 3 letters.'
81
+ }], children: _jsx(TextInput, { placeholder: "Enter first name", className: "rounded-xl h-12" }) }) }), _jsx(Col, { md: 12, xs: 24, children: _jsx(Form.Item, { label: "Last Name", name: "lastName", rules: [{
82
+ required: true,
83
+ message: 'Enter at least 3 letters.'
84
+ }], children: _jsx(TextInput, { placeholder: "Enter last name", className: "rounded-xl h-12" }) }) })] })] }), _jsxs("div", { className: "mb-8 pb-8 border-b border-gray-100", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "Additional Details" }), _jsxs(Row, { gutter: 20, children: [_jsx(Col, { md: 12, xs: 24, children: _jsx(Form.Item, { label: _jsx("span", { className: "text-gray-500", children: "Gender" }), name: "gender", children: _jsx(RadioGroup, { disabled: true, orientation: "horizontal", options: [
85
+ { label: 'Male', value: 'M' },
86
+ { label: 'Female', value: 'F' }
87
+ ] }) }) }), _jsx(Col, { md: 12, xs: 24, children: _jsx(Form.Item, { label: _jsx("span", { className: "text-gray-500", children: "Date of Birth" }), name: "dateOfBirth", children: _jsx(DatePicker, { disabled: true, format: "dd/MM/yyyy", iconPosition: "right", className: "!bg-gray-50 rounded-xl h-12" }) }) })] }), _jsxs(Flex, { align: "center", className: "gap-2 p-3 bg-amber-50 rounded-xl border border-amber-100", children: [_jsx(Info, { size: 16, className: "text-amber-600 flex-shrink-0" }), _jsx(Typography, { type: "body", size: "small", className: "text-amber-800", children: "Please contact support to update Gender or Date of Birth." })] })] }), _jsxs("div", { className: "mb-8", children: [_jsx(Typography, { type: "utility", size: "large", className: "text-gray-700 font-medium mb-4", children: "About You" }), _jsx(Form.Item, { label: "Bio", name: "bio", children: _jsx(TextArea, { maxCount: 150, placeholder: "Tell us a bit about yourself..." }) })] }), _jsx(Flex, { justify: "end", className: "pt-4 border-t border-gray-100", children: _jsx(Button, { type: "submit", loading: loading, disabled: !isDisabled, className: "px-8", size: "large", children: "Save Changes" }) })] }));
88
+ };
89
+ export { EditProfile };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { View, Text } from 'react-native';
4
+ // Native implementation placeholder
5
+ const EditProfile = () => {
6
+ return (_jsx(View, { children: _jsx(Text, { children: "Edit Profile - Native Implementation" }) }));
7
+ };
8
+ export { EditProfile };
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useState, useCallback } from 'react';
3
+ import { Button, Typography, ResponsiveModal, OTPInput } from '@truworth/twc-web-design';
4
+ import { useProfile } from '../../hooks/internal/useProfile';
5
+ const OtpVerificationModal = ({ open, onOpenChange, phone, onSuccess, successMessage = 'Mobile number updated', title = 'Confirm Mobile Number' }) => {
6
+ const { loading, sendMobileOtp, verifyMobileOtp } = useProfile();
7
+ const [otp, setOtp] = useState('');
8
+ const [otpError, setOtpError] = useState(false);
9
+ const [resendTimer, setResendTimer] = useState(180);
10
+ const [timeLeft, setTimeLeft] = useState('03:00');
11
+ // Timer countdown
12
+ useEffect(() => {
13
+ if (!resendTimer)
14
+ return;
15
+ const intervalId = setInterval(() => {
16
+ const minute = Math.floor(resendTimer / 60);
17
+ const seconds = resendTimer % 60;
18
+ const formattedTime = `${minute < 10 ? '0' : ''}${minute}:${seconds < 10 ? '0' : ''}${seconds}`;
19
+ setTimeLeft(formattedTime);
20
+ setResendTimer(prev => prev - 1);
21
+ }, 1000);
22
+ return () => clearInterval(intervalId);
23
+ }, [resendTimer]);
24
+ // Send OTP when modal opens
25
+ useEffect(() => {
26
+ if (open) {
27
+ sendMobileOtp();
28
+ setResendTimer(180);
29
+ setOtp('');
30
+ setOtpError(false);
31
+ }
32
+ // eslint-disable-next-line react-hooks/exhaustive-deps
33
+ }, [open]);
34
+ const handleChange = useCallback((code) => {
35
+ setOtp(code);
36
+ setOtpError(false);
37
+ }, []);
38
+ const handleVerify = useCallback(() => {
39
+ setOtpError(false);
40
+ verifyMobileOtp(otp, () => {
41
+ onOpenChange(false);
42
+ onSuccess?.();
43
+ }, () => {
44
+ setOtpError(true);
45
+ });
46
+ }, [otp, verifyMobileOtp, onOpenChange, onSuccess]);
47
+ const handleResend = useCallback(() => {
48
+ setResendTimer(180);
49
+ setOtpError(false);
50
+ setOtp('');
51
+ sendMobileOtp();
52
+ }, [sendMobileOtp]);
53
+ const handleClose = useCallback(() => {
54
+ setOtp('');
55
+ setOtpError(false);
56
+ onOpenChange(false);
57
+ }, [onOpenChange]);
58
+ const maskedPhone = phone ? `XXXXXXX${phone.slice(-3)}` : '';
59
+ return (_jsxs(ResponsiveModal, { size: "sm", title: _jsx(Typography, { type: "heading", size: "h6", children: title }), footer: null, open: open, showCloseButton: false, onOpenChange: handleClose, children: [_jsxs(Typography, { type: "body", size: "large", className: "mb-7 text-gray-400 text-center", children: ["OTP sent to", _jsx("span", { className: "pl-2 text-secondary-dark", children: maskedPhone })] }), _jsx(OTPInput, { className: "!w-full", onChange: handleChange, error: otpError, maxLength: 6, value: otp }), otpError && (_jsx(Typography, { type: "body", size: "medium", className: "text-utility-danger-main mt-2", children: "Please enter the correct OTP." })), resendTimer < 60 ? (_jsxs("div", { className: "flex justify-between items-center my-7", children: [_jsx(Typography, { type: "utility", size: "medium", className: "text-gray-400", children: "Didn't receive OTP?" }), _jsx(Button, { size: "small", variant: "link", onClick: handleResend, children: "Resend Code" })] })) : (_jsxs(Typography, { type: "utility", size: "medium", className: "my-7 text-gray-400", children: ["Resend Code in ", timeLeft] })), _jsx(Button, { isFullWidth: true, onClick: handleVerify, loading: loading, disabled: otp.length < 6, children: "Continue" })] }));
60
+ };
61
+ export { OtpVerificationModal };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ // Native implementation placeholder
3
+ // For mobile, OTP verification would typically use a different flow
4
+ const OtpVerificationModal = () => {
5
+ return null;
6
+ };
7
+ export { OtpVerificationModal };
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useState } from 'react';
3
+ import { AvatarWithInitials, ResponsiveModal, Button, Flex, Typography } from '@truworth/twc-web-design';
4
+ import { Lock, Phone, UserRound, Loader2, ChevronRight } from 'lucide-react';
5
+ import { EditProfile } from '../EditProfile';
6
+ import { EditMobileNumber } from '../EditMobileNumber';
7
+ import { EditPassword } from '../EditPassword';
8
+ import { useProfile } from '../../hooks/internal/useProfile';
9
+ import { useAuthContext } from '../../../../hooks/useAuthContext';
10
+ const tabs = [
11
+ {
12
+ key: 'edit-profile',
13
+ label: 'Edit Profile',
14
+ icon: _jsx(UserRound, { className: "w-5 h-5" })
15
+ },
16
+ {
17
+ key: 'mobile-number',
18
+ label: 'Mobile Number',
19
+ icon: _jsx(Phone, { className: "w-5 h-5" })
20
+ },
21
+ {
22
+ key: 'password',
23
+ label: 'Password',
24
+ icon: _jsx(Lock, { className: "w-5 h-5" })
25
+ }
26
+ ];
27
+ const ProfileWebComponent = ({ onProfileUpdate, onMobileUpdate, onPasswordUpdate, containerClassName = '' }) => {
28
+ const { isLoadingProfile } = useAuthContext();
29
+ const { profile, profileData } = useProfile();
30
+ const [activeTab, setActiveTab] = useState('edit-profile');
31
+ const [showUnsavedModal, setShowUnsavedModal] = useState(false);
32
+ const [pendingTab, setPendingTab] = useState(null);
33
+ const [hasDirtyForm, setHasDirtyForm] = useState(false);
34
+ // Show loading state while profile is being fetched
35
+ if (isLoadingProfile && !profile) {
36
+ return (_jsx("div", { className: containerClassName, children: _jsx(Flex, { justify: "center", align: "center", className: "py-20", children: _jsx(Loader2, { className: "w-8 h-8 animate-spin text-primary" }) }) }));
37
+ }
38
+ const fullName = `${profile?.firstName || ''} ${profile?.lastName || ''}`.trim();
39
+ const handleTabChange = (key) => {
40
+ if (hasDirtyForm) {
41
+ setShowUnsavedModal(true);
42
+ setPendingTab(key);
43
+ }
44
+ else {
45
+ setActiveTab(key);
46
+ }
47
+ };
48
+ const handleConfirmLeave = () => {
49
+ setHasDirtyForm(false);
50
+ if (pendingTab) {
51
+ setActiveTab(pendingTab);
52
+ setPendingTab(null);
53
+ }
54
+ setShowUnsavedModal(false);
55
+ };
56
+ const renderTabContent = () => {
57
+ switch (activeTab) {
58
+ case 'edit-profile':
59
+ return _jsx(EditProfile, { onSuccess: onProfileUpdate });
60
+ case 'mobile-number':
61
+ return _jsx(EditMobileNumber, { onSuccess: onMobileUpdate });
62
+ case 'password':
63
+ return _jsx(EditPassword, { onSuccess: onPasswordUpdate });
64
+ default:
65
+ return null;
66
+ }
67
+ };
68
+ return (_jsxs("div", { className: `max-w-5xl mx-auto ${containerClassName}`, children: [_jsxs("div", { className: "mb-6", children: [_jsx(Typography, { type: "heading", size: "h4", className: "text-gray-900", children: "Account Settings" }), _jsx(Typography, { type: "body", size: "medium", className: "text-gray-500 mt-1", children: "Manage your profile and preferences" })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-6", children: [_jsxs("div", { className: "lg:col-span-4 space-y-4", children: [_jsx("div", { className: "bg-white rounded-2xl border border-gray-100 p-5 shadow-sm", children: _jsxs(Flex, { align: "center", className: "gap-4", children: [_jsxs("div", { className: "relative", children: [_jsx(AvatarWithInitials, { size: "large", name: fullName || 'User', url: profileData?.image, className: "w-16 h-16 text-xl" }), _jsx("div", { className: "absolute -bottom-1 -right-1 w-5 h-5 bg-green-500 rounded-full border-2 border-white" })] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx(Typography, { type: "body", size: "large", className: "font-semibold text-gray-900 truncate", children: fullName || 'Welcome' }), _jsx(Typography, { type: "body", size: "small", className: "text-gray-500 truncate", children: profile?.email })] })] }) }), _jsx("div", { className: "bg-white rounded-2xl border border-gray-100 overflow-hidden shadow-sm", children: _jsx("nav", { className: "p-2", children: tabs.map((tab) => (_jsxs("button", { onClick: () => handleTabChange(tab.key), className: `
69
+ w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left transition-all duration-200
70
+ ${activeTab === tab.key
71
+ ? 'bg-primary/10'
72
+ : 'hover:bg-gray-50'}
73
+ `, children: [_jsx("div", { className: `
74
+ w-9 h-9 rounded-lg flex items-center justify-center transition-colors
75
+ ${activeTab === tab.key
76
+ ? 'bg-primary text-white'
77
+ : 'bg-gray-100 text-gray-500'}
78
+ `, children: tab.icon }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx(Typography, { type: "body", size: "medium", className: `font-medium ${activeTab === tab.key ? 'text-primary' : 'text-gray-900'}`, children: tab.label }) }), _jsx(ChevronRight, { className: `w-4 h-4 transition-colors ${activeTab === tab.key ? 'text-primary' : 'text-gray-300'}` })] }, tab.key))) }) })] }), _jsx("div", { className: "lg:col-span-8", children: _jsx("div", { className: "bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden", children: _jsx("div", { className: "p-6 lg:p-8", children: renderTabContent() }) }) })] }), _jsx(ResponsiveModal, { title: "", size: "sm", open: showUnsavedModal, footer: null, showCloseButton: false, onClose: () => setShowUnsavedModal(false), onOpenChange: () => setShowUnsavedModal(false), children: _jsxs(Flex, { direction: "column", align: "center", className: "py-6", children: [_jsx("div", { className: "w-16 h-16 rounded-full bg-amber-100 flex items-center justify-center mb-4", children: _jsx("svg", { className: "w-8 h-8 text-amber-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }) }), _jsx(Typography, { type: "heading", size: "h6", className: "mb-2 text-gray-900", children: "Unsaved Changes" }), _jsx(Typography, { type: "body", size: "medium", className: "text-gray-500 text-center mb-6", children: "You have unsaved changes. Are you sure you want to leave?" }), _jsxs(Flex, { className: "gap-3 w-full", children: [_jsx(Button, { isFullWidth: true, variant: "outline", onClick: () => setShowUnsavedModal(false), children: "Stay" }), _jsx(Button, { isFullWidth: true, onClick: handleConfirmLeave, children: "Leave" })] })] }) })] }));
79
+ };
80
+ export { ProfileWebComponent };
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ // Native implementation - Profile is typically handled differently on mobile
3
+ const ProfileWebComponent = () => {
4
+ if (process.env.NODE_ENV !== 'production') {
5
+ console.warn('[@truworth/twc-auth] ProfileWebComponent is only available for web');
6
+ }
7
+ return null;
8
+ };
9
+ export { ProfileWebComponent };
@@ -0,0 +1,185 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useAuthContext } from '../../../../hooks/useAuthContext';
3
+ import { useRequest } from '../../../../hooks/useRequest';
4
+ import { showMessage } from '../../../../helpers/show-message';
5
+ import { parseISO } from 'date-fns';
6
+ const useProfile = () => {
7
+ const { profile, getUserProfile } = useAuthContext();
8
+ const { makeRequest } = useRequest();
9
+ const [loading, setLoading] = useState(false);
10
+ const [imageUrl, setImageUrl] = useState('');
11
+ const [profileData, setProfileData] = useState({
12
+ firstName: '',
13
+ lastName: '',
14
+ dateOfBirth: new Date(),
15
+ gender: '',
16
+ phone: '',
17
+ bio: '',
18
+ image: '',
19
+ countryCode: '91'
20
+ });
21
+ useEffect(() => {
22
+ if (profile) {
23
+ const dob = profile.dateOfBirth && typeof profile.dateOfBirth === 'string'
24
+ ? parseISO(profile.dateOfBirth)
25
+ : profile.dateOfBirth || new Date();
26
+ setProfileData({
27
+ firstName: profile.firstName || '',
28
+ lastName: profile.lastName || '',
29
+ gender: profile.gender || '',
30
+ dateOfBirth: dob,
31
+ image: profile.image || '',
32
+ phone: profile.phone || '',
33
+ bio: profile.bio || '',
34
+ countryCode: profile.countryCode || '91'
35
+ });
36
+ if (profile.image) {
37
+ setImageUrl(profile.image);
38
+ }
39
+ }
40
+ }, [profile]);
41
+ const updateProfile = async (data, onSuccess) => {
42
+ setLoading(true);
43
+ try {
44
+ await makeRequest({
45
+ url: '/user/profile',
46
+ method: 'POST',
47
+ body: data,
48
+ onSuccess: () => {
49
+ showMessage({ message: 'Your profile information is updated.' });
50
+ getUserProfile?.();
51
+ onSuccess?.();
52
+ },
53
+ onFailure: () => {
54
+ showMessage({ message: 'Failed to update profile.' });
55
+ }
56
+ });
57
+ }
58
+ finally {
59
+ setLoading(false);
60
+ }
61
+ };
62
+ const uploadProfileImage = async (file) => {
63
+ const fileData = new FormData();
64
+ fileData.append('file', file);
65
+ try {
66
+ const response = await makeRequest({
67
+ url: '/upload/profile-picture',
68
+ method: 'POST',
69
+ body: fileData,
70
+ headers: {
71
+ 'Content-Type': 'multipart/form-data'
72
+ }
73
+ });
74
+ return response?.fileName || null;
75
+ }
76
+ catch {
77
+ showMessage({ message: 'Failed to upload image.' });
78
+ return null;
79
+ }
80
+ };
81
+ const updateMobileNumber = async (data, onSuccess, onFailure) => {
82
+ setLoading(true);
83
+ try {
84
+ await makeRequest({
85
+ url: '/user/mobile-number',
86
+ method: 'POST',
87
+ body: data,
88
+ onSuccess: () => {
89
+ onSuccess?.();
90
+ },
91
+ onFailure: () => {
92
+ showMessage({ message: 'Failed to update mobile number.' });
93
+ onFailure?.();
94
+ }
95
+ });
96
+ }
97
+ finally {
98
+ setLoading(false);
99
+ }
100
+ };
101
+ const sendMobileOtp = async (onSuccess, onFailure) => {
102
+ try {
103
+ await makeRequest({
104
+ url: '/auth/mobile/send-otp',
105
+ method: 'POST',
106
+ onSuccess: () => onSuccess?.(),
107
+ onFailure: () => onFailure?.()
108
+ });
109
+ }
110
+ catch {
111
+ onFailure?.();
112
+ }
113
+ };
114
+ const verifyMobileOtp = async (otp, onSuccess, onFailure) => {
115
+ setLoading(true);
116
+ try {
117
+ await makeRequest({
118
+ url: '/auth/mobile/verify-otp',
119
+ method: 'POST',
120
+ body: { otp },
121
+ onSuccess: () => {
122
+ showMessage({ message: 'Mobile number updated successfully.' });
123
+ getUserProfile?.();
124
+ onSuccess?.();
125
+ },
126
+ onFailure: () => {
127
+ onFailure?.();
128
+ }
129
+ });
130
+ }
131
+ finally {
132
+ setLoading(false);
133
+ }
134
+ };
135
+ const changePassword = async (data, onSuccess, onFailure) => {
136
+ setLoading(true);
137
+ try {
138
+ // First, validate current password
139
+ const initResponse = await makeRequest({
140
+ url: '/auth/change-password/initialize-session',
141
+ method: 'POST',
142
+ body: { password: data.currentPassword }
143
+ });
144
+ if (initResponse?.token) {
145
+ // Then change the password
146
+ await makeRequest({
147
+ url: '/auth/change-password',
148
+ method: 'POST',
149
+ body: { password: data.newPassword, token: initResponse.token },
150
+ onSuccess: () => {
151
+ showMessage({ message: 'Your password has been updated.' });
152
+ onSuccess?.();
153
+ },
154
+ onFailure: () => {
155
+ showMessage({ message: 'Failed to change password.' });
156
+ onFailure?.();
157
+ }
158
+ });
159
+ }
160
+ else {
161
+ onFailure?.('Invalid current password');
162
+ }
163
+ }
164
+ catch {
165
+ onFailure?.('Current password is incorrect');
166
+ }
167
+ finally {
168
+ setLoading(false);
169
+ }
170
+ };
171
+ return {
172
+ loading,
173
+ profile,
174
+ profileData,
175
+ imageUrl,
176
+ setImageUrl,
177
+ updateProfile,
178
+ uploadProfileImage,
179
+ updateMobileNumber,
180
+ sendMobileOtp,
181
+ verifyMobileOtp,
182
+ changePassword
183
+ };
184
+ };
185
+ export { useProfile };
@@ -0,0 +1,6 @@
1
+ export { ProfileWebComponent } from './components/ProfileWebComponent';
2
+ export { EditProfile } from './components/EditProfile';
3
+ export { EditMobileNumber } from './components/EditMobileNumber';
4
+ export { EditPassword } from './components/EditPassword';
5
+ export { OtpVerificationModal } from './components/OtpVerificationModal';
6
+ export { useProfile } from './hooks/internal/useProfile';
@@ -0,0 +1,6 @@
1
+ export { ProfileWebComponent } from './components/ProfileWebComponent/index.native';
2
+ export { EditProfile } from './components/EditProfile/index.native';
3
+ export { EditMobileNumber } from './components/EditMobileNumber/index.native';
4
+ export { EditPassword } from './components/EditPassword/index.native';
5
+ export { OtpVerificationModal } from './components/OtpVerificationModal/index.native';
6
+ export { useProfile } from './hooks/internal/useProfile';
@@ -0,0 +1 @@
1
+ export {};
@@ -23,6 +23,6 @@ const Welcome = ({ onClickEnterEmail }) => {
23
23
  // Fallback to default logo
24
24
  return (_jsx("img", { width: 160, height: 35, className: 'md:mt-0 mt-6 md:h-[40px] h-[30px]', src: `${CDN_IMAGES_URL}/new-twc_logo.svg` }));
25
25
  };
26
- return (_jsx(AdvancedTransitionWrapper, { type: 'slide', duration: 0.3, children: _jsxs(Flex, { justify: 'center', align: "center", direction: 'column', className: 'h-screen w-[80%] mx-auto gap-6', children: [renderLogo(), _jsxs(Flex, { justify: "center", align: "center", direction: "column", className: 'gap-2', children: [_jsx(Typography, { type: 'heading', size: 'h5', className: 'text-center text-[#1A1A1A] font-semibold', children: "Welcome!" }), _jsx(Typography, { type: 'utility', size: 'large', color: "gray-400", className: 'text-center font-semibold', children: "Select an option to proceed" })] }), _jsx(Button, { label: "Enter Your Email", variant: "secondary", onClick: onClickEnterEmail, leftIcon: _jsx(Mail, { size: 20 }), isFullWidth: true })] }) }));
26
+ return (_jsx(AdvancedTransitionWrapper, { type: 'slide', duration: 0.3, children: _jsxs(Flex, { justify: 'center', align: "center", direction: 'column', className: 'h-screen w-[80%] mx-auto gap-6', children: [renderLogo(), _jsxs(Flex, { justify: "center", align: "center", direction: "column", className: 'gap-2', children: [_jsx(Typography, { type: 'heading', size: 'h5', className: 'text-center text-gray-900 font-semibold', children: "Welcome!" }), _jsx(Typography, { type: 'utility', size: 'large', color: "gray-400", className: 'text-center font-semibold', children: "Select an option to proceed" })] }), _jsx(Button, { label: "Enter Your Email", variant: "secondary", onClick: onClickEnterEmail, leftIcon: _jsx(Mail, { size: 20 }), isFullWidth: true })] }) }));
27
27
  };
28
28
  export default Welcome;
@@ -1,3 +1,3 @@
1
1
  import type { ConfirmationModalProps } from './types';
2
- declare const ConfirmationModal: ({ title, visible, onClose, onProceed, onDismiss, primaryLabel, secondaryLabel, iconName, iconColor, description }: ConfirmationModalProps) => import("react/jsx-runtime").JSX.Element;
2
+ declare const ConfirmationModal: ({ title, visible, onClose, onProceed, onDismiss, primaryLabel, secondaryLabel, iconName, iconClassName, description }: ConfirmationModalProps) => import("react/jsx-runtime").JSX.Element;
3
3
  export { ConfirmationModal };
@@ -4,7 +4,10 @@ type ConfirmationModalProps = {
4
4
  primaryLabel: string;
5
5
  secondaryLabel?: string;
6
6
  iconName?: string;
7
+ /** @deprecated Use iconClassName instead for Tailwind classes */
7
8
  iconColor?: string;
9
+ /** Tailwind classes for icon styling, e.g. "text-primary" or "text-utility-warning-main" */
10
+ iconClassName?: string;
8
11
  onClose: () => void;
9
12
  onProceed?: () => void;
10
13
  onDismiss?: () => void;
@@ -0,0 +1,4 @@
1
+ export declare const strongPasswordRequirements: {
2
+ regex: RegExp;
3
+ text: string;
4
+ }[];
@@ -9,3 +9,5 @@ export * from './navigator';
9
9
  export * from './screens/Login/components/LoginWebComponent/index';
10
10
  export * from './screens/SignUp/components/SignUpWebComponent/index';
11
11
  export * from './screens/SSOLogin/SSOCallback/components/SSOCallbackComponent/index';
12
+ export * from './screens/Profile';
13
+ export { strongPasswordRequirements } from './constants/password-requirements';
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface EditMobileNumberProps {
3
+ onSuccess?: () => void;
4
+ }
5
+ declare const EditMobileNumber: React.FC<EditMobileNumberProps>;
6
+ export { EditMobileNumber };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const EditMobileNumber: React.FC<any>;
3
+ export { EditMobileNumber };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface EditPasswordProps {
3
+ onSuccess?: () => void;
4
+ }
5
+ declare const EditPassword: React.FC<EditPasswordProps>;
6
+ export { EditPassword };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const EditPassword: React.FC<any>;
3
+ export { EditPassword };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface EditProfileProps {
3
+ onSuccess?: () => void;
4
+ }
5
+ declare const EditProfile: React.FC<EditProfileProps>;
6
+ export { EditProfile };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const EditProfile: React.FC<any>;
3
+ export { EditProfile };
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { OtpVerificationModalProps } from '../../types';
3
+ declare const OtpVerificationModal: React.FC<OtpVerificationModalProps>;
4
+ export { OtpVerificationModal };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const OtpVerificationModal: React.FC<any>;
3
+ export { OtpVerificationModal };
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { ProfileWebComponentProps } from '../../types';
3
+ declare const ProfileWebComponent: React.FC<ProfileWebComponentProps>;
4
+ export { ProfileWebComponent };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const ProfileWebComponent: React.FC<any>;
3
+ export { ProfileWebComponent };
@@ -0,0 +1,26 @@
1
+ import type { ProfileData } from '../../types';
2
+ declare const useProfile: () => {
3
+ loading: boolean;
4
+ profile: import("../../../..").MemberProfile | null;
5
+ profileData: ProfileData;
6
+ imageUrl: string;
7
+ setImageUrl: import("react").Dispatch<import("react").SetStateAction<string>>;
8
+ updateProfile: (data: {
9
+ firstName: string;
10
+ lastName: string;
11
+ bio?: string;
12
+ image?: string;
13
+ }, onSuccess?: () => void) => Promise<void>;
14
+ uploadProfileImage: (file: File) => Promise<string | null>;
15
+ updateMobileNumber: (data: {
16
+ countryCode: string;
17
+ mobileNo: string;
18
+ }, onSuccess?: () => void, onFailure?: () => void) => Promise<void>;
19
+ sendMobileOtp: (onSuccess?: () => void, onFailure?: () => void) => Promise<void>;
20
+ verifyMobileOtp: (otp: string, onSuccess?: () => void, onFailure?: () => void) => Promise<void>;
21
+ changePassword: (data: {
22
+ currentPassword: string;
23
+ newPassword: string;
24
+ }, onSuccess?: () => void, onFailure?: (error?: string) => void) => Promise<void>;
25
+ };
26
+ export { useProfile };
@@ -0,0 +1,7 @@
1
+ export { ProfileWebComponent } from './components/ProfileWebComponent';
2
+ export { EditProfile } from './components/EditProfile';
3
+ export { EditMobileNumber } from './components/EditMobileNumber';
4
+ export { EditPassword } from './components/EditPassword';
5
+ export { OtpVerificationModal } from './components/OtpVerificationModal';
6
+ export { useProfile } from './hooks/internal/useProfile';
7
+ export type * from './types';
@@ -0,0 +1,7 @@
1
+ export { ProfileWebComponent } from './components/ProfileWebComponent/index.native';
2
+ export { EditProfile } from './components/EditProfile/index.native';
3
+ export { EditMobileNumber } from './components/EditMobileNumber/index.native';
4
+ export { EditPassword } from './components/EditPassword/index.native';
5
+ export { OtpVerificationModal } from './components/OtpVerificationModal/index.native';
6
+ export { useProfile } from './hooks/internal/useProfile';
7
+ export type * from './types';
@@ -0,0 +1,53 @@
1
+ import type React from 'react';
2
+ interface ProfileData {
3
+ firstName: string;
4
+ lastName: string;
5
+ dateOfBirth: Date | string;
6
+ gender: string;
7
+ bio?: string;
8
+ image?: string;
9
+ phone?: string;
10
+ countryCode?: string;
11
+ }
12
+ interface EditProfileProps {
13
+ profileData: ProfileData;
14
+ imageUrl?: string;
15
+ setImageUrl: React.Dispatch<React.SetStateAction<string>>;
16
+ editProfileForm: any;
17
+ onSuccess?: () => void;
18
+ }
19
+ interface EditMobileNumberProps {
20
+ countryCode?: string;
21
+ editPhoneForm: any;
22
+ onSuccess?: () => void;
23
+ }
24
+ interface EditPasswordProps {
25
+ editPasswordForm: any;
26
+ onSuccess?: () => void;
27
+ }
28
+ interface OtpVerificationModalProps {
29
+ open: boolean;
30
+ onOpenChange: (open: boolean) => void;
31
+ phone: string;
32
+ countryCode?: string;
33
+ onSuccess?: () => void;
34
+ successMessage?: string;
35
+ title?: string | React.ReactNode;
36
+ }
37
+ interface ProfileWebComponentProps {
38
+ /** Optional header component to wrap the profile content */
39
+ HeaderComponent?: React.ComponentType<{
40
+ children: React.ReactNode;
41
+ }>;
42
+ /** Optional footer component */
43
+ FooterComponent?: React.ComponentType;
44
+ /** Callback when profile is updated successfully */
45
+ onProfileUpdate?: () => void;
46
+ /** Callback when mobile number is updated successfully */
47
+ onMobileUpdate?: () => void;
48
+ /** Callback when password is updated successfully */
49
+ onPasswordUpdate?: () => void;
50
+ /** Custom container className */
51
+ containerClassName?: string;
52
+ }
53
+ export type { ProfileData, EditProfileProps, EditMobileNumberProps, EditPasswordProps, OtpVerificationModalProps, ProfileWebComponentProps };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "description": "Truworth Auth Package for React Native and Web",
7
- "version": "2.0.0",
7
+ "version": "3.0.1",
8
8
  "main": "build/src/index.js",
9
9
  "types": "build/types/index.d.ts",
10
10
  "files": [
@@ -27,7 +27,7 @@
27
27
  "@react-navigation/native": "6.1.17",
28
28
  "@react-navigation/native-stack": "6.10.0",
29
29
  "@truworth/twc-rn-common": "1.1.2",
30
- "@truworth/twc-web-design": "1.11.0",
30
+ "@truworth/twc-web-design": "2.0.0",
31
31
  "@twotalltotems/react-native-otp-input": "1.3.11",
32
32
  "@types/crypto-js": "^4.2.2",
33
33
  "@types/fbemitter": "^2.0.35",