Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/features/identity-setup/components/ConfirmChange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ export const ConfirmChange = ({
const { translate } = useTranslationContext();
const newEmail = userNewEmail.address + '@' + userNewEmail.domain;

const handleSubmit = (e: React.SubmitEvent) => {
e.preventDefault();
if (isDisabled) return;
onConfirmChanges();
};

return (
<div className="flex flex-col gap-5 justify-center items-center">
<form onSubmit={handleSubmit} className="flex flex-col gap-5 justify-center items-center">
{/* Avatar */}
<div className="flex flex-col">
<Avatar fullName={userFullName} diameter={80} />
Expand All @@ -49,10 +55,10 @@ export const ConfirmChange = ({
<div className="flex border w-full border-gray-10" />

<div className="flex flex-col w-full">
<Button loading={isLoading} disabled={isDisabled} onClick={onConfirmChanges}>
<Button type="submit" loading={isLoading} disabled={isDisabled}>
{translate('identitySetup.confirmChanges.action')}
</Button>
</div>
</div>
</form>
);
};
11 changes: 8 additions & 3 deletions src/features/identity-setup/components/ConfirmPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export const ConfirmPassword = ({ userFullName, onBack, onNext }: ConfirmPasswor
onNext(password);
};

const handleSubmit = (e: React.SubmitEvent) => {
e.preventDefault();
onConfirmPassword();
};

return (
<Fragment>
<div>
Expand All @@ -28,7 +33,7 @@ export const ConfirmPassword = ({ userFullName, onBack, onNext }: ConfirmPasswor
</div>
</Button>
</div>
<div className="flex flex-col gap-5 justify-center items-center">
<form onSubmit={handleSubmit} className="flex flex-col gap-5 justify-center items-center">
{/* Avatar */}
<div className="flex flex-col">
<Avatar fullName={userFullName} diameter={80} />
Expand All @@ -54,9 +59,9 @@ export const ConfirmPassword = ({ userFullName, onBack, onNext }: ConfirmPasswor
</div>

<div className="flex flex-col w-full">
<Button onClick={onConfirmPassword}>{translate('identitySetup.confirmPassword.action')}</Button>
<Button type="submit">{translate('identitySetup.confirmPassword.action')}</Button>
</div>
</div>
</form>
</Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useTranslationContext } from '@/i18n';
import { CheckCircleIcon, MinusCircleIcon, XCircleIcon } from '@phosphor-icons/react';
import type { EmailAddressRule, EmailAddressRuleStatus } from '../hooks/emailAddressRules';

interface EmailAddressRulesPanelProps {
rules: EmailAddressRule[];
}

const STATUS_TEXT_CLASSNAME: Record<EmailAddressRuleStatus, string> = {
idle: 'text-gray-50',
valid: 'text-green',
invalid: 'text-red',
};

const STATUS_ICON: Record<EmailAddressRuleStatus, typeof CheckCircleIcon> = {
idle: MinusCircleIcon,
valid: CheckCircleIcon,
invalid: XCircleIcon,
};

export const EmailAddressRulesPanel = ({ rules }: EmailAddressRulesPanelProps) => {
const { translate } = useTranslationContext();

return (
<ul className="flex flex-col gap-1.5 pt-3">
{rules.map((rule) => {
const StatusIcon = STATUS_ICON[rule.status];

return (
<li key={rule.id} className={`flex items-center gap-2 text-sm ${STATUS_TEXT_CLASSNAME[rule.status]}`}>
<StatusIcon size={16} weight="fill" className="shrink-0" />
{translate(rule.labelKey)}
</li>
);
})}
</ul>
);
};

export default EmailAddressRulesPanel;
24 changes: 20 additions & 4 deletions src/features/identity-setup/components/SelectMailInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@
domains: EmailDomainsResponse;
onChangeValue: (value: string) => void;
onChangeDomain: (domain: string) => void;
onFocusChange?: (isFocused: boolean) => void;
}

const SelectMailInput = ({ value, onChangeValue, selectedDomain, domains, onChangeDomain }: SelectMailInputProps) => {
const SelectMailInput = ({
value,
onChangeValue,
selectedDomain,
domains,
onChangeDomain,
onFocusChange,
}: SelectMailInputProps) => {
const [isFocused, setIsFocused] = useState(false);

Comment thread
xabg2 marked this conversation as resolved.
const menuItems = domains.map(({ domain }) => (
Expand All @@ -38,11 +46,19 @@
className="h-full min-w-0 flex-1 bg-transparent pl-3 text-lg text-gray-100 outline-none placeholder:text-gray-40"
Comment thread
xabg2 marked this conversation as resolved.
value={value}
onChange={(e) => onChangeValue(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
onFocus={() => {
setIsFocused(true);
onFocusChange?.(true);
}}
onBlur={() => {
setIsFocused(false);
onFocusChange?.(false);
}}
/>

<div className="flex flex-col z-10">
{/* The Dropdown's internal buttons don't expose a `type` prop, so inside a
<form> they'd default to type="submit" and prematurely submit it. */}
<div className="flex flex-col z-10" onClick={(e) => e.preventDefault()}>

Check warning on line 61 in src/features/identity-setup/components/SelectMailInput.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.

See more on https://sonarcloud.io/project/issues?id=internxt_mail-web&issues=AZ9rccgQSHe1d1PwYlqD&open=AZ9rccgQSHe1d1PwYlqD&pullRequest=73

Check warning on line 61 in src/features/identity-setup/components/SelectMailInput.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Visible, non-interactive elements with click handlers must have at least one keyboard listener.

See more on https://sonarcloud.io/project/issues?id=internxt_mail-web&issues=AZ9rccgQSHe1d1PwYlqE&open=AZ9rccgQSHe1d1PwYlqE&pullRequest=73
<Dropdown
classButton="flex items-center gap-1 px-3 text-sm font-medium text-gray-60transition-colors whitespace-nowrap"
classMenuItems="right-0 top-1 min-w-[160px] rounded-lg shadow-subtle-hard text-gray-100"
Expand Down
63 changes: 40 additions & 23 deletions src/features/identity-setup/components/UpdateEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useTranslationContext } from '@/i18n';
import type { EmailDomainsResponse } from '@internxt/sdk/dist/mail/types';
import { Avatar, Button } from '@internxt/ui';
import { useState } from 'react';
import { useUpdateEmail } from '../hooks/useUpdateEmail';
import { EmailAddressRulesPanel } from './EmailAddressRulesPanel';
import SelectMailInput from './SelectMailInput';
import type { EmailDomainsResponse } from '@internxt/sdk/dist/mail/types';

interface UpdateEmailProps {
userFullName: string;
Expand All @@ -13,25 +14,37 @@

export const UpdateEmail = ({ userFullName, activeDomains, currentEmail, onNext }: UpdateEmailProps) => {
const { translate, translateArray } = useTranslationContext();
const [username, setUsername] = useState<string>('');
const [domain, setDomain] = useState<string>(activeDomains[0]?.domain ?? '');
const [usernameError, setUsernameError] = useState<string>('');
const {
username,
rules,
isValid,
validateAddress,
domain,
setDomain,
setIsUsernameFocused,
isPanelVisible,
handleSubmit,
submit,
} = useUpdateEmail({ activeDomains, onNext });

const descriptions = translateArray('identitySetup.updateEmail.description', {
name: userFullName,
current_email: currentEmail,
});

const handleConfirm = () => {
if (!username.trim()) {
setUsernameError(translate('errors.identitySetup.usernameRequired'));
return;
}
onNext({ address: username, domain });
const handleFormKeyDown = (e: React.KeyboardEvent<HTMLFormElement>) => {
if (e.key !== 'Enter' || (e.target as HTMLElement).tagName === 'BUTTON') return;

e.preventDefault();
submit();
};

return (
<div className="flex flex-col gap-5 justify-center items-center">
<form
onSubmit={handleSubmit}
onKeyDown={handleFormKeyDown}
className="flex flex-col gap-5 justify-center items-center"
>

Check warning on line 47 in src/features/identity-setup/components/UpdateEmail.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Non-interactive elements should not be assigned mouse or keyboard event listeners.

See more on https://sonarcloud.io/project/issues?id=internxt_mail-web&issues=AZ9rccc9SHe1d1PwYlqB&open=AZ9rccc9SHe1d1PwYlqB&pullRequest=73
{/* Avatar */}
<div className="flex flex-col">
<Avatar fullName={userFullName} diameter={80} />
Expand All @@ -52,26 +65,30 @@

{/* Email input */}
<div className="flex flex-col w-full">
<span className="mb-1 text-sm font-medium text-gray-100">{translate('identitySetup.updateEmail.mail')}</span>
<SelectMailInput
value={username}
onChangeValue={(value) => {
setUsername(value);
if (value.trim()) setUsernameError('');
}}
onChangeValue={validateAddress}
selectedDomain={domain}
domains={activeDomains}
onChangeDomain={setDomain}
onFocusChange={setIsUsernameFocused}
/>
{usernameError ? (
<p className="text-sm text-red">{usernameError}</p>
) : (
<p className="text-sm text-gray-50">{translate('identitySetup.updateEmail.mailType')}</p>
)}
<div
className={`grid transition-all duration-200 ease-out ${
isPanelVisible ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'
}`}
>
<div className="overflow-hidden">{isPanelVisible && <EmailAddressRulesPanel rules={rules} />}</div>
</div>
{!isPanelVisible && <p className="text-sm text-gray-50">{translate('identitySetup.updateEmail.mailType')}</p>}
</div>

<div className="flex flex-col w-full">
<Button onClick={handleConfirm}>{translate('identitySetup.updateEmail.action')}</Button>
<Button type="submit" disabled={!isValid}>
{translate('identitySetup.updateEmail.action')}
</Button>
</div>
</div>
</form>
);
};
Loading
Loading