From 0e0dec9c9e2d817f6b6f56e507fa53dc57faf9e6 Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Mon, 12 Dec 2022 10:13:52 +0800 Subject: [PATCH] refactor: onboarding --- package.json | 4 +- src/routes/onboarding/index.tsx | 65 ++++-------- src/routes/onboarding/steps.ts | 40 ++++++++ src/routes/onboarding/steps/adminAccount.tsx | 101 +++++++++---------- src/routes/onboarding/steps/inviteLink.tsx | 5 +- src/routes/onboarding/steps/serverName.tsx | 5 +- src/routes/onboarding/steps/welcomePage.tsx | 5 +- src/routes/onboarding/steps/whoCanSignUp.tsx | 32 ++++-- src/routes/onboarding/styled.tsx | 5 - src/routes/onboarding/useServerSetup.ts | 75 -------------- yarn.lock | 25 +---- 11 files changed, 151 insertions(+), 211 deletions(-) create mode 100644 src/routes/onboarding/steps.ts delete mode 100644 src/routes/onboarding/useServerSetup.ts diff --git a/package.json b/package.json index 3a00e39b..b0e258b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vocechat-web", - "version": "0.3.27", + "version": "0.3.29", "private": true, "homepage": "https://voce.chat", "dependencies": { @@ -62,6 +62,7 @@ "react-string-replace": "^1.1.0", "react-syntax-highlighter": "^15.5.0", "react-textarea-autosize": "^8.4.0", + "react-use-wizard": "^2.2.1", "resolve": "^1.22.1", "rooks": "^7.4.2", "slate": "^0.87.0", @@ -71,7 +72,6 @@ "style-loader": "^3.3.1", "styled-components": "^5.3.6", "styled-reset": "^4.4.3", - "swiper": "^8.4.5", "terser-webpack-plugin": "^5.3.6", "tippy.js": "^6.3.7", "typescript": "^4.9.3", diff --git a/src/routes/onboarding/index.tsx b/src/routes/onboarding/index.tsx index 79bf208c..e3dc1937 100644 --- a/src/routes/onboarding/index.tsx +++ b/src/routes/onboarding/index.tsx @@ -1,41 +1,37 @@ -import React, { FC } from "react"; +import React, { useState } from "react"; import { Helmet } from "react-helmet"; -import { Swiper, SwiperSlide } from "swiper/react"; -import "swiper/css"; +import { useWizard, Wizard } from 'react-use-wizard'; + import WelcomePage from "./steps/welcomePage"; import ServerName from "./steps/serverName"; import AdminAccount from "./steps/adminAccount"; import WhoCanSignUp from "./steps/whoCanSignUp"; import InviteLink from "./steps/inviteLink"; import DonePage from "./steps/donePage"; -import useServerSetup, { steps } from "./useServerSetup"; +import steps from "./steps"; import StyledOnboardingPage from "./styled"; import { useTranslation } from "react-i18next"; -interface Props { - step: string; - setStep: (step: string) => void; -} -const Navigator: FC = ({ step, setStep }) => { - - const index = steps.map((value) => value.name).indexOf(step); - const canJumpTo = steps.find((value) => value.name === step)?.canJumpTo || []; +const Navigator = () => { + const { activeStep, goToStep } = useWizard(); + const canJumpTo = steps[activeStep]?.canJumpTo || []; + console.log("active step", activeStep); return (
{steps.map((stepToRender, indexToRender) => { const clickable = canJumpTo.includes(stepToRender.name); - const nodeCls = `node ${indexToRender === index ? "emphasized" : ""} ${indexToRender > index ? "disabled" : "" + const nodeCls = `node ${indexToRender === activeStep ? "emphasized" : ""} ${indexToRender > activeStep ? "disabled" : "" } ${clickable ? "clickable" : ""}`; - const arrowCls = `arrow ${indexToRender >= index ? "disabled" : ""}`; + const arrowCls = `arrow ${indexToRender >= activeStep ? "disabled" : ""}`; return ( { if (clickable) { - setStep(stepToRender.name); + goToStep(indexToRender); } }} > @@ -51,41 +47,22 @@ const Navigator: FC = ({ step, setStep }) => { export default function OnboardingPage() { const { t } = useTranslation("welcome"); - const serverSetup = useServerSetup(); + const [serverName, setServerName] = useState(""); return ( <> {t("onboarding.title")} - - serverSetup.setSwiper(swiper)} - > - - - - - - - - - - - - - - {/* lazy call invite link API */} - {({ isActive }) => { - return isActive ? : null; - }} - - - - - + }> + + + + + {/* lazy call invite link API */} + + + ); diff --git a/src/routes/onboarding/steps.ts b/src/routes/onboarding/steps.ts new file mode 100644 index 00000000..f8e6f8b9 --- /dev/null +++ b/src/routes/onboarding/steps.ts @@ -0,0 +1,40 @@ +// import { useState } from "react"; +import { t } from 'i18next'; +// `name` for in-code usage, `label` for display +export interface Step { + name: string; + label: string; + canJumpTo?: string[]; +} + +const steps: Step[] = [ + { + name: "welcomePage", + label: t("welcome:onboarding.welcome_page") + }, + { + name: "serverName", + label: t("welcome:onboarding.set_name") + }, + { + name: "adminAccount", + label: t("welcome:onboarding.admin_account") + }, + { + name: "whoCanSignUp", + label: t("welcome:onboarding.who_sign_up") + }, + { + name: "inviteLink", + label: t("welcome:onboarding.invites"), + canJumpTo: ["whoCanSignUp"] + }, + { + name: "donePage", + label: t("welcome:onboarding.done"), + canJumpTo: ["whoCanSignUp", "inviteLink"] + } +]; + + +export default steps; diff --git a/src/routes/onboarding/steps/adminAccount.tsx b/src/routes/onboarding/steps/adminAccount.tsx index 74d6f5e5..8a855d34 100644 --- a/src/routes/onboarding/steps/adminAccount.tsx +++ b/src/routes/onboarding/steps/adminAccount.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, FC } from "react"; +import { useEffect, useState, FC, useRef } from "react"; import styled from "styled-components"; import toast from "react-hot-toast"; import { useDispatch } from "react-redux"; @@ -13,6 +13,7 @@ import { useLoginMutation } from "../../../app/services/auth"; import { updateInitialized } from "../../../app/slices/auth.data"; import { useAppSelector } from "../../../app/store"; import { useTranslation } from "react-i18next"; +import { useWizard } from "react-use-wizard"; const StyledWrapper = styled.div` height: 100%; @@ -36,7 +37,9 @@ const StyledWrapper = styled.div` margin-bottom: 24px; } - > .input { + form { + > .input { + margin-bottom: 20px; width: 360px; height: 44px; font-weight: 400; @@ -46,18 +49,13 @@ const StyledWrapper = styled.div` border: 1px solid #d0d5dd; border-radius: 8px; box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); - > .inner { padding: 0; font-weight: 400; font-size: 16px; line-height: 24px; } - - &:not(:nth-last-child(2)) { - margin-bottom: 20px; - } - } + }} > .button { width: 360px; @@ -66,13 +64,13 @@ const StyledWrapper = styled.div` `; type Props = { serverName: string; - nextStep: () => void; }; -const AdminAccount: FC = ({ serverName, nextStep }) => { +const AdminAccount: FC = ({ serverName }) => { const { t } = useTranslation("welcome", { keyPrefix: "onboarding" }); + const { nextStep } = useWizard(); + const formRef = useRef(); const loggedIn = useAppSelector((store) => !!store.authData.token); const dispatch = useDispatch(); - const [createAdmin, { isLoading: isSigningUp, isError: signUpError, isSuccess: signUpOk }] = useCreateAdminMutation(); const [login, { isLoading: isLoggingIn, isError: loginError, }] = useLoginMutation(); const { data: serverData } = useGetServerQuery(); @@ -127,50 +125,51 @@ const AdminAccount: FC = ({ serverName, nextStep }) => { {t("admin_title")} {t("admin_desc")} - setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - setConfirm(e.target.value)} - /> +
+ setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + setConfirm(e.target.value)} + /> + { - // Verification for admin credentials - if (email === "") { - toast.error("Please enter admin email!"); - return; - } else if (password === "") { - toast.error("Please enter admin password!"); - return; - } else if (password.length < 6) { - toast.error("Password length greater than 6!"); - return; - } else if (password !== confirm) { - toast.error("Two passwords do not match!"); - return; + const formEle = formRef?.current; + if (formEle) { + if (!formEle.checkValidity()) { + formEle.reportValidity(); + return; + } + // nextStep(); + createAdmin({ + email, + name: "Admin", + password, + gender: 0 + }); } - createAdmin({ - email, - name: "Admin", - password, - gender: 0 - }); - }} > {!(isSigningUp || isLoggingIn || isUpdatingServer) ? t("sign") : "..."} diff --git a/src/routes/onboarding/steps/inviteLink.tsx b/src/routes/onboarding/steps/inviteLink.tsx index 44b91896..9cb01148 100644 --- a/src/routes/onboarding/steps/inviteLink.tsx +++ b/src/routes/onboarding/steps/inviteLink.tsx @@ -3,6 +3,7 @@ import StyledInput from "../../../common/component/styled/Input"; import StyledButton from "../../../common/component/styled/Button"; import useInviteLink from "../../../common/hook/useInviteLink"; import { useTranslation } from "react-i18next"; +import { useWizard } from "react-use-wizard"; const StyledWrapper = styled.div` height: 100%; @@ -73,10 +74,10 @@ const StyledWrapper = styled.div` margin-top: 24px; } `; - -export default function InviteLink({ nextStep }: { nextStep: () => void }) { +export default function InviteLink() { const { t } = useTranslation("welcome", { keyPrefix: "onboarding" }); const { t: ct } = useTranslation(); + const { nextStep } = useWizard(); const { link, linkCopied, copyLink } = useInviteLink(); return ( diff --git a/src/routes/onboarding/steps/serverName.tsx b/src/routes/onboarding/steps/serverName.tsx index 052e2fb7..b48b18cf 100644 --- a/src/routes/onboarding/steps/serverName.tsx +++ b/src/routes/onboarding/steps/serverName.tsx @@ -4,6 +4,7 @@ import toast from "react-hot-toast"; import StyledInput from "../../../common/component/styled/Input"; import StyledButton from "../../../common/component/styled/Button"; import { useTranslation } from "react-i18next"; +import { useWizard } from "react-use-wizard"; const StyledWrapper = styled.div` height: 100%; @@ -49,10 +50,10 @@ const StyledWrapper = styled.div` type Props = { serverName: string; setServerName: (name: string) => void; - nextStep: () => void; }; -const ServerName: FC = ({ serverName, setServerName, nextStep }) => { +const ServerName: FC = ({ serverName, setServerName }) => { const { t } = useTranslation("welcome", { keyPrefix: "onboarding" }); + const { nextStep } = useWizard(); return ( {t("new_server")} diff --git a/src/routes/onboarding/steps/welcomePage.tsx b/src/routes/onboarding/steps/welcomePage.tsx index a9111fa0..ddcaba4d 100644 --- a/src/routes/onboarding/steps/welcomePage.tsx +++ b/src/routes/onboarding/steps/welcomePage.tsx @@ -1,7 +1,9 @@ import styled from "styled-components"; + import StyledButton from "../../../common/component/styled/Button"; import PlayIcon from "../../../assets/icons/play.svg?url"; import { useTranslation } from "react-i18next"; +import { useWizard } from "react-use-wizard"; const StyledWrapper = styled.div` height: 100%; @@ -44,8 +46,9 @@ const StyledWrapper = styled.div` } `; -export default function WelcomePage({ nextStep }: { nextStep: () => void }) { +export default function WelcomePage() { const { t } = useTranslation("welcome", { keyPrefix: "onboarding" }); + const { nextStep } = useWizard(); return ( {t("welcome")} diff --git a/src/routes/onboarding/steps/whoCanSignUp.tsx b/src/routes/onboarding/steps/whoCanSignUp.tsx index 36311b10..bcb8fca6 100644 --- a/src/routes/onboarding/steps/whoCanSignUp.tsx +++ b/src/routes/onboarding/steps/whoCanSignUp.tsx @@ -6,6 +6,7 @@ import StyledButton from "../../../common/component/styled/Button"; import { useGetLoginConfigQuery, useUpdateLoginConfigMutation } from "../../../app/services/server"; import { WhoCanSignUp } from "../../../types/server"; import { useTranslation } from "react-i18next"; +import { useWizard } from "react-use-wizard"; const StyledWrapper = styled.div` height: 100%; @@ -40,16 +41,23 @@ const StyledWrapper = styled.div` } `; -export default function SignUpSetting({ nextStep }: { nextStep: () => void }) { +export default function SignUpSetting() { const { t } = useTranslation("welcome"); - const { data: loginConfig } = useGetLoginConfigQuery(); + const { t: st } = useTranslation("setting"); + const { nextStep } = useWizard(); + const { data: loginConfig, refetch } = useGetLoginConfigQuery(); const [updateLoginConfig, { isSuccess, error }] = useUpdateLoginConfigMutation(); const [value, setValue] = useState(); + useEffect(() => { + refetch(); + }, []); // Sync to `value` when `loginConfig` is fetched useEffect(() => { if (loginConfig) { + console.log("login config", loginConfig.who_can_sign_up); + setValue(loginConfig.who_can_sign_up); } }, [loginConfig]); @@ -69,21 +77,27 @@ export default function SignUpSetting({ nextStep }: { nextStep: () => void }) { {t("onboarding.invite_title")} {t("onboarding.invite_desc")} - + />} { + // nextStep(); if (loginConfig !== undefined) { - updateLoginConfig({ - ...loginConfig, - who_can_sign_up: value - }); + if (loginConfig.who_can_sign_up !== value) { + + updateLoginConfig({ + ...loginConfig, + who_can_sign_up: value + }); + } else { + nextStep(); + } } }} > diff --git a/src/routes/onboarding/styled.tsx b/src/routes/onboarding/styled.tsx index 1e91c3d5..24595664 100644 --- a/src/routes/onboarding/styled.tsx +++ b/src/routes/onboarding/styled.tsx @@ -3,7 +3,6 @@ import styled from "styled-components"; const StyledOnboardingPage = styled.div` height: 100vh; overflow-y: auto; - > .navigator { position: absolute; top: 20px; @@ -39,10 +38,6 @@ const StyledOnboardingPage = styled.div` } } } - - > .swiper { - height: 100%; - } `; export default StyledOnboardingPage; diff --git a/src/routes/onboarding/useServerSetup.ts b/src/routes/onboarding/useServerSetup.ts deleted file mode 100644 index 321fcd97..00000000 --- a/src/routes/onboarding/useServerSetup.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useCallback, useState } from "react"; -import { Swiper } from "swiper/types"; -import { t } from 'i18next'; -// `name` for in-code usage, `label` for display -export interface Step { - name: string; - label: string; - canJumpTo?: string[]; -} - -const steps: Step[] = [ - { - name: "welcomePage", - label: t("welcome:onboarding.welcome_page") - }, - { - name: "serverName", - label: t("welcome:onboarding.set_name") - }, - { - name: "adminAccount", - label: t("welcome:onboarding.admin_account") - }, - { - name: "whoCanSignUp", - label: t("welcome:onboarding.who_sign_up") - }, - { - name: "inviteLink", - label: t("welcome:onboarding.invites"), - canJumpTo: ["whoCanSignUp"] - }, - { - name: "donePage", - label: t("welcome:onboarding.done"), - canJumpTo: ["whoCanSignUp", "inviteLink"] - } -]; - -export default function useServerSetup() { - const [swiper, setSwiper] = useState(null); - - const [index, setIndex] = useState(0); - const step = steps[index].name; - const setStep = useCallback( - (name: string) => { - const newIndex = steps.map((step) => step.name).indexOf(name); - setIndex(newIndex); - if (swiper !== null) { - swiper.slideTo(newIndex); - } - }, - [swiper] - ); - const nextStep = useCallback(() => { - setIndex((prev) => prev + 1); - if (swiper !== null) { - swiper.slideNext(500); - } - }, [swiper]); - - const [serverName, setServerName] = useState(""); - - return { - swiper, - setSwiper, - step, - setStep, - nextStep, - serverName, - setServerName - }; -} - -export { steps }; diff --git a/yarn.lock b/yarn.lock index d82978a6..59bf5e6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5750,13 +5750,6 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" -dom7@^4.0.4: - version "4.0.4" - resolved "https://mirrors.cloud.tencent.com/npm/dom7/-/dom7-4.0.4.tgz#8b68c5d8e5e2ed0fddb1cb93e433bc9060c8f3fb" - integrity sha512-DSSgBzQ4rJWQp1u6o+3FVwMNnT5bzQbMb+o31TjYYeRi05uAcpF8koxdfzeoe5ElzPmua7W7N28YJhF7iEKqIw== - dependencies: - ssr-window "^4.0.0" - domelementtype@1: version "1.3.1" resolved "https://mirrors.cloud.tencent.com/npm/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" @@ -10654,6 +10647,11 @@ react-universal-interface@^0.6.2: resolved "https://mirrors.cloud.tencent.com/npm/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== +react-use-wizard@^2.2.1: + version "2.2.1" + resolved "https://mirrors.cloud.tencent.com/npm/react-use-wizard/-/react-use-wizard-2.2.1.tgz#95313e4ea2647dc1804904df4a73ac5e1f49790c" + integrity sha512-GcoV059pGKcR+gxkeDhTFt+1lz0pnCIN6E4Y2PpppoUOLNjL7+h3710pZegvCkHB8dhTHd3tXIUZtQEQHd1Y7A== + react-use@^17.3.2: version "17.4.0" resolved "https://mirrors.cloud.tencent.com/npm/react-use/-/react-use-17.4.0.tgz#cefef258b0a6c534a5c8021c2528ac6e1a4cdc6d" @@ -11534,11 +11532,6 @@ sprintf-js@~1.0.2: resolved "https://mirrors.cloud.tencent.com/npm/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -ssr-window@^4.0.0, ssr-window@^4.0.2: - version "4.0.2" - resolved "https://mirrors.cloud.tencent.com/npm/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be" - integrity sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ== - stable@^0.1.8: version "0.1.8" resolved "https://mirrors.cloud.tencent.com/npm/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -11857,14 +11850,6 @@ svgo@^2.7.0, svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" -swiper@^8.4.5: - version "8.4.5" - resolved "https://mirrors.cloud.tencent.com/npm/swiper/-/swiper-8.4.5.tgz#149ca81f67393d3f33abddd0f968fc37e99980b5" - integrity sha512-zveyEFBBv4q1sVkbJHnuH4xCtarKieavJ4SxP0QEHvdpPLJRuD7j/Xg38IVVLbp7Db6qrPsLUePvxohYx39Agw== - dependencies: - dom7 "^4.0.4" - ssr-window "^4.0.2" - symbol-tree@^3.2.4: version "3.2.4" resolved "https://mirrors.cloud.tencent.com/npm/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"