diff --git a/package-lock.json b/package-lock.json index e195524..f0b2d0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "socious-wallet", - "version": "1.6.6", + "version": "1.6.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "socious-wallet", - "version": "1.6.6", + "version": "1.6.7", "dependencies": { "@capacitor/app": "^6.0.0", "@capacitor/device": "^6.0.0", "@datadog/browser-rum": "^5.23.0", + "@hookform/resolvers": "^3.9.0", "@hyperledger/identus-edge-agent-sdk": "^6.1.0", "@pluto-encrypted/indexdb": "^1.12.2", "@reduxjs/toolkit": "^2.2.7", @@ -42,6 +43,7 @@ "react-bootstrap": "^2.10.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", + "react-hook-form": "^7.53.0", "react-i18next": "^15.0.2", "react-redux": "^9.1.2", "react-router-dom": "^6.22.0", @@ -49,7 +51,8 @@ "text-encoding": "^0.7.0", "typescript": "^4.9.5", "wasm-loader": "^1.3.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "yup": "^1.4.0" }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", @@ -3371,6 +3374,14 @@ "node": ">=10" } }, + "node_modules/@hookform/resolvers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", + "integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -22656,6 +22667,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "node_modules/protobufjs": { "version": "6.11.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", @@ -23139,6 +23155,21 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-hook-form": { + "version": "7.53.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz", + "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", @@ -26334,6 +26365,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -26375,6 +26411,11 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/touch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", @@ -28301,6 +28342,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/z-schema": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-6.0.1.tgz", @@ -30574,6 +30637,12 @@ } } }, + "@hookform/resolvers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", + "integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -44395,6 +44464,11 @@ } } }, + "property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "protobufjs": { "version": "6.11.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", @@ -44767,6 +44841,12 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-hook-form": { + "version": "7.53.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz", + "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==", + "requires": {} + }, "react-i18next": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", @@ -47175,6 +47255,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -47204,6 +47289,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "touch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", @@ -48661,6 +48751,24 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, + "yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "requires": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } + }, "z-schema": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-6.0.1.tgz", diff --git a/package.json b/package.json index 5e70350..b4f46d1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@capacitor/app": "^6.0.0", "@capacitor/device": "^6.0.0", "@datadog/browser-rum": "^5.23.0", + "@hookform/resolvers": "^3.9.0", "@hyperledger/identus-edge-agent-sdk": "^6.1.0", "@pluto-encrypted/indexdb": "^1.12.2", "@reduxjs/toolkit": "^2.2.7", @@ -37,6 +38,7 @@ "react-bootstrap": "^2.10.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", + "react-hook-form": "^7.53.0", "react-i18next": "^15.0.2", "react-redux": "^9.1.2", "react-router-dom": "^6.22.0", @@ -44,7 +46,8 @@ "text-encoding": "^0.7.0", "typescript": "^4.9.5", "wasm-loader": "^1.3.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "yup": "^1.4.0" }, "scripts": { "start": "craco start", diff --git a/public/icons/open-eye.svg b/public/icons/open-eye.svg new file mode 100644 index 0000000..06ce77b --- /dev/null +++ b/public/icons/open-eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg index 63f81ad..fff1f48 100644 --- a/src/assets/images/logo.svg +++ b/src/assets/images/logo.svg @@ -1,10 +1,34 @@ - - - + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Input/index.module.scss b/src/components/Input/index.module.scss new file mode 100644 index 0000000..8e1ee5a --- /dev/null +++ b/src/components/Input/index.module.scss @@ -0,0 +1,55 @@ +.label { + font-size: 14px; + font-weight: 500; + line-height: 20px; + color: $color-gray-4; +} + +.container { + border: 1px solid $color-white-5; + border-radius: 8px; + box-shadow: 0px 1px 2px 0px $box-shadow; + + &:focus-within { + border-color: $primary; + box-shadow: 0 0 0 0.25rem $box-shadow-primary; + } + + .input { + background-color: $white; + border: none; + padding: 0.625rem 0.875rem; + + &:hover, + &:focus { + border: none; + box-shadow: none; + background-color: $white; + } + + &--borderless { + border-right: 0; + } + } + + .postfix { + background-color: $white; + border: none; + padding: 0.625rem 1rem; + cursor: pointer; + } +} + +.hint, +.error { + margin-top: 0.375rem; + margin-bottom: 0 !important; + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: $gray; +} + +.error { + color: $color-red-2; +} diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx new file mode 100644 index 0000000..4618d61 --- /dev/null +++ b/src/components/Input/index.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { Form, InputGroup } from 'react-bootstrap'; +import Icon from '../Icon'; +import { InputProps } from './index.types'; +import styles from './index.module.scss'; + +const Input: React.FC = ({ + value, + onChange, + register, + name = '', + type = 'text', + label, + placeholder, + hasPostfixIcon = false, + postfixIcon, + onPostfixClick, + hintMessage = '', + errorMessage = '', +}) => { + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + const inputType = type === 'password' && (isPasswordVisible ? 'text' : 'password'); + + const handlePostfixClick = () => { + if (onPostfixClick) { + onPostfixClick?.(); + } + if (type === 'password') { + setIsPasswordVisible(!isPasswordVisible); + } + }; + + return ( + + {label && {label}} + + + {hasPostfixIcon && ( + + {type === 'password' ? : postfixIcon} + + )} + + {hintMessage &&

{hintMessage}

} + {errorMessage &&

{errorMessage}

} + + ); +}; + +export default Input; diff --git a/src/components/Input/index.types.ts b/src/components/Input/index.types.ts new file mode 100644 index 0000000..c492fa0 --- /dev/null +++ b/src/components/Input/index.types.ts @@ -0,0 +1,16 @@ +import { ChangeEvent } from 'react'; + +export interface InputProps { + register?: any; + name?: string; + value?: string; + onChange?: (event: ChangeEvent) => void; + type?: 'text' | 'password'; + label?: string; + placeholder?: string; + hasPostfixIcon?: boolean; + postfixIcon?: React.ReactNode; + onPostfixClick?: () => void; + hintMessage?: string; + errorMessage?: string; +} diff --git a/src/pages/Backup/index.module.scss b/src/pages/Backup/index.module.scss new file mode 100644 index 0000000..b5df3ee --- /dev/null +++ b/src/pages/Backup/index.module.scss @@ -0,0 +1,76 @@ +.card { + &__container { + padding-top: env(safe-area-inset-top) !important; + padding: 0; + height: 46rem; + width: 27.5rem; + + @include media-breakpoint-down(md) { + height: 100%; + width: 100%; + } + } + + &__header { + width: 100%; + @include flex(space-between, center); + padding: 1.5rem 1rem; + + .back { + @include flex(normal, center); + gap: 0.25rem; + font-size: 14px; + font-weight: 600; + color: $gray; + } + } + + &__content { + @include flex(center, stretch, column); + gap: 1.5rem; + width: 100%; + flex: 1; + padding: 0 1rem; + height: 100%; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0.25rem; + } + + &::-webkit-scrollbar-thumb { + background-color: $primary; + border-radius: 0.5rem; + } + + .title { + @include flex(center, center, column); + gap: 0.5rem; + font-size: 16px; + font-weight: 400; + line-height: 24px; + color: $gray; + text-align: center; + + &--bold { + font-size: 24px; + font-weight: 600; + line-height: 32px; + color: $black; + } + } + } +} + +.error { + font-size: 16px; + line-height: 24px; + font-weight: 500; + color: $color-red-2; +} + +.button { + font-weight: 500 !important; + margin: 0.5rem 1rem 1.5rem; + width: calc(100% - 2rem); +} diff --git a/src/pages/Backup/index.services.ts b/src/pages/Backup/index.services.ts new file mode 100644 index 0000000..e08ba6f --- /dev/null +++ b/src/pages/Backup/index.services.ts @@ -0,0 +1,54 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import i18next from 'i18next'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; +import { passwordPattern } from 'src/utilities'; + +const schema = yup + .object() + .shape({ + newPass: yup + .string() + .required(i18next.t('backup-form.new-pass-error1')) + .min(8, i18next.t('backup-form.new-pass-error2')) + .matches(passwordPattern, i18next.t('backup-form.new-pass-error3')), + confirmPass: yup + .string() + .required(i18next.t('backup-form.confirm-pass-error1')) + .oneOf([yup.ref('newPass')], i18next.t('backup-form.confirm-pass-error2')), + }) + .required(); + +export const useBackup = () => { + const { t: translate } = useTranslation(); + const navigate = useNavigate(); + const [errorMessage, setErrorMessage] = useState(''); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = () => { + try { + //TODO encrypt seed-phrases + password and write into file to download + } catch { + setErrorMessage(translate('backup-error')); + } + }; + + return { + translate, + navigate, + register, + errors, + handleSubmit, + onSubmit, + errorMessage, + }; +}; diff --git a/src/pages/Backup/index.tsx b/src/pages/Backup/index.tsx new file mode 100644 index 0000000..980e24d --- /dev/null +++ b/src/pages/Backup/index.tsx @@ -0,0 +1,56 @@ +import { Button } from 'react-bootstrap'; +import Card from 'src/components/Card'; +import Icon from 'src/components/Icon'; +import Input from 'src/components/Input'; +import NavigationBar from 'src/containers/NavigationBar'; +import { useBackup } from './index.services'; +import styles from './index.module.scss'; + +function Backup() { + const { translate, navigate, register, errors, handleSubmit, onSubmit, errorMessage } = useBackup(); + + return ( +
+ +
+
navigate('/settings')}> + + {translate('backup-back-link')} +
+
+
+
+

{translate('backup-title')}

+ {translate('backup-subtitle')} +
+ + +
+ {errorMessage} + + +
+
+ ); +} + +export default Backup; diff --git a/src/pages/Intro/index.module.scss b/src/pages/Intro/index.module.scss index a1e4b1a..9c0982f 100644 --- a/src/pages/Intro/index.module.scss +++ b/src/pages/Intro/index.module.scss @@ -1,21 +1,20 @@ -//FIXME: bootstrap's predefined text color not work -.card { - &__text { - color: $gray; - text-align: center; - line-height: 1.5rem; - } - - &__subtitle { - text-align: center; - } +.logo { + border-radius: 12px; +} - &__secondary_btn { - color: $gray !important; - } +.title { + font-size: 24px; + font-weight: bold; + line-height: 32px; + color: $black; + text-align: center; +} - &__secondary_btn:hover { - color: $gray !important; - background-color: $light-rgb !important; - } +.subtitle { + @include flex(center, center, column); + font-size: 16px; + font-weight: 400; + line-height: 24px; + color: $gray; + text-align: center; } diff --git a/src/pages/Intro/index.tsx b/src/pages/Intro/index.tsx index 72badfe..36f8869 100644 --- a/src/pages/Intro/index.tsx +++ b/src/pages/Intro/index.tsx @@ -9,8 +9,9 @@ function Intro() { const { t: translate } = useTranslation(); const navigate = useNavigate(); const { state } = useAppContext(); - if (state.did) return ; + const { did } = state || {}; + if (did) return ; return (
navigate('/register'), + onClick: () => navigate('/setup-pass'), }, { children: translate('intro-restore-button'), - variant: 'inherit', - className: `fw-bold w-100 py-2 ${styles['card__secondary_btn']}`, + variant: 'light', + className: 'fw-bold w-100 py-2', onClick: () => navigate('/import'), }, ]} >
- Socious + Socious
-

{translate('intro-welcome')}

-
-
{translate('intro-title')}
-
{translate('intro-subtitle')}
+

{translate('intro-welcome')}

+
+ {translate('intro-title')} + {translate('intro-subtitle')}
diff --git a/src/pages/Settings/index.services.ts b/src/pages/Settings/index.services.ts index e673790..2bbb0c5 100644 --- a/src/pages/Settings/index.services.ts +++ b/src/pages/Settings/index.services.ts @@ -6,9 +6,11 @@ import { APP_VERSION } from 'src/config'; import { RootState } from 'src/store/redux'; import { setLanguage, setSystemLanguage } from 'src/store/redux/language.reducers'; import { languages } from 'src/constants/languages'; +import { useNavigate } from 'react-router-dom'; const useSettings = () => { const { t: translate } = useTranslation(); + const navigate = useNavigate(); const dispatch = useDispatch(); const { language, system } = useSelector((state: RootState) => state.language); const { state } = useAppContext(); @@ -18,9 +20,15 @@ const useSettings = () => { open: false, }); const settingsItems = [ + { + title: translate('settings-items.backup'), + action: () => navigate('/backup'), + clickable: true, + }, { title: translate('settings-items.remove'), action: () => setOpenModal({ name: 'remove', open: true }), + clickable: true, }, // { // title: 'Contact us', @@ -34,14 +42,17 @@ const useSettings = () => { title: translate('settings-items.language'), value: system ? translate('settings-items.automatic') : languages.find(lang => lang.value === language)?.original, action: () => setOpenModal({ name: 'language', open: true }), + clickable: true, }, { title: translate('settings-items.version'), subtitle: APP_VERSION, + clickable: false, }, { title: translate('settings-items.platform'), subtitle: device.platform, + clickable: false, }, ]; diff --git a/src/pages/Settings/index.tsx b/src/pages/Settings/index.tsx index 763bd5a..1b81660 100644 --- a/src/pages/Settings/index.tsx +++ b/src/pages/Settings/index.tsx @@ -38,12 +38,10 @@ function Settings() { {item.title} {item.subtitle && {item.subtitle}}
- {item.value && ( -
- {item.value} - -
- )} +
+ {item.value} + {item.clickable && } +
))} diff --git a/src/router/index.tsx b/src/router/index.tsx index 67e13d3..a77df31 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -17,6 +17,7 @@ import AppUrlListener from 'src/containers/AppUrlListener'; import Settings from 'src/pages/Settings'; import SetupPass from 'src/pages/SetupPass'; import CreatePass from 'src/pages/CreatePass'; +import Backup from 'src/pages/Backup'; import { config } from 'src/config'; export const blueprint: RouteObject[] = [ @@ -33,7 +34,8 @@ export const blueprint: RouteObject[] = [ { path: '/intro', element: }, { path: '/setup-pass', element: }, { path: '/create-pass', element: }, - { path: '/register', element: }, + { path: '/backup', element: }, + // { path: '/register', element: }, // { path: '/confirm', element: }, { path: '/created', element: }, { path: '/verify', element: }, diff --git a/src/styles/constants/_variables.scss b/src/styles/constants/_variables.scss index a753f38..0fb06a3 100644 --- a/src/styles/constants/_variables.scss +++ b/src/styles/constants/_variables.scss @@ -26,20 +26,24 @@ $badge-font-weight: 400; //My Variables //Colors $box-shadow: #1018280f; +$box-shadow-primary: #004a4640; $border-color: #eaecf0; $color-gray-1: #161b26; $color-gray-2: #333741; $color-gray-3: #94969c; +$color-gray-4: #344054; $color-white-1: #f2f4f7; $color-white-2: #cecfd2; $color-white-3: #f0f1f1; $color-white-4: #f5f5f6; +$color-white-5: #d0d5dd; $color-blue-1: #0c111d; $color-red-1: #f97066; +$color-red-2: #d92d20; //Borders $border-lg: 12px; diff --git a/src/translations/locales/en/backup.json b/src/translations/locales/en/backup.json new file mode 100644 index 0000000..45d4c7b --- /dev/null +++ b/src/translations/locales/en/backup.json @@ -0,0 +1,19 @@ +{ + "backup-title": "Create a password", + "backup-subtitle": "Create a password to keep your backup file safe. You will need it when importing your wallet to a new device.", + "backup-back-link": "Back", + "backup-form": { + "new-pass-label": "Set a password", + "new-pass-placeholder": "New Password", + "new-pass-hint": "Your password must be at least 8 characters with lowercase, uppercase, and a special character.", + "new-pass-error1": "Password is required", + "new-pass-error2": "Minimum 8 characters", + "new-pass-error3": "Password complexity is week", + "confirm-pass-label": "Confirm your password", + "confirm-pass-placeholder": "Confirm Password", + "confirm-pass-error1": "Confirm password is required", + "confirm-pass-error2": "Passwords must match", + "button": "Continue" + }, + "backup-error": "File could not be saved. Please try again, and select a new location if necessary." +} diff --git a/src/translations/locales/en/settings.json b/src/translations/locales/en/settings.json index 30ebcb6..2243c13 100644 --- a/src/translations/locales/en/settings.json +++ b/src/translations/locales/en/settings.json @@ -1,6 +1,7 @@ { "settings-card-header": "Settings", "settings-items": { + "backup": "Backup wallet", "remove": "Remove wallet", "language": "Language", "automatic": "Automatic", diff --git a/src/translations/locales/en/translation.ts b/src/translations/locales/en/translation.ts index 2c21395..3ac6edf 100644 --- a/src/translations/locales/en/translation.ts +++ b/src/translations/locales/en/translation.ts @@ -10,6 +10,7 @@ import settings from './settings.json'; import credentials from './credentials.json'; import setupPass from './setup-pass.json'; import createPass from './create-pass.json'; +import backup from './backup.json'; import general from './general.json'; export function generateTranslationFile() { @@ -27,6 +28,7 @@ export function generateTranslationFile() { credentials, setupPass, createPass, + backup, general, ); } diff --git a/src/translations/locales/jp/backup.json b/src/translations/locales/jp/backup.json new file mode 100644 index 0000000..99b56ce --- /dev/null +++ b/src/translations/locales/jp/backup.json @@ -0,0 +1,19 @@ +{ + "backup-title": "パスワードを作成", + "backup-subtitle": "バックアップファイルを安全に保つためのパスワードを作成します。新しいデバイスにウォレットをインポートするときに必要です。", + "backup-back-link": "戻る", + "backup-form": { + "new-pass-label": "パスワードを設定", + "new-pass-placeholder": "新しいパスワード", + "new-pass-hint": "パスワードは8文字以上で、小文字、大文字、特殊文字を含める必要があります。", + "new-pass-error1": "パスワードは必須です", + "new-pass-error2": "最低8文字が必要です", + "new-pass-error3": "パスワードの複雑さが不十分です", + "confirm-pass-label": "パスワードを確認", + "confirm-pass-placeholder": "パスワードを確認", + "confirm-pass-error1": "確認用パスワードは必須です", + "confirm-pass-error2": "パスワードは一致する必要があります", + "button": "続ける" + }, + "backup-error": "ファイルを保存できませんでした。もう一度お試しください。必要に応じて、新しい場所を選択してください。" +} diff --git a/src/translations/locales/jp/settings.json b/src/translations/locales/jp/settings.json index c9c9633..ae7a6c9 100644 --- a/src/translations/locales/jp/settings.json +++ b/src/translations/locales/jp/settings.json @@ -1,6 +1,7 @@ { "settings-card-header": "設定", "settings-items": { + "backup": "バックアップウォレット", "remove": "ウォレットを削除", "language": "言語", "automatic": "自動", diff --git a/src/translations/locales/jp/translation.ts b/src/translations/locales/jp/translation.ts index 2c21395..3ac6edf 100644 --- a/src/translations/locales/jp/translation.ts +++ b/src/translations/locales/jp/translation.ts @@ -10,6 +10,7 @@ import settings from './settings.json'; import credentials from './credentials.json'; import setupPass from './setup-pass.json'; import createPass from './create-pass.json'; +import backup from './backup.json'; import general from './general.json'; export function generateTranslationFile() { @@ -27,6 +28,7 @@ export function generateTranslationFile() { credentials, setupPass, createPass, + backup, general, ); } diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 2326217..ece8971 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -49,3 +49,6 @@ export const logger = (error: Error, errorInfo: ErrorInfo) => { }); addError(error); }; + +//REGEX +export const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$%^&+=!])[A-Za-z\d@#$%^&+=!]{8,}$/;