feat: add license upgrade flag to server
This commit is contained in:
+6
-3
@@ -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",
|
||||||
|
|||||||
@@ -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>({
|
||||||
|
|||||||
@@ -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 || {};
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user