feat: support buy link (#54)

This commit is contained in:
Zhang Minghan 2024-01-23 11:32:35 +08:00
parent 2add5714cd
commit 71e26ad722
13 changed files with 88 additions and 17 deletions

View File

@ -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);
});
}

View File

@ -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: {

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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`} />

View File

@ -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 格式)"
},

View File

@ -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",

View File

@ -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": "招待コード",

View File

@ -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": "Код приглашения",

View File

@ -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

View File

@ -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,
}
}