feat: mobile app download toaster
This commit is contained in:
@@ -9,12 +9,14 @@
|
||||
"logout": "Log Out",
|
||||
"change_avatar": "Change Avatar",
|
||||
"cancel": "Cancel",
|
||||
"dismiss": "Dismiss",
|
||||
"search": "Search",
|
||||
"new_channel": "New Channel",
|
||||
"new_private_channel": "New Private Channel",
|
||||
"new_msg": "New Message",
|
||||
"invite_people": "Invite People",
|
||||
"create": "Create",
|
||||
"open": "Open",
|
||||
"update": "Update",
|
||||
"upload": "Upload",
|
||||
"done": "Done",
|
||||
@@ -41,5 +43,7 @@
|
||||
},
|
||||
"status": {
|
||||
"uploading": "Uploading"
|
||||
}
|
||||
},
|
||||
"new_version": "<0>New Version</0> Available",
|
||||
"mobile_app": "Check out our <0>Mobile APP</0>"
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
"logout": "退出登录",
|
||||
"change_avatar": "修改头像",
|
||||
"cancel": "取消",
|
||||
"dismiss": "关闭",
|
||||
"new_channel": "新建频道",
|
||||
"new_private_channel": "新建私有频道",
|
||||
"new_msg": "发消息",
|
||||
"invite_people": "邀请TA人",
|
||||
"search": "搜索",
|
||||
"create": "创建",
|
||||
"open": "打开",
|
||||
"update": "更新",
|
||||
"upload": "上传",
|
||||
"done": "完成",
|
||||
@@ -42,5 +44,7 @@
|
||||
},
|
||||
"status": {
|
||||
"uploading": "上传中"
|
||||
}
|
||||
},
|
||||
"new_version": "有<0>新版本</0>",
|
||||
"mobile_app": "欢迎下载使用<0>手机APP</0>"
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ export const vapidKey = `BOmzyZhw-DcIGYQ77mzQUVqLlcvn0bm_76P_kc7rpwRxzXNbui-JP8i
|
||||
export const tokenHeader = "X-API-Key";
|
||||
export const FILE_SLICE_SIZE = 1000 * 200 * 8; //200kb
|
||||
export const FILE_IMAGE_SIZE = 1000 * 10000 * 8; //10mb
|
||||
export const KEY_MOBILE_APP_TIP = "MOBILE_APP_TIP";
|
||||
export const KEY_LOGIN_USER = "VOCECHAT_LOGIN_USER";
|
||||
export const KEY_TOKEN = "VOCECHAT_TOKEN";
|
||||
export const KEY_EXPIRE = "VOCECHAT_TOKEN_EXPIRE";
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { KEY_MOBILE_APP_TIP } from "../../app/config";
|
||||
import Button from "./styled/Button";
|
||||
|
||||
interface ContainerProps {
|
||||
id: string;
|
||||
}
|
||||
const Container = (props: ContainerProps) => {
|
||||
const { id } = props;
|
||||
const { t } = useTranslation();
|
||||
const handleOpen = () => {
|
||||
localStorage.removeItem(KEY_MOBILE_APP_TIP);
|
||||
toast.dismiss(id);
|
||||
window.open("https://voce.chat#download");
|
||||
};
|
||||
const handleClose = () => {
|
||||
localStorage.removeItem(KEY_MOBILE_APP_TIP);
|
||||
toast.dismiss(id);
|
||||
};
|
||||
return <div className="flex items-center gap-2 whitespace-nowrap">
|
||||
<div>
|
||||
<Trans i18nKey={"mobile_app"}>
|
||||
<strong className="font-bold" />
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button className="mini main" onClick={handleOpen}>
|
||||
{t("action.open")}
|
||||
</Button>
|
||||
<Button className="mini cancel" onClick={handleClose}>
|
||||
{t("action.dismiss")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
interface Props { }
|
||||
const Index: FC<Props> = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
setVisible(!!localStorage.getItem(KEY_MOBILE_APP_TIP));
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
toast((t) => <Container id={t.id} />, {
|
||||
duration: Infinity,
|
||||
position: "top-right"
|
||||
});
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -1,5 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { FC, useEffect } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { KEY_MOBILE_APP_TIP } from "../../app/config";
|
||||
import Button from "./styled/Button";
|
||||
|
||||
interface Props {
|
||||
@@ -8,15 +10,24 @@ interface Props {
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({ id, handleUpdate }) => {
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
localStorage.setItem(KEY_MOBILE_APP_TIP, "1");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<strong className="whitespace-nowrap font-bold">New Version</strong> Available
|
||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||
<div>
|
||||
<Trans i18nKey={"new_version"}>
|
||||
<strong className="font-bold" />
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button className="mini main" onClick={handleUpdate}>
|
||||
Update
|
||||
{t("action.update")}
|
||||
</Button>
|
||||
<Button className="mini cancel" onClick={() => toast.dismiss(id)}>
|
||||
Dismiss
|
||||
{t("action.dismiss")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import "./common/DayjsSetting";
|
||||
import "./common/TippySetting";
|
||||
import { register } from "./serviceWorkerRegistration";
|
||||
import MarkdownStyleOverride from "./common/component/MarkdownStyleOverride";
|
||||
import MobileAppTip from "./common/component/MobileAppTip";
|
||||
import ReduxRoutes from "./routes";
|
||||
import NewVersion from "./common/component/NewVersion";
|
||||
// import i18n (needs to be bundled ;))
|
||||
@@ -26,6 +27,7 @@ root.render(
|
||||
<ReduxRoutes />
|
||||
</DndProvider>
|
||||
<MarkdownStyleOverride />
|
||||
<MobileAppTip />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user