Skip to content

Commit

Permalink
feat: add new designed FirstTimeSetPassword.tsx (#1694)
Browse files Browse the repository at this point in the history
* feat: add lock gif

* refactor: add new FirstTimeSetPassword design to Loading.tsx

* feat: redesign FirstTimeSetPassword.tsx

* feat: create DecisionButtons.tsx

* feat: create MatchPasswordField.tsx

* chore: add new components

* refactor: update GradientButton.tsx

* fix: remove unused prop

* refactor: enhance PasswordInput.tsx

* fix: wrong support Email address

* fix: image color issue

* fix: ci build issue
  • Loading branch information
AMIRKHANEF authored Jan 7, 2025
1 parent f0a36a3 commit 3558cf3
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 78 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/extension-polkagate/src/assets/gif/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
// @ts-nocheck

export { default as handWave } from './handWave.gif';
export { default as Lock } from './Lock.gif';
44 changes: 44 additions & 0 deletions packages/extension-polkagate/src/components/DecisionButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2019-2025 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

/* eslint-disable react/jsx-max-props-per-line */
import { ArrowForwardIosRounded as ArrowForwardIosRoundedIcon } from '@mui/icons-material';
import { Container, useTheme } from '@mui/material';
import React from 'react';

import { GradientButton, NeonButton } from '.';

interface Props {
arrow?: boolean;
onPrimaryClick: () => void;
onSecondaryClick: () => void;
primaryBtnText: string;
secondaryBtnText: string;
disabled?: boolean;
}

function DecisionButtons ({ arrow = false, disabled, onPrimaryClick, onSecondaryClick, primaryBtnText, secondaryBtnText }: Props): React.ReactElement {
const theme = useTheme();

return (
<Container disableGutters sx={{ alignItems: 'center', columnGap: '5px', display: 'flex', justifyContent: 'space-between' }}>
<NeonButton
contentPlacement='center'
onClick={onSecondaryClick}
style={{ height: '44px', width: '30%' }}
text={secondaryBtnText}
/>
<GradientButton
disabled={disabled}
endIconNode={arrow
? <ArrowForwardIosRoundedIcon sx={{ color: 'text.primary', fontSize: '13px', stroke: `${theme.palette.text.primary}`, strokeWidth: 1.1, zIndex: 10 }} />
: undefined}
onClick={onPrimaryClick}
style={{ height: '48px', width: '70%' }}
text={primaryBtnText}
/>
</Container>
);
}

export default DecisionButtons;
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ interface Props {
disabled?: boolean;
onClick: React.MouseEventHandler<HTMLButtonElement>;
StartIcon?: Icon;
startIconNode?: React.ReactNode;
EndIcon?: Icon;
endIconNode?: React.ReactNode;
text: string;
contentPlacement?: 'start' | 'center' | 'end';
style?: React.CSSProperties;
}

export default function GradientButton ({ EndIcon, StartIcon, contentPlacement = 'center', disabled, onClick, style, text }: Props): React.ReactElement<Props> {
export default function GradientButton ({ EndIcon, StartIcon, contentPlacement = 'center', disabled, endIconNode, onClick, startIconNode, style, text }: Props): React.ReactElement<Props> {
const theme = useTheme();

const [hovered, setHovered] = useState<boolean>(false);
Expand All @@ -39,6 +41,7 @@ export default function GradientButton ({ EndIcon, StartIcon, contentPlacement =
opacity: disabled ? 0.3 : 1,
paddingInline: '24px',
position: 'relative',
transition: 'all 250ms ease-out',
...style
} as SxProps<Theme>;

Expand Down Expand Up @@ -76,10 +79,12 @@ export default function GradientButton ({ EndIcon, StartIcon, contentPlacement =
return (
<Grid component='button' container item onClick={disabled ? noop : onClick} onMouseEnter={toggleHovered} onMouseLeave={toggleHovered} sx={GradientButtonStyle}>
{StartIcon && <StartIcon color={theme.palette.text.primary} size='20' style={{ zIndex: 10 }} variant='Bulk' />}
<Typography sx={{ fontFamily: 'Inter', fontSize: '14px', fontWeight: 600, px: '10px', width: 'fit-content', zIndex: 10 }}>
{startIconNode && startIconNode}
<Typography sx={{ fontFamily: 'Inter', fontSize: '14px', fontWeight: 600, pl: '10px', pr: '2px', width: 'fit-content', zIndex: 10 }}>
{text}
</Typography>
{EndIcon && <EndIcon color={theme.palette.text.primary} size='20' style={{ zIndex: 10 }} variant='Bulk' />}
{endIconNode && endIconNode}
<Grid sx={GradientBackground}></Grid>
</Grid>
);
Expand Down
40 changes: 12 additions & 28 deletions packages/extension-polkagate/src/components/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@ import type { Theme } from '@mui/material';
import { Box, Grid, useTheme } from '@mui/material';
import React, { useEffect, useMemo, useState } from 'react';

import { logoBlack, logoMotionDark, logoMotionLight, logoWhite } from '../assets/logos';
import { logoMotionDark, logoMotionLight } from '../assets/logos';
import { useExtensionLockContext } from '../context/ExtensionLockContext';
import useIsExtensionPopup from '../hooks/useIsExtensionPopup';
import AskToSetPassword from '../popup/passwordManagement/AskToSetPassword';
import { STEPS } from '../popup/passwordManagement/constants';
import FirstTimeSetPassword from '../popup/passwordManagement/FirstTimeSetPassword';
import ForgotPasswordConfirmation from '../popup/passwordManagement/ForgotPasswordConfirmation';
import Login from '../popup/passwordManagement/Login';
import PasswordSettingAlert from '../popup/passwordManagement/PasswordSettingAlert';
import { ALLOWED_URL_ON_RESET_PASSWORD, MAYBE_LATER_PERIOD, NO_PASS_PERIOD } from '../util/constants';

// import FirstTimeSetPassword from '../popup/passwordManagement/FirstTimeSetPassword';

interface Props {
children?: React.ReactNode;
}
Expand Down Expand Up @@ -100,13 +98,13 @@ export const setStorage = (label: string, data: unknown, stringify = false) => {

const MAX_WAITING_TIME = 1000; // ms

const StillLogo = ({ theme }: { theme: Theme }) => (
<Box
component='img'
src={theme.palette.mode === 'dark' ? logoBlack as string : logoWhite as string}
sx={{ height: 'fit-content', width: '37%' }}
/>
);
// const StillLogo = ({ theme }: { theme: Theme }) => (
// <Box
// component='img'
// src={theme.palette.mode === 'dark' ? logoBlack as string : logoWhite as string}
// sx={{ height: 'fit-content', width: '37%' }}
// />
// );

const FlyingLogo = ({ theme }: { theme: Theme }) => (
<Grid alignItems='center' container item justifyContent='center' sx={{ height: '100vh', width: '100%' }}>
Expand Down Expand Up @@ -225,16 +223,6 @@ export default function Loading ({ children }: Props): React.ReactElement<Props>
setStep={setStep}
/>
}
{step === STEPS.SET_PASSWORD &&
<>
<Grid container item justifyContent='center' mt='33px' my='35px'>
<StillLogo theme={theme} />
</Grid>
<Grid container sx={{ position: 'absolute', top: '165px' }}>
<PasswordSettingAlert />
</Grid>
</>
}
<Grid container item>
{isFlying && isPopupOpenedByExtension
? <FlyingLogo theme={theme} />
Expand All @@ -245,13 +233,9 @@ export default function Loading ({ children }: Props): React.ReactElement<Props>
/>
}
{step === STEPS.SET_PASSWORD &&
// <FirstTimeSetPassword
// hashedPassword={hashedPassword}
// onPassChange={onPassChange}
// setHashedPassword={setHashedPassword}
// setStep={setStep}
// />
<></>
<FirstTimeSetPassword
setStep={setStep}
/>
}
{step !== undefined && [STEPS.SHOW_LOGIN].includes(step) &&
<Login
Expand Down
116 changes: 116 additions & 0 deletions packages/extension-polkagate/src/components/MatchPasswordField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2019-2025 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

/* eslint-disable react/jsx-max-props-per-line */

import { Container } from '@mui/material';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { blake2AsHex } from '@polkadot/util-crypto';

import { useTranslation } from '../hooks';
import { PasswordInput } from '.';

interface Props {
onSetPassword?: () => Promise<void>;
statusSetter?: React.Dispatch<React.SetStateAction<PASSWORD_STATUS | undefined>>;
hashPassword?: boolean;
setConfirmedPassword: React.Dispatch<React.SetStateAction<string | undefined>>;
style?: React.CSSProperties;
focused?: boolean;
}

export enum PASSWORD_STATUS {
EMPTY_PASS,
EMPTY_REPEATED,
NOT_MATCHED,
WEEK_PASS,
MATCHED
}

const MIN_LENGTH = 6;

function MatchPasswordField ({ focused = false, hashPassword = false, onSetPassword, setConfirmedPassword, statusSetter, style }: Props): React.ReactElement {
const { t } = useTranslation();

const [password, setPassword] = useState<string>();
const [repeatPassword, setRepeatPassword] = useState<string>();

const passwordStatus = useMemo(() => {
if (!password) {
return PASSWORD_STATUS.EMPTY_PASS;
}

if (password.length < MIN_LENGTH) {
return PASSWORD_STATUS.WEEK_PASS;
}

if (!repeatPassword) {
return PASSWORD_STATUS.EMPTY_REPEATED;
}

if (password === repeatPassword) {
return PASSWORD_STATUS.MATCHED;
}

return PASSWORD_STATUS.NOT_MATCHED;
}, [password, repeatPassword]);

useEffect(() => {
statusSetter?.(passwordStatus);

if (passwordStatus === PASSWORD_STATUS.MATCHED && password) {
const finalPassword = hashPassword
? blake2AsHex(password, 256) // Hash the string with a 256-bit output
: password;

setConfirmedPassword(finalPassword);
} else {
setConfirmedPassword(undefined);
}
}, [hashPassword, password, passwordStatus, setConfirmedPassword, statusSetter]);

const handlePasswordChange = useCallback((pass: string | null): void => {
if (!pass) {
return setPassword(undefined);
}

setPassword(pass);
}, []);

const handleRepeatPasswordChange = useCallback((pass: string | null): void => {
if (!pass) {
return setRepeatPassword(undefined);
}

setRepeatPassword(pass);
}, []);

const handleConfirm = useCallback(() => {
if (passwordStatus === PASSWORD_STATUS.MATCHED && onSetPassword) {
try {
onSetPassword().catch(console.error);
} catch (error) {
console.error('Error setting password:', error);
}
}
}, [onSetPassword, passwordStatus]);

return (
<Container disableGutters sx={style}>
<PasswordInput
focused={focused}
onPassChange={handlePasswordChange}
style={{ marginBottom: '18px' }}
title={t('Password')}
/>
<PasswordInput
onEnterPress={handleConfirm}
onPassChange={handleRepeatPasswordChange}
title={t('Repeat the password')}
/>
</Container>
);
}

export default MatchPasswordField;
4 changes: 1 addition & 3 deletions packages/extension-polkagate/src/components/NeonButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ interface Props {
EndIcon?: Icon;
contentPlacement?: 'start' | 'center' | 'end';
text: string | { firstPart?: string; secondPart?: string; };
variant?: 'text' | 'contained' | 'outlined';
style?: React.CSSProperties;
}

export default function NeonButton ({ EndIcon, StartIcon, contentPlacement = 'start', disabled, isBusy, onClick, style, text, variant }: Props): React.ReactElement<Props> {
export default function NeonButton ({ EndIcon, StartIcon, contentPlacement = 'start', disabled, isBusy, onClick, style, text }: Props): React.ReactElement<Props> {
const theme = useTheme();

const [hovered, setHovered] = useState(false);
Expand Down Expand Up @@ -94,7 +93,6 @@ export default function NeonButton ({ EndIcon, StartIcon, contentPlacement = 'st
? <StartIcon size={20} variant={hovered ? 'Bold' : 'Bulk'} />
: undefined}
sx={{ ...GeneralButtonStyle, ...StartIconStyle, ...EndIconStyle, ...style }}
variant={variant}
>
{renderText}
</Button>
Expand Down
15 changes: 9 additions & 6 deletions packages/extension-polkagate/src/components/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/* eslint-disable react/jsx-max-props-per-line */

import { IconButton, InputAdornment, styled, TextField, Typography, useTheme } from '@mui/material';
import { Container, IconButton, InputAdornment, styled, TextField, Typography, useTheme } from '@mui/material';
import { Check, Eye, EyeSlash } from 'iconsax-react';
import React, { useCallback, useState } from 'react';

Expand Down Expand Up @@ -57,14 +57,16 @@ interface Props {
title?: string;
onPassChange: (pass: string) => void;
onEnterPress?: () => void;
style?: React.CSSProperties;
focused?: boolean;
}

export default function PasswordInput ({ onEnterPress, onPassChange, title }: Props) {
export default function PasswordInput ({ focused = false, onEnterPress, onPassChange, style, title }: Props): React.ReactElement {
const { t } = useTranslation();
const theme = useTheme();

const [showPassword, setShowPassword] = useState<boolean>(false);
const [focused, setFocused] = useState<boolean>(false);
const [focusing, setFocused] = useState<boolean>(false);

const toggle = useCallback(() => setFocused((isFocused) => !isFocused), []);

Expand All @@ -81,7 +83,7 @@ export default function PasswordInput ({ onEnterPress, onPassChange, title }: Pr
}, [onEnterPress]);

return (
<>
<Container disableGutters sx={style}>
{title &&
<Typography fontFamily='Inter' fontSize='13px' fontWeight={500} mb='12px' textAlign='left' width='100%'>
{title}
Expand All @@ -106,10 +108,11 @@ export default function PasswordInput ({ onEnterPress, onPassChange, title }: Pr
),
startAdornment: (
<InputAdornment position='start'>
<Check color={focused ? '#3988FF' : '#AA83DC'} size='22' variant={focused ? 'Bold' : 'Bulk'} />
<Check color={focusing ? '#3988FF' : '#AA83DC'} size='22' variant={focusing ? 'Bold' : 'Bulk'} />
</InputAdornment>
)
}}
autoFocus={focused}
fullWidth
onBlur={toggle}
onChange={onChange}
Expand All @@ -119,6 +122,6 @@ export default function PasswordInput ({ onEnterPress, onPassChange, title }: Pr
theme={theme}
type={showPassword ? 'text' : 'password'}
/>
</>
</Container>
);
}
2 changes: 2 additions & 0 deletions packages/extension-polkagate/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export { default as ColorContext } from './ColorContext';
export * from './contexts';
export { default as Convictions } from './Convictions';
export { default as CopyAddressButton } from './CopyAddressButton';
export { default as DecisionButtons } from './DecisionButtons';
export { default as DisplayInfo } from './DisplayInfo';
export { default as ExtensionPopup } from './ExtensionPopup';
export { default as FormatBalance } from './FormatBalance';
Expand All @@ -59,6 +60,7 @@ export { default as Label } from './Label';
export { default as Label2 } from './Label2';
export { default as Loading } from './Loading';
export { default as Main } from './Main';
export { default as MatchPasswordField } from './MatchPasswordField';
export { default as MenuItem } from './MenuItem';
export { default as MnemonicSeed } from './MnemonicSeed';
export { default as Motion } from './Motion';
Expand Down
4 changes: 2 additions & 2 deletions packages/extension-polkagate/src/partials/PrivacyPolicy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ function PrivacyPolicy ({ openMenu, setPopup }: Props): React.ReactElement {
<span style={{ color: theme.palette.text.secondary, fontFamily: 'Inter', fontSize: '13px', fontWeight: 500, textAlign: 'left' }}>
{t('If you have any questions, please contact us at ')}
</span>
<Link href='mailto:polkagate@outlook.com' sx={{ color: 'text.secondary', textDecoration: 'underline' }}>
{'polkagate@outlook.com'}
<Link href='mailto:support@polkagate.xyz' sx={{ color: 'text.secondary', textDecoration: 'underline' }}>
{'support@polkagate.xyz'}
</Link>
<span style={{ color: theme.palette.text.secondary, fontFamily: 'Inter', fontSize: '13px', fontWeight: 500, textAlign: 'left' }}>
{t(' or follow us on our social media accounts.')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function AskToSetPassword ({ setStep }: Props): React.ReactElement {
<Container disableGutters sx={{ position: 'relative' }}>
<Header />
<GradientBox noGradient style={{ m: 'auto', mt: '8px', width: '359px' }}>
<RedGradient style={{ right: '-8%', top: '20px' }} />
<RedGradient style={{ right: '-8%', top: '20px', zIndex: -1 }} />
<Grid container item justifyContent='center' sx={{ p: '18px 32px 32px' }}>
<Box
component='img'
Expand Down
Loading

0 comments on commit 3558cf3

Please sign in to comment.