mirror of
https://github.com/coaidev/coai.git
synced 2025-05-29 01:40:17 +09:00
feat: support buy link (#54)
This commit is contained in:
parent
2add5714cd
commit
71e26ad722
@ -4,6 +4,7 @@ import {
|
||||
setAppLogo,
|
||||
setAppName,
|
||||
setBlobEndpoint,
|
||||
setBuyLink,
|
||||
setDocsUrl,
|
||||
} from "@/conf/env.ts";
|
||||
|
||||
@ -13,6 +14,7 @@ export type SiteInfo = {
|
||||
docs: string;
|
||||
file: string;
|
||||
announcement: string;
|
||||
buy_link: string;
|
||||
};
|
||||
|
||||
export async function getSiteInfo(): Promise<SiteInfo> {
|
||||
@ -21,7 +23,14 @@ export async function getSiteInfo(): Promise<SiteInfo> {
|
||||
return response.data as SiteInfo;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return { title: "", logo: "", docs: "", file: "", announcement: "" };
|
||||
return {
|
||||
title: "",
|
||||
logo: "",
|
||||
docs: "",
|
||||
file: "",
|
||||
announcement: "",
|
||||
buy_link: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,5 +41,6 @@ export function syncSiteInfo() {
|
||||
setDocsUrl(info.docs);
|
||||
setBlobEndpoint(info.file);
|
||||
setAnnouncement(info.announcement);
|
||||
setBuyLink(info.buy_link);
|
||||
});
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ export type SearchState = {
|
||||
|
||||
export type SiteState = {
|
||||
quota: number;
|
||||
buy_link: string;
|
||||
announcement: string;
|
||||
};
|
||||
|
||||
@ -104,6 +105,7 @@ export const initialSystemState: SystemProps = {
|
||||
},
|
||||
site: {
|
||||
quota: 0,
|
||||
buy_link: "",
|
||||
announcement: "",
|
||||
},
|
||||
mail: {
|
||||
|
@ -114,10 +114,12 @@
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
margin-right: 0.5rem;
|
||||
user-select: none;
|
||||
|
||||
svg {
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ strong {
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y;
|
||||
outline: 0;
|
||||
outline: none;
|
||||
|
||||
@media (max-width: 520px) {
|
||||
& {
|
||||
@ -185,4 +185,5 @@ strong {
|
||||
margin-left: 0.2rem;
|
||||
scale: 0.9;
|
||||
color: hsl(var(--text-secondary));
|
||||
outline: none !important;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ function Tips({ content, children, className, hideTimeout }: TipsProps) {
|
||||
|
||||
return (
|
||||
<DropdownMenu open={drop} onOpenChange={setDrop}>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger className={`select-none outline-none`}>
|
||||
<TooltipProvider>
|
||||
<Tooltip open={tooltip} onOpenChange={setTooltip}>
|
||||
<TooltipTrigger asChild>
|
||||
|
@ -18,6 +18,8 @@ export let docsEndpoint =
|
||||
localStorage.getItem("docs_url") ||
|
||||
import.meta.env.VITE_DOCS_ENDPOINT ||
|
||||
"https://docs.chatnio.net";
|
||||
export let buyLink =
|
||||
localStorage.getItem("buy_link") || import.meta.env.VITE_BUY_LINK || "";
|
||||
|
||||
export const useDeeptrain = !!import.meta.env.VITE_USE_DEEPTRAIN;
|
||||
export const backendEndpoint = import.meta.env.VITE_BACKEND_ENDPOINT || "/api";
|
||||
@ -117,3 +119,12 @@ export function setAnnouncement(announcement: string): void {
|
||||
|
||||
announcementEvent.emit(announcement);
|
||||
}
|
||||
|
||||
export function setBuyLink(link: string): void {
|
||||
/**
|
||||
* set the buy link in localStorage
|
||||
*/
|
||||
link = link.trim() || "";
|
||||
setMemory("buy_link", link);
|
||||
buyLink = link;
|
||||
}
|
||||
|
@ -39,7 +39,12 @@ import { useToast } from "@/components/ui/use-toast.ts";
|
||||
import { useEffectAsync } from "@/utils/hook.ts";
|
||||
import { selectAuthenticated } from "@/store/auth.ts";
|
||||
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||
import { deeptrainEndpoint, docsEndpoint, useDeeptrain } from "@/conf/env.ts";
|
||||
import {
|
||||
buyLink,
|
||||
deeptrainEndpoint,
|
||||
docsEndpoint,
|
||||
useDeeptrain,
|
||||
} from "@/conf/env.ts";
|
||||
import { useRedeem } from "@/api/redeem.ts";
|
||||
import { cn } from "@/components/ui/lib/utils.ts";
|
||||
import { subscriptionDataSelector } from "@/store/globals.ts";
|
||||
@ -213,7 +218,7 @@ function QuotaDialog() {
|
||||
<Button
|
||||
variant={`default`}
|
||||
className={`buy-button`}
|
||||
disabled={amount === 0 || !useDeeptrain}
|
||||
disabled={amount === 0}
|
||||
>
|
||||
<Plus className={`h-4 w-4 mr-2`} />
|
||||
{t("buy.buy", { amount })}
|
||||
@ -234,6 +239,13 @@ function QuotaDialog() {
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
if (!useDeeptrain) {
|
||||
window.open(
|
||||
`${buyLink}?quota=${amount}`,
|
||||
"_blank",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const res = await buyQuota(amount);
|
||||
if (res.status) {
|
||||
toast({
|
||||
@ -314,7 +326,17 @@ function QuotaDialog() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`tip`}>
|
||||
<div
|
||||
className={`tip flex-row items-center justify-center mt-8 mb-4`}
|
||||
>
|
||||
{buyLink && buyLink.length > 0 && (
|
||||
<Button asChild>
|
||||
<a href={buyLink} target={`_blank`}>
|
||||
<ExternalLink className={`h-4 w-4 mr-2`} />
|
||||
{t("buy.buy-link")}
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={`outline`} asChild>
|
||||
<a href={docsEndpoint} target={`_blank`}>
|
||||
<ExternalLink className={`h-4 w-4 mr-2`} />
|
||||
|
@ -147,6 +147,7 @@
|
||||
"input": "输入",
|
||||
"output": "输出",
|
||||
"learn-more": "了解更多",
|
||||
"buy-link": "前往购买",
|
||||
"dialog-title": "购买点数",
|
||||
"dialog-desc": "您确定要购买 {{amount}} 点数吗?",
|
||||
"dialog-cancel": "取消",
|
||||
@ -566,6 +567,8 @@
|
||||
"searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能(速度较慢)。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。",
|
||||
"quota": "用户初始点数",
|
||||
"quotaTip": "用户注册后赠送的点数",
|
||||
"buyLink": "购买链接",
|
||||
"buyLinkPlaceholder": "请输入卡密的购买链接,留空不显示购买按钮",
|
||||
"announcement": "站点公告",
|
||||
"announcementPlaceholder": "请输入站点公告 (支持 Markdown / HTML 格式)"
|
||||
},
|
||||
|
@ -115,7 +115,8 @@
|
||||
"exchange-success": "Redeem Successfully",
|
||||
"exchange-success-prompt": "You have successfully redeemed {{amount}} credits.",
|
||||
"exchange-failed": "Failed",
|
||||
"exchange-failed-prompt": "Redemption failed for {{reason}}"
|
||||
"exchange-failed-prompt": "Redemption failed for {{reason}}",
|
||||
"buy-link": "Go to the deal"
|
||||
},
|
||||
"pkg": {
|
||||
"title": "Packages",
|
||||
@ -453,7 +454,9 @@
|
||||
"mailWhitelist": "Domain Suffix Whitelist",
|
||||
"mailWhitelistSelected": "{{length}} domain email selected",
|
||||
"mailWhitelistSearchPlaceholder": "Search Domain Suffixes",
|
||||
"customWhitelistPlaceholder": "Please enter a list of custom domain suffixes (which will appear in the list of options to choose from), separated by commas, e.g.: example.com, example.net"
|
||||
"customWhitelistPlaceholder": "Please enter a list of custom domain suffixes (which will appear in the list of options to choose from), separated by commas, e.g.: example.com, example.net",
|
||||
"buyLink": "Buy Link",
|
||||
"buyLinkPlaceholder": "Please enter the card secret purchase link, leave blank to not show the purchase button"
|
||||
},
|
||||
"user": "User Management",
|
||||
"invitation-code": "Invitation Code",
|
||||
|
@ -115,7 +115,8 @@
|
||||
"exchange-success": "交換成功",
|
||||
"exchange-success-prompt": "{{amount}}クレジットを正常に引き換えました。",
|
||||
"exchange-failed": "引き換えに失敗しました",
|
||||
"exchange-failed-prompt": "{{reason}}のため、引き換えに失敗しました"
|
||||
"exchange-failed-prompt": "{{reason}}のため、引き換えに失敗しました",
|
||||
"buy-link": "購入しに行く"
|
||||
},
|
||||
"pkg": {
|
||||
"title": "パック",
|
||||
@ -453,7 +454,9 @@
|
||||
"mailWhitelist": "ドメインサフィックスホワイトリスト",
|
||||
"mailWhitelistSelected": "{{length}}ドメインメールが選択されました",
|
||||
"mailWhitelistSearchPlaceholder": "ドメイン接尾辞を検索",
|
||||
"customWhitelistPlaceholder": "カスタムドメインサフィックスのリスト(選択するオプションのリストに表示されます)をカンマで区切って入力してください。例: example.com、example.net"
|
||||
"customWhitelistPlaceholder": "カスタムドメインサフィックスのリスト(選択するオプションのリストに表示されます)をカンマで区切って入力してください。例: example.com、example.net",
|
||||
"buyLink": "購入リンク",
|
||||
"buyLinkPlaceholder": "カードシークレット購入リンクを入力してください。購入ボタンを表示しない場合は空白のままにしてください"
|
||||
},
|
||||
"user": "ユーザー管理",
|
||||
"invitation-code": "招待コード",
|
||||
|
@ -115,7 +115,8 @@
|
||||
"exchange-success": "Успешный обмен",
|
||||
"exchange-success-prompt": "Вы успешно использовали {{amount}} кредита (-ов).",
|
||||
"exchange-failed": "Сбой обмена",
|
||||
"exchange-failed-prompt": "Не удалось погасить по {{reason}}"
|
||||
"exchange-failed-prompt": "Не удалось погасить по {{reason}}",
|
||||
"buy-link": "Перейти к покупке"
|
||||
},
|
||||
"pkg": {
|
||||
"title": "Пакеты",
|
||||
@ -453,7 +454,9 @@
|
||||
"mailWhitelist": "Белый список суффиксов доменов",
|
||||
"mailWhitelistSelected": "Выбрано адрес эл. почты домена {{length}}",
|
||||
"mailWhitelistSearchPlaceholder": "Поиск суффиксов доменов",
|
||||
"customWhitelistPlaceholder": "Введите список пользовательских суффиксов домена (которые появятся в списке опций на выбор), разделенных запятыми, например: example.com, example.net"
|
||||
"customWhitelistPlaceholder": "Введите список пользовательских суффиксов домена (которые появятся в списке опций на выбор), разделенных запятыми, например: example.com, example.net",
|
||||
"buyLink": "Ссылка на покупку",
|
||||
"buyLinkPlaceholder": "Введите ссылку на секретную покупку карты, оставьте поле пустым, чтобы не показывать кнопку покупки"
|
||||
},
|
||||
"user": "Управление пользователями",
|
||||
"invitation-code": "Код приглашения",
|
||||
|
@ -439,11 +439,6 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
||||
function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// export type SiteState = {
|
||||
// quota: number;
|
||||
// announcement: string;
|
||||
// }
|
||||
|
||||
return (
|
||||
<Paragraph
|
||||
title={t("admin.system.site")}
|
||||
@ -467,6 +462,19 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
|
||||
min={0}
|
||||
/>
|
||||
</ParagraphItem>
|
||||
<ParagraphItem>
|
||||
<Label>{t("admin.system.buyLink")}</Label>
|
||||
<Input
|
||||
value={data.buy_link}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "update:site.buy_link",
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder={t("admin.system.buyLinkPlaceholder")}
|
||||
/>
|
||||
</ParagraphItem>
|
||||
<ParagraphItem rowLayout={true}>
|
||||
<Label>{t("admin.system.announcement")}</Label>
|
||||
<Textarea
|
||||
|
@ -14,6 +14,7 @@ type ApiInfo struct {
|
||||
File string `json:"file"`
|
||||
Docs string `json:"docs"`
|
||||
Announcement string `json:"announcement"`
|
||||
BuyLink string `json:"buy_link"`
|
||||
}
|
||||
|
||||
type generalState struct {
|
||||
@ -26,6 +27,7 @@ type generalState struct {
|
||||
|
||||
type siteState struct {
|
||||
Quota float64 `json:"quota" mapstructure:"quota"`
|
||||
BuyLink string `json:"buy_link" mapstructure:"buylink"`
|
||||
Announcement string `json:"announcement" mapstructure:"announcement"`
|
||||
}
|
||||
|
||||
@ -84,6 +86,7 @@ func (c *SystemConfig) AsInfo() ApiInfo {
|
||||
File: c.General.File,
|
||||
Docs: c.General.Docs,
|
||||
Announcement: c.Site.Announcement,
|
||||
BuyLink: c.Site.BuyLink,
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user