feat: invite in mobile process
This commit is contained in:
@@ -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?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user