From 7884e95bd336c85b7e60d14c0598630bfaeb859a Mon Sep 17 00:00:00 2001
From: rhahao <26148770+rhahao@users.noreply.github.com>
Date: Sun, 10 Nov 2024 12:39:38 +0300
Subject: [PATCH 1/2] feat(features): use stepper for congregation creation
workflow
---
converter/svg/convert.js | 7 +-
.../svg/sources/name=congregation-access.svg | 9 +
src/RootWrap.tsx | 3 +
.../icons/IconCongregationAccess.tsx | 56 ++++++
src/components/icons/index.ts | 3 +-
.../account_chooser/useAccountChooser.tsx | 12 +-
.../criteria/index.tsx | 0
.../congregation_access_code/index.tsx | 177 +++++++++++++++++
.../useCongregationAccessCode.tsx | 101 ++++++++++
.../index.tsx | 20 +-
.../useCongregationDetails.tsx} | 14 +-
.../criteria/index.tsx | 0
.../congregation_master_key/index.tsx | 179 ++++++++++++++++++
.../useCongregationMasterKey.tsx | 105 ++++++++++
.../vip/congregation_create/index.tsx | 71 +++++--
.../useCongregationCreate.tsx | 37 +++-
.../congregation_access_code/index.tsx | 110 +----------
.../useCongregationAccessCode.tsx | 79 +-------
.../congregation_master_key/index.tsx | 111 +----------
.../useCongregationMasterKey.tsx | 79 +-------
.../vip/oauth/button_base/useButtonBase.tsx | 10 +-
src/features/app_start/vip/startup/index.tsx | 12 +-
.../app_start/vip/startup/useStartup.tsx | 114 +++++++++--
.../app_start/vip/terms_use/useTermsUse.tsx | 2 +-
.../index.tsx | 15 +-
.../useUserAccountCreated.tsx | 19 ++
.../app_start/vip/vip_info_tip/index.tsx | 52 ++---
.../language_switcher/useLanguage.tsx | 10 +-
src/hooks/useUserAutoLogin.tsx | 14 +-
src/locales/en/onboarding.json | 5 +-
src/services/i18n/index.ts | 2 +-
src/states/app.ts | 12 +-
32 files changed, 967 insertions(+), 473 deletions(-)
create mode 100644 converter/svg/sources/name=congregation-access.svg
create mode 100644 src/components/icons/IconCongregationAccess.tsx
rename src/features/app_start/vip/{congregation_encryption => congregation_create}/congregation_access_code/criteria/index.tsx (100%)
create mode 100644 src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx
create mode 100644 src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx
rename src/features/app_start/vip/congregation_create/{congregation_info => congregation_details}/index.tsx (89%)
rename src/features/app_start/vip/congregation_create/{congregation_info/useCongregationInfo.tsx => congregation_details/useCongregationDetails.tsx} (94%)
rename src/features/app_start/vip/{congregation_encryption => congregation_create}/congregation_master_key/criteria/index.tsx (100%)
create mode 100644 src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx
create mode 100644 src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx
rename src/features/app_start/vip/{congregation_create/account_created => user_account_created}/index.tsx (81%)
create mode 100644 src/features/app_start/vip/user_account_created/useUserAccountCreated.tsx
diff --git a/converter/svg/convert.js b/converter/svg/convert.js
index 23acba71f2..f1f20bcedb 100644
--- a/converter/svg/convert.js
+++ b/converter/svg/convert.js
@@ -63,9 +63,12 @@ for await (const svgFile of svgFiles) {
);
if (componentName === 'IconLoading') {
- data.replace(' animation,', ' animation: rotate 2s linear infinite,');
+ data = data.replace(
+ ' animation,',
+ ' animation: "rotate 2s linear infinite",'
+ );
} else {
- data.replace(' animation,', '');
+ data = data.replace(' animation,', '');
}
data = data.replace('${iconName}', originalName);
diff --git a/converter/svg/sources/name=congregation-access.svg b/converter/svg/sources/name=congregation-access.svg
new file mode 100644
index 0000000000..01ef3ab154
--- /dev/null
+++ b/converter/svg/sources/name=congregation-access.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/RootWrap.tsx b/src/RootWrap.tsx
index fcbdaab2ab..2d80078baa 100644
--- a/src/RootWrap.tsx
+++ b/src/RootWrap.tsx
@@ -54,6 +54,9 @@ const theme = createTheme({
span: {
fontFamily: `${font} !important`,
},
+ text: {
+ fontFamily: `${font} !important`,
+ },
},
},
},
diff --git a/src/components/icons/IconCongregationAccess.tsx b/src/components/icons/IconCongregationAccess.tsx
new file mode 100644
index 0000000000..afad858544
--- /dev/null
+++ b/src/components/icons/IconCongregationAccess.tsx
@@ -0,0 +1,56 @@
+import { SvgIcon, SxProps, Theme } from '@mui/material';
+
+type IconProps = {
+ color?: string;
+ width?: number;
+ height?: number;
+ sx?: SxProps;
+ className?: string;
+};
+
+const IconCongregationAccess = ({
+ color = '#222222',
+ width = 24,
+ height = 24,
+ sx = {},
+ className,
+}: IconProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default IconCongregationAccess;
diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts
index e1445f9929..3e72214d20 100644
--- a/src/components/icons/index.ts
+++ b/src/components/icons/index.ts
@@ -59,6 +59,7 @@ export { default as IconCompassOn } from './IconCompassOn';
export { default as IconComputerVideo } from './IconComputerVideo';
export { default as IconComputer } from './IconComputer';
export { default as IconConference } from './IconConference';
+export { default as IconCongregationAccess } from './IconCongregationAccess';
export { default as IconCongregation } from './IconCongregation';
export { default as IconContactUs } from './IconContactUs';
export { default as IconContact } from './IconContact';
@@ -186,8 +187,8 @@ export { default as IconPause } from './IconPause';
export { default as IconPermissionsPending } from './IconPermissionsPending';
export { default as IconPersonSearch } from './IconPersonSearch';
export { default as IconPerson } from './IconPerson';
-export { default as IconPersonPlaceholder } from './IconPersonPlaceholder';
export { default as IconPersonalDay } from './IconPersonalDay';
+export { default as IconPersonPlaceholder } from './IconPersonPlaceholder';
export { default as IconPhone } from './IconPhone';
export { default as IconPinCode } from './IconPinCode';
export { default as IconPin } from './IconPin';
diff --git a/src/features/app_start/shared/account_chooser/useAccountChooser.tsx b/src/features/app_start/shared/account_chooser/useAccountChooser.tsx
index 57bf36e23e..696e474a4f 100644
--- a/src/features/app_start/shared/account_chooser/useAccountChooser.tsx
+++ b/src/features/app_start/shared/account_chooser/useAccountChooser.tsx
@@ -1,15 +1,21 @@
-import { setIsAccountChoose } from '@services/recoil/app';
import { dbAppSettingsUpdate } from '@services/dexie/settings';
+import { useSetRecoilState } from 'recoil';
+import { isAccountChooseState, isUserAccountCreatedState } from '@states/app';
const useAccountChooser = () => {
+ const setIsAccountChoose = useSetRecoilState(isAccountChooseState);
+ const setIsUserAccountCreated = useSetRecoilState(isUserAccountCreatedState);
+
const handleChoosePocket = async () => {
await dbAppSettingsUpdate({ 'user_settings.account_type': 'pocket' });
- await setIsAccountChoose(false);
+ setIsUserAccountCreated(false);
+ setIsAccountChoose(false);
};
const handleChooseVIP = async () => {
await dbAppSettingsUpdate({ 'user_settings.account_type': 'vip' });
- await setIsAccountChoose(false);
+ setIsUserAccountCreated(false);
+ setIsAccountChoose(false);
};
return { handleChoosePocket, handleChooseVIP };
diff --git a/src/features/app_start/vip/congregation_encryption/congregation_access_code/criteria/index.tsx b/src/features/app_start/vip/congregation_create/congregation_access_code/criteria/index.tsx
similarity index 100%
rename from src/features/app_start/vip/congregation_encryption/congregation_access_code/criteria/index.tsx
rename to src/features/app_start/vip/congregation_create/congregation_access_code/criteria/index.tsx
diff --git a/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx b/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx
new file mode 100644
index 0000000000..c7583a2816
--- /dev/null
+++ b/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx
@@ -0,0 +1,177 @@
+import { Box } from '@mui/material';
+import { IconCongregationAccess, IconError, IconLoading } from '@icons/index';
+import { useAppTranslation } from '@hooks/index';
+import useCongregationAccessCode from './useCongregationAccessCode';
+import Button from '@components/button';
+import Criteria from './criteria';
+import InfoMessage from '@components/info-message';
+import TextField from '@components/textfield';
+import Typography from '@components/typography';
+import VipInfoTip from '@features/app_start/vip/vip_info_tip';
+
+const CongregationAccessCode = () => {
+ const { t } = useAppTranslation();
+
+ const {
+ isLengthPassed,
+ isNumberPassed,
+ isLowerCasePassed,
+ isUpperCasePassed,
+ isSpecialSymbolPassed,
+ isProcessing,
+ hideMessage,
+ message,
+ title,
+ variant,
+ isMatch,
+ setTmpAccessCode,
+ setTmpAccessCodeVerify,
+ tmpAccessCode,
+ tmpAccessCodeVerify,
+ btnActionDisabled,
+ handleSetAccessCode,
+ } = useCongregationAccessCode();
+
+ return (
+
+
+
+
+
+ {t('tr_congregationAccessCodeNotice')}
+
+
+
+
+
+ setTmpAccessCode(e.target.value)}
+ startIcon={ }
+ resetHelperPadding={true}
+ />
+ setTmpAccessCodeVerify(e.target.value)}
+ startIcon={ }
+ resetHelperPadding={true}
+ helperText={
+
+
+
+
+
+
+
+
+ }
+ />
+
+
+ : null
+ }
+ disabled={btnActionDisabled}
+ >
+ {t('tr_congregationAccessCodeSet')}
+
+
+
+
+
+
+
+
+ }
+ messageHeader={title}
+ message={message}
+ onClose={hideMessage}
+ />
+
+
+
+ );
+};
+
+export default CongregationAccessCode;
diff --git a/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx b/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx
new file mode 100644
index 0000000000..28309fbb5f
--- /dev/null
+++ b/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx
@@ -0,0 +1,101 @@
+import { useEffect, useState } from 'react';
+import { useAppTranslation } from '@hooks/index';
+import { encryptData, generateKey } from '@services/encryption/index';
+import { displayOnboardingFeedback } from '@services/recoil/app';
+import { getMessageByCode } from '@services/i18n/translation';
+import { apiSetCongregationAccessCode } from '@services/api/congregation';
+import { dbAppSettingsUpdate } from '@services/dexie/settings';
+import useFeedback from '@features/app_start/shared/hooks/useFeedback';
+
+const useCongregationAccessCode = () => {
+ const { t } = useAppTranslation();
+
+ const { hideMessage, message, showMessage, title, variant } = useFeedback();
+
+ const [tmpAccessCode, setTmpAccessCode] = useState('');
+ const [tmpAccessCodeVerify, setTmpAccessCodeVerify] = useState('');
+ const [isLengthPassed, setIsLengthPassed] = useState(false);
+ const [isNumberPassed, setIsNumberPassed] = useState(false);
+ const [isLowerCasePassed, setIsLowerCasePassed] = useState(false);
+ const [isUpperCasePassed, setIsUpperCasePassed] = useState(false);
+ const [isSpecialSymbolPassed, setIsSpecialSymbolPassed] = useState(false);
+ const [isMatch, setIsMatch] = useState(false);
+ const [isProcessing, setIsProcessing] = useState(false);
+
+ const btnActionDisabled =
+ !isLengthPassed ||
+ !isNumberPassed ||
+ !isLowerCasePassed ||
+ !isUpperCasePassed ||
+ !isSpecialSymbolPassed ||
+ !isMatch;
+
+ const handleSetAccessCode = async () => {
+ if (isProcessing) return;
+ hideMessage();
+ setIsProcessing(true);
+
+ try {
+ const encryptionKey = generateKey();
+ const encryptedKey = encryptData(encryptionKey, tmpAccessCode);
+
+ const { status, data } = await apiSetCongregationAccessCode(encryptedKey);
+
+ if (status !== 200) {
+ await displayOnboardingFeedback({
+ title: t('tr_errorGeneric'),
+ message: getMessageByCode(data.message),
+ });
+ showMessage();
+
+ setIsProcessing(false);
+ return;
+ }
+
+ await dbAppSettingsUpdate({
+ 'cong_settings.cong_access_code': tmpAccessCode,
+ });
+ } catch (err) {
+ await displayOnboardingFeedback({
+ title: t('tr_errorGeneric'),
+ message: getMessageByCode(err.message),
+ });
+ showMessage();
+
+ setIsProcessing(false);
+ }
+ };
+
+ useEffect(() => {
+ setIsLengthPassed(tmpAccessCode.length >= 8);
+ setIsNumberPassed(/\d/.test(tmpAccessCode));
+ setIsLowerCasePassed(/[a-z]/.test(tmpAccessCode));
+ setIsUpperCasePassed(/[A-Z]/.test(tmpAccessCode));
+ setIsSpecialSymbolPassed(/[!@#$%^&*(),.?"’:{}|<>]/.test(tmpAccessCode));
+ setIsMatch(
+ tmpAccessCode.length > 0 && tmpAccessCode === tmpAccessCodeVerify
+ );
+ }, [tmpAccessCode, tmpAccessCodeVerify]);
+
+ return {
+ tmpAccessCode,
+ setTmpAccessCode,
+ isLengthPassed,
+ isNumberPassed,
+ isLowerCasePassed,
+ isUpperCasePassed,
+ isSpecialSymbolPassed,
+ isProcessing,
+ message,
+ title,
+ hideMessage,
+ variant,
+ isMatch,
+ setTmpAccessCodeVerify,
+ tmpAccessCodeVerify,
+ btnActionDisabled,
+ handleSetAccessCode,
+ };
+};
+
+export default useCongregationAccessCode;
diff --git a/src/features/app_start/vip/congregation_create/congregation_info/index.tsx b/src/features/app_start/vip/congregation_create/congregation_details/index.tsx
similarity index 89%
rename from src/features/app_start/vip/congregation_create/congregation_info/index.tsx
rename to src/features/app_start/vip/congregation_create/congregation_details/index.tsx
index 8fd365cecd..4265673e04 100644
--- a/src/features/app_start/vip/congregation_create/congregation_info/index.tsx
+++ b/src/features/app_start/vip/congregation_create/congregation_details/index.tsx
@@ -1,21 +1,16 @@
import { Box } from '@mui/material';
import { IconAccount, IconError, IconLoading } from '@icons/index';
import { useAppTranslation } from '@hooks/index';
-import useCongregationInfo from './useCongregationInfo';
+import useCongregationDetails from './useCongregationDetails';
import Button from '@components/button';
import Checkbox from '@components/checkbox';
import CongregationSelector from '@components/congregation_selector';
import CountrySelector from '@components/country_selector';
import InfoMessage from '@components/info-message';
import TextField from '@components/textfield';
-import PageHeader from '@features/app_start/shared/page_header';
import VipInfoTip from '@features/app_start/vip/vip_info_tip';
-const CongregationInfo = ({
- setIsCreate,
-}: {
- setIsCreate: (value: boolean) => void;
-}) => {
+const CongregationDetails = () => {
const { t } = useAppTranslation();
const {
@@ -35,7 +30,7 @@ const CongregationInfo = ({
handleToggleApproval,
isElderApproved,
congregation,
- } = useCongregationInfo();
+ } = useCongregationDetails();
return (
- setIsCreate(false)}
- />
-
{
+const useCongregationDetails = () => {
const { t } = useAppTranslation();
const { hideMessage, message, showMessage, title, variant } = useFeedback();
+ const setCurrentStep = useSetRecoilState(congregationCreateStepState);
+
const settings = useRecoilValue(settingsState);
const [isProcessing, setIsProcessing] = useState(false);
@@ -141,8 +142,7 @@ const useCongregationInfo = () => {
setUserID(result.user_id);
- setIsCongAccountCreate(false);
- setIsEncryptionCodeOpen(true);
+ setCurrentStep(1);
} catch (err) {
setIsProcessing(false);
@@ -176,4 +176,4 @@ const useCongregationInfo = () => {
};
};
-export default useCongregationInfo;
+export default useCongregationDetails;
diff --git a/src/features/app_start/vip/congregation_encryption/congregation_master_key/criteria/index.tsx b/src/features/app_start/vip/congregation_create/congregation_master_key/criteria/index.tsx
similarity index 100%
rename from src/features/app_start/vip/congregation_encryption/congregation_master_key/criteria/index.tsx
rename to src/features/app_start/vip/congregation_create/congregation_master_key/criteria/index.tsx
diff --git a/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx b/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx
new file mode 100644
index 0000000000..14e30efdfc
--- /dev/null
+++ b/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx
@@ -0,0 +1,179 @@
+import { Box } from '@mui/material';
+import { IconEncryptionKey, IconError, IconLoading } from '@icons/index';
+import { useAppTranslation } from '@hooks/index';
+import useCongregationMasterKey from './useCongregationMasterKey';
+import Button from '@components/button';
+import Criteria from './criteria';
+import InfoMessage from '@components/info-message';
+import TextField from '@components/textfield';
+import Typography from '@components/typography';
+import Markup from '@components/text_markup';
+
+const CongregationMasterKey = () => {
+ const { t } = useAppTranslation();
+
+ const {
+ isLengthPassed,
+ isNumberPassed,
+ isLowerCasePassed,
+ isUpperCasePassed,
+ isSpecialSymbolPassed,
+ isProcessing,
+ hideMessage,
+ message,
+ title,
+ variant,
+ isMatch,
+ setTmpMasterKey,
+ setTmpMasterKeyVerify,
+ tmpMasterKey,
+ tmpMasterKeyVerify,
+ btnActionDisabled,
+ handleSetMasterKey,
+ } = useCongregationMasterKey();
+
+ return (
+
+
+
+
+
+
+ {t('tr_encryptionCodeNotice')}
+
+
+
+
+
+ setTmpMasterKey(e.target.value)}
+ startIcon={ }
+ resetHelperPadding={true}
+ />
+ setTmpMasterKeyVerify(e.target.value)}
+ startIcon={ }
+ resetHelperPadding={true}
+ helperText={
+
+
+
+
+
+
+
+
+ }
+ />
+
+
+ : null
+ }
+ disabled={btnActionDisabled}
+ >
+ {t('tr_encryptionCodeSet')}
+
+
+
+
+
+ }
+ messageHeader={title}
+ message={message}
+ onClose={hideMessage}
+ />
+
+
+ );
+};
+
+export default CongregationMasterKey;
diff --git a/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx b/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx
new file mode 100644
index 0000000000..500f2b8e0f
--- /dev/null
+++ b/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx
@@ -0,0 +1,105 @@
+import { useEffect, useState } from 'react';
+import { useSetRecoilState } from 'recoil';
+import { useAppTranslation } from '@hooks/index';
+import { encryptData, generateKey } from '@services/encryption/index';
+import { displayOnboardingFeedback } from '@services/recoil/app';
+import { getMessageByCode } from '@services/i18n/translation';
+import { apiSetCongregationMasterKey } from '@services/api/congregation';
+import { dbAppSettingsUpdate } from '@services/dexie/settings';
+import { congregationCreateStepState } from '@states/app';
+import useFeedback from '@features/app_start/shared/hooks/useFeedback';
+
+const useCongregationMasterKey = () => {
+ const { t } = useAppTranslation();
+
+ const { hideMessage, message, showMessage, title, variant } = useFeedback();
+
+ const setCurrentStep = useSetRecoilState(congregationCreateStepState);
+
+ const [tmpMasterKey, setTmpMasterKey] = useState('');
+ const [tmpMasterKeyVerify, setTmpMasterKeyVerify] = useState('');
+ const [isLengthPassed, setIsLengthPassed] = useState(false);
+ const [isNumberPassed, setIsNumberPassed] = useState(false);
+ const [isLowerCasePassed, setIsLowerCasePassed] = useState(false);
+ const [isUpperCasePassed, setIsUpperCasePassed] = useState(false);
+ const [isSpecialSymbolPassed, setIsSpecialSymbolPassed] = useState(false);
+ const [isMatch, setIsMatch] = useState(false);
+ const [isProcessing, setIsProcessing] = useState(false);
+
+ const btnActionDisabled =
+ !isLengthPassed ||
+ !isNumberPassed ||
+ !isLowerCasePassed ||
+ !isUpperCasePassed ||
+ !isSpecialSymbolPassed ||
+ !isMatch;
+
+ const handleSetMasterKey = async () => {
+ if (isProcessing) return;
+ hideMessage();
+ setIsProcessing(true);
+
+ try {
+ const encryptionKey = generateKey();
+ const encryptedKey = encryptData(encryptionKey, tmpMasterKey);
+
+ const { status, data } = await apiSetCongregationMasterKey(encryptedKey);
+
+ if (status !== 200) {
+ await displayOnboardingFeedback({
+ title: t('tr_errorGeneric'),
+ message: getMessageByCode(data.message),
+ });
+ showMessage();
+
+ setIsProcessing(false);
+ return;
+ }
+
+ await dbAppSettingsUpdate({
+ 'cong_settings.cong_master_key': tmpMasterKey,
+ });
+
+ setCurrentStep(2);
+ } catch (err) {
+ await displayOnboardingFeedback({
+ title: t('tr_errorGeneric'),
+ message: getMessageByCode(err.message),
+ });
+ showMessage();
+
+ setIsProcessing(false);
+ }
+ };
+
+ useEffect(() => {
+ setIsLengthPassed(tmpMasterKey.length >= 16);
+ setIsNumberPassed(/\d/.test(tmpMasterKey));
+ setIsLowerCasePassed(/[a-z]/.test(tmpMasterKey));
+ setIsUpperCasePassed(/[A-Z]/.test(tmpMasterKey));
+ setIsSpecialSymbolPassed(/[!@#$%^&*(),.?"’:{}|<>]/.test(tmpMasterKey));
+ setIsMatch(tmpMasterKey.length > 0 && tmpMasterKey === tmpMasterKeyVerify);
+ }, [tmpMasterKey, tmpMasterKeyVerify]);
+
+ return {
+ handleSetMasterKey,
+ tmpMasterKey,
+ setTmpMasterKey,
+ tmpMasterKeyVerify,
+ setTmpMasterKeyVerify,
+ isLengthPassed,
+ isNumberPassed,
+ isLowerCasePassed,
+ isUpperCasePassed,
+ isSpecialSymbolPassed,
+ isProcessing,
+ message,
+ title,
+ hideMessage,
+ variant,
+ isMatch,
+ btnActionDisabled,
+ };
+};
+
+export default useCongregationMasterKey;
diff --git a/src/features/app_start/vip/congregation_create/index.tsx b/src/features/app_start/vip/congregation_create/index.tsx
index b72770e2fc..9acd3b76bd 100644
--- a/src/features/app_start/vip/congregation_create/index.tsx
+++ b/src/features/app_start/vip/congregation_create/index.tsx
@@ -1,19 +1,68 @@
+import { Box, Step, StepLabel, Stepper } from '@mui/material';
+import { useAppTranslation } from '@hooks/index';
import useCongregationCreate from './useCongregationCreate';
-import UserAccountCreated from './account_created';
-import CongregationInfo from './congregation_info';
+import PageHeader from '@features/app_start/shared/page_header';
+import Typography from '@components/typography';
const CongregationCreate = () => {
- const { isCreate, setIsCreate } = useCongregationCreate();
+ const { t } = useAppTranslation();
+
+ const { steps, currentStep } = useCongregationCreate();
return (
- <>
- {!isCreate && (
- setIsCreate(value)} />
- )}
- {isCreate && (
- setIsCreate(value)} />
- )}
- >
+
+
+
+
+ {steps.map((step, index) => (
+
+
+
+ {step.label}
+
+
+
+ ))}
+
+
+ {steps[currentStep].Component}
+
);
};
diff --git a/src/features/app_start/vip/congregation_create/useCongregationCreate.tsx b/src/features/app_start/vip/congregation_create/useCongregationCreate.tsx
index b8a89e067d..f99885a7a2 100644
--- a/src/features/app_start/vip/congregation_create/useCongregationCreate.tsx
+++ b/src/features/app_start/vip/congregation_create/useCongregationCreate.tsx
@@ -1,9 +1,40 @@
-import { useState } from 'react';
+import { useEffect, useMemo } from 'react';
+import { useRecoilValue } from 'recoil';
+import { useAppTranslation } from '@hooks/index';
+import { congregationCreateStepState } from '@states/app';
+import CongregationAccessCode from './congregation_access_code';
+import CongregationDetails from './congregation_details';
+import CongregationMasterKey from './congregation_master_key';
const useCongregationCreate = () => {
- const [isCreate, setIsCreate] = useState(false);
+ const { t } = useAppTranslation();
- return { isCreate, setIsCreate };
+ const currentStep = useRecoilValue(congregationCreateStepState);
+
+ const steps = useMemo(() => {
+ return [
+ {
+ label: t('tr_congregationDetails'),
+ Component: ,
+ },
+ { label: t('tr_createMasterKey'), Component: },
+ {
+ label: t('tr_createAccessCode'),
+ Component: ,
+ },
+ ];
+ }, [t]);
+
+ useEffect(() => {
+ const stepIconTexts: NodeListOf =
+ document.querySelectorAll('.MuiStepIcon-text');
+
+ stepIconTexts.forEach((text) => {
+ text.classList.add('label-small-medium');
+ });
+ }, []);
+
+ return { steps, currentStep };
};
export default useCongregationCreate;
diff --git a/src/features/app_start/vip/congregation_encryption/congregation_access_code/index.tsx b/src/features/app_start/vip/congregation_encryption/congregation_access_code/index.tsx
index 6264a6f448..60fb189a58 100644
--- a/src/features/app_start/vip/congregation_encryption/congregation_access_code/index.tsx
+++ b/src/features/app_start/vip/congregation_encryption/congregation_access_code/index.tsx
@@ -1,39 +1,27 @@
import { Box } from '@mui/material';
-import { IconEncryptionKey, IconError, IconLoading } from '@icons/index';
+import { IconCongregationAccess, IconError, IconLoading } from '@icons/index';
import { useAppTranslation } from '@hooks/index';
import useCongregationAccessCode from './useCongregationAccessCode';
import PageHeader from '@features/app_start/shared/page_header';
import Button from '@components/button';
import InfoMessage from '@components/info-message';
import TextField from '@components/textfield';
-import Typography from '@components/typography';
import WaitingLoader from '@components/waiting_loader';
-import Criteria from './criteria';
-import VipInfoTip from '@features/app_start/vip/vip_info_tip';
const CongregationAccessCode = () => {
const { t } = useAppTranslation();
const {
isLoading,
- isLengthPassed,
- isNumberPassed,
- isLowerCasePassed,
- isUpperCasePassed,
- isSpecialSymbolPassed,
isProcessing,
hideMessage,
message,
title,
variant,
- handleAction,
- isSetupCode,
- isMatch,
setTmpAccessCode,
- setTmpAccessCodeVerify,
tmpAccessCode,
- tmpAccessCodeVerify,
btnActionDisabled,
+ handleValidateAccessCode,
} = useCongregationAccessCode();
return (
@@ -60,32 +48,9 @@ const CongregationAccessCode = () => {
>
- {isSetupCode && (
-
-
-
- {t('tr_congregationAccessCodeNotice')}
-
-
- )}
-
{
>
setTmpAccessCode(e.target.value)}
- startIcon={ }
+ startIcon={ }
resetHelperPadding={true}
/>
- {isSetupCode && (
- setTmpAccessCodeVerify(e.target.value)}
- startIcon={ }
- resetHelperPadding={true}
- helperText={
- isSetupCode ? (
-
-
-
-
-
-
-
-
- ) : null
- }
- />
- )}
: null
}
disabled={btnActionDisabled}
>
- {t(
- isSetupCode
- ? 'tr_congregationAccessCodeSet'
- : 'tr_encryptionCodeValidate'
- )}
+ {t('tr_encryptionCodeValidate')}
-
-
{
const congNumber = useRecoilValue(congNumberState);
const [isLoading, setIsLoading] = useState(true);
- const [isSetupCode, setIsSetupCode] = useState(true);
const [tmpAccessCode, setTmpAccessCode] = useState('');
const [tmpAccessCodeVerify, setTmpAccessCodeVerify] = useState('');
const [isLengthPassed, setIsLengthPassed] = useState(false);
- const [isNumberPassed, setIsNumberPassed] = useState(false);
- const [isLowerCasePassed, setIsLowerCasePassed] = useState(false);
- const [isUpperCasePassed, setIsUpperCasePassed] = useState(false);
- const [isSpecialSymbolPassed, setIsSpecialSymbolPassed] = useState(false);
- const [isMatch, setIsMatch] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const [congAccessCode, setCongAccessCode] = useState('');
- const btnActionDisabled =
- !isLengthPassed ||
- !isNumberPassed ||
- !isLowerCasePassed ||
- !isUpperCasePassed ||
- !isSpecialSymbolPassed ||
- (isSetupCode && !isMatch);
-
- const handleAction = () => {
- if (isSetupCode) handleSetAccessCode();
- if (!isSetupCode) handleValidateAccessCode();
- };
-
- const handleSetAccessCode = async () => {
- if (isProcessing) return;
- hideMessage();
- setIsProcessing(true);
-
- try {
- const encryptionKey = generateKey();
- const encryptedKey = encryptData(encryptionKey, tmpAccessCode);
-
- const { status, data } = await apiSetCongregationAccessCode(encryptedKey);
-
- if (status !== 200) {
- await displayOnboardingFeedback({
- title: t('tr_errorGeneric'),
- message: getMessageByCode(data.message),
- });
- showMessage();
-
- setIsProcessing(false);
- return;
- }
-
- await dbAppSettingsUpdate({
- 'cong_settings.cong_access_code': tmpAccessCode,
- });
- } catch (err) {
- await displayOnboardingFeedback({
- title: t('tr_errorGeneric'),
- message: getMessageByCode(err.message),
- });
- showMessage();
-
- setIsProcessing(false);
- }
- };
+ const btnActionDisabled = !isLengthPassed;
const handleValidateAccessCode = async () => {
if (isProcessing) return;
@@ -137,7 +78,6 @@ const useCongregationAccessCode = () => {
await setCongID(result.cong_id);
setCongAccessCode(result.cong_access_code);
- setIsSetupCode(result.cong_access_code.length === 0);
setIsLoading(false);
};
@@ -147,32 +87,19 @@ const useCongregationAccessCode = () => {
useEffect(() => {
setIsLengthPassed(tmpAccessCode.length >= 8);
- setIsNumberPassed(/\d/.test(tmpAccessCode));
- setIsLowerCasePassed(/[a-z]/.test(tmpAccessCode));
- setIsUpperCasePassed(/[A-Z]/.test(tmpAccessCode));
- setIsSpecialSymbolPassed(/[!@#$%^&*(),.?"’:{}|<>]/.test(tmpAccessCode));
- setIsMatch(
- tmpAccessCode.length > 0 && tmpAccessCode === tmpAccessCodeVerify
- );
}, [tmpAccessCode, tmpAccessCodeVerify]);
return {
isLoading,
- isSetupCode,
tmpAccessCode,
setTmpAccessCode,
isLengthPassed,
- isNumberPassed,
- isLowerCasePassed,
- isUpperCasePassed,
- isSpecialSymbolPassed,
isProcessing,
- handleAction,
+ handleValidateAccessCode,
message,
title,
hideMessage,
variant,
- isMatch,
setTmpAccessCodeVerify,
tmpAccessCodeVerify,
btnActionDisabled,
diff --git a/src/features/app_start/vip/congregation_encryption/congregation_master_key/index.tsx b/src/features/app_start/vip/congregation_encryption/congregation_master_key/index.tsx
index 7a0bbcb213..2b2c5f7a9f 100644
--- a/src/features/app_start/vip/congregation_encryption/congregation_master_key/index.tsx
+++ b/src/features/app_start/vip/congregation_encryption/congregation_master_key/index.tsx
@@ -1,38 +1,27 @@
import { Box } from '@mui/material';
-import PageHeader from '@features/app_start/shared/page_header';
+import { IconEncryptionKey, IconError, IconLoading } from '@icons/index';
+import { useAppTranslation } from '@hooks/index';
+import useCongregationMasterKey from './useCongregationMasterKey';
import Button from '@components/button';
import InfoMessage from '@components/info-message';
+import PageHeader from '@features/app_start/shared/page_header';
import TextField from '@components/textfield';
-import Typography from '@components/typography';
import WaitingLoader from '@components/waiting_loader';
-import Criteria from './criteria';
-import { IconEncryptionKey, IconError, IconLoading } from '@icons/index';
-import { useAppTranslation } from '@hooks/index';
-import useCongregationMasterKey from './useCongregationMasterKey';
const CongregationEncryption = () => {
const { t } = useAppTranslation();
const {
- isLoading,
- isLengthPassed,
- isNumberPassed,
- isLowerCasePassed,
- isUpperCasePassed,
- isSpecialSymbolPassed,
isProcessing,
hideMessage,
message,
title,
variant,
- handleAction,
- isSetupCode,
- isMatch,
setTmpMasterKey,
- setTmpMasterKeyVerify,
tmpMasterKey,
- tmpMasterKeyVerify,
btnActionDisabled,
+ isLoading,
+ handleValidateMasterKey,
} = useCongregationMasterKey();
return (
@@ -59,32 +48,9 @@ const CongregationEncryption = () => {
>
- {isSetupCode && (
-
-
-
- {t('tr_encryptionCodeNotice')}
-
-
- )}
-
{
>
{
startIcon={ }
resetHelperPadding={true}
/>
- {isSetupCode && (
- setTmpMasterKeyVerify(e.target.value)}
- startIcon={ }
- resetHelperPadding={true}
- helperText={
- isSetupCode ? (
-
-
-
-
-
-
-
-
- ) : null
- }
- />
- )}
: null
}
disabled={btnActionDisabled}
>
- {t(
- isSetupCode
- ? 'tr_encryptionCodeSet'
- : 'tr_encryptionCodeValidate'
- )}
+ {t('tr_encryptionCodeValidate')}
diff --git a/src/features/app_start/vip/congregation_encryption/congregation_master_key/useCongregationMasterKey.tsx b/src/features/app_start/vip/congregation_encryption/congregation_master_key/useCongregationMasterKey.tsx
index 9a24a8e88c..94e73986de 100644
--- a/src/features/app_start/vip/congregation_encryption/congregation_master_key/useCongregationMasterKey.tsx
+++ b/src/features/app_start/vip/congregation_encryption/congregation_master_key/useCongregationMasterKey.tsx
@@ -4,15 +4,9 @@ import { handleDeleteDatabase } from '@services/app';
import { useAppTranslation, useFirebaseAuth } from '@hooks/index';
import { userSignOut } from '@services/firebase/auth';
import useFeedback from '@features/app_start/shared/hooks/useFeedback';
-import {
- decryptData,
- encryptData,
- generateKey,
-} from '@services/encryption/index';
+import { decryptData } from '@services/encryption/index';
import { apiValidateMe } from '@services/api/user';
import { displayOnboardingFeedback, setCongID } from '@services/recoil/app';
-import { getMessageByCode } from '@services/i18n/translation';
-import { apiSetCongregationMasterKey } from '@services/api/congregation';
import { dbAppSettingsUpdate } from '@services/dexie/settings';
import { congNumberState } from '@states/settings';
@@ -26,66 +20,13 @@ const useCongregationMasterKey = () => {
const congNumber = useRecoilValue(congNumberState);
const [isLoading, setIsLoading] = useState(true);
- const [isSetupCode, setIsSetupCode] = useState(true);
const [tmpMasterKey, setTmpMasterKey] = useState('');
const [tmpMasterKeyVerify, setTmpMasterKeyVerify] = useState('');
const [isLengthPassed, setIsLengthPassed] = useState(false);
- const [isNumberPassed, setIsNumberPassed] = useState(false);
- const [isLowerCasePassed, setIsLowerCasePassed] = useState(false);
- const [isUpperCasePassed, setIsUpperCasePassed] = useState(false);
- const [isSpecialSymbolPassed, setIsSpecialSymbolPassed] = useState(false);
- const [isMatch, setIsMatch] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const [congMasterKey, setCongMasterKey] = useState('');
- const btnActionDisabled =
- !isLengthPassed ||
- !isNumberPassed ||
- !isLowerCasePassed ||
- !isUpperCasePassed ||
- !isSpecialSymbolPassed ||
- (isSetupCode && !isMatch);
-
- const handleAction = () => {
- if (isSetupCode) handleSetMasterKey();
- if (!isSetupCode) handleValidateMasterKey();
- };
-
- const handleSetMasterKey = async () => {
- if (isProcessing) return;
- hideMessage();
- setIsProcessing(true);
-
- try {
- const encryptionKey = generateKey();
- const encryptedKey = encryptData(encryptionKey, tmpMasterKey);
-
- const { status, data } = await apiSetCongregationMasterKey(encryptedKey);
-
- if (status !== 200) {
- await displayOnboardingFeedback({
- title: t('tr_errorGeneric'),
- message: getMessageByCode(data.message),
- });
- showMessage();
-
- setIsProcessing(false);
- return;
- }
-
- await dbAppSettingsUpdate({
- 'cong_settings.cong_master_key': tmpMasterKey,
- });
- } catch (err) {
- await displayOnboardingFeedback({
- title: t('tr_errorGeneric'),
- message: getMessageByCode(err.message),
- });
- showMessage();
-
- setIsProcessing(false);
- }
- };
+ const btnActionDisabled = !isLengthPassed;
const handleValidateMasterKey = async () => {
if (isProcessing) return;
@@ -135,10 +76,7 @@ const useCongregationMasterKey = () => {
}
await setCongID(result.cong_id);
-
setCongMasterKey(result.cong_master_key);
- setIsSetupCode(result.cong_master_key.length === 0);
-
setIsLoading(false);
};
@@ -147,32 +85,21 @@ const useCongregationMasterKey = () => {
useEffect(() => {
setIsLengthPassed(tmpMasterKey.length >= 16);
- setIsNumberPassed(/\d/.test(tmpMasterKey));
- setIsLowerCasePassed(/[a-z]/.test(tmpMasterKey));
- setIsUpperCasePassed(/[A-Z]/.test(tmpMasterKey));
- setIsSpecialSymbolPassed(/[!@#$%^&*(),.?"’:{}|<>]/.test(tmpMasterKey));
- setIsMatch(tmpMasterKey.length > 0 && tmpMasterKey === tmpMasterKeyVerify);
}, [tmpMasterKey, tmpMasterKeyVerify]);
return {
isLoading,
- isSetupCode,
tmpMasterKey,
setTmpMasterKey,
tmpMasterKeyVerify,
setTmpMasterKeyVerify,
isLengthPassed,
- isNumberPassed,
- isLowerCasePassed,
- isUpperCasePassed,
- isSpecialSymbolPassed,
isProcessing,
- handleAction,
+ handleValidateMasterKey,
message,
title,
hideMessage,
variant,
- isMatch,
btnActionDisabled,
};
};
diff --git a/src/features/app_start/vip/oauth/button_base/useButtonBase.tsx b/src/features/app_start/vip/oauth/button_base/useButtonBase.tsx
index ee209cf6ec..145a71cdb7 100644
--- a/src/features/app_start/vip/oauth/button_base/useButtonBase.tsx
+++ b/src/features/app_start/vip/oauth/button_base/useButtonBase.tsx
@@ -2,10 +2,10 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import {
currentProviderState,
isAuthProcessingState,
- isCongAccountCreateState,
isEmailAuthState,
isEncryptionCodeOpenState,
isUnauthorizedRoleState,
+ isUserAccountCreatedState,
isUserMfaVerifyState,
isUserSignInState,
tokenDevState,
@@ -33,7 +33,7 @@ const useButtonBase = ({ provider, isEmail }) => {
const [isUserSignIn, setIsUserSignIn] = useRecoilState(isUserSignInState);
const setUserMfaVerify = useSetRecoilState(isUserMfaVerifyState);
- const setIsCongAccountCreate = useSetRecoilState(isCongAccountCreateState);
+ const setIsUserAccountCreated = useSetRecoilState(isUserAccountCreatedState);
const setIsUnauthorizedRole = useSetRecoilState(isUnauthorizedRoleState);
const setIsEncryptionCodeOpen = useSetRecoilState(isEncryptionCodeOpenState);
const setIsEmailAuth = useSetRecoilState(isEmailAuthState);
@@ -107,14 +107,14 @@ const useButtonBase = ({ provider, isEmail }) => {
if (nextStep.isVerifyMFA) {
setTokenDev(code);
setIsUserSignIn(false);
- setIsCongAccountCreate(false);
+ setIsUserAccountCreated(false);
setIsUnauthorizedRole(false);
setUserMfaVerify(true);
}
if (nextStep.createCongregation) {
setIsUserSignIn(false);
- setIsCongAccountCreate(true);
+ setIsUserAccountCreated(true);
}
if (nextStep.encryption) {
@@ -162,7 +162,7 @@ const useButtonBase = ({ provider, isEmail }) => {
const handleUnauthorizedUser = () => {
setUserMfaVerify(true);
- setIsCongAccountCreate(false);
+ setIsUserAccountCreated(false);
setIsUnauthorizedRole(true);
};
diff --git a/src/features/app_start/vip/startup/index.tsx b/src/features/app_start/vip/startup/index.tsx
index be2e296e83..49aca4382b 100644
--- a/src/features/app_start/vip/startup/index.tsx
+++ b/src/features/app_start/vip/startup/index.tsx
@@ -1,28 +1,34 @@
+import useStartup from './useStartup';
import CongregationCreate from '../congregation_create';
import CongregationEncryption from '../congregation_encryption';
import EmailAuth from '../email_auth';
import EmailLinkAuthentication from '../email_link_authentication';
import Signin from '../signin';
import TermsUse from '../terms_use';
+import UserAccountCreated from '../user_account_created';
import VerifyMFA from '../verify_mfa';
-import useStartup from './useStartup';
+import WaitingLoader from '@components/waiting_loader';
const VipStartup = () => {
const {
isUserSignIn,
isUserMfaVerify,
- isCongAccountCreate,
isEmailAuth,
isEmailLinkAuth,
isEncryptionCodeOpen,
+ isUserAccountCreated,
+ isCongCreate,
+ isLoading,
} = useStartup();
return (
<>
+ {!isCongCreate && !isEncryptionCodeOpen && isLoading && }
{isUserSignIn && }
{isUserMfaVerify && }
- {isCongAccountCreate && }
+ {isUserAccountCreated && }
+ {isCongCreate && }
{isEmailAuth && }
{isEmailLinkAuth && }
{isEncryptionCodeOpen && }
diff --git a/src/features/app_start/vip/startup/useStartup.tsx b/src/features/app_start/vip/startup/useStartup.tsx
index 39e42dbc3f..fba292ec0e 100644
--- a/src/features/app_start/vip/startup/useStartup.tsx
+++ b/src/features/app_start/vip/startup/useStartup.tsx
@@ -1,13 +1,15 @@
-import { useCallback, useEffect } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRecoilValue, useSetRecoilState } from 'recoil';
-import { useFirebaseAuth } from '@hooks/index';
import {
+ congIDState,
+ congregationCreateStepState,
cookiesConsentState,
isCongAccountCreateState,
isEmailAuthState,
isEmailLinkAuthenticateState,
isEncryptionCodeOpenState,
+ isUserAccountCreatedState,
isUserMfaVerifyState,
isUserSignInState,
offlineOverrideState,
@@ -26,27 +28,37 @@ import {
congNameState,
congAccessCodeState,
congRoleState,
+ congMasterKeyState,
+ congNumberState,
} from '@states/settings';
-import { APP_ROLES } from '@constants/index';
-import { loadApp, runUpdater } from '@services/app';
+import { APP_ROLES, VIP_ROLES } from '@constants/index';
+import { handleDeleteDatabase, loadApp, runUpdater } from '@services/app';
+import { apiValidateMe } from '@services/api/user';
+import { userSignOut } from '@services/firebase/auth';
const useStartup = () => {
- const { isAuthenticated } = useFirebaseAuth();
-
const [searchParams] = useSearchParams();
const setCookiesConsent = useSetRecoilState(cookiesConsentState);
+ const setCongCreate = useSetRecoilState(isCongAccountCreateState);
+ const setCurrentStep = useSetRecoilState(congregationCreateStepState);
+ const setCongID = useSetRecoilState(congIDState);
const isEmailLinkAuth = useRecoilValue(isEmailLinkAuthenticateState);
const isEmailAuth = useRecoilValue(isEmailAuthState);
const isUserSignIn = useRecoilValue(isUserSignInState);
const isUserMfaVerify = useRecoilValue(isUserMfaVerifyState);
- const isCongAccountCreate = useRecoilValue(isCongAccountCreateState);
+ const isUserAccountCreated = useRecoilValue(isUserAccountCreatedState);
const isOfflineOverride = useRecoilValue(offlineOverrideState);
const congName = useRecoilValue(congNameState);
const congRole = useRecoilValue(congRoleState);
const isEncryptionCodeOpen = useRecoilValue(isEncryptionCodeOpenState);
const congAccessCode = useRecoilValue(congAccessCodeState);
+ const isCongCreate = useRecoilValue(isCongAccountCreateState);
+ const congMasterKey = useRecoilValue(congMasterKeyState);
+ const congNumber = useRecoilValue(congNumberState);
+
+ const [isLoading, setIsLoading] = useState(true);
const showSignin = useCallback(() => {
setIsUserSignIn(true);
@@ -55,30 +67,37 @@ const useStartup = () => {
setUserMfaSetup(false);
}, []);
- const runNotAuthenticatedStep = useCallback(async () => {
+ const runStartupCheck = useCallback(async () => {
+ setIsLoading(true);
+
if (isOfflineOverride) {
+ setIsLoading(false);
showSignin();
return;
}
if (congName.length === 0) {
+ setIsLoading(false);
showSignin();
return;
}
const approvedRole = congRole.some((role) => APP_ROLES.includes(role));
+ const masterKeyNeeded = congRole.some((role) => VIP_ROLES.includes(role));
if (!approvedRole) {
+ setIsLoading(false);
showSignin();
return;
}
- if (congAccessCode.length === 0) {
- setIsEncryptionCodeOpen(true);
- return;
- }
+ const allowOpen =
+ (masterKeyNeeded &&
+ congMasterKey.length > 0 &&
+ congAccessCode.length > 0) ||
+ (!masterKeyNeeded && congAccessCode.length > 0);
- if (congAccessCode.length > 0) {
+ if (allowOpen) {
setIsSetup(false);
await loadApp();
await runUpdater();
@@ -86,8 +105,67 @@ const useStartup = () => {
setIsSetup(false);
setIsAppLoad(false);
}, 1000);
+
+ return;
+ }
+
+ const { status, result } = await apiValidateMe();
+
+ if (status === 403) {
+ await userSignOut();
+ return;
+ }
+
+ // congregation not found -> user not authorized and delete local data
+ if (status === 404) {
+ await handleDeleteDatabase();
+ return;
+ }
+
+ if (status === 200) {
+ if (congNumber.length > 0 && result.cong_number !== congNumber) {
+ await handleDeleteDatabase();
+ return;
+ }
}
- }, [isOfflineOverride, congName, congRole, showSignin, congAccessCode]);
+
+ const remoteMasterKey = result.cong_master_key;
+ const remoteAccessCode = result.cong_access_code;
+
+ if (remoteMasterKey.length === 0 || remoteAccessCode.length === 0) {
+ setCongID(result.cong_id);
+
+ if (masterKeyNeeded && remoteMasterKey.length === 0) {
+ setCurrentStep(1);
+ }
+
+ if (
+ masterKeyNeeded &&
+ remoteMasterKey.length > 0 &&
+ remoteAccessCode.length === 0
+ ) {
+ setCurrentStep(2);
+ }
+
+ setIsLoading(false);
+ setCongCreate(true);
+ return;
+ }
+
+ if (congAccessCode.length === 0) {
+ setIsEncryptionCodeOpen(true);
+ }
+
+ setIsLoading(false);
+ }, [
+ isOfflineOverride,
+ congName,
+ congRole,
+ showSignin,
+ congAccessCode,
+ congMasterKey,
+ congNumber,
+ ]);
useEffect(() => {
const checkLink = async () => {
@@ -99,8 +177,8 @@ const useStartup = () => {
}, [searchParams]);
useEffect(() => {
- if (!isAuthenticated) runNotAuthenticatedStep();
- }, [isAuthenticated, runNotAuthenticatedStep]);
+ runStartupCheck();
+ }, [runStartupCheck]);
useEffect(() => {
const checkCookiesConsent = async () => {
@@ -115,9 +193,11 @@ const useStartup = () => {
isEmailAuth,
isUserSignIn,
isUserMfaVerify,
- isCongAccountCreate,
+ isUserAccountCreated,
isEmailLinkAuth,
isEncryptionCodeOpen,
+ isCongCreate,
+ isLoading,
};
};
diff --git a/src/features/app_start/vip/terms_use/useTermsUse.tsx b/src/features/app_start/vip/terms_use/useTermsUse.tsx
index b9c711c38f..ac7b729275 100644
--- a/src/features/app_start/vip/terms_use/useTermsUse.tsx
+++ b/src/features/app_start/vip/terms_use/useTermsUse.tsx
@@ -19,7 +19,7 @@ const useTermsUse = () => {
const font = params.get('font') || 'Inter';
localStorage.setItem('userConsent', 'accept');
- localStorage.setItem('app_lang', lang);
+ localStorage.setItem('ui_lang', lang);
localStorage.setItem('app_font', font);
setCookiesConsent(true);
diff --git a/src/features/app_start/vip/congregation_create/account_created/index.tsx b/src/features/app_start/vip/user_account_created/index.tsx
similarity index 81%
rename from src/features/app_start/vip/congregation_create/account_created/index.tsx
rename to src/features/app_start/vip/user_account_created/index.tsx
index 537219369a..f883bfbe96 100644
--- a/src/features/app_start/vip/congregation_create/account_created/index.tsx
+++ b/src/features/app_start/vip/user_account_created/index.tsx
@@ -1,16 +1,15 @@
import { Box } from '@mui/material';
+import { useAppTranslation } from '@hooks/index';
+import useUserAccountCreated from './useUserAccountCreated';
import Button from '@components/button';
-import Typography from '@components/typography';
import PageHeader from '@features/app_start/shared/page_header';
-import { useAppTranslation } from '@hooks/index';
+import Typography from '@components/typography';
-const UserAccountCreated = ({
- setIsCreate,
-}: {
- setIsCreate: (value: boolean) => void;
-}) => {
+const UserAccountCreated = () => {
const { t } = useAppTranslation();
+ const { handleCreateCongregation } = useUserAccountCreated();
+
return (
{t('tr_congregationCreateLabel')}
- setIsCreate(true)}>
+
{t('tr_createCongregation')}
diff --git a/src/features/app_start/vip/user_account_created/useUserAccountCreated.tsx b/src/features/app_start/vip/user_account_created/useUserAccountCreated.tsx
new file mode 100644
index 0000000000..9cc1bdef88
--- /dev/null
+++ b/src/features/app_start/vip/user_account_created/useUserAccountCreated.tsx
@@ -0,0 +1,19 @@
+import { useSetRecoilState } from 'recoil';
+import {
+ isCongAccountCreateState,
+ isUserAccountCreatedState,
+} from '@states/app';
+
+const useUserAccountCreated = () => {
+ const setCongCreate = useSetRecoilState(isCongAccountCreateState);
+ const setUserCreated = useSetRecoilState(isUserAccountCreatedState);
+
+ const handleCreateCongregation = () => {
+ setUserCreated(false);
+ setCongCreate(true);
+ };
+
+ return { handleCreateCongregation };
+};
+
+export default useUserAccountCreated;
diff --git a/src/features/app_start/vip/vip_info_tip/index.tsx b/src/features/app_start/vip/vip_info_tip/index.tsx
index b32f04efab..c89a4f0454 100644
--- a/src/features/app_start/vip/vip_info_tip/index.tsx
+++ b/src/features/app_start/vip/vip_info_tip/index.tsx
@@ -12,32 +12,34 @@ const VipInfoTip = (props: VipInfoTipProps) => {
return (
<>
-
-
+ {messageShown && (
+
+
+
+
+
+
+
+ )}
-
-
-
-
{
const [anchorEl, setAnchorEl] = useState(null);
const isMenuOpen = Boolean(anchorEl);
- const handleLangChange = async (app_lang: string) => {
+ const handleLangChange = async (ui_lang: string) => {
const fullnameOption =
- LANGUAGE_LIST.find((record) => record.locale === app_lang)
+ LANGUAGE_LIST.find((record) => record.locale === ui_lang)
.fullnameOption || FullnameOption.FIRST_BEFORE_LAST;
const nameOption = structuredClone(settings.cong_settings.fullname_option);
@@ -43,16 +43,16 @@ const useLanguage = () => {
});
const font =
- LANGUAGE_LIST.find((lang) => lang.locale === app_lang)?.font || 'Inter';
+ LANGUAGE_LIST.find((lang) => lang.locale === ui_lang)?.font || 'Inter';
if (cookiesConsent || accountType === 'pocket') {
- localStorage.setItem('app_lang', app_lang);
+ localStorage.setItem('ui_lang', ui_lang);
localStorage.setItem('app_font', font);
}
if (!cookiesConsent && accountType !== 'pocket') {
setParams((params) => {
- params.set('locale', app_lang);
+ params.set('locale', ui_lang);
params.set('font', font);
return params;
});
diff --git a/src/hooks/useUserAutoLogin.tsx b/src/hooks/useUserAutoLogin.tsx
index 6ae486bf9e..4dbb8b6a01 100644
--- a/src/hooks/useUserAutoLogin.tsx
+++ b/src/hooks/useUserAutoLogin.tsx
@@ -81,10 +81,9 @@ const useUserAutoLogin = () => {
useEffect(() => {
const handleLoginData = async () => {
try {
- if (isPendingVip) {
- setAutoLoginStatus('auto login process started');
- return;
- }
+ setAutoLoginStatus('auto login process started');
+
+ if (isPendingVip) return;
if (!dataVip) return;
@@ -170,13 +169,12 @@ const useUserAutoLogin = () => {
useEffect(() => {
const handleLoginData = async () => {
try {
- if (isPendingPocket) {
- setAutoLoginStatus('auto login process started');
- return;
- }
+ if (isPendingPocket) return;
if (!dataPocket) return;
+ setAutoLoginStatus('auto login process started');
+
// congregation not found -> user not authorized and delete local data
if (dataPocket.status === 403) {
await handleDeleteDatabase();
diff --git a/src/locales/en/onboarding.json b/src/locales/en/onboarding.json
index 0508cfa1b1..72be113ddb 100644
--- a/src/locales/en/onboarding.json
+++ b/src/locales/en/onboarding.json
@@ -118,5 +118,8 @@
"tr_illustrationOtherSchedulesHeader": "Many other useful schedules",
"tr_illustrationOtherSchedulesDescription": "Organized makes scheduling hall cleaning, circuit overseer visit, hall maintenance work, public witnessing, congregation duties and other parts of congregation life significantly easier for appointed brothers. It also makes it simpler for publishers to track and participate in these activities.",
"tr_howAreTheCodesDifferent": "How are the codes different?",
- "tr_howAreTheCodesDifferentDesc": "Access code: For all users to join your congregation; Master key: Higher-level access for responsible brothers."
+ "tr_howAreTheCodesDifferentDesc": "Access code: For all users to join your congregation; Master key: Higher-level access for responsible brothers.",
+ "tr_createMasterKey": "Create master key",
+ "tr_createMasterKeyDesc": "First, create your congregation master key. It securely encrypts your congregation's data, including reports and persons, and is essential for administrative tasks such as data export/import and settings adjustments.
",
+ "tr_createAccessCode": "Create access code"
}
diff --git a/src/services/i18n/index.ts b/src/services/i18n/index.ts
index 3190c6f846..cab83b7e58 100644
--- a/src/services/i18n/index.ts
+++ b/src/services/i18n/index.ts
@@ -6,7 +6,7 @@ export const defaultNS = 'ui';
const resources = {};
const getAppLang = () => {
- const langStorage = localStorage.getItem('app_lang');
+ const langStorage = localStorage.getItem('ui_lang');
if (langStorage) {
return langStorage;
diff --git a/src/states/app.ts b/src/states/app.ts
index 46aacc00b2..affdff1ed7 100644
--- a/src/states/app.ts
+++ b/src/states/app.ts
@@ -12,7 +12,7 @@ import { settingsState } from './settings';
import { sourcesState } from './sources';
const getAppLang = () => {
- const langStorage = localStorage.getItem('app_lang');
+ const langStorage = localStorage.getItem('ui_lang');
if (langStorage) {
return langStorage;
@@ -163,6 +163,11 @@ export const isEmailBlockedState = atom({
default: false,
});
+export const isUserAccountCreatedState = atom({
+ key: 'isUserAccountCreated',
+ default: false,
+});
+
export const isCongAccountCreateState = atom({
key: 'isCongAccountCreate',
default: false,
@@ -559,3 +564,8 @@ export const demoNoticeOpenState = atom({
key: 'demoNoticeOpen',
default: true,
});
+
+export const congregationCreateStepState = atom({
+ key: 'congregationCreateStep',
+ default: 0,
+});
From 7acab770335e3dcf13ef35e562fd21362e877871 Mon Sep 17 00:00:00 2001
From: rhahao <26148770+rhahao@users.noreply.github.com>
Date: Sun, 10 Nov 2024 12:46:09 +0300
Subject: [PATCH 2/2] feat(features): use stepper for congregation creation
workflow
---
.../congregation_access_code/index.tsx | 14 +++++++++++---
.../congregation_master_key/index.tsx | 1 +
src/locales/en/onboarding.json | 5 +++--
3 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx b/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx
index c7583a2816..d78ba09110 100644
--- a/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx
+++ b/src/features/app_start/vip/congregation_create/congregation_access_code/index.tsx
@@ -5,6 +5,7 @@ import useCongregationAccessCode from './useCongregationAccessCode';
import Button from '@components/button';
import Criteria from './criteria';
import InfoMessage from '@components/info-message';
+import Markup from '@components/text_markup';
import TextField from '@components/textfield';
import Typography from '@components/typography';
import VipInfoTip from '@features/app_start/vip/vip_info_tip';
@@ -51,6 +52,13 @@ const CongregationAccessCode = () => {
width: '100%',
}}
>
+
+
{
alignItems: 'center',
gap: '8px',
borderRadius: 'var(--radius-l)',
- background: 'var(--red-secondary)',
+ background: 'var(--orange-secondary)',
marginBottom: '32px',
}}
>
-
-
+
+
{t('tr_congregationAccessCodeNotice')}
diff --git a/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx b/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx
index 14e30efdfc..248816d94c 100644
--- a/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx
+++ b/src/features/app_start/vip/congregation_create/congregation_master_key/index.tsx
@@ -57,6 +57,7 @@ const CongregationMasterKey = () => {
color="var(--grey-400)"
style={{ marginBottom: '24px' }}
/>
+
First, create your congregation master key. It securely encrypts your congregation's data, including reports and persons, and is essential for administrative tasks such as data export/import and settings adjustments.
",
- "tr_createAccessCode": "Create access code"
+ "tr_createAccessCode": "Create access code",
+ "tr_createAccessCodeDesc": "Finally, create the access code. This unique code allows new users to connect and log in securely. It encrypts data and is required for accessing your congregation in the Organized app.
"
}