feat: frontend url setting
This commit is contained in:
@@ -58,6 +58,10 @@
|
||||
"enable": "Enable",
|
||||
"disable": "Disabled"
|
||||
},
|
||||
"url": {
|
||||
"title": "Update Frontend URL",
|
||||
"desc": "Update the frontend url"
|
||||
},
|
||||
"lang": {
|
||||
"title": "Language",
|
||||
"desc": "Setting the language",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"set_name": "Set Name",
|
||||
"admin_account": "Admin Account",
|
||||
"who_sign_up": "Who Can Sign Up",
|
||||
"invites": "Invites"
|
||||
"invites": "Invites",
|
||||
"update_domain_tip": "Incorrect link domain?Please update it here:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +55,13 @@
|
||||
"enable": "开启",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"url": {
|
||||
"title": "更新前端网址",
|
||||
"desc": "更新当前的前端网址"
|
||||
},
|
||||
"lang": {
|
||||
"title": "语言",
|
||||
"desc": "设置界面语言",
|
||||
"en": "英文",
|
||||
"zh": "中文",
|
||||
"jp": "日文"
|
||||
"desc": "设置界面语言"
|
||||
}
|
||||
},
|
||||
"license": {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"set_name": "设置名称",
|
||||
"admin_account": "管理员账号",
|
||||
"who_sign_up": "注册权限",
|
||||
"invites": "邀请"
|
||||
"invites": "邀请",
|
||||
"update_domain_tip": "链接域名有问题?可在此更新设置:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,9 +94,10 @@ export const channelApi = createApi({
|
||||
transformResponse: (link: string) => {
|
||||
// 确保http开头
|
||||
const _link = link.startsWith("http") ? link : `http://${link}`;
|
||||
return _link;
|
||||
// 替换掉域名
|
||||
const invite = new URL(_link);
|
||||
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
|
||||
// const invite = new URL(_link);
|
||||
// return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
|
||||
}
|
||||
}),
|
||||
removeChannel: builder.query<void, number>({
|
||||
|
||||
@@ -195,6 +195,12 @@ export const serverApi = createApi({
|
||||
body: data
|
||||
})
|
||||
}),
|
||||
updateFrontendUrl: builder.mutation<void, string>({
|
||||
query: (url) => ({
|
||||
url: `/admin/system/update_frontend_url?frontend_url=${encodeURIComponent(url)}`,
|
||||
method: "POST",
|
||||
})
|
||||
}),
|
||||
getLicense: builder.query<LicenseResponse, void>({
|
||||
query: () => ({
|
||||
url: `/license`
|
||||
@@ -285,5 +291,6 @@ export const {
|
||||
useGetLicensePaymentUrlMutation,
|
||||
useLazyGetGeneratedLicenseQuery,
|
||||
useLazyGetBotRelatedChannelsQuery,
|
||||
useSendMessageByBotMutation
|
||||
useSendMessageByBotMutation,
|
||||
useUpdateFrontendUrlMutation
|
||||
} = serverApi;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" stroke="#088AB2" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1130_82786)">
|
||||
<path d="M9.99984 13.3334V10M9.99984 6.66669H10.0082M18.3332 10C18.3332 14.6024 14.6022 18.3334 9.99984 18.3334C5.39746 18.3334 1.6665 14.6024 1.6665 10C1.6665 5.39765 5.39746 1.66669 9.99984 1.66669C14.6022 1.66669 18.3332 5.39765 18.3332 10Z" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1130_82786">
|
||||
<rect width="20" height="20" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 575 B |
@@ -1,17 +1,19 @@
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
import { useGetServerVersionQuery } from '../../app/services/server';
|
||||
import { compareVersion } from '../utils';
|
||||
|
||||
type Props = {
|
||||
empty?: boolean,
|
||||
version: string,
|
||||
children: ReactElement
|
||||
}
|
||||
|
||||
const ServerVersionChecker = ({ version, children }: PropsWithChildren<Props>) => {
|
||||
const ServerVersionChecker = ({ empty = false, version, children }: Props) => {
|
||||
const { data: currentVersion, isSuccess } = useGetServerVersionQuery();
|
||||
if (!isSuccess) return null;
|
||||
const res = compareVersion(currentVersion, version);
|
||||
if (res < 0) return <div className='flex flex-col gap-2 items-start'>
|
||||
<span className='text-gray-400 text-sm'>Server version:<strong className='font-bold'>{version}</strong> needed at least 🚨</span>
|
||||
if (res < 0) return empty ? null : <div className='flex flex-col gap-2 items-start border border-solid border-orange-500 p-3 rounded-lg'>
|
||||
<span className='text-gray-400 text-sm'>This function needs server version:<strong className='font-bold'>{version}</strong> at least 🚨</span>
|
||||
<span className='text-gray-400 text-sm'>Your current version:<strong className='font-bold'>{currentVersion}</strong></span>
|
||||
<span className='text-gray-400 text-sm'>Please upgrade the Server!</span>
|
||||
<a className='text-blue-500 underline' href="https://doc.voce.chat/install/install-by-docker#update-vocechat-docker" target="_blank" rel="noopener noreferrer">How to Update VoceChat Server 📖 </a>
|
||||
|
||||
@@ -1,98 +1,61 @@
|
||||
import styled from "styled-components";
|
||||
import StyledInput from "../../../common/component/styled/Input";
|
||||
import StyledButton from "../../../common/component/styled/Button";
|
||||
import useInviteLink from "../../../common/hook/useInviteLink";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWizard } from "react-use-wizard";
|
||||
import { ChangeEvent, useState, useEffect } from "react";
|
||||
import { useUpdateFrontendUrlMutation } from "../../../app/services/server";
|
||||
import { toast } from "react-hot-toast";
|
||||
import InfoIcon from '../../../assets/icons/info.svg';
|
||||
import ServerVersionChecker from "../../../common/component/ServerVersionChecker";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> .primaryText {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .secondaryText {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
> .tip {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #475467;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .link {
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
border: 1px solid #f4f4f5;
|
||||
box-shadow: 0 1px 2px rgba(31, 41, 55, 0.08);
|
||||
border-radius: 4px;
|
||||
width: 374px;
|
||||
display: flex;
|
||||
|
||||
> input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 11px 0 11px 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #78787c;
|
||||
}
|
||||
|
||||
> button {
|
||||
padding: 0 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #22ccee;
|
||||
transition: color 150ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: #088ab2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .button {
|
||||
width: 124px;
|
||||
height: 44px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
`;
|
||||
export default function InviteLink() {
|
||||
const [updateUrl, { isSuccess, isLoading }] = useUpdateFrontendUrlMutation();
|
||||
const { t } = useTranslation("welcome", { keyPrefix: "onboarding" });
|
||||
const { t: ct } = useTranslation();
|
||||
const { nextStep } = useWizard();
|
||||
const { link, linkCopied, copyLink } = useInviteLink();
|
||||
const { link, linkCopied, copyLink, generateNewLink } = useInviteLink();
|
||||
const [frontUrl, setFrontUrl] = useState(location.origin);
|
||||
const handleUpdateUrl = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
setFrontUrl(evt.target.value);
|
||||
};
|
||||
const updateFrontUrl = () => {
|
||||
updateUrl(frontUrl);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
generateNewLink();
|
||||
toast.success("Update Successfully!");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<span className="primaryText">{t("invite_title")}</span>
|
||||
<span className="secondaryText">{t("last_tip")}</span>
|
||||
<span className="tip">{t("last_desc")}</span>
|
||||
<div className="link">
|
||||
<StyledInput className="large" readOnly placeholder="Generating" value={link} />
|
||||
<StyledButton onClick={copyLink} className="ghost small border_less">
|
||||
<div className="h-full flex flex-col items-center justify-center relative">
|
||||
<span className="text-2xl mb-2 font-bold">{t("invite_title")}</span>
|
||||
<span className="text-sm mb-10 text-gray-400">{t("last_tip")}</span>
|
||||
<span className="text-sm text-gray-500 mb-2 font-semibold">{t("last_desc")}</span>
|
||||
<div className="w-[400px] rounded shadow-md flex border border-solid border-gray-100">
|
||||
<StyledInput className="large !border-none !shadow-none" readOnly placeholder="Generating" value={link} />
|
||||
<StyledButton onClick={copyLink} className="ghost small border_less !px-2 hover:!text-[#088ab2]">
|
||||
{linkCopied ? "Copied" : ct("action.copy")}
|
||||
</StyledButton>
|
||||
</div>
|
||||
<StyledButton className="button" onClick={nextStep}>
|
||||
<StyledButton className="w-32 h-11 mt-6" onClick={nextStep}>
|
||||
{t("done")}
|
||||
</StyledButton>
|
||||
</StyledWrapper>
|
||||
<ServerVersionChecker version="0.3.2" empty={true}>
|
||||
<div className="absolute left-1/2 -translate-x-1/2 bottom-8 border-2 border-solid border-[#67E3F9] bg-[#F5FEFF] rounded-lg px-2 py-3 flex justify-start gap-4">
|
||||
<InfoIcon />
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<span className="text-sm text-[#0E7090] mb-1">{t("update_domain_tip")}</span>
|
||||
<div className="w-[400px] rounded flex gap-2">
|
||||
<StyledInput type={"url"} className="!shadow-none !bg-transparent" placeholder="Frontend URL" value={frontUrl} onChange={handleUpdateUrl} />
|
||||
<StyledButton disabled={!frontUrl || isLoading} onClick={updateFrontUrl} className="small ">
|
||||
{ct("action.update")}
|
||||
</StyledButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ServerVersionChecker>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// import React from 'react'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUpdateFrontendUrlMutation } from '../../../app/services/server';
|
||||
import StyledInput from "../../../common/component/styled/Input";
|
||||
import StyledButton from "../../../common/component/styled/Button";
|
||||
import { ChangeEvent, useState, useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import ServerVersionChecker from '../../../common/component/ServerVersionChecker';
|
||||
// type Props = {}
|
||||
|
||||
const Index = () => {
|
||||
const [url, setUrl] = useState(location.origin);
|
||||
const { t } = useTranslation("setting");
|
||||
const { t: ct } = useTranslation();
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
// update
|
||||
setUrl(evt.target.value);
|
||||
};
|
||||
const handleUpdate = () => {
|
||||
updateUrl(url);
|
||||
};
|
||||
const [updateUrl, { isLoading, isSuccess }] = useUpdateFrontendUrlMutation();
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Update Successfully!");
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<div className="setting">
|
||||
<p className="label">{t("overview.url.title")}</p>
|
||||
<p className="tip">
|
||||
<span className="txt">
|
||||
{t("overview.url.desc")}
|
||||
</span>
|
||||
</p>
|
||||
<div className="flex items-center gap-4 mt-4">
|
||||
<ServerVersionChecker version="0.3.2">
|
||||
<StyledInput placeholder='frontend url' value={url} onChange={handleChange} />
|
||||
<StyledButton disabled={!url || isLoading} className='' onClick={handleUpdate}> {ct("action.update")}</StyledButton>
|
||||
</ServerVersionChecker>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -6,7 +6,8 @@ import { useAppSelector } from "../../../app/store";
|
||||
import { LoginConfig, WhoCanSignUp } from "../../../types/server";
|
||||
import useConfig from "../../../common/hook/useConfig";
|
||||
import Server from './server';
|
||||
import Language from './language';
|
||||
import Language from './Language';
|
||||
import FrontendURL from "./FrontendURL";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
@@ -130,8 +131,10 @@ export default function Overview() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FrontendURL />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Language />
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface EntityId {
|
||||
id: number;
|
||||
}
|
||||
export type AuthType = "register" | "login";
|
||||
export type PriceType = "subscription" | "payment";
|
||||
export type PriceSubscriptionDuration = "month" | "quarter" | "year";
|
||||
export type Price = {
|
||||
|
||||
Reference in New Issue
Block a user