Initialize GUI has profile attributes
This commit is contained in:
@@ -13,11 +13,22 @@ interface InitializeAccountProps {
|
||||
const InitializeAccount: React.FC<InitializeAccountProps> = ({ onInitialized, language, onLanguageChange }) => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [birthDate, setBirthDate] = useState('');
|
||||
const [height, setHeight] = useState('');
|
||||
const [weight, setWeight] = useState('');
|
||||
const [gender, setGender] = useState<'MALE' | 'FEMALE' | 'OTHER' | ''>('');
|
||||
|
||||
const handleInitialize = async () => {
|
||||
setIsSubmitting(true);
|
||||
setError('');
|
||||
const res = await initializeAccount(language);
|
||||
|
||||
const profileData: any = {};
|
||||
if (birthDate) profileData.birthDate = birthDate;
|
||||
if (height) profileData.height = parseFloat(height);
|
||||
if (weight) profileData.weight = parseFloat(weight);
|
||||
if (gender) profileData.gender = gender;
|
||||
|
||||
const res = await initializeAccount(language, profileData);
|
||||
if (res.success && res.user) {
|
||||
onInitialized(res.user);
|
||||
} else {
|
||||
@@ -32,8 +43,8 @@ const InitializeAccount: React.FC<InitializeAccountProps> = ({ onInitialized, la
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-surface flex flex-col items-center justify-center p-6 bg-surface">
|
||||
<div className="w-full max-w-sm bg-surface-container p-8 rounded-[28px] shadow-elevation-2 flex flex-col items-center">
|
||||
<div className="min-h-screen bg-surface flex flex-col items-center justify-center p-6 sm:p-8">
|
||||
<div className="w-full max-w-sm bg-surface-container p-8 rounded-[28px] shadow-elevation-2 flex flex-col items-center max-h-[90vh] overflow-y-auto custom-scrollbar">
|
||||
<div className="w-16 h-16 bg-primary-container rounded-2xl flex items-center justify-center text-on-primary-container mb-6 shadow-elevation-1">
|
||||
<Globe size={32} />
|
||||
</div>
|
||||
@@ -78,6 +89,59 @@ const InitializeAccount: React.FC<InitializeAccountProps> = ({ onInitialized, la
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-full mb-8">
|
||||
<div className="space-y-4 animate-in fade-in slide-in-from-top-2 duration-300">
|
||||
<div>
|
||||
<label htmlFor="birthDate" className="block text-xs text-on-surface-variant mb-1 ml-1">{t('birth_date', language)}</label>
|
||||
<input
|
||||
id="birthDate"
|
||||
type="date"
|
||||
value={birthDate}
|
||||
onChange={(e) => setBirthDate(e.target.value)}
|
||||
className="w-full p-4 rounded-2xl bg-surface-container-high border border-outline-variant/30 text-on-surface focus:border-primary focus:outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="height" className="block text-xs text-on-surface-variant mb-1 ml-1">{t('height', language)}</label>
|
||||
<input
|
||||
id="height"
|
||||
type="number"
|
||||
placeholder="cm"
|
||||
value={height}
|
||||
onChange={(e) => setHeight(e.target.value)}
|
||||
className="w-full p-4 rounded-2xl bg-surface-container-high border border-outline-variant/30 text-on-surface focus:border-primary focus:outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="weight" className="block text-xs text-on-surface-variant mb-1 ml-1">{t('my_weight', language).split('(')[0].trim()}</label>
|
||||
<input
|
||||
id="weight"
|
||||
type="number"
|
||||
placeholder="kg"
|
||||
value={weight}
|
||||
onChange={(e) => setWeight(e.target.value)}
|
||||
className="w-full p-4 rounded-2xl bg-surface-container-high border border-outline-variant/30 text-on-surface focus:border-primary focus:outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="gender" className="block text-xs text-on-surface-variant mb-1 ml-1">{t('gender', language)}</label>
|
||||
<select
|
||||
id="gender"
|
||||
value={gender}
|
||||
onChange={(e) => setGender(e.target.value as any)}
|
||||
className="w-full p-4 rounded-2xl bg-surface-container-high border border-outline-variant/30 text-on-surface focus:border-primary focus:outline-none transition-all appearance-none"
|
||||
>
|
||||
<option value="">{t('select_gender', language)}</option>
|
||||
<option value="MALE">{t('male', language)}</option>
|
||||
<option value="FEMALE">{t('female', language)}</option>
|
||||
<option value="OTHER">{t('other', language)}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleInitialize}
|
||||
disabled={isSubmitting}
|
||||
|
||||
@@ -302,8 +302,9 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
<h3 className="text-sm font-bold text-primary mb-4">{t('personal_data', lang)}</h3>
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Scale size={10} /> {t('weight_kg', lang)}</label>
|
||||
<label htmlFor="profileWeight" className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Scale size={10} /> {t('weight_kg', lang)}</label>
|
||||
<input
|
||||
id="profileWeight"
|
||||
data-testid="profile-weight-input"
|
||||
type="number"
|
||||
step="0.1"
|
||||
@@ -313,8 +314,8 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Ruler size={10} /> {t('height', lang)}</label>
|
||||
<input data-testid="profile-height-input" type="number" value={height} onChange={(e) => setHeight(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none" />
|
||||
<label htmlFor="profileHeight" className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Ruler size={10} /> {t('height', lang)}</label>
|
||||
<input id="profileHeight" data-testid="profile-height-input" type="number" value={height} onChange={(e) => setHeight(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<DatePicker
|
||||
@@ -326,8 +327,8 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><PersonStanding size={10} /> {t('gender', lang)}</label>
|
||||
<select data-testid="profile-gender" value={gender} onChange={(e) => setGender(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none text-sm py-1 bg-surface-container-high">
|
||||
<label htmlFor="profileGender" className="text-[10px] text-on-surface-variant flex gap-1 items-center"><PersonStanding size={10} /> {t('gender', lang)}</label>
|
||||
<select id="profileGender" data-testid="profile-gender" value={gender} onChange={(e) => setGender(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none text-sm py-1 bg-surface-container-high">
|
||||
<option value="MALE">{t('male', lang)}</option>
|
||||
<option value="FEMALE">{t('female', lang)}</option>
|
||||
<option value="OTHER">{t('other', lang)}</option>
|
||||
|
||||
@@ -429,7 +429,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
||||
`}
|
||||
>
|
||||
<label className={`
|
||||
<label htmlFor={id} className={`
|
||||
absolute top-2 left-4 text-label-sm font-medium transition-colors flex items-center gap-1
|
||||
${isOpen ? 'text-primary' : 'text-on-surface-variant'}
|
||||
`}>
|
||||
@@ -437,6 +437,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
||||
</label>
|
||||
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
value={textInputValue || formattedValue}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -149,9 +149,9 @@ export const getMe = async (): Promise<{ success: boolean; user?: User; error?:
|
||||
return { success: false, error: 'Failed to fetch user' };
|
||||
}
|
||||
};
|
||||
export const initializeAccount = async (language: string): Promise<{ success: boolean; user?: User; error?: string }> => {
|
||||
export const initializeAccount = async (language: string, profile?: Partial<UserProfile>): Promise<{ success: boolean; user?: User; error?: string }> => {
|
||||
try {
|
||||
const res = await api.post<ApiResponse<{ user: User }>>('/auth/initialize', { language });
|
||||
const res = await api.post<ApiResponse<{ user: User }>>('/auth/initialize', { language, ...profile });
|
||||
if (res.success && res.data) {
|
||||
return { success: true, user: res.data.user };
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ const translations = {
|
||||
init_start: 'Get Started',
|
||||
init_lang_en_desc: 'GUI and default exercise names will be in English',
|
||||
init_lang_ru_desc: 'GUI and default exercise names will be in Russian',
|
||||
select_gender: 'Select Gender',
|
||||
|
||||
// General
|
||||
date: 'Date',
|
||||
@@ -265,6 +266,7 @@ const translations = {
|
||||
init_start: 'Начать работу',
|
||||
init_lang_en_desc: 'Интерфейс и названия упражнений по умолчанию будут на английском',
|
||||
init_lang_ru_desc: 'Интерфейс и названия упражнений по умолчанию будут на русском',
|
||||
select_gender: 'Выберите пол',
|
||||
|
||||
// General
|
||||
date: 'Дата',
|
||||
|
||||
Reference in New Issue
Block a user