From 2674fe36f419e97f255a796b524d74a2afa76337 Mon Sep 17 00:00:00 2001 From: Tristan Yang Date: Thu, 1 Jun 2023 19:34:07 +0800 Subject: [PATCH] feat: invite in mobile process --- public/locales/en/auth.json | 6 ++ src/hooks/useDownload.ts | 29 ++++++ src/routes/chat/GuestBlankPlaceholder.tsx | 2 +- src/routes/index.tsx | 12 +++ src/routes/reg/InviteInMobile.tsx | 105 ++++++++++++++++++++++ src/routes/reg/Register.tsx | 12 ++- src/utils.tsx | 2 +- 7 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/hooks/useDownload.ts create mode 100644 src/routes/reg/InviteInMobile.tsx diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json index 780fd8b9..e1a40d43 100644 --- a/public/locales/en/auth.json +++ b/public/locales/en/auth.json @@ -48,5 +48,11 @@ "title": "Magic link expired", "desc": "Go back to your original VoceChat tab and request a new magic link.", "desc_close": "You can close this window now." + }, + "invite_mobile": { + "join": "Join our Server", + "start_download": "Start by downloading VoceChat mobile app", + "open": "Open VoceChat", + "have_already": "Have the app already?" } } diff --git a/src/hooks/useDownload.ts b/src/hooks/useDownload.ts new file mode 100644 index 00000000..421e9b49 --- /dev/null +++ b/src/hooks/useDownload.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; + +type Download = { link: string; icon: string }; + +const useDownload = () => { + const [download, setDownload] = useState(null); + useEffect(() => { + const isAndroid = typeof window !== "undefined" ? navigator.userAgent.match(/Android/i) : false; + setDownload( + isAndroid + ? [ + { + link: "https://play.google.com/store/apps/details?id=com.privoce.vocechatclient", + icon: "https://voce.chat/img/icon.app.google.play.png" + }, + { + link: "https://s.voce.chat/vocechat.android.apk", + icon: "https://voce.chat/img/icon.app.apk.png" + } + ] + : { + link: "https://apps.apple.com/app/vocechat/id1631779678", + icon: "https://voce.chat/img/icon.app.ios.png" + } + ); + }, []); + return download; +}; +export default useDownload; diff --git a/src/routes/chat/GuestBlankPlaceholder.tsx b/src/routes/chat/GuestBlankPlaceholder.tsx index acc3d43e..8fd6e6ad 100644 --- a/src/routes/chat/GuestBlankPlaceholder.tsx +++ b/src/routes/chat/GuestBlankPlaceholder.tsx @@ -23,7 +23,7 @@ const GuestBlankPlaceholder = () => { clearLocalData(); navigateTo("/login"); }; - const qrUrl = `http://voce.chat/url?s=${encodeURIComponent(BASE_ORIGIN)}`; + const qrUrl = BASE_ORIGIN; return (

diff --git a/src/routes/index.tsx b/src/routes/index.tsx index d211e263..7f937d54 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -14,6 +14,7 @@ import store, { useAppSelector } from "../app/store"; import NotFoundPage from "./404"; import InvitePrivate from "./invitePrivate"; import LazyIt from "./lazy"; +import InviteInMobile from "./reg/InviteInMobile"; // import Welcome from './Welcome' // const HomePage = lazy(() => import("./home")); @@ -77,6 +78,16 @@ const PageRoutes = () => { } /> + + + + + + } + /> { } /> + { + const { magic_token } = useParams(); + const { t } = useTranslation("auth", { keyPrefix: "invite_mobile" }); + const download = useDownload(); + const [inviteLink, setInviteLink] = useState(""); + const { copy, copied } = useCopy(); + useEffect(() => { + if (!isMobile() && inviteLink) { + location.href = inviteLink; + } + }, [inviteLink]); + useEffect(() => { + if (magic_token) { + setInviteLink(`${location.origin}/#/register?magic_token=${magic_token}`); + } + }, [magic_token]); + const app_link = `vocechat://i?magic_link=${encodeURIComponent(inviteLink)}`; + const webLink = `${inviteLink}&ctx=web`; + if (!inviteLink) return null; + return ( +
+
+ APP grid + + {t("join")}! + +
+ +

+ {t("have_already")} + + {t("open")} + +

+
+ {webLink && ( + + Continue with webapp + + )} +
+ + 👇App not showing? You may copy the following invitation link and paste it into VoceChat + App. + +
+ {inviteLink} +
+ + {copied ? "Copied" : `Copy`} + +
+ {download ? ( + Array.isArray(download) ? ( +
    + {" "} + {download.map((d) => { + const { link, icon } = d; + return ( +
  • + + App Download Icon + +
  • + ); + })} +
+ ) : ( + + App Download Icon + + ) + ) : null} +
+
+ ); +}; + +export default InviteInMobile; diff --git a/src/routes/reg/Register.tsx b/src/routes/reg/Register.tsx index fdd231e8..1bbeee52 100644 --- a/src/routes/reg/Register.tsx +++ b/src/routes/reg/Register.tsx @@ -2,7 +2,7 @@ import { ChangeEvent, FormEvent, useEffect, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import BASE_URL, { KEY_LOCAL_MAGIC_TOKEN } from "@/app/config"; import { @@ -16,6 +16,7 @@ import { useAppSelector } from "@/app/store"; import Divider from "@/components/Divider"; import Button from "@/components/styled/Button"; import Input from "@/components/styled/Input"; +import { isMobile } from "@/utils"; import SocialLoginButtons from "../login/SocialLoginButtons"; import EmailNextTip from "./EmailNextStepTip"; import { useMagicToken } from "./index"; @@ -29,6 +30,8 @@ interface AuthForm { } export default function Register() { + let [searchParams] = useSearchParams(new URLSearchParams(location.search)); + const ctx = searchParams.get("ctx"); const serverName = useAppSelector((store) => store.server.name); const { t } = useTranslation("auth"); const { t: ct } = useTranslation(); @@ -52,6 +55,13 @@ export default function Register() { //本地存一下 magic token 后续oauth流程用到 localStorage.setItem(KEY_LOCAL_MAGIC_TOKEN, magic_token); } + // 如果是移动端访问,并且没标识,则跳转 + useEffect(() => { + if (ctx !== "web" && isMobile() && magic_token) { + navigateTo(`/invite_mobile/${magic_token}`); + } + }, [ctx, magic_token]); + // send reg link useEffect(() => { if (isSuccess && data) { diff --git a/src/utils.tsx b/src/utils.tsx index 3b228997..53b3bdd6 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -415,5 +415,5 @@ export const transformInviteLink = (link: string) => { const invite = new URL(_link); const tmpLink = `${location.origin}${invite.pathname}${invite.hash}${invite.search}`; - return `https://voce.chat/url?i=${encodeURIComponent(tmpLink)}`; + return tmpLink; };