diff --git a/package-lock.json b/package-lock.json index 3043e932..293294a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "lmm-oa-sws", "version": "1.0.0-alpha.1", "dependencies": { "@emotion/react": "^11.4.1", @@ -7643,9 +7644,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001247", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001247.tgz", - "integrity": "sha512-4rS7co+7+AoOSPRPOPUt5/GdaqZc0EsUpWk66ofE3HJTAajUK2Ss2VwoNzVN69ghg8lYYlh0an0Iy4LIHHo9UQ==", + "version": "1.0.30001300", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", + "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/browserslist" @@ -38612,9 +38613,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001247", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001247.tgz", - "integrity": "sha512-4rS7co+7+AoOSPRPOPUt5/GdaqZc0EsUpWk66ofE3HJTAajUK2Ss2VwoNzVN69ghg8lYYlh0an0Iy4LIHHo9UQ==" + "version": "1.0.30001300", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", + "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==" }, "canvg": { "version": "3.0.9", diff --git a/src/appStates/appCongregation.js b/src/appStates/appCongregation.js index c294fbab..fbb89de5 100644 --- a/src/appStates/appCongregation.js +++ b/src/appStates/appCongregation.js @@ -1,41 +1,46 @@ -import { atom } from "recoil"; +import { atom } from 'recoil'; export const congNameState = atom({ - key: 'congName', - default: '', -}) + key: 'congName', + default: '', +}); export const congNumberState = atom({ - key: 'congNumber', - default: '', -}) + key: 'congNumber', + default: '', +}); export const congIDState = atom({ - key: 'congID', - default: '', -}) + key: 'congID', + default: '', +}); export const congPasswordState = atom({ - key: 'congPassword', - default: '', -}) + key: 'congPassword', + default: '', +}); export const isErrorCongNameState = atom({ - key: 'isErrorCongName', - default: false, -}) + key: 'isErrorCongName', + default: false, +}); export const isErrorCongNumberState = atom({ - key: 'isErrorCongNumber', - default: false, -}) + key: 'isErrorCongNumber', + default: false, +}); export const meetingDayState = atom({ - key: 'meetingDay', - default: 3, -}) + key: 'meetingDay', + default: 3, +}); export const classCountState = atom({ - key: 'classCount', - default: 1, -}) \ No newline at end of file + key: 'classCount', + default: 1, +}); + +export const liveClassState = atom({ + key: 'liveClass', + default: false, +}); diff --git a/src/appStates/appMe.js b/src/appStates/appMe.js deleted file mode 100644 index c21ce1f0..00000000 --- a/src/appStates/appMe.js +++ /dev/null @@ -1,11 +0,0 @@ -import { atom } from "recoil"; - -export const userPersoCodeState = atom({ - key: 'userPersoCode', - default: '', -}) - -export const isErrorPersoCodeState = atom({ - key: 'isErrorPersoCode', - default: false, -}) \ No newline at end of file diff --git a/src/appStates/appSettings.js b/src/appStates/appSettings.js index 7659f1df..0f6ef9b6 100644 --- a/src/appStates/appSettings.js +++ b/src/appStates/appSettings.js @@ -22,12 +22,7 @@ export const isAboutOpenState = atom({ }); export const isLoginOpenState = atom({ - key: 'isLoginState', - default: false, -}); - -export const isLoggedState = atom({ - key: 'isLoggedState', + key: 'isLoginOpen', default: false, }); @@ -107,3 +102,48 @@ export const shortDateFormatState = selector({ return format; }, }); + +export const isDeleteDbOpenState = atom({ + key: 'isDeleteDbOpen', + default: false, +}); + +export const isBackupDbOpenState = atom({ + key: 'isBackupDbOpen', + default: false, +}); + +export const isBackupOfflineState = atom({ + key: 'isBackupOffline', + default: false, +}); + +export const isBackupOnlineState = atom({ + key: 'isBackupOnline', + default: false, +}); + +export const isRestoreOfflineState = atom({ + key: 'isRestoreOffline', + default: false, +}); + +export const isRestoreOnlineState = atom({ + key: 'isRestoreOnline', + default: false, +}); + +export const backupEncryptedState = atom({ + key: 'backupEncrypted', + default: {}, +}); + +export const backupJsonDataState = atom({ + key: 'backupJsonData', + default: undefined, +}); + +export const isUserLoggedState = atom({ + key: 'isUserLogged', + default: false, +}); diff --git a/src/components/root/AppMenus.js b/src/components/root/AppMenus.js index c9682288..0a3ebf7f 100644 --- a/src/components/root/AppMenus.js +++ b/src/components/root/AppMenus.js @@ -1,8 +1,8 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; -import { useLocation } from "react-router-dom"; +import { useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import styled from "@emotion/styled"; +import styled from '@emotion/styled'; import CssBaseline from '@mui/material/CssBaseline'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; @@ -16,246 +16,259 @@ import MenuIcon from '@mui/icons-material/Menu'; import Toolbar from '@mui/material/Toolbar'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; -import AppDrawer from "./AppDrawer"; -import AppLanguage from "./AppLanguage"; +import AppDrawer from './AppDrawer'; +import AppLanguage from './AppLanguage'; import * as serviceWorkerRegistration from '../../serviceWorkerRegistration'; -import { isAboutOpenState, isLoginOpenState, uidUserState } from '../../appStates/appSettings'; +import { + isAboutOpenState, + isLoginOpenState, + isUserLoggedState, + uidUserState, +} from '../../appStates/appSettings'; const Offset = styled('div')(({ theme }) => theme.mixins.toolbar); const drawerWidth = 240; const AppMenus = (props) => { - const location = useLocation(); - const [mobileOpen, setMobileOpen] = useState(false); - const [appBarTitle, setAppBarTitle] = useState(''); - const { enabledInstall, isLoading, installPwa } = props; + const location = useLocation(); + const [mobileOpen, setMobileOpen] = useState(false); + const [appBarTitle, setAppBarTitle] = useState(''); + const { enabledInstall, isLoading, installPwa } = props; - const [uidUser, setUidUser] = useRecoilState(uidUserState); + const [isUserLogged, setIsUserLogged] = useRecoilState(isUserLoggedState); - const setIsAboutOpen = useSetRecoilState(isAboutOpenState); - const setIsLoginOpen = useSetRecoilState(isLoginOpenState); + const setIsAboutOpen = useSetRecoilState(isAboutOpenState); + const setIsLoginOpen = useSetRecoilState(isLoginOpenState); + const setUidUser = useSetRecoilState(uidUserState); - const { t } = useTranslation(); + const { t } = useTranslation(); - const handleInstallPwa = () => { - installPwa(); - } + const handleInstallPwa = () => { + installPwa(); + }; - const handleDrawerToggle = () => { - setMobileOpen(!mobileOpen) - } + const handleDrawerToggle = () => { + setMobileOpen(!mobileOpen); + }; - const handleAbout = () => { - setIsAboutOpen(true); - } + const handleAbout = () => { + setIsAboutOpen(true); + }; - const handleLogin = () => { - setIsLoginOpen(true); - } + const handleLogin = () => { + setIsLoginOpen(true); + }; - const handleLogout = () => { - setUidUser(''); - } + const handleLogout = () => { + setUidUser(''); + setIsUserLogged(false); + }; - useEffect(() => { - if (location.pathname === "/") { - setAppBarTitle(t("global.home")); - } else if (location.pathname === "/Students") { - setAppBarTitle(t("global.students")); - } else if (location.pathname === "/Schedule") { - setAppBarTitle(t("global.schedule")); - } else if (location.pathname === "/SourceMaterial") { - setAppBarTitle(t("global.sourceMaterial")); - } else if (location.pathname === "/Settings") { - setAppBarTitle(t("global.settings")); - } else if (location.pathname === "/Administration") { - setAppBarTitle(t("global.administration")); - } else if (location.pathname === "/Help") { - setAppBarTitle(t("global.help")); - }; - if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { - } else { - serviceWorkerRegistration.update(); - } - }, [t, location.pathname]) + useEffect(() => { + if (location.pathname === '/') { + setAppBarTitle(t('global.home')); + } else if (location.pathname === '/Students') { + setAppBarTitle(t('global.students')); + } else if (location.pathname === '/Schedule') { + setAppBarTitle(t('global.schedule')); + } else if (location.pathname === '/SourceMaterial') { + setAppBarTitle(t('global.sourceMaterial')); + } else if (location.pathname === '/Settings') { + setAppBarTitle(t('global.settings')); + } else if (location.pathname === '/Administration') { + setAppBarTitle(t('global.administration')); + } else if (location.pathname === '/Help') { + setAppBarTitle(t('global.help')); + } + if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { + } else { + serviceWorkerRegistration.update(); + } + }, [t, location.pathname]); - return ( - - - theme.zIndex.drawer + 1, - height: '50px !important', - minHeight: '50px !important', - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }} - > - - - - - - - - LMM-OA | - - {appBarTitle} - - - - - {(!isLoading && enabledInstall) && ( - handleInstallPwa()} - > - - - )} - - {navigator.onLine && ( - <> - {uidUser.length === 0 && ( - - - - - - )} - {uidUser.length > 0 && ( - - - - - - )} - - )} - handleAbout()} - > - - - - - - - theme.zIndex.drawer + 2, - '@media screen and (max-width: 959px)': { - '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, - display: 'block', - }, - '@media screen and (min-width: 960px)': { - display: 'none', - }, - }} - > - - LMM-OA - - - - - - - - - - ); -} - -export default AppMenus; \ No newline at end of file + return ( + + + theme.zIndex.drawer + 1, + height: '50px !important', + minHeight: '50px !important', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }} + > + + + + + + + + LMM-OA | + + {appBarTitle} + + + + + {!isLoading && enabledInstall && ( + handleInstallPwa()} + > + + + )} + + {navigator.onLine && ( + <> + {!isUserLogged && ( + + + + + + )} + {isUserLogged && ( + + + + + + )} + + )} + handleAbout()} + > + + + + + + + theme.zIndex.drawer + 2, + '@media screen and (max-width: 959px)': { + '& .MuiDrawer-paper': { + boxSizing: 'border-box', + width: drawerWidth, + }, + display: 'block', + }, + '@media screen and (min-width: 960px)': { + display: 'none', + }, + }} + > + + LMM-OA + + + + + + + + + + ); +}; + +export default AppMenus; diff --git a/src/components/root/Login.js b/src/components/root/Login.js index ada8777d..f47a52d6 100644 --- a/src/components/root/Login.js +++ b/src/components/root/Login.js @@ -17,355 +17,385 @@ import IconButton from '@mui/material/IconButton'; import Link from '@mui/material/Link'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { apiHostState, isLoginOpenState, uidUserState } from '../../appStates/appSettings'; -import { appMessageState, appSeverityState, appSnackOpenState } from '../../appStates/appNotification'; +import { + apiHostState, + isLoginOpenState, + isUserLoggedState, + uidUserState, +} from '../../appStates/appSettings'; +import { + appMessageState, + appSeverityState, + appSnackOpenState, +} from '../../appStates/appNotification'; const Login = () => { - const [isNewAccount, setIsNewAccount] = useState(false); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [confPassword, setConfPassword] = useState(''); - const [btnDisabled, setBtnDisabled] = useState(true); - const [isProcessing, setIsProcessing] = useState(false); - const [isCompleted, setIsCompleted] = useState(false); - const [verifyLink, setVerifyLink] = useState(''); - const [isLogin, setIsLogin] = useState(false); + const [isNewAccount, setIsNewAccount] = useState(false); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confPassword, setConfPassword] = useState(''); + const [btnDisabled, setBtnDisabled] = useState(true); + const [isProcessing, setIsProcessing] = useState(false); + const [isCompleted, setIsCompleted] = useState(false); + const [verifyLink, setVerifyLink] = useState(''); + const [isLogin, setIsLogin] = useState(false); - const [isOpen, setIsOpen] = useRecoilState(isLoginOpenState); + const [isOpen, setIsOpen] = useRecoilState(isLoginOpenState); - const apiHost = useRecoilValue(apiHostState); - const setUidUser = useSetRecoilState(uidUserState); - const setAppSnackOpen = useSetRecoilState(appSnackOpenState); - const setAppSeverity = useSetRecoilState(appSeverityState); - const setAppMessage = useSetRecoilState(appMessageState); + const apiHost = useRecoilValue(apiHostState); - const { t } = useTranslation(); + const setUidUser = useSetRecoilState(uidUserState); + const setAppSnackOpen = useSetRecoilState(appSnackOpenState); + const setAppSeverity = useSetRecoilState(appSeverityState); + const setAppMessage = useSetRecoilState(appMessageState); + const setIsUserLogged = useSetRecoilState(isUserLoggedState); - const handleClose = (event, reason) => { - if (reason === 'backdropClick' || reason === 'escapeKeyDown') { - return; - } - setIsOpen(false); - }; + const { t } = useTranslation(); - const handleLogin = async () => { - setIsProcessing(true); - const reqPayload = { - email: email, - password: password, - } + const handleClose = (event, reason) => { + if (reason === 'backdropClick' || reason === 'escapeKeyDown') { + return; + } + setIsOpen(false); + }; - if (apiHost !== '') { - fetch(`${apiHost}api/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(reqPayload), - }) - .then(async (res) => { - const data = await res.json(); - if (res.status === 200) { - if (data.verified) { - setIsCompleted(true); - setIsProcessing(false); - setUidUser(data.message); - handleClose(); - } else { - setIsLogin(true); - if (data.message !== 'VERIFY_FAILED') { - setVerifyLink(data.message); - } - setIsCompleted(true); - setIsProcessing(false); - } - } else { - let warnMsg = ''; - if (data.message === 'EMAIL_NOT_FOUND' || data.message === 'INVALID_EMAIL' || data.message === 'INVALID_PASSWORD' || data.message === 'MISSING_EMAIL') { - warnMsg = t("login.accountNotFound") - } else if (data.message === 'USER_DISABLED') { - warnMsg = t("login.accountDisabled") - } else { - warnMsg = data.message; - } - setIsProcessing(false); - setAppMessage(warnMsg); - setAppSeverity('warning') - setAppSnackOpen(true); - } - }) - .catch((err) => { - setIsProcessing(false); - setAppMessage(err.message); - setAppSeverity('error'); - setAppSnackOpen(true); - }) - } - } + const handleLogin = async () => { + setIsProcessing(true); + const reqPayload = { + email: email, + password: password, + }; - const handleCreateAccount = async () => { - setIsProcessing(true); - const reqPayload = { - email: email, - password: password, - } + if (apiHost !== '') { + fetch(`${apiHost}api/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(reqPayload), + }) + .then(async (res) => { + const data = await res.json(); + if (res.status === 200) { + if (data.verified) { + setIsCompleted(true); + setIsProcessing(false); + setUidUser(data.message); + setIsUserLogged(true); + handleClose(); + } else { + setIsLogin(true); + if (data.message !== 'VERIFY_FAILED') { + setVerifyLink(data.message); + } + setIsCompleted(true); + setIsProcessing(false); + } + } else { + let warnMsg = ''; + if ( + data.message === 'EMAIL_NOT_FOUND' || + data.message === 'INVALID_EMAIL' || + data.message === 'INVALID_PASSWORD' || + data.message === 'MISSING_EMAIL' + ) { + warnMsg = t('login.accountNotFound'); + } else if (data.message === 'USER_DISABLED') { + warnMsg = t('login.accountDisabled'); + } else { + warnMsg = data.message; + } + setIsProcessing(false); + setAppMessage(warnMsg); + setAppSeverity('warning'); + setAppSnackOpen(true); + } + }) + .catch((err) => { + setIsProcessing(false); + setAppMessage(err.message); + setAppSeverity('error'); + setAppSnackOpen(true); + }); + } + }; - if (apiHost !== '') { - fetch(`${apiHost}api/create-account`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(reqPayload), - }) - .then(async (res) => { - const data = await res.json(); - if (res.status === 200) { - if (data.message !== 'VERIFY_FAILED') { - setVerifyLink(data.message); - } - setIsCompleted(true); - setIsProcessing(false); - } else { - setIsProcessing(false); - setAppMessage(data.message); - setAppSeverity('warning') - setAppSnackOpen(true); - } - }) - .catch((err) => { - setIsProcessing(false); - setAppMessage(err.message); - setAppSeverity('error') - setAppSnackOpen(true); - }) - } - } + const handleCreateAccount = async () => { + setIsProcessing(true); + const reqPayload = { + email: email, + password: password, + }; - const handleTypeLoginCheck = (e) => { - setIsNewAccount(e.target.checked); - setPassword(''); - setConfPassword(''); - } + if (apiHost !== '') { + fetch(`${apiHost}api/create-account`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(reqPayload), + }) + .then(async (res) => { + const data = await res.json(); + if (res.status === 200) { + if (data.message !== 'VERIFY_FAILED') { + setVerifyLink(data.message); + } + setIsCompleted(true); + setIsProcessing(false); + } else { + setIsProcessing(false); + setAppMessage(data.message); + setAppSeverity('warning'); + setAppSnackOpen(true); + } + }) + .catch(() => { + setIsProcessing(false); + setAppMessage(t('login.createFailed')); + setAppSeverity('error'); + setAppSnackOpen(true); + }); + } + }; - useEffect(() => { - if (email.length > 0 ) { - if (isNewAccount) { - if (password.length > 0 && password === confPassword) { - setBtnDisabled(false) - } else { - setBtnDisabled(true) - } - } else { - if (password.length > 0) { - setBtnDisabled(false) - } else { - setBtnDisabled(true) - } - } - } else { - setBtnDisabled(true) - } - }, [isNewAccount, email, password, confPassword]) + const handleTypeLoginCheck = (e) => { + setIsNewAccount(e.target.checked); + setPassword(''); + setConfPassword(''); + }; - return ( - - - - - - - - - App logo - - LMM-OA - - - {!isCompleted && ( - - setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - {isNewAccount && ( - setConfPassword(e.target.value)} - /> - )} - - )} - {isProcessing && ( - - - - )} - {!isProcessing && ( - <> - {isCompleted && ( - <> - {isLogin && ( - <> - {verifyLink.length === 0 && ( - {t("login.accountNotVerifiedNoLink")} - )} - {verifyLink.length > 0 && ( - {t("login.accountNotVerified")}{t("login.accountActivate")}. - )} - - )} - {!isLogin && ( - <> - {verifyLink.length === 0 && ( - {t("login.accountCreateWithoutLink")} - )} - {verifyLink.length > 0 && ( - {t("login.accountCreateWithLink")}{t("login.accountActivate")}. - )} - - )} - - - - - - )} - {!isCompleted && ( - - - - } - label={ - - {t("login.newAccount")} - - } - /> - - - - )} - - )} - - - ); -} - -export default Login; \ No newline at end of file + useEffect(() => { + if (email.length > 0) { + if (isNewAccount) { + if (password.length > 0 && password === confPassword) { + setBtnDisabled(false); + } else { + setBtnDisabled(true); + } + } else { + if (password.length > 0) { + setBtnDisabled(false); + } else { + setBtnDisabled(true); + } + } + } else { + setBtnDisabled(true); + } + }, [isNewAccount, email, password, confPassword]); + + return ( + + + + + + + + + App logo + + LMM-OA + + + {!isCompleted && ( + + setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + {isNewAccount && ( + setConfPassword(e.target.value)} + /> + )} + + )} + {isProcessing && ( + + + + )} + {!isProcessing && ( + <> + {isCompleted && ( + <> + {isLogin && ( + <> + {verifyLink.length === 0 && ( + + {t('login.accountNotVerifiedNoLink')} + + )} + {verifyLink.length > 0 && ( + + {t('login.accountNotVerified')} + + {t('login.accountActivate')} + + . + + )} + + )} + {!isLogin && ( + <> + {verifyLink.length === 0 && ( + + {t('login.accountCreateWithoutLink')} + + )} + {verifyLink.length > 0 && ( + + {t('login.accountCreateWithLink')} + + {t('login.accountActivate')} + + . + + )} + + )} + + + + + + )} + {!isCompleted && ( + + + + } + label={ + + {t('login.newAccount')} + + } + /> + + + + )} + + )} + + + ); +}; + +export default Login; diff --git a/src/components/settings/BasicSettings.js b/src/components/settings/BasicSettings.js index 25d44992..d9e193d0 100644 --- a/src/components/settings/BasicSettings.js +++ b/src/components/settings/BasicSettings.js @@ -1,4 +1,6 @@ -import { useEffect, useState } from "react"; +import { useState } from 'react'; +import { useRecoilState, useSetRecoilState } from 'recoil'; +import { useTranslation } from 'react-i18next'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; @@ -7,185 +9,208 @@ import MenuItem from '@mui/material/MenuItem'; import SaveIcon from '@mui/icons-material/Save'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { dbGetAppSettings, dbUpdateAppSettings } from "../../indexedDb/dbAppSettings"; +import { dbUpdateAppSettings } from '../../indexedDb/dbAppSettings'; +import { + appMessageState, + appSeverityState, + appSnackOpenState, +} from '../../appStates/appNotification'; +import { + classCountState, + congNameState, + congNumberState, + liveClassState, + meetingDayState, +} from '../../appStates/appCongregation'; -const BasicSettings = (props) => { - const [congName, setCongName] = useState(""); - const [congNumber, setCongNumber] = useState(""); - const [isErrorCongName, setIsErrorCongName] = useState(false); - const [isErrorCongNumber, setIsErrorCongNumber] = useState(false); - const [meetingDay, setMeetingDay] = useState(3); - const [classCount, setClassCount] = useState(1); - const [liveClass, setLiveClass] = useState(false); +const BasicSettings = () => { + const { t } = useTranslation(); - const handleCongNameChange = (value) => { - if (value) { - setIsErrorCongName(false); - } else { - setIsErrorCongName(true); - } - setCongName(value); - } + const [isErrorCongName, setIsErrorCongName] = useState(false); + const [isErrorCongNumber, setIsErrorCongNumber] = useState(false); - const handleCongNumberChange = (value) => { - if (value) { - setIsErrorCongNumber(false); - } else { - setIsErrorCongNumber(true); - } - setCongNumber(value); - } + const [congName, setCongName] = useRecoilState(congNameState); + const [congNumber, setCongNumber] = useRecoilState(congNumberState); + const [meetingDay, setMeetingDay] = useRecoilState(meetingDayState); + const [classCount, setClassCount] = useRecoilState(classCountState); + const [liveClass, setLiveClass] = useRecoilState(liveClassState); - const handleMeetingDayChange = (e) => { - setMeetingDay(e.target.value) - } + const setAppSnackOpen = useSetRecoilState(appSnackOpenState); + const setAppSeverity = useSetRecoilState(appSeverityState); + const setAppMessage = useSetRecoilState(appMessageState); - const handleClassChange = (e) => { - setClassCount(e.target.value) - } + const [tempCongName, setTempCongName] = useState(congName); + const [tempCongNumber, setTempCongNumber] = useState(congNumber); + const [tempMeetingDay, setTempMeetingDay] = useState(meetingDay); + const [tempClassCount, setTempClassCount] = useState(classCount); + const [tempLiveClass, setTempLiveClass] = useState(liveClass); - const saveAppSettings = async () => { - var obj = {}; - obj.cong_name = congName; - obj.cong_number = congNumber; - obj.class_count = classCount; - obj.meeting_day = meetingDay; - obj.liveEventClass = liveClass; - await dbUpdateAppSettings(obj); - props.setAppSnackOpen(true); - props.setAppSeverity("success"); - props.setAppMessage("Vita ny fanavaozana ny mombamomba ny fiangonana"); - }; + const handleCongNameChange = (value) => { + if (value) { + setIsErrorCongName(false); + } else { + setIsErrorCongName(true); + } + setTempCongName(value); + }; - useEffect(() => { - const loadInfo = async () => { - const appSettings = await dbGetAppSettings(); - setCongName(appSettings.cong_name); - setCongNumber(appSettings.cong_number); - setClassCount(appSettings.class_count); - setMeetingDay(appSettings.meeting_day); - setLiveClass(appSettings.liveEventClass); - }; - loadInfo(); - }, []) + const handleCongNumberChange = (value) => { + if (value) { + setIsErrorCongNumber(false); + } else { + setIsErrorCongNumber(true); + } + setTempCongNumber(value); + }; - return ( - <> - - MOMBAMOMBA NY FIANGONANA - - Ampidiro eto ambany ny anaran’ny fiangonana sy nomerao - handleCongNameChange(e.target.value)} - /> - handleCongNumberChange(e.target.value)} - /> - - Ampidiro eto ambany ny andro hanaovana ny fivoriana andavanandro, sy ny isan’ny kilasy misy anjaran’ny mpianatra - - Alatsinainy - Talata - Alarobia - Alakamisy - Zoma - Asabotsy - - - Kilasy 1 - Kilasy 2 - - - - setLiveClass(e.target.checked)} - color="primary" - />} - label="Mpianatra afaka mandray anjara mivantana ihany" - /> - - - - - ); -} - -export default BasicSettings; \ No newline at end of file + const handleMeetingDayChange = (e) => { + setTempMeetingDay(e.target.value); + }; + + const handleClassChange = (e) => { + setTempClassCount(e.target.value); + }; + + const saveAppSettings = async () => { + var obj = {}; + obj.cong_name = tempCongName; + obj.cong_number = tempCongNumber; + obj.class_count = tempClassCount; + obj.meeting_day = tempMeetingDay; + obj.liveEventClass = tempLiveClass; + await dbUpdateAppSettings(obj); + + setCongName(tempCongName); + setCongNumber(tempCongNumber); + setClassCount(tempClassCount); + setMeetingDay(tempMeetingDay); + setLiveClass(tempLiveClass); + + setAppSnackOpen(true); + setAppSeverity('success'); + setAppMessage(t('settings.saved')); + }; + + return ( + <> + + {t('settings.aboutCongregation')} + + handleCongNameChange(e.target.value)} + /> + handleCongNumberChange(e.target.value)} + /> + + + {t('global.monday')} + {t('global.tuesday')} + {t('global.wednesday')} + {t('global.thursday')} + {t('global.friday')} + {t('global.saturday')} + + + {t('global.oneClass')} + {t('global.twoClass')} + + + + setTempLiveClass(e.target.checked)} + color='primary' + /> + } + label={t('settings.liveClass')} + /> + + + + + ); +}; + +export default BasicSettings; diff --git a/src/components/settings/DataStorage.js b/src/components/settings/DataStorage.js index 4986f143..48a13159 100644 --- a/src/components/settings/DataStorage.js +++ b/src/components/settings/DataStorage.js @@ -1,4 +1,6 @@ -import { useState } from "react"; +import { useEffect, useState } from 'react'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useTranslation } from 'react-i18next'; import { fileDialog } from 'file-select-dialog'; import BackupIcon from '@mui/icons-material/Backup'; import Box from '@mui/material/Box'; @@ -14,153 +16,174 @@ import MenuItem from '@mui/material/MenuItem'; import SaveIcon from '@mui/icons-material/Save'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import Typography from '@mui/material/Typography'; -import { dbExportDb } from "../../indexedDb/dbUtility"; - -const DataStorage = (props) => { - const [anchorEl, setAnchorEl] = useState(null); - - let isMenuOpen = Boolean(anchorEl); - - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - } - - const backupDb = async () => { - handleClose(); - await dbExportDb(); - }; - - const prepBackupDbOnline = async () => { - - } - - const restoreDb = async () => { - handleClose(); - const file = await fileDialog({ - accept: '.db', - strict: true - }); - - const getEncryptedText = () => { - return new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.readAsText(file); - reader.onloadend = () => resolve(reader.result); - reader.onerror = error => reject(error); - }); - }; - - const backupData = await getEncryptedText() - - try { - const myKey = ''; - const Cryptr = require('cryptr'); - const cryptr = new Cryptr(myKey); - const decryptedData = cryptr.decrypt(backupData); - fetch(decryptedData) - .then(res => res.blob()) - .then(blob => { - props.setJsonFile(blob); - props.setIsRedirect(true); - }) - } catch (error) { - var errorCode = error.code; - var errorMessage = error.message; - if (error.message === "Unsupported state or unable to authenticate data") { - props.setAppSnackOpen(true); - props.setAppSeverity("error"); - props.setAppMessage("Tsy afaka mamerina io rakitra fiandry io ny kaody manokana ampiasainao izao."); - } else { - props.setAppSnackOpen(true); - props.setAppSeverity("error"); - props.setAppMessage(`(${errorCode}) ${errorMessage}`); - } - } - }; - - const prepRestoreDbOnline = async () => { - - } - - const handleDelete = () => { - - } - - return ( - <> - FITEHIRIZANA -
- - - - - - - - Hamorona - - - - - - Hamorona (Internet) - - - - - - - Hampiditra - - - - - - Hampiditra (Internet) - - - -
-
- - Hamafa tanteraka ny rakitra ampiasain’ny LMM-OA. - - -
- - ); -} - -export default DataStorage; \ No newline at end of file +import DialogDbBackup from './DialogDbBackup'; +import DialogDbDeletion from './DialogDbDeletion'; +import { + backupEncryptedState, + isBackupDbOpenState, + isBackupOfflineState, + isBackupOnlineState, + isDeleteDbOpenState, + isRestoreOfflineState, + isRestoreOnlineState, + isUserLoggedState, +} from '../../appStates/appSettings'; + +const DataStorage = () => { + const { t } = useTranslation(); + + const [anchorEl, setAnchorEl] = useState(null); + + const [isBackupDb, setIsBackupDb] = useRecoilState(isBackupDbOpenState); + + const setIsDeleteDb = useSetRecoilState(isDeleteDbOpenState); + const setBackupOffline = useSetRecoilState(isBackupOfflineState); + const setBackupOnline = useSetRecoilState(isBackupOnlineState); + const setRestoreOffline = useSetRecoilState(isRestoreOfflineState); + const setRestoreOnline = useSetRecoilState(isRestoreOnlineState); + const setBackupFile = useSetRecoilState(backupEncryptedState); + + const isUserLogged = useRecoilValue(isUserLoggedState); + + let isMenuOpen = Boolean(anchorEl); + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const backupDb = async () => { + handleClose(); + setBackupOnline(false); + setRestoreOnline(false); + setRestoreOffline(false); + setBackupOffline(true); + setIsBackupDb(true); + }; + + const backupOnlineDb = async () => { + handleClose(); + setBackupOffline(false); + setRestoreOnline(false); + setRestoreOffline(false); + setBackupOnline(true); + setIsBackupDb(true); + }; + + const restoreDb = async () => { + handleClose(); + const file = await fileDialog({ + accept: '.db', + strict: true, + }); + + setBackupFile(file); + setBackupOffline(false); + setBackupOnline(false); + setRestoreOnline(false); + setRestoreOffline(true); + setIsBackupDb(true); + }; + + const restoreOnlineDb = async () => { + handleClose(); + setBackupOffline(false); + setBackupOnline(false); + setRestoreOffline(false); + setRestoreOnline(true); + setIsBackupDb(true); + }; + + const handleDelete = () => { + setIsDeleteDb(true); + }; + + useEffect(() => { + setIsDeleteDb(false); + }, [setIsDeleteDb]); + + return ( + <> + {isBackupDb && } + + + {t('settings.dataStorage')} + +
+ + + + + + + + {t('settings.createLocalBackup')} + + {isUserLogged && ( + + + + + {t('settings.createOnlineBackup')} + + )} + + + + + + {t('settings.restoreLocalBackup')} + + {isUserLogged && ( + + + + + {t('settings.restoreOnlineBackup')} + + )} + + +
+
+ + {t('settings.eraseDesc')} + + +
+ + ); +}; + +export default DataStorage; diff --git a/src/components/settings/DialogDbBackup.js b/src/components/settings/DialogDbBackup.js index dd8911bf..8ba64735 100644 --- a/src/components/settings/DialogDbBackup.js +++ b/src/components/settings/DialogDbBackup.js @@ -1,4 +1,8 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil'; +import { useTranslation } from 'react-i18next'; +import UAParser from 'ua-parser-js'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -8,173 +12,760 @@ import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import StorageIcon from '@mui/icons-material/Storage'; +import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; +import { + appMessageState, + appSeverityState, + appSnackOpenState, +} from '../../appStates/appNotification'; +import { + apiHostState, + backupEncryptedState, + backupJsonDataState, + isBackupDbOpenState, + isBackupOfflineState, + isBackupOnlineState, + isRestoreOfflineState, + isRestoreOnlineState, + uidUserState, +} from '../../appStates/appSettings'; +import { dbExportDb, dbExportJsonDb } from '../../indexedDb/dbUtility'; -const DialogDbBackup = (props) => { - const { - backupDbOnline, - backupType, - hasBackup, - backupDate, - backupDevice, - backupNewDevice, - isLoadingBackup, - open, - restoreDbOnline, - setOpen - } = props; - const [isDisabled, setIsDisabled] = useState(true); - const [dateFormat, setDateFormat] = useState(""); - - const handleClose = () => { - setOpen(false); - }; - - const handleDialogActions = () => { - if (backupType === "backup") { - backupDbOnline(); - handleClose(); - } else if (backupType === "restore") { - restoreDbOnline(); - handleClose(); - } - } - - useEffect(() => { - if (backupDate !== "") { - var timestamp = backupDate; - var date = new Date(timestamp); - setDateFormat(date.toLocaleDateString() + " " + date.toLocaleTimeString()); - } - }, [backupDate]) - - useEffect(() => { - setIsDisabled(isLoadingBackup); - }, [isLoadingBackup]) - - return ( -
- - Rakitra Fiandry - - <> - {isLoadingBackup && ( - - )} - {!isLoadingBackup && ( - <> - {(backupType === "backup") && ( - <> - {hasBackup && ( - <> - Efa misy rakitra fiandry any amin’ny internet, ka tianao hosoloina ve? - - - Vaovao - - {backupNewDevice.split("|")[0]} - {backupNewDevice.split("|")[1]} - - - - - - Any amin’ny internet - - {backupDevice.split("|")[0]} - {backupDevice.split("|")[1]} - {dateFormat} - - - - )} - {!hasBackup && ( - <> - Handefa rakitra fiandry any amin’ny internet: - - - {backupNewDevice.split("|")[0]} - {backupNewDevice.split("|")[1]} - - - )} - - )} - {(backupType === "restore") && ( - <> - Hamerina rakitra fiandry avy any amin’ny internet - - - {backupDevice.split("|")[0]} - {backupDevice.split("|")[1]} - {dateFormat} - - - )} - - )} - - - - - - - -
- ); -} - -export default DialogDbBackup; \ No newline at end of file +const DialogDbBackup = () => { + let history = useHistory(); + const { t } = useTranslation(); + + const [isDisabled, setIsDisabled] = useState(true); + const [dateFormat, setDateFormat] = useState(''); + const [backupCreatePwd, setBackupCreatePwd] = useState(''); + const [backupConfirmPwd, setBackupConfirmPwd] = useState(''); + const [backupNewDevice, setBackupNewDevice] = useState(''); + const [isLoadingBackup, setIsLoadingBackup] = useState(true); + const [hasBackup, setHasBackup] = useState(false); + const [backupData, setBackupData] = useState(''); + const [backupType, setBackupType] = useState(''); + const [backupDate, setBackupDate] = useState(''); + const [backupDevice, setBackupDevice] = useState(''); + + const [open, setOpen] = useRecoilState(isBackupDbOpenState); + + const isBackupOffline = useRecoilValue(isBackupOfflineState); + const isBackupOnline = useRecoilValue(isBackupOnlineState); + const isRestoreOffline = useRecoilValue(isRestoreOfflineState); + const isRestoreOnline = useRecoilValue(isRestoreOnlineState); + const apiHost = useRecoilValue(apiHostState); + const uid = useRecoilValue(uidUserState); + const fileBackup = useRecoilValue(backupEncryptedState); + + const setAppSnackOpen = useSetRecoilState(appSnackOpenState); + const setAppSeverity = useSetRecoilState(appSeverityState); + const setAppMessage = useSetRecoilState(appMessageState); + const setJsonData = useSetRecoilState(backupJsonDataState); + + const handleClose = useCallback( + (event, reason) => { + if (reason === 'clickaway' || reason === 'backdropClick') { + return; + } + setOpen(false); + }, + [setOpen] + ); + + const offlineBackupDb = async () => { + setIsLoadingBackup(true); + setIsDisabled(true); + + dbExportDb(backupConfirmPwd) + .then(() => { + setAppSnackOpen(true); + setAppSeverity('success'); + setAppMessage(t('settings.backupSuccess')); + setBackupCreatePwd(''); + setBackupConfirmPwd(''); + handleClose(); + }) + .catch(() => { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupError')); + setBackupCreatePwd(''); + setBackupConfirmPwd(''); + handleClose(); + }); + }; + + const onlineBackup = () => { + setIsLoadingBackup(true); + setBackupCreatePwd(''); + setBackupConfirmPwd(''); + dbExportJsonDb(backupConfirmPwd) + .then((data) => { + fetch(`${apiHost}api/user/send-backup`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + uid: uid, + }, + body: JSON.stringify({ + backup_type: 'lmmoa', + backup_data: data, + backup_date: new Date().getTime(), + backup_device: backupNewDevice, + }), + }) + .then((result) => { + if (result.status === 200) { + setAppSnackOpen(true); + setAppSeverity('success'); + setAppMessage(t('settings.backupSuccess')); + } else { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupError')); + } + handleClose(); + }) + .catch(() => { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupError')); + handleClose(); + }); + }) + .catch(() => { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupError')); + handleClose(); + }); + }; + + const offlineRestoreDb = async () => { + setIsLoadingBackup(true); + setIsDisabled(true); + + const getEncryptedText = () => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.readAsText(fileBackup); + reader.onloadend = () => resolve(reader.result); + reader.onerror = (error) => reject(error); + }); + }; + + const backupData = await getEncryptedText(); + + try { + const Cryptr = require('cryptr'); + const cryptr = new Cryptr(backupConfirmPwd); + const decryptedData = cryptr.decrypt(backupData); + fetch(decryptedData) + .then((res) => res.blob()) + .then((blob) => { + setJsonData(blob); + history.push('/DBRestore'); + handleClose(); + }); + } catch (error) { + if ( + error.message === 'Unsupported state or unable to authenticate data' + ) { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRestoreInvalidPassword')); + } else { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRestoreError')); + } + handleClose(); + } + }; + + const onlineRestoreDb = async () => { + setIsLoadingBackup(true); + setIsDisabled(true); + + try { + const Cryptr = require('cryptr'); + const cryptr = new Cryptr(backupConfirmPwd); + const decryptedData = cryptr.decrypt(backupData); + fetch(decryptedData) + .then((res) => res.blob()) + .then((blob) => { + setJsonData(blob); + history.push('/DBRestore'); + handleClose(); + }); + } catch (error) { + if ( + error.message === 'Unsupported state or unable to authenticate data' + ) { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRestoreInvalidPassword')); + } else { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRestoreError')); + } + handleClose(); + } + }; + + const handleDialogActions = () => { + if (isBackupOffline) { + offlineBackupDb(); + } else if (isBackupOnline) { + onlineBackup(); + } else if (isRestoreOffline) { + offlineRestoreDb(); + } else if (isRestoreOnline) { + onlineRestoreDb(); + } + }; + + useEffect(() => { + if (backupDate !== '') { + var timestamp = backupDate; + var date = new Date(timestamp); + setDateFormat( + date.toLocaleDateString() + ' ' + date.toLocaleTimeString() + ); + } + }, [backupDate]); + + useEffect(() => { + if (isBackupOffline || isBackupOnline) { + if (backupCreatePwd.length === 0 && backupConfirmPwd.length === 0) { + setIsDisabled(true); + } else { + if (backupCreatePwd === backupConfirmPwd) { + setIsDisabled(false); + } else { + setIsDisabled(true); + } + } + } + }, [isBackupOffline, isBackupOnline, backupCreatePwd, backupConfirmPwd]); + + useEffect(() => { + if (isBackupOffline) { + setIsLoadingBackup(false); + } + }, [isBackupOffline]); + + useEffect(() => { + if (isRestoreOffline) { + setIsLoadingBackup(false); + if (backupConfirmPwd.length === 0) { + setIsDisabled(true); + } else { + setIsDisabled(false); + } + } + }, [isRestoreOffline, backupConfirmPwd]); + + useEffect(() => { + if (isBackupOnline && open) { + // Retrieve online backup if any + const getOnlineBackup = async () => { + if (apiHost !== '') { + try { + const result = await fetch(`${apiHost}api/user/get-backup`, { + method: 'GET', + headers: { + uid: uid, + }, + }); + + if (result.status === 200) { + const data = await result.json(); + setBackupDevice(data.message.backup_device); + setBackupDate(data.message.backup_date); + setHasBackup(true); + } else if (result.status === 404) { + setHasBackup(false); + } else { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRetrieveFailed')); + handleClose(); + } + } catch { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRetrieveFailed')); + handleClose(); + } + } + setIsLoadingBackup(false); + }; + + setIsLoadingBackup(true); + setIsDisabled(true); + setBackupType('backup'); + const parser = new UAParser(); + const ua = parser.getResult(); + setBackupNewDevice( + ua.os.name + + ' ' + + ua.os.version + + '|' + + ua.browser.name + + ' ' + + ua.browser.version + ); + // Retrieve online backup if any + getOnlineBackup(); + } + }, [ + isBackupOnline, + open, + apiHost, + uid, + handleClose, + setAppMessage, + setAppSeverity, + setAppSnackOpen, + t, + ]); + + useEffect(() => { + if (isRestoreOnline && open) { + // Retrieve online backup if any + const getOnlineBackup = async () => { + if (apiHost !== '') { + try { + const result = await fetch(`${apiHost}api/user/get-backup`, { + method: 'GET', + headers: { + uid: uid, + }, + }); + + if (result.status === 200) { + const data = await result.json(); + setBackupData(data.message.backup_data); + setBackupDevice(data.message.backup_device); + setBackupDate(data.message.backup_date); + setHasBackup(true); + setBackupType('restore'); + } else if (result.status === 404) { + setAppSnackOpen(true); + setAppSeverity('info'); + setAppMessage(t('settings.backupOnlineNone')); + handleClose(); + } else { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRetrieveFailed')); + handleClose(); + } + } catch { + setAppSnackOpen(true); + setAppSeverity('error'); + setAppMessage(t('settings.backupRetrieveFailed')); + handleClose(); + } + } + setIsLoadingBackup(false); + }; + + setIsLoadingBackup(true); + setIsDisabled(true); + + // Retrieve online backup if any + getOnlineBackup(); + } + }, [ + isRestoreOnline, + open, + apiHost, + uid, + handleClose, + setAppMessage, + setAppSeverity, + setAppSnackOpen, + t, + ]); + + useEffect(() => { + if (isRestoreOnline) { + if (backupConfirmPwd.length === 0) { + setIsDisabled(true); + } else { + setIsDisabled(false); + } + } + }, [isRestoreOnline, backupConfirmPwd]); + + return ( +
+ + + {t('settings.backup')} + + + <> + {isBackupOffline && ( + <> + {isLoadingBackup && ( + + )} + {!isLoadingBackup && ( + + + {t('settings.createPassword')} + + setBackupCreatePwd(e.target.value)} + /> + setBackupConfirmPwd(e.target.value)} + /> + + )} + + )} + {isRestoreOffline && ( + <> + {isLoadingBackup && ( + + )} + {!isLoadingBackup && ( + + + {t('settings.restoreIntro')} + + setBackupConfirmPwd(e.target.value)} + /> + + )} + + )} + {isLoadingBackup && (isBackupOnline || isRestoreOnline) && ( + + )} + {!isLoadingBackup && (isBackupOnline || isRestoreOnline) && ( + <> + {backupType === 'backup' && ( + <> + {hasBackup && ( + <> + + {t('settings.backupOnlineSendUpdate')} + + + + + {t('settings.backupNew')} + + + + {backupNewDevice.split('|')[0]} + + + {backupNewDevice.split('|')[1]} + + + + + + + + {t('settings.backupOld')} + + + + {backupDevice.split('|')[0]} + + + {backupDevice.split('|')[1]} + + + {dateFormat} + + + + + )} + {!hasBackup && ( + <> + + {t('settings.backupOnlineSendNew')} + + + + + {backupNewDevice.split('|')[0]} + + + {backupNewDevice.split('|')[1]} + + + + )} + + + {t('settings.createPassword')} + + setBackupCreatePwd(e.target.value)} + /> + setBackupConfirmPwd(e.target.value)} + /> + + + )} + {backupType === 'restore' && ( + <> + {t('settings.restoreOnlineIntro')} + + + + {backupDevice.split('|')[0]} + + + {backupDevice.split('|')[1]} + + + {dateFormat} + + + + + {t('settings.restoreIntro')} + + setBackupConfirmPwd(e.target.value)} + /> + + + )} + + )} + + + + + + + +
+ ); +}; + +export default DialogDbBackup; diff --git a/src/components/settings/DialogDbDeletion.js b/src/components/settings/DialogDbDeletion.js index a615150a..3fec2873 100644 --- a/src/components/settings/DialogDbDeletion.js +++ b/src/components/settings/DialogDbDeletion.js @@ -1,4 +1,5 @@ -import { useState } from 'react'; +import { useRecoilState } from 'recoil'; +import { useTranslation } from 'react-i18next'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; @@ -6,49 +7,49 @@ import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import { deleteDb } from '../../indexedDb/dbUtility'; -import { useEffect } from 'react'; +import { isDeleteDbOpenState } from '../../appStates/appSettings'; -const DialogDbDeletion = (props) => { - const [open, setOpen] = useState(true); +const DialogDbDeletion = () => { + const { t } = useTranslation(); - const handleClose = () => { - props.setOpen(false); - }; + const [open, setOpen] = useRecoilState(isDeleteDbOpenState); - const handleDelete = async () => { - await deleteDb(); - window.location.href = './'; - }; + const handleClose = () => { + setOpen(false); + }; - useEffect(() => { - setOpen(props.open) - }, [props.open]) + const handleDelete = async () => { + await deleteDb(); + window.location.href = './'; + }; - return ( -
- - {"Hofafana tanteraka ve ny rakitra LMM-OA?"} - - - Ho voafafa daholo ny rakitra rehetra momba ny LMM-OA, anisan’izany ny lisitry ny mpianatra, fandaharana, mombamomba ny fiangonana. - - - - - - - -
- ); -} - -export default DialogDbDeletion; \ No newline at end of file + return ( +
+ + + {t('settings.deleteDbTitle')} + + + + {t('settings.deleteDbDesc')} + + + + + + + +
+ ); +}; + +export default DialogDbDeletion; diff --git a/src/components/startup/StepperCongregation.js b/src/components/startup/StepperCongregation.js index e84b8a9c..67f894a5 100644 --- a/src/components/startup/StepperCongregation.js +++ b/src/components/startup/StepperCongregation.js @@ -2,68 +2,78 @@ import { useRecoilState } from 'recoil'; import { useTranslation } from 'react-i18next'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { congNameState, congNumberState, isErrorCongNameState, isErrorCongNumberState } from '../../appStates/appCongregation'; +import { + congNameState, + congNumberState, + isErrorCongNameState, + isErrorCongNumberState, +} from '../../appStates/appCongregation'; const StepperCongregation = () => { - const { t } = useTranslation(); + const { t } = useTranslation(); - const [congName, setCongName] = useRecoilState(congNameState); - const [congNumber, setCongNumber] = useRecoilState(congNumberState); - const [isErrorCongName, setIsErrorCongName] = useRecoilState(isErrorCongNameState); - const [isErrorCongNumber, setIsErrorCongNumber] = useRecoilState(isErrorCongNumberState); + const [congName, setCongName] = useRecoilState(congNameState); + const [congNumber, setCongNumber] = useRecoilState(congNumberState); + const [isErrorCongName, setIsErrorCongName] = + useRecoilState(isErrorCongNameState); + const [isErrorCongNumber, setIsErrorCongNumber] = useRecoilState( + isErrorCongNumberState + ); - const handleCongNameChange = (value) => { - if (value) { - setIsErrorCongName(false); - } else { - setIsErrorCongName(true); - } - setCongName(value); - } + const handleCongNameChange = (value) => { + if (value) { + setIsErrorCongName(false); + } else { + setIsErrorCongName(true); + } + setCongName(value); + }; - const handleCongNumberChange = (value) => { - if (value) { - setIsErrorCongNumber(false); - } else { - setIsErrorCongNumber(true); - } - setCongNumber(value); - } + const handleCongNumberChange = (value) => { + if (value) { + setIsErrorCongNumber(false); + } else { + setIsErrorCongNumber(true); + } + setCongNumber(value); + }; - return ( -
- {t("startup.congInfoDescription")} - handleCongNameChange(e.target.value)} - /> - handleCongNumberChange(e.target.value)} - /> -
- ); -} - -export default StepperCongregation; \ No newline at end of file + return ( +
+ + {t('startup.congInfoDescription')} + + handleCongNameChange(e.target.value)} + /> + handleCongNumberChange(e.target.value)} + /> +
+ ); +}; + +export default StepperCongregation; diff --git a/src/components/startup/StepperMeetingDetails.js b/src/components/startup/StepperMeetingDetails.js index 465fc4da..185405fd 100644 --- a/src/components/startup/StepperMeetingDetails.js +++ b/src/components/startup/StepperMeetingDetails.js @@ -1,74 +1,79 @@ -import { useRecoilState } from "recoil"; -import { useTranslation } from "react-i18next"; +import { useRecoilState } from 'recoil'; +import { useTranslation } from 'react-i18next'; import Box from '@mui/material/Box'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { classCountState, meetingDayState } from "../../appStates/appCongregation"; +import { + classCountState, + meetingDayState, +} from '../../appStates/appCongregation'; const StepperMeetingDetails = () => { - const { t } = useTranslation(); + const { t } = useTranslation(); - const [meetingDay, setMeetingDay] = useRecoilState(meetingDayState); - const [classCount, setClassCount] = useRecoilState(classCountState); + const [meetingDay, setMeetingDay] = useRecoilState(meetingDayState); + const [classCount, setClassCount] = useRecoilState(classCountState); - const handleMeetingDayChange = (e) => { - setMeetingDay(e.target.value) - } + const handleMeetingDayChange = (e) => { + setMeetingDay(e.target.value); + }; - const handleClassChange = (e) => { - setClassCount(e.target.value) - } + const handleClassChange = (e) => { + setClassCount(e.target.value); + }; - return ( - - {t("startup.meetingInfoDescription")} - - {t("global.monday")} - {t("global.tuesday")} - {t("global.wednesday")} - {t("global.thursday")} - {t("global.friday")} - {t("global.saturday")} - - - {t("global.oneClass")} - {t("global.twoClass")} - - - ); -} - -export default StepperMeetingDetails; \ No newline at end of file + return ( + + + {t('startup.meetingInfoDescription')} + + + {t('global.monday')} + {t('global.tuesday')} + {t('global.wednesday')} + {t('global.thursday')} + {t('global.friday')} + {t('global.saturday')} + + + {t('global.oneClass')} + {t('global.twoClass')} + + + ); +}; + +export default StepperMeetingDetails; diff --git a/src/indexedDb/dbUtility.js b/src/indexedDb/dbUtility.js index 10290264..6182f654 100644 --- a/src/indexedDb/dbUtility.js +++ b/src/indexedDb/dbUtility.js @@ -57,29 +57,32 @@ export const dbRestoreDb = async (fileJSON) => { await importDB(fileJSON); }; -export const dbExportDb = async () => { - const blob = await exportDB(appDb); - const convertBase64 = () => { - return new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = () => resolve(reader.result); - reader.onerror = (error) => reject(error); - }); - }; +export const dbExportDb = async (passcode) => { + try { + const blob = await exportDB(appDb); + const convertBase64 = () => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => resolve(reader.result); + reader.onerror = (error) => reject(error); + }); + }; - const data = await convertBase64(); - const myKey = ''; - const Cryptr = require('cryptr'); - const cryptr = new Cryptr(myKey); - const encryptedData = cryptr.encrypt(data); + const data = await convertBase64(); + const Cryptr = require('cryptr'); + const cryptr = new Cryptr(passcode); + const encryptedData = cryptr.encrypt(data); - const newBlob = new Blob([encryptedData], { type: 'text/csv' }); + const newBlob = new Blob([encryptedData], { type: 'text/csv' }); - download(newBlob, 'lmm-oa.backup.db', 'text/plain'); + download(newBlob, 'lmm-oa.backup.db', 'text/plain'); + } catch { + return; + } }; -export const dbExportJsonDb = async () => { +export const dbExportJsonDb = async (passcode) => { const blob = await exportDB(appDb); const convertBase64 = () => { return new Promise((resolve, reject) => { @@ -91,9 +94,8 @@ export const dbExportJsonDb = async () => { }; const data = await convertBase64(); - const myKey = ''; const Cryptr = require('cryptr'); - const cryptr = new Cryptr(myKey); + const cryptr = new Cryptr(passcode); const encryptedData = cryptr.encrypt(data); return encryptedData; }; diff --git a/src/locales/e.json b/src/locales/e.json index 37c4487e..20cb52f3 100644 --- a/src/locales/e.json +++ b/src/locales/e.json @@ -95,6 +95,7 @@ "global.auxClass2": "Auxiliary Classroom 2", "global.allWeeks": "All weeks", "global.preview": "Preview", + "global.continue": "Continue", "startup.welcomeTitle": "Welcome to LMM-OA", "startup.welcomeDescription": "LMM-OA (or Life and Ministry Meeting Overseer Assistant) is an application designed for the Life and Ministry Meeting Overseer.", @@ -122,6 +123,7 @@ "login.accountDisabled": "The account that you are trying to access is currently disabled.", "login.accountNotVerified": "You are logged in, but you are account is not yet activated. Please visit this link to activate it: ", "login.accountNotVerifiedNoLink": "Please login again to activate your account", + "login.createFailed": "An error occured and your account could not be created", "administration.congConnectInfo": "CONNECTION INFORMATION", "administration.swsPocketAccess": "ACCESS TO SWS POCKET", @@ -225,6 +227,33 @@ "help.qAddSourceMaterial": "How to add source materials?", "help.aAddSourceMaterial": "Click the Add new week, to add new week into the source material. But you may add weeks too using an EPUB file downloaded from JW.ORG. Click the Import EPUB, and browse to the location where you have downloaded the EPUB file.", + "settings.aboutCongregation": "ABOUT CONGREGATION", + "settings.blankRequired": "Please provide input", + "settings.liveClass": "Students for live meeting only", + "settings.saved": "Congregation settings updated successfully", + "settings.dataStorage": "DATA STORAGE", + "settings.backup": "Backup", + "settings.createLocalBackup": "Create", + "settings.createOnlineBackup": "Create online backup", + "settings.restoreLocalBackup": "Restore", + "settings.restoreOnlineBackup": "Restore online backup", + "settings.eraseDesc": "Erase all data used by LMM-OA.", + "settings.deleteDbTitle": "Are you sure to delete all LMM-OA data?", + "settings.deleteDbDesc": "All data used by LMM-OA will be deleted. This includes the students list, schedules, and all congregation details.", + "settings.createPassword": "Create a strong password to backup your LMM-OA data", + "settings.backupSuccess": "Your backup is successfully saved", + "settings.backupError": "An error occured and your backup is not saved", + "settings.backupRetrieveFailed": "An error occured while retrieving your backup", + "settings.backupOnlineSendNew": "Sending backup data to the internet", + "settings.backupOnlineSendUpdate": "You already have backup stored online, do you want to replace it?", + "settings.backupOnlineNone": "You do not yet have any backup stored online", + "settings.backupNew": "New backup", + "settings.backupOld": "Old backup", + "settings.restoreIntro": "Provide the password to open this backup", + "settings.restoreOnlineIntro": "Restore from online backup", + "settings.backupRestoreInvalidPassword": "The password to open the backup data is incorrect", + "settings.backupRestoreError": "An error occurred while processing your backup data", + "settings.backupInvalidFile": "The file selected is not a valid backup for LMM-OA. Please choose the correct file", "about.description": "LMM-OA (Life and Ministry Meeting Overseer Assistant) is an application to coordinate and manage students assignment schedule for Midweek Meeting." } \ No newline at end of file diff --git a/src/locales/mg.json b/src/locales/mg.json index 5108949e..e76dfeb1 100644 --- a/src/locales/mg.json +++ b/src/locales/mg.json @@ -95,6 +95,7 @@ "global.auxClass2": "Efitrano fahatelo", "global.allWeeks": "Herinandro rehetra", "global.preview": "Hijery mialoha", + "global.continue": "Hanohy", "startup.welcomeTitle": "Tongasoa eto amin’ny LMM-OA", "startup.welcomeDescription": "Programa natao ho ampiasain’ny Mpiandraikitra ny Fivoriana Momba ny Fiainantsika sy ny Fanompoana ny LMM-OA (na Life and Ministry Meeting Overseer Assistant).", @@ -122,6 +123,7 @@ "login.accountDisabled": "Natsahatra io kaonty tianao hidirana io.", "login.accountNotVerified": "Tafiditra ianao fa saingy tsy mbola azo ampiasaina ny kaontinao. Tsindrio ity rohy ity mba hampandehanana ny kaontinao: ", "login.accountNotVerifiedNoLink": "Miverena miditra indray mba hampandehanana ny kaontinao", + "login.createFailed": "Nisy olana ka tsy afaka namboarina ny kaontinao", "administration.congConnectInfo": "FANAZAVANA AMIN’NY FAMPIASANA INTERNET", "administration.swsPocketAccess": "AFAKA MAMPIASA SWS POCKET", @@ -226,5 +228,33 @@ "help.qAddSourceMaterial": "Ahoana ny fomba fampidirina ireo loharanon-kevitra?", "help.aAddSourceMaterial": "Afaka mampiditra herinandro vaovao ianao, eo amin’ilay hoe Herinandro vaovao. Azo ampidirina amin’ny alalan’ny EPUB nalaina mialoha avy tao amin’ny JW.ORG koa anefa izy io. Tsindrio ilay hoe Haka EPUB, ary safidio ilay rakitra EPUB ao amin’ny fitaovana ampiasainao.", + "settings.aboutCongregation": "MOMBAMOMBA NY FIANGONANA", + "settings.blankRequired": "Mila fenoina", + "settings.liveClass": "Mpianatra afaka mandray anjara mivantana ihany", + "settings.saved": "Vita ny fanavaozana ny mombamomba ny fiangonana", + "settings.dataStorage": "FITEHIRIZANA", + "settings.backup": "Rakitra Fiandry", + "settings.createLocalBackup": "Hamorona", + "settings.createOnlineBackup": "Hamorona sy alefa internet", + "settings.restoreLocalBackup": "Hamerina", + "settings.restoreOnlineBackup": "Hamerina avy amin’ny internet", + "settings.eraseDesc": "Hamafa tanteraka ny rakitra ampiasain’ny LMM-OA.", + "settings.deleteDbTitle": "Hofafana tanteraka ve ny rakitra LMM-OA?", + "settings.deleteDbDesc": "Ho voafafa daholo ny rakitra rehetra momba ny LMM-OA, anisan’izany ny lisitry ny mpianatra, fandaharana, mombamomba ny fiangonana.", + "settings.createPassword": "Mamorona tenimiafiana mba hanamboarana ny rakitra fiandry", + "settings.backupSuccess": "Voatahiry soa aman-tsara ny rakitra fiandry", + "settings.backupError": "Nisy olana ka tsy voatahiry ny rakitra fiandry", + "settings.backupRetrieveFailed": "Nisy olana teo am-pikarohana ny rakitra fiandry", + "settings.backupOnlineSendNew": "Handefa rakitra fiandry any amin’ny internet", + "settings.backupOnlineSendUpdate": "Efa misy rakitra fiandry any amin’ny internet, ka tianao hosoloina ve?", + "settings.backupOnlineNone": "Tsy mbola manana rakitra fiandry any amin’ny internet ianao", + "settings.backupNew": "Vaovao", + "settings.backupOld": "Taloha", + "settings.restoreIntro": "Ampidiro ny tenimiafina hanokafana ny rakitra fiandry", + "settings.restoreOnlineIntro": "Hamerina rakitra fiandry avy any amin’ny internet", + "settings.backupRestoreInvalidPassword": "Diso ny tenimiafina nampidirinao mba hanokafana ny rakitra fiandry", + "settings.backupRestoreError": "Nisy olana teo am-panokafana ny rakitra fiandry nosafidianao", + "settings.backupInvalidFile": "Tsy rakitra fiandry ho an’ny LMM-OA io nosafidianao io, fa hamarino indray mandeha.", + "about.description": "Programa fandaminana anjaran’ny mpianatra amin’ny fivoriana andavanandro ny LMM-OA (Life and Ministry Meeting Overseer Assistant)." } \ No newline at end of file diff --git a/src/pages/DBRestore.js b/src/pages/DBRestore.js index a7ff66be..1e6464e5 100644 --- a/src/pages/DBRestore.js +++ b/src/pages/DBRestore.js @@ -1,108 +1,118 @@ -import { useEffect, useState } from "react"; -import { Redirect } from "react-router-dom"; +import { useEffect, useState } from 'react'; +import { Redirect } from 'react-router-dom'; +import { useRecoilState } from 'recoil'; import CircularProgress from '@mui/material/CircularProgress'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import Container from '@mui/material/Container'; import ErrorIcon from '@mui/icons-material/Error'; import Typography from '@mui/material/Typography'; -import { dbRestoreDb, isValidJSON } from "../indexedDb/dbUtility"; +import { backupJsonDataState } from '../appStates/appSettings'; +import { dbRestoreDb, isValidJSON } from '../indexedDb/dbUtility'; const sharedStyles = { - jsonLoad: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - height: '70vh', - }, - textCircular: { - marginTop: '10px', - }, + jsonLoad: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '70vh', + }, + textCircular: { + marginTop: '10px', + }, }; -const DBRestore = (props) => { - const [isValid, setIsValid] = useState(false); - const [isComplete, setIsComplete] = useState(false); - const [isLoading, setIsLoading] = useState(true); - var fileJSON = ""; - if (props.location.state !== undefined) { - fileJSON = props.location.state.jsonFile; - } +const DBRestore = () => { + const [isValid, setIsValid] = useState(false); + const [isComplete, setIsComplete] = useState(false); + const [isLoading, setIsLoading] = useState(true); - useEffect(() => { - const validateJSON = async () => { - if (fileJSON.type === "application/json") { - const isValid = await isValidJSON(fileJSON); - if (isValid === true) { - setIsValid(true); - } else { - setIsValid(false); - } - } else if (fileJSON.type === "text/json") { - setIsValid(true); - } else { - setIsValid(false); - } - setIsLoading(false); - }; - validateJSON(); - }, [fileJSON]) + const [fileJSON, setFileJSON] = useRecoilState(backupJsonDataState); - useEffect(() => { - const loadJSON = async () => { - await dbRestoreDb(fileJSON); - setIsComplete(true); - setTimeout(() => { - window.location.href = './'; - }, 2000); - } + useEffect(() => { + const validateJSON = async () => { + if (fileJSON && fileJSON.type === 'application/json') { + const isValid = await isValidJSON(fileJSON); + if (isValid === true) { + setIsValid(true); + } else { + setIsValid(false); + } + } else if (fileJSON && fileJSON.type === 'text/json') { + setIsValid(true); + } else { + setIsValid(false); + } + setIsLoading(false); + }; + validateJSON(); + }, [fileJSON]); - if (isValid === true) { - loadJSON(); - }; - }, [fileJSON, isValid]) + useEffect(() => { + const loadJSON = async () => { + await dbRestoreDb(fileJSON); + setIsComplete(true); + setTimeout(() => { + window.location.href = './'; + }, 2000); + }; - if (fileJSON === "") { - return - } + if (isValid === true) { + loadJSON(); + } + }, [fileJSON, isValid, setFileJSON]); - return ( -
- {(!isLoading && !isValid) && ( - - - - Tsy rakitra misy ny fitehirizana momba ny LMM-OA io nosafidianao io, fa hamarino indray mandeha. - - - )} - {(!isLoading && isValid) && ( - - {!isComplete && ( - <> - - - Miandrasa kely ... - - - )} - {isComplete && ( - <> - - - Vita ny famerenana ny rakitra fiandry ho an’ny LMM-OA. - - - )} - - )} -
- ); -} - -export default DBRestore; \ No newline at end of file + if (fileJSON === undefined) { + return ( + + ); + } + + return ( +
+ {!isLoading && !isValid && ( + + + + Tsy rakitra misy ny fitehirizana momba ny LMM-OA io nosafidianao io, + fa hamarino indray mandeha. + + + )} + {!isLoading && isValid && ( + + {!isComplete && ( + <> + + + Miandrasa kely ... + + + )} + {isComplete && ( + <> + + + Vita ny famerenana ny rakitra fiandry ho an’ny LMM-OA. + + + )} + + )} +
+ ); +}; + +export default DBRestore; diff --git a/src/pages/Settings.js b/src/pages/Settings.js index d9c626e7..e26341ae 100644 --- a/src/pages/Settings.js +++ b/src/pages/Settings.js @@ -1,63 +1,52 @@ -import { useState } from "react"; -import { Redirect } from "react-router-dom"; +import { useState } from 'react'; +import { Redirect } from 'react-router-dom'; import Box from '@mui/material/Box'; -import BasicSettings from "../components/settings/BasicSettings"; -import DataStorage from "../components/settings/DataStorage"; -import UseInternet from "../components/settings/UseInternet"; +import BasicSettings from '../components/settings/BasicSettings'; +import DataStorage from '../components/settings/DataStorage'; const sharedStyles = { - settingItem: { - flex: "1 1 350px", - borderRadius: '10px', - margin: "10px 2px", - padding: '5px', - }, -} + settingItem: { + flex: '1 1 350px', + borderRadius: '10px', + margin: '10px 2px', + padding: '5px', + }, +}; const Settings = (props) => { - const [isRedirect, setIsRedirect] = useState(false); - const [jsonFile, setJsonFile] = useState(""); + const [isRedirect, setIsRedirect] = useState(false); + const [jsonFile, setJsonFile] = useState(''); - if (isRedirect === true) { - return - } + if (isRedirect === true) { + return ( + + ); + } - return ( - - - props.setAppSnackOpen(value)} - setAppSeverity={(value) => props.setAppSeverity(value)} - setAppMessage={(value) => props.setAppMessage(value)} - /> - - - props.setAppSnackOpen(value)} - setAppSeverity={(value) => props.setAppSeverity(value)} - setAppMessage={(value) => props.setAppMessage(value)} - /> - - - setIsRedirect(value)} - setJsonFile={(value) => setJsonFile(value)} - setAppSnackOpen={(value) => props.setAppSnackOpen(value)} - setAppSeverity={(value) => props.setAppSeverity(value)} - setAppMessage={(value) => props.setAppMessage(value)} - /> - - - ); -} - -export default Settings; \ No newline at end of file + return ( + + + + + + setIsRedirect(value)} + setJsonFile={(value) => setJsonFile(value)} + /> + + + ); +}; + +export default Settings; diff --git a/src/pages/Startup.js b/src/pages/Startup.js index f897abb4..3e58950a 100644 --- a/src/pages/Startup.js +++ b/src/pages/Startup.js @@ -14,234 +14,295 @@ import StepperWelcome from '../components/startup/StepperWelcome'; import StepperCongregation from '../components/startup/StepperCongregation'; import StepperMeetingDetails from '../components/startup/StepperMeetingDetails'; import { initAppDb, isDbExist } from '../indexedDb/dbUtility'; -import { checkSrcUpdate, dbGetListWeekType, dbGetYearList } from '../indexedDb/dbSourceMaterial'; -import { dbGetAppSettings, dbUpdateAppSettings } from '../indexedDb/dbAppSettings'; -import { classCountState, congIDState, congNameState, congNumberState, isErrorCongNameState, isErrorCongNumberState, meetingDayState } from '../appStates/appCongregation'; +import { + checkSrcUpdate, + dbGetListWeekType, + dbGetYearList, +} from '../indexedDb/dbSourceMaterial'; +import { + dbGetAppSettings, + dbUpdateAppSettings, +} from '../indexedDb/dbAppSettings'; +import { + classCountState, + congIDState, + congNameState, + congNumberState, + isErrorCongNameState, + isErrorCongNumberState, + liveClassState, + meetingDayState, +} from '../appStates/appCongregation'; import { appLangState, isAppLoadState } from '../appStates/appSettings'; -import { assTypeListState, weekTypeListState, yearsListState } from '../appStates/appSourceMaterial'; -import { allStudentsState, filteredStudentsState } from '../appStates/appStudents'; +import { + assTypeListState, + weekTypeListState, + yearsListState, +} from '../appStates/appSourceMaterial'; +import { + allStudentsState, + filteredStudentsState, +} from '../appStates/appStudents'; import { dbGetStudents } from '../indexedDb/dbPersons'; import { dbGetListAssType } from '../indexedDb/dbAssignment'; const Startup = () => { - const { t, i18n } = useTranslation(); - - const [isSetup, setIsSetup] = useState(false); - - const [congName, setCongName] = useRecoilState(congNameState); - const [congNumber, setCongNumber] = useRecoilState(congNumberState); - const [isErrorCongName, setIsErrorCongName] = useRecoilState(isErrorCongNameState); - const [isErrorCongNumber, setIsErrorCongNumber] = useRecoilState(isErrorCongNumberState); - const [meetingDay, setMeetingDay] = useRecoilState(meetingDayState); - const [classCount, setClassCount] = useRecoilState(classCountState); - - const setCongID = useSetRecoilState(congIDState); - const setAppLang = useSetRecoilState(appLangState); - const setIsAppLoad = useSetRecoilState(isAppLoadState); - const setDbStudents = useSetRecoilState(allStudentsState); - const setStudents = useSetRecoilState(filteredStudentsState); - const setYearsList = useSetRecoilState(yearsListState); - const setAssTypeList = useSetRecoilState(assTypeListState); - const setWeekTypeList = useSetRecoilState(weekTypeListState); - - const [activeStep, setActiveStep] = useState(0); - const steps = [ - t("startup.welcomeTitle"), - t("startup.congInfoTitle"), - t("startup.meetingInfoTitle"), - t("startup.setupCompleteTitle"), - ]; - - const getStepContent = (step) => { - if (step === 0) { - return - } else if (step === 1) { - return - } else if (step === 2) { - return - } else if (step === 3) { - return ( - {t("startup.setupCompleteDescription")} - ) - } - } - - const handleNext = async () => { - setIsErrorCongName(false); - setIsErrorCongNumber(false); - if (activeStep < steps.length - 1) { - let hasError = false; - - if (activeStep === 1) { - if (congName === "") { - setIsErrorCongName(true) - hasError = true; - } - if (congNumber === "") { - setIsErrorCongNumber(true) - hasError = true; - } - if (!hasError) { - setActiveStep((prevActiveStep) => prevActiveStep + 1); - } - } else { - setActiveStep((prevActiveStep) => prevActiveStep + 1); - } - } else if (activeStep === steps.length - 1) { - await handleDbInit(); - } - }; - - const handleDbInit = async () => { - setIsSetup(false); - await initAppDb(); - var obj = {}; - obj.cong_name = congName; - obj.cong_number = congNumber; - obj.class_count = classCount; - obj.meeting_day = meetingDay; - obj.liveEventClass = false; - await dbUpdateAppSettings(obj); - - await checkSrcUpdate(); - setTimeout(() => { - setIsAppLoad(false); - }, 1000); - } - - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - - useEffect (() => { - const isExist = async () => { - const isDataExist = await isDbExist(); - if (isDataExist) { - setIsSetup(false); - await initAppDb(); - await checkSrcUpdate(); - - const {cong_number, cong_name, class_count, meeting_day, cong_ID, app_lang} = await dbGetAppSettings(); - setCongNumber(cong_number); - setCongName(cong_name); - setClassCount(class_count); - setMeetingDay(meeting_day); - setCongID(cong_ID); - setAppLang(app_lang); - - i18n.changeLanguage(app_lang); - - const weekTypeList = await dbGetListWeekType(); - setWeekTypeList(weekTypeList); - - const assTypeList = await dbGetListAssType(); - setAssTypeList(assTypeList); - - const data = await dbGetStudents(); - setDbStudents(data); - setStudents(data); - - const years = await dbGetYearList(); - setYearsList(years); - - setTimeout(() => { - setIsAppLoad(false); - }, 1000); - } else { - setIsSetup(true); - }; - } - - isExist(); - - return () => { - //clean up - } - }, [i18n, setIsAppLoad, setClassCount, setCongID, setCongName, setCongNumber, setMeetingDay, setAppLang, setDbStudents, setStudents, setYearsList, setAssTypeList, setWeekTypeList]) - - - if (isSetup) { - return ( - - - - - - - App logo - LMM-OA App - - - {steps.map((label, index) => ( - - {label} - - {getStepContent(index)} -
-
- - -
-
-
-
- ))} -
-
-
- ) - } - - return ( -
-
- App logo -
- -
- ); -} - -export default Startup; \ No newline at end of file + const { t, i18n } = useTranslation(); + + const [isSetup, setIsSetup] = useState(false); + + const [congName, setCongName] = useRecoilState(congNameState); + const [congNumber, setCongNumber] = useRecoilState(congNumberState); + const [isErrorCongName, setIsErrorCongName] = + useRecoilState(isErrorCongNameState); + const [isErrorCongNumber, setIsErrorCongNumber] = useRecoilState( + isErrorCongNumberState + ); + const [meetingDay, setMeetingDay] = useRecoilState(meetingDayState); + const [classCount, setClassCount] = useRecoilState(classCountState); + + const setCongID = useSetRecoilState(congIDState); + const setAppLang = useSetRecoilState(appLangState); + const setIsAppLoad = useSetRecoilState(isAppLoadState); + const setDbStudents = useSetRecoilState(allStudentsState); + const setStudents = useSetRecoilState(filteredStudentsState); + const setYearsList = useSetRecoilState(yearsListState); + const setAssTypeList = useSetRecoilState(assTypeListState); + const setWeekTypeList = useSetRecoilState(weekTypeListState); + const setLiveClass = useSetRecoilState(liveClassState); + + const [activeStep, setActiveStep] = useState(0); + const steps = [ + t('startup.welcomeTitle'), + t('startup.congInfoTitle'), + t('startup.meetingInfoTitle'), + t('startup.setupCompleteTitle'), + ]; + + const getStepContent = (step) => { + if (step === 0) { + return ; + } else if (step === 1) { + return ; + } else if (step === 2) { + return ; + } else if (step === 3) { + return ( + + {t('startup.setupCompleteDescription')} + + ); + } + }; + + const handleNext = async () => { + setIsErrorCongName(false); + setIsErrorCongNumber(false); + if (activeStep < steps.length - 1) { + let hasError = false; + + if (activeStep === 1) { + if (congName === '') { + setIsErrorCongName(true); + hasError = true; + } + if (congNumber === '') { + setIsErrorCongNumber(true); + hasError = true; + } + if (!hasError) { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + } + } else { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + } + } else if (activeStep === steps.length - 1) { + await handleDbInit(); + } + }; + + const handleDbInit = async () => { + setIsSetup(false); + await initAppDb(); + var obj = {}; + obj.cong_name = congName; + obj.cong_number = congNumber; + obj.class_count = classCount; + obj.meeting_day = meetingDay; + obj.liveEventClass = false; + await dbUpdateAppSettings(obj); + + await checkSrcUpdate(); + setTimeout(() => { + setIsAppLoad(false); + }, 1000); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + useEffect(() => { + const isExist = async () => { + const isDataExist = await isDbExist(); + if (isDataExist) { + setIsSetup(false); + await initAppDb(); + await checkSrcUpdate(); + + const { + cong_number, + cong_name, + class_count, + meeting_day, + cong_ID, + app_lang, + liveEventClass, + } = await dbGetAppSettings(); + setCongNumber(cong_number); + setCongName(cong_name); + setClassCount(class_count); + setMeetingDay(meeting_day); + setCongID(cong_ID); + setAppLang(app_lang); + setLiveClass(liveEventClass); + + i18n.changeLanguage(app_lang); + + const weekTypeList = await dbGetListWeekType(); + setWeekTypeList(weekTypeList); + + const assTypeList = await dbGetListAssType(); + setAssTypeList(assTypeList); + + const data = await dbGetStudents(); + setDbStudents(data); + setStudents(data); + + const years = await dbGetYearList(); + setYearsList(years); + + setTimeout(() => { + setIsAppLoad(false); + }, 1000); + } else { + setIsSetup(true); + } + }; + + isExist(); + + return () => { + //clean up + }; + }, [ + i18n, + setIsAppLoad, + setClassCount, + setCongID, + setCongName, + setCongNumber, + setLiveClass, + setMeetingDay, + setAppLang, + setDbStudents, + setStudents, + setYearsList, + setAssTypeList, + setWeekTypeList, + ]); + + if (isSetup) { + return ( + + + + + + + App logo + LMM-OA App + + + {steps.map((label, index) => ( + + {label} + + {getStepContent(index)} +
+
+ + +
+
+
+
+ ))} +
+
+
+ ); + } + + return ( +
+
+ App logo +
+ +
+ ); +}; + +export default Startup;