feat: add license upgrade flag to server

This commit is contained in:
Tristan Yang
2023-03-22 11:32:14 +08:00
parent f67d1a7dc3
commit dba894c7e4
6 changed files with 51 additions and 22 deletions
+6 -3
View File
@@ -11,12 +11,15 @@ let prices: Price[] = [
type: "booking", type: "booking",
} }
]; ];
const official_dev = `https://dev.voce.chat`;
const local_dev = `https://dev.voce.chat`; // const local_dev = `http://07333.qicp.vip:3030`;
const local_dev = official_dev;
// const local_dev = `http://localhost:3333`; // const local_dev = `http://localhost:3333`;
export const BASE_ORIGIN = process.env.REACT_APP_RELEASE export const BASE_ORIGIN = process.env.REACT_APP_RELEASE
? `${location.origin}` ? `${location.origin}`
: local_dev; : local_dev;
export const IS_OFFICIAL_DEMO = BASE_ORIGIN === official_dev;
const BASE_URL = `${BASE_ORIGIN}/api`; const BASE_URL = `${BASE_ORIGIN}/api`;
export const getLicensePriceList = () => { export const getLicensePriceList = () => {
const ps = prices.map((p, idx) => { const ps = prices.map((p, idx) => {
@@ -69,7 +72,7 @@ export const PAYMENT_URL_PREFIX =
process.env.NODE_ENV === "production" process.env.NODE_ENV === "production"
? `https://vera.nicegoodthings.com` ? `https://vera.nicegoodthings.com`
: `http://localhost:4000`; : `http://localhost:4000`;
export const CACHE_VERSION = `0.3.33`; export const CACHE_VERSION = `0.3.36`;
export const GuestRoutes = ["/", "/chat", "/chat/channel/:channel_id"]; export const GuestRoutes = ["/", "/chat", "/chat/channel/:channel_id"];
export const ContentTypes = { export const ContentTypes = {
text: "text/plain", text: "text/plain",
+19 -2
View File
@@ -1,5 +1,5 @@
import { createApi } from "@reduxjs/toolkit/query/react"; import { createApi } from "@reduxjs/toolkit/query/react";
import BASE_URL, { ContentTypes, PAYMENT_URL_PREFIX } from "../config"; import BASE_URL, { ContentTypes, IS_OFFICIAL_DEMO, PAYMENT_URL_PREFIX } from "../config";
import { updateInfo } from "../slices/server"; import { updateInfo } from "../slices/server";
import baseQuery from "./base.query"; import baseQuery from "./base.query";
import { RootState } from "../store"; import { RootState } from "../store";
@@ -214,7 +214,24 @@ export const serverApi = createApi({
getLicense: builder.query<LicenseResponse, void>({ getLicense: builder.query<LicenseResponse, void>({
query: () => ({ query: () => ({
url: `/license` url: `/license`
}) }),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
// vocechat官方demo 则忽略
if (IS_OFFICIAL_DEMO) return;
const rootStore = getState() as RootState;
const { upgraded: prevValue } = rootStore.server;
try {
const { data: { user_limit } } = await queryFulfilled;
const currValue = user_limit > 20;
if (prevValue !== currValue) {
dispatch(updateInfo({ upgraded: currValue }));
}
} catch {
console.error("update license upgraded status failed ");
}
}
}), }),
getLicensePaymentUrl: builder.mutation<RenewLicenseResponse, RenewLicense>({ getLicensePaymentUrl: builder.mutation<RenewLicenseResponse, RenewLicense>({
+4 -1
View File
@@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Server } from "../../types/server"; import { Server } from "../../types/server";
export interface StoredServer extends Server { export interface StoredServer extends Server {
upgraded: boolean,
logo: string; logo: string;
inviteLink?: { inviteLink?: {
link: string; link: string;
@@ -9,6 +10,7 @@ export interface StoredServer extends Server {
}; };
} }
const initialState: StoredServer = { const initialState: StoredServer = {
upgraded: false,
name: "", name: "",
description: "", description: "",
logo: "", logo: "",
@@ -27,6 +29,7 @@ const serverSlice = createSlice({
}, },
fillServer(state, action: PayloadAction<StoredServer>) { fillServer(state, action: PayloadAction<StoredServer>) {
const { const {
upgraded = false,
inviteLink = { inviteLink = {
link: "", link: "",
expire: 0 expire: 0
@@ -35,7 +38,7 @@ const serverSlice = createSlice({
name = "", name = "",
description = "" description = ""
} = action.payload || {}; } = action.payload || {};
return { name, logo, description, inviteLink }; return { upgraded, name, logo, description, inviteLink };
}, },
updateInfo(state, action: PayloadAction<Partial<StoredServer>>) { updateInfo(state, action: PayloadAction<Partial<StoredServer>>) {
const values = action.payload || {}; const values = action.payload || {};
+6 -3
View File
@@ -12,6 +12,7 @@ import IconInvite from "../../assets/icons/placeholder.invite.svg";
import IconDownload from "../../assets/icons/placeholder.download.svg"; import IconDownload from "../../assets/icons/placeholder.download.svg";
import UsersModal from "./UsersModal"; import UsersModal from "./UsersModal";
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
import useLicense from "../hook/useLicense";
interface Props { interface Props {
@@ -28,6 +29,7 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
const [inviteModalVisible, setInviteModalVisible] = useState(false); const [inviteModalVisible, setInviteModalVisible] = useState(false);
const [createChannelVisible, setCreateChannelVisible] = useState(false); const [createChannelVisible, setCreateChannelVisible] = useState(false);
const [userListVisible, setUserListVisible] = useState(false); const [userListVisible, setUserListVisible] = useState(false);
const { upgraded } = useLicense();
const toggleChannelModalVisible = () => { const toggleChannelModalVisible = () => {
setCreateChannelVisible((prev) => !prev); setCreateChannelVisible((prev) => !prev);
}; };
@@ -45,13 +47,11 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-gray-700"> <div className="flex flex-col gap-8 -mt-[50px] dark:bg-gray-700">
<div className="flex flex-col gap-2 items-center group"> <div className="flex flex-col gap-2 items-center group">
<h2 className="text-center text-3xl text-slate-700 dark:text-white font-bold">{t("title", { name: server.name })}</h2> <h2 className="text-center text-3xl text-slate-700 dark:text-white font-bold">{t("title", { name: server.name })}</h2>
<p className="text-sm text-gray-400 max-w-md text-center relative break-all"> <p className="text-sm text-gray-400 max-w-md text-center relative break-all whitespace-pre">
<Linkify options={ <Linkify options={
{ {
render: { render: {
url: ({ content, attributes: { href: link } }) => { url: ({ content, attributes: { href: link } }) => {
// console.log("attr", link);
// if (!url) return <>{content}</>;
return <> return <>
<a className="text-primary-400" target="_blank" href={link} rel="noreferrer"> <a className="text-primary-400" target="_blank" href={link} rel="noreferrer">
{content} {content}
@@ -77,6 +77,7 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
<IconChat className={classes.boxIcon} /> <IconChat className={classes.boxIcon} />
<div className={classes.boxTip}>{chatTip}</div> <div className={classes.boxTip}>{chatTip}</div>
</button> </button>
{!upgraded && <>
<a href={"https://voce.chat#download"} target={"_blank"} rel="noreferrer" className={classes.box} > <a href={"https://voce.chat#download"} target={"_blank"} rel="noreferrer" className={classes.box} >
<IconDownload className={classes.boxIcon} /> <IconDownload className={classes.boxIcon} />
<div className={classes.boxTip}>{t("download")}</div> <div className={classes.boxTip}>{t("download")}</div>
@@ -85,6 +86,8 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
<IconAsk className={classes.boxIcon} /> <IconAsk className={classes.boxIcon} />
<div className={classes.boxTip}>{t("help")}</div> <div className={classes.boxTip}>{t("help")}</div>
</a> </a>
</>
}
</div> </div>
</div> </div>
{createChannelVisible && ( {createChannelVisible && (
+3 -2
View File
@@ -7,8 +7,8 @@ import {
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
const useLicense = () => { const useLicense = () => {
const { userCount, isGuest } = useAppSelector((store) => { const { userCount, isGuest, upgraded } = useAppSelector((store) => {
return { userCount: store.users.ids.length, isGuest: store.authData.guest }; return { userCount: store.users.ids.length, isGuest: store.authData.guest, upgraded: store.server.upgraded };
}); });
const { data: license, refetch: refetchLicense } = useGetLicenseQuery(undefined, { const { data: license, refetch: refetchLicense } = useGetLicenseQuery(undefined, {
refetchOnMountOrArgChange: true, refetchOnMountOrArgChange: true,
@@ -36,6 +36,7 @@ const useLicense = () => {
}, [upserted]); }, [upserted]);
const lUserLimit = license?.user_limit ?? Number.MAX_SAFE_INTEGER; const lUserLimit = license?.user_limit ?? Number.MAX_SAFE_INTEGER;
return { return {
upgraded,
reachLimit: userCount >= lUserLimit, reachLimit: userCount >= lUserLimit,
license, license,
checked, checked,
+5 -3
View File
@@ -1,3 +1,4 @@
import { useTranslation } from "react-i18next";
import MyAccount from "./MyAccount"; import MyAccount from "./MyAccount";
import Overview from "./Overview"; import Overview from "./Overview";
import Logins from "./config/Logins"; import Logins from "./config/Logins";
@@ -12,7 +13,6 @@ import ManageMembers from "../../common/component/ManageMembers";
import Version from "../../common/component/Version"; import Version from "../../common/component/Version";
// import ConfigAgora from "./config/Agora"; // import ConfigAgora from "./config/Agora";
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
import { useTranslation } from "react-i18next";
import ServerVersionChecker from "../../common/component/ServerVersionChecker"; import ServerVersionChecker from "../../common/component/ServerVersionChecker";
import useLicense from "../../common/hook/useLicense"; import useLicense from "../../common/hook/useLicense";
@@ -98,7 +98,7 @@ const navs = [
]; ];
const useNavs = () => { const useNavs = () => {
const { license: licenseInfo } = useLicense(); const { upgraded } = useLicense();
const { t } = useTranslation("setting"); const { t } = useTranslation("setting");
const loginUser = useAppSelector((store) => { const loginUser = useAppSelector((store) => {
return store.authData.user; return store.authData.user;
@@ -107,11 +107,13 @@ const useNavs = () => {
const { name, items, ...rest } = n; const { name, items, ...rest } = n;
return { return {
name, name,
// @ts-ignore
title: t(`nav.${name}`), title: t(`nav.${name}`),
items: items.map(item => { items: items.map(item => {
const { name, ...rest } = item; const { name, ...rest } = item;
return { return {
name, name,
// @ts-ignore
title: t(`nav.${name}`), title: t(`nav.${name}`),
...rest ...rest
}; };
@@ -126,7 +128,7 @@ const useNavs = () => {
// about 特殊处理下 // about 特殊处理下
if (nav.name == "about") { if (nav.name == "about") {
// 有付费,但是普通用户,则不显示about // 有付费,但是普通用户,则不显示about
return licenseInfo?.user_limit === 20; return !upgraded;
} else { } else {
return !nav.admin; return !nav.admin;
} }