mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
refactor(ui): main flow mobile design review fix part 1 (#717)
* fix(ui): use description.or for all non-social methods divider use description.or for all non-social methods divider * fix(ui): should not validate format in sign-in form should not validate format in sign-in form * refactor(ui): add clear-icon and refine component import path add clear-icon and refin component import path * fix(ui): remove passcode input error border remove passcode input error border * refactor(ui): hide error border of confirm passcode hide error border of confirm passcode * fix(ui): fix i18n key fix i18n key * refactor(ui): show clear icon for password in create-account form show clear icon for password in create-account form * fix(ui): update passwordless confirm modal confirm button text update passwordless confirm modal confirm button text * refactor(ui): divider style update divider style update * fix(ui): always show social links expand button toggle social link * fix(ui): extract mobile width style extract mobile width style * fix(ui): fix create account link fix create account link * fix(ui): cr fix
This commit is contained in:
parent
39bf3ccd8a
commit
92e8ed199d
31 changed files with 144 additions and 84 deletions
3
packages/ui/src/assets/icons/clear-icon.svg
Normal file
3
packages/ui/src/assets/icons/clear-icon.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="18" id="clear" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 18C13.9706 18 18 13.9706 18 9C18 4.02944 13.9706 0 9 0C4.02944 0 0 4.02944 0 9C0 13.9706 4.02944 18 9 18ZM5.46447 6.87868C5.07394 6.48816 5.07394 5.85499 5.46447 5.46447C5.85499 5.07394 6.48816 5.07394 6.87868 5.46447L9 7.58579L11.1213 5.46447C11.5118 5.07394 12.145 5.07394 12.5355 5.46447C12.9261 5.85499 12.9261 6.48815 12.5355 6.87868L10.4142 9L12.5355 11.1213C12.9261 11.5118 12.9261 12.145 12.5355 12.5355C12.145 12.9261 11.5118 12.9261 11.1213 12.5355L9 10.4142L6.87868 12.5355C6.48816 12.9261 5.85499 12.9261 5.46447 12.5355C5.07394 12.145 5.07394 11.5118 5.46447 11.1213L7.58579 9L5.46447 6.87868Z" fill="#747778"/>
|
||||
</svg>
|
After Width: | Height: | Size: 793 B |
|
@ -5,8 +5,7 @@ $font-family: 'PingFang SC', 'SF Pro Display', 'Siyuan Heiti', 'Roboto';
|
|||
$font-family-small: 'PingFang SC', 'SF Pro Text', 'Siyuan Heiti', 'Roboto';
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--color-base);
|
||||
color: var(--color-text);
|
||||
font: var(--font-body);
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
@include _.flex-row;
|
||||
font: var(--font-body);
|
||||
color: var(--color-caption);
|
||||
margin: _.unit(4) 0;
|
||||
width: 100%;
|
||||
margin: _.unit(5) 0;
|
||||
|
||||
.line {
|
||||
flex: 1;
|
||||
|
|
|
@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|||
import React, { ReactNode } from 'react';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import { ClearIcon } from '@/components/Icons';
|
||||
import { CloseIcon } from '@/components/Icons';
|
||||
|
||||
import * as modalStyles from '../../scss/modal.module.scss';
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -29,7 +29,7 @@ const Drawer = ({ className, isOpen = false, children, onClose }: Props) => {
|
|||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<ClearIcon className={styles.closeIcon} onClick={onClose} />
|
||||
<CloseIcon className={styles.closeIcon} onClick={onClose} />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { SVGProps } from 'react';
|
||||
|
||||
import CloseIcon from '@/assets/icons/close-icon.svg';
|
||||
import IconClear from '@/assets/icons/clear-icon.svg';
|
||||
|
||||
const ClearIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<use href={`${CloseIcon}#close-icon`} />
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<use href={`${IconClear}#clear`} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
|
11
packages/ui/src/components/Icons/CloseIcon.tsx
Normal file
11
packages/ui/src/components/Icons/CloseIcon.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import React, { SVGProps } from 'react';
|
||||
|
||||
import IconClose from '@/assets/icons/close-icon.svg';
|
||||
|
||||
const CloseIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<use href={`${IconClose}#close-icon`} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseIcon;
|
|
@ -1,3 +1,4 @@
|
|||
export { default as CloseIcon } from './CloseIcon';
|
||||
export { default as ClearIcon } from './ClearIcon';
|
||||
export { default as PrivacyIcon } from './PrivacyIcon';
|
||||
export { default as DownArrowIcon } from './DownArrowIcon';
|
||||
|
|
|
@ -1,25 +1,17 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { useState, useRef, HTMLProps } from 'react';
|
||||
|
||||
import ErrorMessage, { ErrorType } from '../ErrorMessage';
|
||||
import { PrivacyIcon } from '../Icons';
|
||||
import ErrorMessage, { ErrorType } from '@/components/ErrorMessage';
|
||||
import { PrivacyIcon } from '@/components/Icons';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type Props = Omit<HTMLProps<HTMLInputElement>, 'type'> & {
|
||||
className?: string;
|
||||
error?: ErrorType;
|
||||
forceHidden?: boolean;
|
||||
};
|
||||
|
||||
const PasswordInput = ({
|
||||
className,
|
||||
value,
|
||||
error,
|
||||
forceHidden = false,
|
||||
onFocus,
|
||||
onBlur,
|
||||
...rest
|
||||
}: Props) => {
|
||||
const PasswordInput = ({ className, value, error, onFocus, onBlur, ...rest }: Props) => {
|
||||
// Toggle the password visibility
|
||||
const [type, setType] = useState('password');
|
||||
const [onInputFocus, setOnInputFocus] = useState(false);
|
||||
|
@ -45,7 +37,7 @@ const PasswordInput = ({
|
|||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{!forceHidden && value && onInputFocus && (
|
||||
{value && onInputFocus && (
|
||||
<PrivacyIcon
|
||||
className={styles.actionButton}
|
||||
type={iconType}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { useState, useMemo, useRef } from 'react';
|
||||
|
||||
import ErrorMessage, { ErrorType } from '@/components/ErrorMessage';
|
||||
import { ClearIcon, DownArrowIcon } from '@/components/Icons';
|
||||
import { CountryCallingCode, CountryMetaData } from '@/hooks/use-phone-number';
|
||||
|
||||
import ErrorMessage, { ErrorType } from '../ErrorMessage';
|
||||
import { ClearIcon, DownArrowIcon } from '../Icons';
|
||||
import * as styles from './index.module.scss';
|
||||
import * as phoneInputStyles from './phoneInput.module.scss';
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { useState, HTMLProps } from 'react';
|
||||
|
||||
import ErrorMessage, { ErrorType } from '../ErrorMessage';
|
||||
import { ClearIcon } from '../Icons';
|
||||
import ErrorMessage, { ErrorType } from '@/components/ErrorMessage';
|
||||
import { ClearIcon } from '@/components/Icons';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type Props = HTMLProps<HTMLInputElement> & {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { LoadingIcon } from '../Icons';
|
||||
import { LoadingIcon } from '@/components/Icons';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const LoadingLayer = () => (
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { NavArrowIcon } from '../Icons';
|
||||
import { NavArrowIcon } from '@/components/Icons';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
.passcode {
|
||||
@include _.flex-row;
|
||||
@include _.mobile-container-width;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
|
||||
input {
|
||||
|
@ -28,10 +27,6 @@
|
|||
border: _.border(var(--color-primary));
|
||||
}
|
||||
|
||||
&.error {
|
||||
border: _.border(var(--color-error));
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-caption);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import React, {
|
|||
ClipboardEventHandler,
|
||||
} from 'react';
|
||||
|
||||
import ErrorMessage from '../ErrorMessage';
|
||||
import ErrorMessage from '@/components/ErrorMessage';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export const defaultLength = 6;
|
||||
|
@ -199,7 +200,6 @@ const Passcode = ({ name, className, value, length = defaultLength, error, onCha
|
|||
name={`${name}_${index}`}
|
||||
data-id={index}
|
||||
value={codes[index]}
|
||||
className={error ? styles.error : undefined}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
maxLength={2} // Allow overwrite input
|
||||
|
|
|
@ -2,8 +2,8 @@ import React, { ReactNode } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
|
||||
import ConfirmModal from '../ConfirmModal';
|
||||
import TextLink from '../TextLink';
|
||||
import ConfirmModal from '@/components/ConfirmModal';
|
||||
import TextLink from '@/components/TextLink';
|
||||
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
@include _.mobile-container-width;
|
||||
@include _.flex-column;
|
||||
margin: 0 auto;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
|
@ -14,6 +13,10 @@
|
|||
margin-bottom: _.unit(4);
|
||||
}
|
||||
|
||||
.confirmPassword > * {
|
||||
border: _.border();
|
||||
}
|
||||
|
||||
.terms {
|
||||
margin: _.unit(8) 0 _.unit(4);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next';
|
|||
import { register } from '@/apis/register';
|
||||
import Button from '@/components/Button';
|
||||
import Input from '@/components/Input';
|
||||
import PasswordInput from '@/components/Input/PasswordInput';
|
||||
import TermsOfUse from '@/containers/TermsOfUse';
|
||||
import useApi, { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useForm from '@/hooks/use-form';
|
||||
|
@ -89,23 +88,29 @@ const CreateAccount = ({ className }: Props) => {
|
|||
setFieldValue((state) => ({ ...state, username: '' }));
|
||||
}}
|
||||
/>
|
||||
<PasswordInput
|
||||
forceHidden
|
||||
<Input
|
||||
className={styles.inputField}
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder={t('input.password')}
|
||||
{...fieldRegister('password', passwordValidation)}
|
||||
onClear={() => {
|
||||
setFieldValue((state) => ({ ...state, password: '' }));
|
||||
}}
|
||||
/>
|
||||
<PasswordInput
|
||||
forceHidden
|
||||
className={styles.inputField}
|
||||
<Input
|
||||
className={classNames(styles.inputField, styles.confirmPassword)}
|
||||
name="confirm_password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder={t('input.confirm_password')}
|
||||
{...fieldRegister('confirmPassword', (confirmPassword) =>
|
||||
confirmPasswordValidation(fieldValue.password, confirmPassword)
|
||||
)}
|
||||
onClear={() => {
|
||||
setFieldValue((state) => ({ ...state, confirmPassword: '' }));
|
||||
}}
|
||||
/>
|
||||
<TermsOfUse className={styles.terms} />
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
@include _.mobile-container-width;
|
||||
margin: 0 auto;
|
||||
@include _.flex-column;
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
@include _.flex-column;
|
||||
@include _.mobile-container-width;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
|
|
|
@ -44,6 +44,7 @@ const PasswordlessConfirmModal = ({ className, isOpen, type, method, value, onCl
|
|||
<ConfirmModal
|
||||
className={className}
|
||||
isOpen={isOpen}
|
||||
confirmText={type === 'sign-in' ? 'action.sign_in' : 'action.continue'}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirmHandler}
|
||||
>
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
@include _.flex-column;
|
||||
@include _.mobile-container-width;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
|
|
|
@ -19,15 +19,15 @@ const PrimarySocialSignIn = ({ className, isPopup = false, onSocialSignInCallbac
|
|||
const [showAll, setShowAll] = useState(false);
|
||||
const { invokeSocialSignIn, socialConnectors } = useSocial({ onSocialSignInCallback });
|
||||
const isOverSize = socialConnectors.length > defaultSize;
|
||||
const displayAll = showAll || isPopup || !isOverSize;
|
||||
const fullDisplay = isPopup || !isOverSize;
|
||||
|
||||
const displayConnectors = useMemo(() => {
|
||||
if (displayAll) {
|
||||
if (fullDisplay || showAll) {
|
||||
return socialConnectors;
|
||||
}
|
||||
|
||||
return socialConnectors.slice(0, defaultSize);
|
||||
}, [socialConnectors, displayAll]);
|
||||
}, [fullDisplay, showAll, socialConnectors]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.socialLinkList, className)}>
|
||||
|
@ -41,10 +41,11 @@ const PrimarySocialSignIn = ({ className, isPopup = false, onSocialSignInCallbac
|
|||
}}
|
||||
/>
|
||||
))}
|
||||
{!displayAll && (
|
||||
{!fullDisplay && (
|
||||
<ExpandMoreIcon
|
||||
className={classNames(styles.expandIcon, showAll && styles.expanded)}
|
||||
onClick={() => {
|
||||
setShowAll(true);
|
||||
setShowAll(!showAll);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.socialIconList {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
@include _.flex-row;
|
||||
@include _.mobile-container-width;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
|
||||
.socialButton {
|
||||
margin-right: _.unit(8);
|
||||
|
@ -21,12 +20,20 @@
|
|||
}
|
||||
|
||||
.socialLinkList {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
@include _.flex-column;
|
||||
@include _.mobile-container-width;
|
||||
|
||||
.socialLinkButton {
|
||||
margin-bottom: _.unit(4);
|
||||
}
|
||||
|
||||
.expandIcon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
@include _.flex-column;
|
||||
@include _.mobile-container-width;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
|
|
|
@ -13,7 +13,7 @@ import useForm from '@/hooks/use-form';
|
|||
import useTerms from '@/hooks/use-terms';
|
||||
import { SearchParameters } from '@/types';
|
||||
import { getSearchParameters } from '@/utils';
|
||||
import { usernameValidation, passwordValidation } from '@/utils/field-validations';
|
||||
import { requiredValidation } from '@/utils/field-validations';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -81,7 +81,7 @@ const UsernameSignin = ({ className }: Props) => {
|
|||
name="username"
|
||||
autoComplete="username"
|
||||
placeholder={t('input.username')}
|
||||
{...register('username', usernameValidation)}
|
||||
{...register('username', (value) => requiredValidation('username', value))}
|
||||
onClear={() => {
|
||||
setFieldValue((state) => ({ ...state, username: '' }));
|
||||
}}
|
||||
|
@ -91,7 +91,7 @@ const UsernameSignin = ({ className }: Props) => {
|
|||
name="password"
|
||||
autoComplete="current-password"
|
||||
placeholder={t('input.password')}
|
||||
{...register('password', passwordValidation)}
|
||||
{...register('password', (value) => requiredValidation('password', value))}
|
||||
/>
|
||||
{responseErrorMessage && <ErrorMessage>{responseErrorMessage}</ErrorMessage>}
|
||||
<TermsOfUse className={styles.terms} />
|
||||
|
|
|
@ -16,5 +16,5 @@
|
|||
}
|
||||
|
||||
.button {
|
||||
max-width: 360px;
|
||||
@include _.mobile-container-width;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
.wrapper {
|
||||
position: relative;
|
||||
padding: _.unit(8) _.unit(5);
|
||||
min-height: 100vh;
|
||||
@include _.flex-column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.header {
|
||||
margin: _.unit(8);
|
||||
margin-bottom: _.unit(12);
|
||||
}
|
||||
|
||||
|
@ -14,7 +17,7 @@
|
|||
}
|
||||
|
||||
.divider {
|
||||
margin-top: _.unit(4);
|
||||
@include _.mobile-container-width;
|
||||
}
|
||||
|
||||
.primarySignIn {
|
||||
|
@ -26,11 +29,14 @@
|
|||
}
|
||||
|
||||
.otherMethodsLink {
|
||||
margin-top: _.unit(1);
|
||||
margin-bottom: _.unit(1);
|
||||
}
|
||||
|
||||
.createAccount {
|
||||
position: fixed;
|
||||
bottom: _.unit(12);
|
||||
margin-top: _.unit(8);
|
||||
}
|
||||
|
||||
.placeHolder {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,10 @@ import classNames from 'classnames';
|
|||
import React, { useContext } from 'react';
|
||||
|
||||
import BrandingHeader from '@/components/BrandingHeader';
|
||||
import TextLink from '@/components/TextLink';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
import { PrimarySection, SecondarySection } from './registry';
|
||||
import { PrimarySection, SecondarySection, CreateAccoutnLink } from './registry';
|
||||
|
||||
const SignIn = () => {
|
||||
const { experienceSettings } = useContext(PageContext);
|
||||
|
@ -25,12 +24,7 @@ const SignIn = () => {
|
|||
primarySignInMethod={experienceSettings?.primarySignInMethod}
|
||||
secondarySignInMethods={experienceSettings?.secondarySignInMethods}
|
||||
/>
|
||||
<TextLink
|
||||
className={styles.createAccount}
|
||||
type="secondary"
|
||||
href="/register"
|
||||
text="action.create_account"
|
||||
/>
|
||||
<CreateAccoutnLink primarySignInMethod={experienceSettings?.primarySignInMethod} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import Divider from '@/components/Divider';
|
||||
import TextLink from '@/components/TextLink';
|
||||
import { EmailPasswordless, PhonePasswordless } from '@/containers/Passwordless';
|
||||
import SignInMethodsLink from '@/containers/SignInMethodsLink';
|
||||
import { PrimarySocialSignIn, SecondarySocialSignIn } from '@/containers/SocialSignIn';
|
||||
|
@ -49,20 +50,49 @@ export const SecondarySection = ({
|
|||
return (
|
||||
<>
|
||||
<Divider label="description.continue_with" className={styles.divider} />
|
||||
<SignInMethodsLink signInMethods={localMethods} className={styles.otherMethodsLink} />
|
||||
<SignInMethodsLink signInMethods={localMethods} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SignInMethodsLink signInMethods={localMethods} template="sign_in_with" />
|
||||
<SignInMethodsLink
|
||||
signInMethods={localMethods}
|
||||
template="sign_in_with"
|
||||
className={styles.otherMethodsLink}
|
||||
/>
|
||||
{secondarySignInMethods.includes('social') && (
|
||||
<>
|
||||
<Divider label="description.continue_with" className={styles.divider} />
|
||||
<Divider label="description.or" className={styles.divider} />
|
||||
<SecondarySocialSignIn />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const CreateAccoutnLink = ({
|
||||
primarySignInMethod,
|
||||
}: {
|
||||
primarySignInMethod?: SignInMethod;
|
||||
}) => {
|
||||
switch (primarySignInMethod) {
|
||||
case 'username':
|
||||
case 'email':
|
||||
case 'sms':
|
||||
return (
|
||||
<>
|
||||
<div className={styles.placeHolder} />
|
||||
<TextLink
|
||||
className={styles.createAccount}
|
||||
type="secondary"
|
||||
href={`/register/${primarySignInMethod}`}
|
||||
text="action.create_account"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -29,6 +29,11 @@
|
|||
color: var(--color-text);
|
||||
}
|
||||
|
||||
@mixin mobile-container-width {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
@function border($color: transparent, $width: 1) {
|
||||
@return #{$width}px solid #{$color};
|
||||
}
|
||||
|
|
|
@ -3,6 +3,15 @@ import { ErrorType } from '@/components/ErrorMessage';
|
|||
const usernameRegex = /^[A-Z_a-z-][\w-]*$/;
|
||||
const emailRegex = /^\S+@\S+\.\S+$/;
|
||||
|
||||
export const requiredValidation = (
|
||||
type: 'username' | 'password',
|
||||
value: string
|
||||
): ErrorType | undefined => {
|
||||
if (!value) {
|
||||
return type === 'username' ? 'username_required' : 'password_required';
|
||||
}
|
||||
};
|
||||
|
||||
export const usernameValidation = (username: string): ErrorType | undefined => {
|
||||
if (!username) {
|
||||
return 'username_required';
|
||||
|
|
Loading…
Add table
Reference in a new issue