feat: invite in mobile process

This commit is contained in:
Tristan Yang
2023-06-01 19:34:07 +08:00
parent 62e8aa906e
commit 2674fe36f4
7 changed files with 165 additions and 3 deletions
+6
View File
@@ -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?"
}
}
+29
View File
@@ -0,0 +1,29 @@
import { useEffect, useState } from "react";
type Download = { link: string; icon: string };
const useDownload = () => {
const [download, setDownload] = useState<Download | Download[] | null>(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;
+1 -1
View File
@@ -23,7 +23,7 @@ const GuestBlankPlaceholder = () => {
clearLocalData();
navigateTo("/login");
};
const qrUrl = `http://voce.chat/url?s=${encodeURIComponent(BASE_ORIGIN)}`;
const qrUrl = BASE_ORIGIN;
return (
<section className="flex flex-col items-center text-center">
<h2 className="text-3xl text-gray-600 dark:text-gray-50 font-bold">
+12
View File
@@ -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 = () => {
</LazyIt>
}
/>
<Route
path="/invite_mobile/:magic_token"
element={
<LazyIt>
<RequireNoAuth>
<InviteInMobile />
</RequireNoAuth>
</LazyIt>
}
/>
<Route
path="/cb/:type/:payload"
element={
@@ -113,6 +124,7 @@ const PageRoutes = () => {
</LazyIt>
}
/>
<Route
path="/register"
element={
+105
View File
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import StyledButton from "@/components/styled/Button";
import useCopy from "@/hooks/useCopy";
import useDownload from "@/hooks/useDownload";
import { isMobile } from "@/utils";
// type Props = {};
const InviteInMobile = () => {
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 (
<main className="flex h-screen flex-col items-center justify-between">
<div className="relative">
<img
src="https://voce.chat/img/app.grid.png"
className="object-cover max-w-[unset]"
alt="APP grid"
/>
<span className="absolute left-1/2 bottom-8 -translate-x-1/2 bg-transparent font-bold text-lg ">
{t("join")}!
</span>
</div>
<p className="text-md text-gray-600 my-5">
{t("have_already")}
<a href={app_link} className="text-primary-500 px-2">
{t("open")}
</a>
</p>
<div className="flex flex-col items-center mb-12">
{webLink && (
<a href={webLink} className="p-2 mt-2 rounded bg-primary-500 text-white">
Continue with webapp
</a>
)}
<div className="text-gray-600 w-[80%] flex flex-col items-center gap-2 my-4">
<i className="text-gray-400 not-italic text-xs break-words text-center">
👇App not showing? You may copy the following invitation link and paste it into VoceChat
App.
</i>
<div
className="text-left bg-gray-200 font-bold p-2 rounded-md break-all overflow-y-scroll resize-none"
spellCheck={false}
>
{inviteLink}
</div>
<StyledButton onClick={copy.bind(null, inviteLink, false)} className="small w-fit">
{copied ? "Copied" : `Copy`}
</StyledButton>
</div>
{download ? (
Array.isArray(download) ? (
<ul className="my-10">
{" "}
{download.map((d) => {
const { link, icon } = d;
return (
<li key={link}>
<a href={link} target="_blank" rel="noopener noreferrer">
<img
alt="App Download Icon"
src={icon}
className="max-w-[80%] h-auto m-auto mb-2"
/>
</a>
</li>
);
})}
</ul>
) : (
<a href={download.link} target="_blank" rel="noopener noreferrer" className="my-10">
<img
alt="App Download Icon"
src={download.icon}
className="max-w-[80%] h-auto m-auto"
/>
</a>
)
) : null}
</div>
</main>
);
};
export default InviteInMobile;
+11 -1
View File
@@ -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) {
+1 -1
View File
@@ -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;
};