feat: license payment
This commit is contained in:
@@ -3,6 +3,30 @@
|
|||||||
const BASE_URL = process.env.REACT_APP_RELEASE
|
const BASE_URL = process.env.REACT_APP_RELEASE
|
||||||
? `${location.origin}/api`
|
? `${location.origin}/api`
|
||||||
: `https://dev.voce.chat/api`;
|
: `https://dev.voce.chat/api`;
|
||||||
|
export const LicensePriceList = [
|
||||||
|
{
|
||||||
|
title: "VoceChat Pro",
|
||||||
|
limit: 100,
|
||||||
|
pid: "price_1LkICIGGoUDRyc3jqrdPEnkY",
|
||||||
|
desc: "User Limit: 100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "VoceChat Supreme",
|
||||||
|
limit: 99999,
|
||||||
|
pid: "price_1LkKqdGGoUDRyc3jnpZvXhzx",
|
||||||
|
desc: "User Limit: No Limit"
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// title: "VoceChat Enterprise",
|
||||||
|
// limit: 500,
|
||||||
|
// // pid: "price_1LkJNsGGoUDRyc3jkVNf4VPE",
|
||||||
|
// pid: "price_1LkQGpGGoUDRyc3jGTh3GYHw",
|
||||||
|
// desc: "User Limit: 500"
|
||||||
|
// }
|
||||||
|
];
|
||||||
|
export const PAYMENT_URL_PREFIX = process.env.REACT_APP_RELEASE
|
||||||
|
? `https://vera.nicegoodthings.com`
|
||||||
|
: `http://localhost:4000`;
|
||||||
export const CACHE_VERSION = `0.3.1`;
|
export const CACHE_VERSION = `0.3.1`;
|
||||||
export const GuestRoutes = ["/", "/chat", "/chat/channel/:channel_id"];
|
export const GuestRoutes = ["/", "/chat", "/chat/channel/:channel_id"];
|
||||||
export const ContentTypes = {
|
export const ContentTypes = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||||
import BASE_URL from "../config";
|
import BASE_URL, { 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";
|
||||||
@@ -14,7 +14,9 @@ import {
|
|||||||
SMTPConfig,
|
SMTPConfig,
|
||||||
AgoraConfig,
|
AgoraConfig,
|
||||||
GithubAuthConfig,
|
GithubAuthConfig,
|
||||||
LicenseResponse
|
LicenseResponse,
|
||||||
|
RenewLicense,
|
||||||
|
RenewLicenseResponse
|
||||||
} from "../../types/server";
|
} from "../../types/server";
|
||||||
|
|
||||||
const defaultExpireDuration = 7 * 24 * 60 * 60;
|
const defaultExpireDuration = 7 * 24 * 60 * 60;
|
||||||
@@ -195,6 +197,18 @@ export const serverApi = createApi({
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
getLicensePaymentUrl: builder.mutation<RenewLicenseResponse, RenewLicense>({
|
||||||
|
query: (data) => ({
|
||||||
|
url: `${PAYMENT_URL_PREFIX}/vocechat/payment/create`,
|
||||||
|
method: "POST",
|
||||||
|
body: data
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
getGeneratedLicense: builder.query<{ license: string }, string>({
|
||||||
|
query: (session_id) => ({
|
||||||
|
url: `${PAYMENT_URL_PREFIX}/vocechat/licenses/${session_id}`
|
||||||
|
})
|
||||||
|
}),
|
||||||
checkLicense: builder.mutation<LicenseResponse, string>({
|
checkLicense: builder.mutation<LicenseResponse, string>({
|
||||||
query: (license) => ({
|
query: (license) => ({
|
||||||
url: "/license/check",
|
url: "/license/check",
|
||||||
@@ -241,5 +255,7 @@ export const {
|
|||||||
useCreateAdminMutation,
|
useCreateAdminMutation,
|
||||||
useUpsertLicenseMutation,
|
useUpsertLicenseMutation,
|
||||||
useCheckLicenseMutation,
|
useCheckLicenseMutation,
|
||||||
useGetLicenseQuery
|
useGetLicenseQuery,
|
||||||
|
useGetLicensePaymentUrlMutation,
|
||||||
|
useLazyGetGeneratedLicenseQuery
|
||||||
} = serverApi;
|
} = serverApi;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -4,8 +4,10 @@ import {
|
|||||||
useGetLicenseQuery,
|
useGetLicenseQuery,
|
||||||
useUpsertLicenseMutation
|
useUpsertLicenseMutation
|
||||||
} from "../../app/services/server";
|
} from "../../app/services/server";
|
||||||
|
import { useAppSelector } from "../../app/store";
|
||||||
|
|
||||||
const useLicense = () => {
|
const useLicense = () => {
|
||||||
|
const userCount = useAppSelector((store) => store.users.ids.length);
|
||||||
const { data: license } = useGetLicenseQuery();
|
const { data: license } = useGetLicenseQuery();
|
||||||
const [check, { isLoading: isChecking, isSuccess: checked }] = useCheckLicenseMutation();
|
const [check, { isLoading: isChecking, isSuccess: checked }] = useCheckLicenseMutation();
|
||||||
const [upsert, { isSuccess: upserted, isLoading: upserting }] = useUpsertLicenseMutation();
|
const [upsert, { isSuccess: upserted, isLoading: upserting }] = useUpsertLicenseMutation();
|
||||||
@@ -21,8 +23,10 @@ const useLicense = () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
console.log("uuu", userCount, license);
|
||||||
|
const lUserLimit = license?.user_limit ?? 0;
|
||||||
return {
|
return {
|
||||||
|
reachLimit: userCount >= lUserLimit,
|
||||||
license,
|
license,
|
||||||
checked,
|
checked,
|
||||||
checking: isChecking,
|
checking: isChecking,
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { useLazyGetGeneratedLicenseQuery } from "../../app/services/server";
|
||||||
|
import useLicense from "../../common/hook/useLicense";
|
||||||
|
import Button from "../../common/component/styled/Button";
|
||||||
|
import checkIcon from "../../assets/icons/check.png";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
const Styled = styled.section`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
width: 512px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-radius: 20px;
|
||||||
|
.check {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
.head {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 32px;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #999;
|
||||||
|
padding: 0 0 30px 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaymentSuccess = ({ sid }: Props) => {
|
||||||
|
const navigateTo = useNavigate();
|
||||||
|
const { upsertLicense, upserting, upserted } = useLicense();
|
||||||
|
const [getGeneratedLicense, { data, isError, isLoading, isSuccess }] =
|
||||||
|
useLazyGetGeneratedLicenseQuery();
|
||||||
|
useEffect(() => {
|
||||||
|
if (sid) {
|
||||||
|
getGeneratedLicense(sid);
|
||||||
|
}
|
||||||
|
}, [sid]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess && data) {
|
||||||
|
const l = data.license;
|
||||||
|
upsertLicense(l);
|
||||||
|
}
|
||||||
|
}, [data, isSuccess]);
|
||||||
|
const handleBack = () => {
|
||||||
|
navigateTo("/");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Styled>
|
||||||
|
<img className="check" src={checkIcon} alt="check icon" />
|
||||||
|
<h1 className="head">Payment Success!</h1>
|
||||||
|
<p className="desc">
|
||||||
|
{upserting ? "Renewing the License, do not close the window!" : ""}
|
||||||
|
{upserted ? "Renew the License Successfully!" : ""}
|
||||||
|
{isError ? "Invalided Stripe Session ID" : ""}
|
||||||
|
</p>
|
||||||
|
<Button disabled={isLoading || upserting} className="back" onClick={handleBack}>
|
||||||
|
Back Home
|
||||||
|
</Button>
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PaymentSuccess;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import PaymentSuccess from "./PaymentSuccess";
|
||||||
|
import StyledWrapper from "./styled";
|
||||||
|
// type Props = {
|
||||||
|
// type: "payment_success";
|
||||||
|
// };
|
||||||
|
// 该页面服务于一些第三方服务的回调,比如stripe付款成功的回调
|
||||||
|
export default function CallbackPage() {
|
||||||
|
const { type = "", payload = "" } = useParams();
|
||||||
|
if (type == "payment_success") {
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<PaymentSuccess sid={payload} />
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <StyledWrapper>callback page</StyledWrapper>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
||||||
@@ -14,6 +14,7 @@ const RegPage = lazy(() => import("./reg/Register"));
|
|||||||
const LoginPage = lazy(() => import("./login"));
|
const LoginPage = lazy(() => import("./login"));
|
||||||
const OAuthPage = lazy(() => import("./oauth"));
|
const OAuthPage = lazy(() => import("./oauth"));
|
||||||
const UsersPage = lazy(() => import("./users"));
|
const UsersPage = lazy(() => import("./users"));
|
||||||
|
const CallbackPage = lazy(() => import("./callback"));
|
||||||
const FavoritesPage = lazy(() => import("./favs"));
|
const FavoritesPage = lazy(() => import("./favs"));
|
||||||
const OnboardingPage = lazy(() => import("./onboarding"));
|
const OnboardingPage = lazy(() => import("./onboarding"));
|
||||||
const InvitePage = lazy(() => import("./invite"));
|
const InvitePage = lazy(() => import("./invite"));
|
||||||
@@ -51,6 +52,7 @@ const PageRoutes = () => {
|
|||||||
<Suspense fallback={<Loading fullscreen={true} />}>
|
<Suspense fallback={<Loading fullscreen={true} />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/guest_login" element={<GuestLogining />} />
|
<Route path="/guest_login" element={<GuestLogining />} />
|
||||||
|
<Route path="/cb/:type/:payload" element={<CallbackPage />} />
|
||||||
<Route path="/oauth/:token" element={<OAuthPage />} />
|
<Route path="/oauth/:token" element={<OAuthPage />} />
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
|
|||||||
@@ -1,34 +1,14 @@
|
|||||||
import { ChangeEvent, useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Tippy from "@tippyjs/react";
|
// import Tippy from "@tippyjs/react";
|
||||||
import toast from "react-hot-toast";
|
// import toast from "react-hot-toast";
|
||||||
import { hideAll } from "tippy.js";
|
// import { hideAll } from "tippy.js";
|
||||||
import Textarea from "../../common/component/styled/Textarea";
|
import Textarea from "../../common/component/styled/Textarea";
|
||||||
import Button from "../../common/component/styled/Button";
|
import Button from "../../common/component/styled/Button";
|
||||||
import useLicense from "../../common/hook/useLicense";
|
import useLicense from "../../common/hook/useLicense";
|
||||||
|
import LicensePriceListModal from "./LicensePriceListModal";
|
||||||
|
|
||||||
const StyledConfirm = styled.div`
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid orange;
|
|
||||||
background-color: #fff;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
width: 250px;
|
|
||||||
.tip {
|
|
||||||
color: orange;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const StyledInfo = styled.div`
|
const StyledInfo = styled.div`
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -58,55 +38,37 @@ const StyledInfo = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const Styled = styled.div`
|
const Styled = styled.div`
|
||||||
max-width: 500px;
|
max-width: 760px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
> .input {
|
> .license {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
/* flex-direction: column; */
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 8px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function License() {
|
export default function License() {
|
||||||
const { license: licenseInfo, checking, upserting, upsertLicense } = useLicense();
|
const { license: licenseInfo } = useLicense();
|
||||||
const [license, setLicense] = useState("");
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const handleUpsert = async () => {
|
const handleRenewLicense = () => {
|
||||||
if (!license) {
|
toggleModalVisible();
|
||||||
toast.error("License Empty");
|
|
||||||
hideAll();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const success = await upsertLicense(license);
|
|
||||||
if (!success) {
|
|
||||||
toast.error("Invalid License");
|
|
||||||
hideAll();
|
|
||||||
} else {
|
|
||||||
toast.success("License Updated!");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const handleLicenseInput = (evt: ChangeEvent<HTMLTextAreaElement>) => {
|
const toggleModalVisible = () => {
|
||||||
setLicense(evt.target.value);
|
setModalVisible((prev) => !prev);
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
|
||||||
if (licenseInfo?.base58) {
|
|
||||||
setLicense(licenseInfo.base58);
|
|
||||||
}
|
|
||||||
}, [licenseInfo]);
|
|
||||||
|
|
||||||
const disableBtn = checking || upserting || !license || license === licenseInfo?.base58;
|
// const disableBtn = !reachLimit;
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Styled>
|
<Styled>
|
||||||
<div className="input">
|
<div className="license">
|
||||||
<Tippy
|
<Textarea disabled rows={14} id="license" value={licenseInfo?.base58} />
|
||||||
placement="right-start"
|
|
||||||
visible={!!licenseInfo}
|
|
||||||
content={
|
|
||||||
<StyledInfo>
|
<StyledInfo>
|
||||||
<div className="item">
|
<div className="item">
|
||||||
<span className="label">Signed</span>
|
<span className="label">Signed</span>
|
||||||
@@ -115,7 +77,6 @@ export default function License() {
|
|||||||
<div className="item">
|
<div className="item">
|
||||||
<span className="label">Domains</span>
|
<span className="label">Domains</span>
|
||||||
<ul className="info">
|
<ul className="info">
|
||||||
{" "}
|
|
||||||
{licenseInfo?.domains.map((d) => {
|
{licenseInfo?.domains.map((d) => {
|
||||||
return <li key={d}>{d}</li>;
|
return <li key={d}>{d}</li>;
|
||||||
})}
|
})}
|
||||||
@@ -138,31 +99,15 @@ export default function License() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</StyledInfo>
|
</StyledInfo>
|
||||||
}
|
|
||||||
>
|
|
||||||
<Textarea rows={15} id="license" value={license} onChange={handleLicenseInput} />
|
|
||||||
</Tippy>
|
|
||||||
</div>
|
</div>
|
||||||
<Tippy
|
<Button onClick={handleRenewLicense}>Renew License</Button>
|
||||||
interactive
|
|
||||||
placement="bottom-start"
|
|
||||||
trigger="click"
|
|
||||||
content={
|
|
||||||
<StyledConfirm>
|
|
||||||
<div className="tip">Are you sure to update License?</div>
|
|
||||||
<div className="btns">
|
|
||||||
<Button onClick={() => hideAll()} className="cancel small">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button disabled={disableBtn} className="small danger" onClick={handleUpsert}>
|
|
||||||
Yes
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</StyledConfirm>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button disabled={disableBtn}>Update License</Button>
|
|
||||||
</Tippy>
|
|
||||||
</Styled>
|
</Styled>
|
||||||
|
{modalVisible && (
|
||||||
|
<LicensePriceListModal
|
||||||
|
closeModal={toggleModalVisible}
|
||||||
|
// domain={licenseInfo?.domains ? licenseInfo.domains.join("|") : ""}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { FC, useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import Modal from "../../common/component/Modal";
|
||||||
|
import StyledModal from "../../common/component/styled/Modal";
|
||||||
|
import Button from "../../common/component/styled/Button";
|
||||||
|
import StyledRadio from "../../common/component/styled/Radio";
|
||||||
|
|
||||||
|
import { useGetLicensePaymentUrlMutation } from "../../app/services/server";
|
||||||
|
import { LicensePriceList } from "../../app/config";
|
||||||
|
// import { LicenseMetadata, RenewLicense } from "../../types/server";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
closeModal: () => void;
|
||||||
|
// domain: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||||
|
const [getUrl, { isLoading, isSuccess }] = useGetLicensePaymentUrlMutation();
|
||||||
|
const [selectPrice, setSelectPrice] = useState(
|
||||||
|
`${LicensePriceList[0].pid}|${LicensePriceList[0].limit}`
|
||||||
|
);
|
||||||
|
const handleRenew = async () => {
|
||||||
|
const [priceId, user_limit] = selectPrice.split("|");
|
||||||
|
const resp = await getUrl({
|
||||||
|
priceId,
|
||||||
|
metadata: {
|
||||||
|
user_limit: Number(user_limit),
|
||||||
|
expire: "2035-01-01",
|
||||||
|
// 本地,则*通配符
|
||||||
|
domain: location.hostname.startsWith("localhost") ? "*" : location.hostname
|
||||||
|
},
|
||||||
|
cancel_url: location.href,
|
||||||
|
success_url: `${location.origin}/#/cb/payment_success`
|
||||||
|
});
|
||||||
|
if ("error" in resp) {
|
||||||
|
toast.error("Payment link initialized failed!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("aaaa", resp.data);
|
||||||
|
// todo
|
||||||
|
location.href = resp.data.session_url;
|
||||||
|
};
|
||||||
|
const handlePriceSelect = (price: string) => {
|
||||||
|
setSelectPrice(price);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal id="modal-modal">
|
||||||
|
<StyledModal
|
||||||
|
title="Renew License"
|
||||||
|
description="Please select the price"
|
||||||
|
buttons={
|
||||||
|
<>
|
||||||
|
<Button onClick={closeModal} className="ghost">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button disabled={isLoading || isSuccess} onClick={handleRenew} className="danger">
|
||||||
|
{isLoading ? "Initialize Payment Url" : isSuccess ? "Redirecting" : "Renew"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledRadio
|
||||||
|
options={LicensePriceList.map(({ title, desc }) => `${title} [${desc}]`)}
|
||||||
|
values={LicensePriceList.map(({ pid, limit }) => `${pid}|${limit}`)}
|
||||||
|
value={selectPrice}
|
||||||
|
onChange={(v) => {
|
||||||
|
console.log("wtff", v);
|
||||||
|
handlePriceSelect(v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledModal>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LicensePriceListModal;
|
||||||
@@ -10,7 +10,6 @@ import SaveTip from "../../common/component/SaveTip";
|
|||||||
import StyledRadio from "../../common/component/styled/Radio";
|
import StyledRadio from "../../common/component/styled/Radio";
|
||||||
import { useAppSelector } from "../../app/store";
|
import { useAppSelector } from "../../app/store";
|
||||||
import { LoginConfig, WhoCanSignUp } from "../../types/server";
|
import { LoginConfig, WhoCanSignUp } from "../../types/server";
|
||||||
import Toggle from "../../common/component/styled/Toggle";
|
|
||||||
import useConfig from "../../common/hook/useConfig";
|
import useConfig from "../../common/hook/useConfig";
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
|||||||
@@ -67,6 +67,20 @@ export interface LicenseResponse {
|
|||||||
base58: string;
|
base58: string;
|
||||||
user_limit: number;
|
user_limit: number;
|
||||||
}
|
}
|
||||||
|
export interface LicenseMetadata {
|
||||||
|
expire: string;
|
||||||
|
user_limit: number;
|
||||||
|
domain: string | string[];
|
||||||
|
}
|
||||||
|
export interface RenewLicense {
|
||||||
|
priceId: string;
|
||||||
|
metadata: LicenseMetadata;
|
||||||
|
cancel_url: string;
|
||||||
|
success_url: string;
|
||||||
|
}
|
||||||
|
export interface RenewLicenseResponse {
|
||||||
|
session_url: string;
|
||||||
|
}
|
||||||
export interface CreateAdminDTO extends Pick<User, "email" | "name" | "gender"> {
|
export interface CreateAdminDTO extends Pick<User, "email" | "name" | "gender"> {
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user