Merge branch 'feat/new-workflow-ui' of github.com:fudiwei/certimate into fudiwei-feat/new-workflow-ui

This commit is contained in:
yoan 2024-12-11 21:26:42 +08:00
commit a6c002146c
196 changed files with 4043 additions and 3368 deletions

View File

@ -11,7 +11,10 @@ import (
)
type BarkNotifierConfig struct {
// Bark 服务地址。
// 零值时默认使用官方服务器。
ServerUrl string `json:"serverUrl"`
// Bark 设备密钥。
DeviceKey string `json:"deviceKey"`
}

View File

@ -10,8 +10,10 @@ import (
)
type DingTalkNotifierConfig struct {
// 钉钉机器人的 Token。
AccessToken string `json:"accessToken"`
Secret string `json:"secret"`
// 钉钉机器人的 Secret。
Secret string `json:"secret"`
}
type DingTalkNotifier struct {

View File

@ -13,12 +13,20 @@ import (
)
type EmailNotifierConfig struct {
SmtpHost string `json:"smtpHost"`
SmtpPort int32 `json:"smtpPort"`
SmtpTLS bool `json:"smtpTLS"`
Username string `json:"username"`
Password string `json:"password"`
SenderAddress string `json:"senderAddress"`
// SMTP 服务器地址。
SmtpHost string `json:"smtpHost"`
// SMTP 服务器端口。
// 零值时根据是否启用 TLS 决定。
SmtpPort int32 `json:"smtpPort"`
// 是否启用 TLS。
SmtpTLS bool `json:"smtpTLS"`
// 用户名。
Username string `json:"username"`
// 密码。
Password string `json:"password"`
// 发件人邮箱。
SenderAddress string `json:"senderAddress"`
// 收件人邮箱。
ReceiverAddress string `json:"receiverAddress"`
}

View File

@ -10,6 +10,7 @@ import (
)
type LarkNotifierConfig struct {
// 飞书 Webhook 地址。
WebhookUrl string `json:"webhookUrl"`
}

View File

@ -11,6 +11,7 @@ import (
)
type ServerChanNotifierConfig struct {
// ServerChan 服务地址。
Url string `json:"url"`
}

View File

@ -10,8 +10,10 @@ import (
)
type TelegramNotifierConfig struct {
// Telegram API Token。
ApiToken string `json:"apiToken"`
ChatId int64 `json:"chatId"`
// Telegram Chat ID。
ChatId int64 `json:"chatId"`
}
type TelegramNotifier struct {

View File

@ -10,6 +10,7 @@ import (
)
type WebhookNotifierConfig struct {
// Webhook URL。
Url string `json:"url"`
}

View File

@ -17,9 +17,12 @@ import (
)
type AliyunCASUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
Region string `json:"region"`
// 阿里云地域。
Region string `json:"region"`
}
type AliyunCASUploader struct {

View File

@ -20,9 +20,12 @@ import (
)
type AliyunSLBUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 阿里云 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
Region string `json:"region"`
// 阿里云地域。
Region string `json:"region"`
}
type AliyunSLBUploader struct {

View File

@ -19,7 +19,9 @@ import (
)
type ByteplusCDNUploaderConfig struct {
// BytePlus AccessKey。
AccessKey string `json:"accessKey"`
// BytePlus SecretKey。
SecretKey string `json:"secretKey"`
}

View File

@ -13,7 +13,9 @@ import (
)
type DogeCloudUploaderConfig struct {
// 多吉云 AccessKey。
AccessKey string `json:"accessKey"`
// 多吉云 SecretKey。
SecretKey string `json:"secretKey"`
}

View File

@ -22,9 +22,12 @@ import (
)
type HuaweiCloudELBUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
// 华为云地域。
Region string `json:"region"`
}
type HuaweiCloudELBUploader struct {

View File

@ -18,9 +18,12 @@ import (
)
type HuaweiCloudSCMUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
// 华为云地域。
Region string `json:"region"`
}
type HuaweiCloudSCMUploader struct {

View File

@ -15,7 +15,9 @@ import (
)
type QiniuSSLCertUploaderConfig struct {
// 七牛云 AccessKey。
AccessKey string `json:"accessKey"`
// 七牛云 SecretKey。
SecretKey string `json:"secretKey"`
}

View File

@ -13,7 +13,9 @@ import (
)
type TencentCloudSSLUploaderConfig struct {
SecretId string `json:"secretId"`
// 腾讯云 SecretId。
SecretId string `json:"secretId"`
// 腾讯云 SecretKey。
SecretKey string `json:"secretKey"`
}

View File

@ -19,7 +19,9 @@ import (
)
type VolcEngineCDNUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
}

View File

@ -16,7 +16,9 @@ import (
)
type VolcEngineLiveUploaderConfig struct {
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 火山引擎 AccessKeySecret。
AccessKeySecret string `json:"accessKeySecret"`
}

View File

@ -3,6 +3,7 @@
import (
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core"
hcCdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
hcCdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
)
type Client struct {
@ -21,6 +22,7 @@ func (c *Client) UploadDomainMultiCertificatesEx(request *UpdateDomainMultiCerti
if resp, err := c.HcClient.Sync(request, requestDef); err != nil {
return nil, err
} else {
return resp.(*UpdateDomainMultiCertificatesExResponse), nil
temp := resp.(*hcCdnModel.UpdateDomainMultiCertificatesResponse)
return &UpdateDomainMultiCertificatesExResponse{UpdateDomainMultiCertificatesResponse: *temp}, nil
}
}

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title>
</head>

1673
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,16 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@ant-design/pro-components": "^2.8.2",
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
@ -30,6 +28,9 @@
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-table": "^8.20.5",
"ahooks": "^3.8.4",
"antd": "^5.22.2",
"antd-zod": "^6.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
@ -40,17 +41,16 @@
"immer": "^10.1.1",
"jszip": "^3.10.1",
"lucide-react": "^0.417.0",
"moment": "^2.30.1",
"nanoid": "^5.0.7",
"pocketbase": "^0.21.4",
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.1",
"react-i18next": "^15.0.2",
"react-router-dom": "^6.25.1",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1",
"zod": "^3.23.8",
"zustand": "^5.0.1"
},
@ -58,6 +58,7 @@
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.0.0",
"@types/react": "^18.3.3",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",

2
ui/public/logo.svg Normal file
View File

@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.92591302712353 216.92591302712353" height="216.92591302712353" width="216.92591302712353"><g><svg /></g><g id="icon-0"><svg viewBox="0 0 216.92591302712353 216.92591302712353" height="216.92591302712353" width="216.92591302712353"><g><path d="M0 108.463c0-59.902 48.561-108.463 108.463-108.463 59.902 0 108.463 48.561 108.463 108.463 0 59.902-48.561 108.463-108.463 108.463-59.902 0-108.463-48.561-108.463-108.463zM108.463 211.779c57.06 0 103.316-46.256 103.316-103.316 0-57.06-46.256-103.316-103.316-103.316-57.06 0-103.316 46.256-103.316 103.316 0 57.06 46.256 103.316 103.316 103.316z" data-fill-palette-color="accent" fill="#f97317" stroke="transparent" /><ellipse rx="107.37832694842615" ry="107.37832694842615" cx="108.46295651356176" cy="108.46295651356176" fill="#f97317" stroke="transparent" stroke-width="0" fill-opacity="1" data-fill-palette-color="accent" /></g><g transform="matrix(1,0,0,1,64.03903745467258,44.049576960135155)"><svg viewBox="0 0 88.84783811777835 128.82675910685322" height="128.82675910685322" width="88.84783811777835"><g><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0" y="0" viewBox="4.99999928747888 13.445 56.29700142504224 81.6290007125211"
enable-background="new 0 0 100 100" xml:space="preserve" height="128.82675910685322" width="88.84783811777835" class="icon-dxe-0" data-fill-palette-color="quaternary" id="dxe-0"><path d="M58.482 47.222H55.667V35.963C55.667 23.527 45.587 13.445 33.148 13.445S10.63 23.528 10.63 35.963V47.222H7.815A2.813 2.813 0 0 0 5 50.037V92.259A2.813 2.813 0 0 0 7.815 95.074H58.482A2.813 2.813 0 0 0 61.297 92.259V50.037A2.815 2.815 0 0 0 58.482 47.222M35.963 72.039V86.63H30.333V72.039C27.062 70.876 24.703 67.784 24.703 64.111A8.445 8.445 0 0 1 41.591 64.111C41.593 67.784 39.234 70.876 35.963 72.039M47.222 47.222H19.074V35.963C19.074 28.203 25.388 21.889 33.148 21.889S47.222 28.203 47.222 35.963z" fill="#ffffff" data-fill-palette-color="quaternary" /></svg></g></svg></g></svg></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,28 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.92591302712353 216.92591302712353" height="216.92591302712353"
width="216.92591302712353">
<g><svg /></g>
<g id="icon-0"><svg viewBox="0 0 216.92591302712353 216.92591302712353" height="216.92591302712353"
width="216.92591302712353">
<g>
<path
d="M0 108.463c0-59.902 48.561-108.463 108.463-108.463 59.902 0 108.463 48.561 108.463 108.463 0 59.902-48.561 108.463-108.463 108.463-59.902 0-108.463-48.561-108.463-108.463zM108.463 211.779c57.06 0 103.316-46.256 103.316-103.316 0-57.06-46.256-103.316-103.316-103.316-57.06 0-103.316 46.256-103.316 103.316 0 57.06 46.256 103.316 103.316 103.316z"
data-fill-palette-color="accent" fill="#f97317" stroke="transparent" />
<ellipse rx="107.37832694842615" ry="107.37832694842615" cx="108.46295651356176" cy="108.46295651356176"
fill="#f97317" stroke="transparent" stroke-width="0" fill-opacity="1"
data-fill-palette-color="accent" />
</g>
<g transform="matrix(1,0,0,1,64.03903745467258,44.049576960135155)"><svg
viewBox="0 0 88.84783811777835 128.82675910685322" height="128.82675910685322"
width="88.84783811777835">
<g><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
x="0" y="0" viewBox="4.99999928747888 13.445 56.29700142504224 81.6290007125211"
enable-background="new 0 0 100 100" xml:space="preserve" height="128.82675910685322"
width="88.84783811777835" class="icon-dxe-0" data-fill-palette-color="quaternary"
id="dxe-0">
<path
d="M58.482 47.222H55.667V35.963C55.667 23.527 45.587 13.445 33.148 13.445S10.63 23.528 10.63 35.963V47.222H7.815A2.813 2.813 0 0 0 5 50.037V92.259A2.813 2.813 0 0 0 7.815 95.074H58.482A2.813 2.813 0 0 0 61.297 92.259V50.037A2.815 2.815 0 0 0 58.482 47.222M35.963 72.039V86.63H30.333V72.039C27.062 70.876 24.703 67.784 24.703 64.111A8.445 8.445 0 0 1 41.591 64.111C41.593 67.784 39.234 70.876 35.963 72.039M47.222 47.222H19.074V35.963C19.074 28.203 25.388 21.889 33.148 21.889S47.222 28.203 47.222 35.963z"
fill="#ffffff" data-fill-palette-color="quaternary" />
</svg></g>
</svg></g>
</svg></g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

62
ui/src/App.tsx Normal file
View File

@ -0,0 +1,62 @@
import { useEffect, useLayoutEffect, useState } from "react";
import { RouterProvider } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { App, ConfigProvider, theme, type ThemeConfig } from "antd";
import { type Locale } from "antd/es/locale";
import AntdLocaleEnUs from "antd/locale/en_US";
import AntdLocaleZhCN from "antd/locale/zh_CN";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import { localeNames } from "./i18n";
import { useTheme } from "./hooks";
import { router } from "./router.tsx";
const RootApp = () => {
const { i18n } = useTranslation();
const { theme: browserTheme } = useTheme();
const antdLocalesMap: Record<string, Locale> = {
[localeNames.ZH]: AntdLocaleZhCN,
[localeNames.EN]: AntdLocaleEnUs,
};
const [antdLocale, setAntdLocale] = useState(antdLocalesMap[i18n.language]);
const handleLanguageChanged = () => {
setAntdLocale(antdLocalesMap[i18n.language]);
dayjs.locale(i18n.language);
};
i18n.on("languageChanged", handleLanguageChanged);
useLayoutEffect(handleLanguageChanged, [i18n]);
const antdThemesMap: Record<string, ThemeConfig> = {
["light"]: { algorithm: theme.defaultAlgorithm },
["dark"]: { algorithm: theme.darkAlgorithm },
};
const [antdTheme, setAntdTheme] = useState(antdThemesMap[browserTheme]);
useEffect(() => {
setAntdTheme(antdThemesMap[browserTheme]);
const root = window.document.documentElement;
root.classList.remove("light", "dark");
root.classList.add(browserTheme);
}, [browserTheme]);
return (
<ConfigProvider
locale={antdLocale}
theme={{
...antdTheme,
token: {
colorPrimary: "hsl(24.6 95% 53.1%)",
},
}}
>
<App>
<RouterProvider router={router} />
</App>
</ConfigProvider>
);
};
export default RootApp;

View File

@ -1,7 +1,7 @@
import { getPb } from "@/repository/api";
import { getPocketBase } from "@/repository/pocketbase";
export const notifyTest = async (channel: string) => {
const pb = getPb();
const pb = getPocketBase();
const resp = await pb.send("/api/notify/test", {
method: "POST",

View File

@ -1,15 +1,16 @@
import { getPb } from "@/repository/api";
import { Statistics } from "@/domain/statistics";
import { getPocketBase } from "@/repository/pocketbase";
export const get = async () => {
const pb = getPb();
const pb = getPocketBase();
const resp = await pb.send("/api/statistics/get", {
method: "GET",
});
if (resp.code != 0) {
if (resp.code !== 0) {
throw new Error(resp.msg);
}
return resp.data;
return resp.data as Statistics;
};

View File

@ -1,7 +1,7 @@
import { getPb } from "@/repository/api";
import { getPocketBase } from "@/repository/pocketbase";
export const run = async (id: string) => {
const pb = getPb();
const pb = getPocketBase();
const resp = await pb.send("/api/workflow/run", {
method: "POST",

View File

@ -1,25 +0,0 @@
import { useTranslation } from "react-i18next";
import { Languages } from "lucide-react";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
export default function LocaleToggle() {
const { i18n } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Languages className="h-[1.2rem] w-[1.2rem] dark:text-white" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{Object.keys(i18n.store.data).map((key) => (
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>{i18n.store.data[key].name as string}</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -1,62 +0,0 @@
import { createContext, useContext, useEffect, useState } from "react";
type Theme = "dark" | "light" | "system";
type ThemeProviderProps = {
children: React.ReactNode;
defaultTheme?: Theme;
storageKey?: string;
};
type ThemeProviderState = {
theme: Theme;
setTheme: (theme: Theme) => void;
};
const initialState: ThemeProviderState = {
theme: "system",
setTheme: () => null,
};
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
export function ThemeProvider({ children, defaultTheme = "system", storageKey = "vite-ui-theme", ...props }: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme);
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove("light", "dark");
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
root.classList.add(systemTheme);
return;
}
root.classList.add(theme);
}, [theme]);
const value = {
theme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
};
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
);
}
export const useTheme = () => {
const context = useContext(ThemeProviderContext);
if (context === undefined) throw new Error("useTheme must be used within a ThemeProvider");
return context;
};

View File

@ -1,28 +0,0 @@
import { useTranslation } from "react-i18next";
import { Moon, Sun } from "lucide-react";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { useTheme } from "./ThemeProvider";
export function ThemeToggle() {
const { setTheme } = useTheme();
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100 dark:text-white" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>{t("common.theme.light")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>{t("common.theme.dark")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>{t("common.theme.system")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -1,64 +1,114 @@
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "../ui/sheet";
import { Certificate } from "@/domain/certificate";
import { Textarea } from "../ui/textarea";
import { Button } from "../ui/button";
import { Label } from "../ui/label";
import { CustomFile, saveFiles2ZIP } from "@/lib/file";
import { useTranslation } from "react-i18next";
import { Button, Dropdown, Form, Input, message, Space, Tooltip } from "antd";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { ChevronDown as ChevronDownIcon, Clipboard as ClipboardIcon, ThumbsUp as ThumbsUpIcon } from "lucide-react";
type WorkflowLogDetailProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
certificate?: Certificate;
import { type CertificateModel } from "@/domain/certificate";
import { saveFiles2Zip } from "@/utils/file";
type CertificateDetailProps = {
data: CertificateModel;
};
const CertificateDetail = ({ open, onOpenChange, certificate }: WorkflowLogDetailProps) => {
const CertificateDetail = ({ data }: CertificateDetailProps) => {
const { t } = useTranslation();
const handleDownloadClick = async () => {
const zipName = `${certificate?.id}-${certificate?.san}.zip`;
const files: CustomFile[] = [
const [messageApi, MessageContextHolder] = message.useMessage();
const handleDownloadPEMClick = async () => {
const zipName = `${data.id}-${data.san}.zip`;
const files = [
{
name: `${certificate?.san}.pem`,
content: certificate?.certificate ? certificate?.certificate : "",
name: `${data.san}.pem`,
content: data.certificate ?? "",
},
{
name: `${certificate?.san}.key`,
content: certificate?.privateKey ? certificate?.privateKey : "",
name: `${data.san}.key`,
content: data.privateKey ?? "",
},
];
await saveFiles2ZIP(zipName, files);
await saveFiles2Zip(zipName, files);
};
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="sm:max-w-2xl dark:text-stone-200">
<SheetHeader>
<SheetTitle></SheetTitle>
</SheetHeader>
<div>
{MessageContextHolder}
<div className="flex flex-col space-y-5 mt-9">
<div className="flex justify-end">
<Button
size={"sm"}
onClick={() => {
handleDownloadClick();
}}
>
{t("certificate.action.download")}
</Button>
<Form layout="vertical">
<Form.Item>
<div className="flex items-center justify-between w-full mb-2">
<label className="font-medium">{t("certificate.props.certificate_chain")}</label>
<Tooltip title={t("common.button.copy")}>
<CopyToClipboard
text={data.certificate}
onCopy={() => {
messageApi.success(t("common.text.copied"));
}}
>
<Button type="text" icon={<ClipboardIcon size={14} />}></Button>
</CopyToClipboard>
</Tooltip>
</div>
<div className="flex flex-col space-y-3">
<Label>{t("certificate.props.certificate")}</Label>
<Textarea value={certificate?.certificate} rows={10} readOnly={true} />
<Input.TextArea value={data.certificate} rows={10} autoSize={{ maxRows: 10 }} readOnly />
</Form.Item>
<Form.Item>
<div className="flex items-center justify-between w-full mb-2">
<label className="font-medium">{t("certificate.props.private_key")}</label>
<Tooltip title={t("common.button.copy")}>
<CopyToClipboard
text={data.privateKey}
onCopy={() => {
messageApi.success(t("common.text.copied"));
}}
>
<Button type="text" icon={<ClipboardIcon size={14} />}></Button>
</CopyToClipboard>
</Tooltip>
</div>
<div className="flex flex-col space-y-3">
<Label>{t("certificate.props.private.key")}</Label>
<Textarea value={certificate?.privateKey} rows={10} readOnly={true} />
</div>
</div>
</SheetContent>
</Sheet>
<Input.TextArea value={data.privateKey} rows={10} autoSize={{ maxRows: 10 }} readOnly />
</Form.Item>
</Form>
<div className="flex items-center justify-end">
<Dropdown
menu={{
items: [
{
key: "PEM",
label: "PEM",
extra: <ThumbsUpIcon size="14" />,
onClick: () => handleDownloadPEMClick(),
},
{
key: "PFX",
label: "PFX",
onClick: () => {
// TODO: 下载 PFX 格式证书
alert("TODO");
},
},
{
key: "JKS",
label: "JKS",
onClick: () => {
// TODO: 下载 JKS 格式证书
alert("TODO");
},
},
],
}}
>
<Button type="primary">
<Space>
<span>{t("certificate.action.download")}</span>
<ChevronDownIcon size={14} />
</Space>
</Button>
</Dropdown>
</div>
</div>
);
};

View File

@ -0,0 +1,26 @@
import { useEffect, useState } from "react";
import { Drawer } from "antd";
import { type CertificateModel } from "@/domain/certificate";
import CertificateDetail from "./CertificateDetail";
type CertificateDetailDrawerProps = {
data?: CertificateModel;
open?: boolean;
onClose?: () => void;
};
const CertificateDetailDrawer = ({ data, open, onClose }: CertificateDetailDrawerProps) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(data == null);
}, [data]);
return (
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" width={480} onClose={onClose}>
{data ? <CertificateDetail data={data} /> : <></>}
</Drawer>
);
};
export default CertificateDetailDrawer;

View File

@ -1,171 +0,0 @@
import CertificateDetail from "@/components/certificate/CertificateDetail";
import { Button } from "@/components/ui/button";
import { DataTable } from "@/components/workflow/DataTable";
import { Certificate as CertificateType } from "@/domain/certificate";
import { diffDays, getLeftDays } from "@/lib/time";
import { list } from "@/repository/certificate";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom";
type CertificateListProps = {
withPagination?: boolean;
};
const CertificateList = ({ withPagination }: CertificateListProps) => {
const [data, setData] = useState<CertificateType[]>([]);
const [pageCount, setPageCount] = useState<number>(0);
const [open, setOpen] = useState(false);
const [selectedCertificate, setSelectedCertificate] = useState<CertificateType>();
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const fetchData = async (page: number, pageSize?: number) => {
const state = searchParams.get("state");
const resp = await list({ page: page, perPage: pageSize, state: state ?? "" });
setData(resp.items);
setPageCount(resp.totalPages);
};
const navigate = useNavigate();
const columns: ColumnDef<CertificateType>[] = [
{
accessorKey: "san",
header: t("certificate.props.domain"),
cell: ({ row }) => {
let san: string = row.getValue("san");
if (!san) {
san = "";
}
return (
<div>
{san.split(";").map((item, i) => {
return (
<div key={i} className="max-w-[250px] truncate">
{item}
</div>
);
})}
</div>
);
},
},
{
accessorKey: "expireAt",
header: t("certificate.props.expiry"),
cell: ({ row }) => {
const expireAt: string = row.getValue("expireAt");
const data = row.original;
const leftDays = getLeftDays(expireAt);
const allDays = diffDays(data.expireAt, data.created);
return (
<div className="">
{leftDays > 0 ? (
<div className="text-green-500">
{leftDays} / {allDays} {t("certificate.props.expiry.days")}
</div>
) : (
<div className="text-red-500">{t("certificate.props.expiry.expired")}</div>
)}
<div>
{new Date(expireAt).toLocaleString().split(" ")[0]} {t("certificate.props.expiry.text.expire")}
</div>
</div>
);
},
},
{
accessorKey: "workflow",
header: t("certificate.props.workflow"),
cell: ({ row }) => {
const name = row.original.expand.workflow?.name;
const workflowId: string = row.getValue("workflow");
return (
<div className="max-w-[200px] truncate">
<Button
size={"sm"}
variant={"link"}
onClick={() => {
handleWorkflowClick(workflowId);
}}
>
{name}
</Button>
</div>
);
},
},
{
accessorKey: "created",
header: t("certificate.props.created"),
cell: ({ row }) => {
const date: string = row.getValue("created");
return new Date(date).toLocaleString();
},
},
{
id: "actions",
cell: ({ row }) => {
return (
<div className="flex items-center space-x-2">
<Button
size="sm"
variant={"link"}
onClick={() => {
handleView(row.original.id);
}}
>
{t("certificate.action.view")}
</Button>
</div>
);
},
},
];
const handleWorkflowClick = (id: string) => {
navigate(`/workflow/detail?id=${id}`);
};
const handleView = (id: string) => {
setOpen(true);
const certificate = data.find((item) => item.id === id);
setSelectedCertificate(certificate);
};
return (
<>
<DataTable
columns={columns}
onPageChange={fetchData}
data={data}
pageCount={pageCount}
withPagination={withPagination}
fallback={
<div className="flex flex-col items-center">
<div className="text-muted-foreground">{t("certificate.nodata")}</div>
<Button
size={"sm"}
className="w-[120px] mt-3"
onClick={() => {
navigate("/workflow/detail");
}}
>
{t("workflow.action.create")}
</Button>
</div>
}
/>
<CertificateDetail open={open} onOpenChange={setOpen} certificate={selectedCertificate} />
</>
);
};
export default CertificateList;

View File

@ -8,19 +8,21 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type AliyunConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type AliyunConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessAliyunFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const { createAccess, updateAccess } = useAccessStore();
const formSchema = z.object({
id: z.string().optional(),
name: z
@ -56,7 +58,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -81,7 +83,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -184,7 +186,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,19 +8,21 @@ import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { Access, accessProvidersMap, accessTypeFormSchema, type AwsConfig } from "@/domain/access";
import { AccessModel, accessProvidersMap, accessTypeFormSchema, type AwsConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessAwsFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const { createAccess, updateAccess } = useAccessStore();
const formSchema = z.object({
id: z.string().optional(),
name: z
@ -68,7 +70,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -94,7 +96,8 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -227,7 +230,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,19 +8,21 @@ import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type BaiduCloudConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type BaiduCloudConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessBaiduCloudFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const { createAccess, updateAccess } = useAccessStore();
const formSchema = z.object({
id: z.string().optional(),
name: z
@ -56,7 +58,7 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -80,7 +82,8 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -183,7 +186,7 @@ const AccessBaiduCloudForm = ({ data, op, onAfterReq }: AccessBaiduCloudFormProp
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type ByteplusConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type ByteplusConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessByteplusFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -56,7 +56,7 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) =
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -80,7 +80,7 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) =
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -183,7 +183,7 @@ const AccessByteplusForm = ({ data, op, onAfterReq }: AccessByteplusFormProps) =
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type CloudflareConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type CloudflareConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessCloudflareFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -50,7 +50,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -73,7 +73,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -157,7 +157,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type DogeCloudConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type DogeCloudConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessDogeCloudFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -50,7 +50,7 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps)
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -74,7 +74,7 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps)
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -177,7 +177,7 @@ const AccessDogeCloudForm = ({ data, op, onAfterReq }: AccessDogeCloudFormProps)
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { cn } from "@/components/ui/utils";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
@ -23,14 +23,14 @@ import AccessWebhookForm from "./AccessWebhookForm";
import AccessKubernetesForm from "./AccessKubernetesForm";
import AccessVolcengineForm from "./AccessVolcengineForm";
import AccessByteplusForm from "./AccessByteplusForm";
import { Access } from "@/domain/access";
import { AccessModel } from "@/domain/access";
import { AccessTypeSelect } from "./AccessTypeSelect";
type AccessEditProps = {
op: "add" | "edit" | "copy";
className?: string;
trigger: React.ReactNode;
data?: Access;
data?: AccessModel;
outConfigType?: string;
};
@ -273,9 +273,9 @@ const AccessEditDialog = ({ trigger, op, data, className, outConfigType }: Acces
<DialogTitle>
{
{
["add"]: t("access.authorization.add"),
["edit"]: t("access.authorization.edit"),
["copy"]: t("access.authorization.copy"),
["add"]: t("access.action.add"),
["edit"]: t("access.action.edit"),
["copy"]: t("access.action.copy"),
}[op]
}
</DialogTitle>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type GodaddyConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type GodaddyConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessGodaddyFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -56,7 +56,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -80,7 +80,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -179,7 +179,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type HttpreqConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type HttpreqConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessHttpreqFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -62,7 +62,7 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) =>
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -89,7 +89,7 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) =>
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -222,7 +222,7 @@ const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) =>
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type HuaweiCloudConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type HuaweiCloudConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessHuaweiCloudFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -62,7 +62,7 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -87,7 +87,7 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -205,7 +205,7 @@ const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormPr
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,20 +8,20 @@ import { ClientResponseError } from "pocketbase";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { readFileContent } from "@/lib/file";
import { readFileContent } from "@/utils/file";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type KubernetesConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type KubernetesConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessKubernetesFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const fileInputRef = useRef<HTMLInputElement | null>(null);
const [fileName, setFileName] = useState("");
@ -60,7 +60,7 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -82,7 +82,7 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp
if (data.id && op == "edit") {
updateAccess(req);
} else {
addAccess(req);
createAccess(req);
}
} catch (e) {
const err = e as ClientResponseError;
@ -182,7 +182,7 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>
@ -191,4 +191,3 @@ const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProp
};
export default AccessKubernetesForm;

View File

@ -8,18 +8,18 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessLocalFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessLocalForm = ({ data, op, onAfterReq }: AccessLocalFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
@ -41,7 +41,7 @@ const AccessLocalForm = ({ data, op, onAfterReq }: AccessLocalFormProps) => {
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -62,7 +62,7 @@ const AccessLocalForm = ({ data, op, onAfterReq }: AccessLocalFormProps) => {
if (data.id && op == "edit") {
updateAccess(req);
} else {
addAccess(req);
createAccess(req);
}
} catch (e) {
const err = e as ClientResponseError;
@ -136,7 +136,7 @@ const AccessLocalForm = ({ data, op, onAfterReq }: AccessLocalFormProps) => {
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type NamesiloConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type NamesiloConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessNamesiloFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessNamesiloForm = ({ data, op, onAfterReq }: AccessNamesiloFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -50,7 +50,7 @@ const AccessNamesiloForm = ({ data, op, onAfterReq }: AccessNamesiloFormProps) =
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -73,7 +73,7 @@ const AccessNamesiloForm = ({ data, op, onAfterReq }: AccessNamesiloFormProps) =
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -157,7 +157,7 @@ const AccessNamesiloForm = ({ data, op, onAfterReq }: AccessNamesiloFormProps) =
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type PdnsConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type PdnsConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessPdnsFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessPdnsForm = ({ data, op, onAfterReq }: AccessPdnsFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -53,7 +53,7 @@ const AccessPdnsForm = ({ data, op, onAfterReq }: AccessPdnsFormProps) => {
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -78,7 +78,7 @@ const AccessPdnsForm = ({ data, op, onAfterReq }: AccessPdnsFormProps) => {
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -181,7 +181,7 @@ const AccessPdnsForm = ({ data, op, onAfterReq }: AccessPdnsFormProps) => {
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type QiniuConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type QiniuConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessQiniuFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessQiniuForm = ({ data, op, onAfterReq }: AccessQiniuFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -50,7 +50,7 @@ const AccessQiniuForm = ({ data, op, onAfterReq }: AccessQiniuFormProps) => {
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -74,7 +74,7 @@ const AccessQiniuForm = ({ data, op, onAfterReq }: AccessQiniuFormProps) => {
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -177,7 +177,7 @@ const AccessQiniuForm = ({ data, op, onAfterReq }: AccessQiniuFormProps) => {
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,20 +8,20 @@ import { ClientResponseError } from "pocketbase";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { readFileContent } from "@/lib/file";
import { readFileContent } from "@/utils/file";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type SSHConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type SSHConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessSSHFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const fileInputRef = useRef<HTMLInputElement | null>(null);
@ -103,7 +103,7 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
let group = data.group;
if (group == "emptyId") group = "";
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -131,7 +131,7 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
if (data.id && op == "edit") {
updateAccess(req);
} else {
addAccess(req);
createAccess(req);
}
} catch (e) {
const err = e as ClientResponseError;
@ -337,7 +337,7 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type TencentConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type TencentConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessTencentFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessTencentForm = ({ data, op, onAfterReq }: AccessTencentFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -56,7 +56,7 @@ const AccessTencentForm = ({ data, op, onAfterReq }: AccessTencentFormProps) =>
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -80,7 +80,7 @@ const AccessTencentForm = ({ data, op, onAfterReq }: AccessTencentFormProps) =>
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -179,7 +179,7 @@ const AccessTencentForm = ({ data, op, onAfterReq }: AccessTencentFormProps) =>
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -1,6 +1,6 @@
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "@/components/ui/utils";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

View File

@ -8,18 +8,18 @@ import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type VolcengineConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type AccessModel, type VolcengineConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessVolcengineFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessVolcengineForm = ({ data, op, onAfterReq }: AccessVolcengineFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -56,7 +56,7 @@ const AccessVolcengineForm = ({ data, op, onAfterReq }: AccessVolcengineFormProp
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -80,7 +80,7 @@ const AccessVolcengineForm = ({ data, op, onAfterReq }: AccessVolcengineFormProp
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -183,7 +183,7 @@ const AccessVolcengineForm = ({ data, op, onAfterReq }: AccessVolcengineFormProp
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -8,18 +8,18 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { Access, accessProvidersMap, accessTypeFormSchema, WebhookConfig } from "@/domain/access";
import { AccessModel, accessProvidersMap, accessTypeFormSchema, WebhookConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
type AccessWebhookFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
data?: AccessModel;
onAfterReq: () => void;
};
const AccessWebhookForm = ({ data, op, onAfterReq }: AccessWebhookFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { createAccess, updateAccess } = useAccessStore();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@ -47,7 +47,7 @@ const AccessWebhookForm = ({ data, op, onAfterReq }: AccessWebhookFormProps) =>
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
const req: AccessModel = {
id: data.id as string,
name: data.name,
configType: data.configType,
@ -70,7 +70,7 @@ const AccessWebhookForm = ({ data, op, onAfterReq }: AccessWebhookFormProps) =>
updateAccess(req);
return;
}
addAccess(req);
createAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@ -154,7 +154,7 @@ const AccessWebhookForm = ({ data, op, onAfterReq }: AccessWebhookFormProps) =>
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -1,19 +1,17 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { ClientResponseError } from "pocketbase";
import { cn } from "@/components/ui/utils";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { PbErrorData } from "@/domain/base";
import { EmailsSetting } from "@/domain/settings";
import { update } from "@/repository/settings";
import { useConfigContext } from "@/providers/config";
import { type PbErrorData } from "@/domain/base";
import { useContactStore } from "@/stores/contact";
type EmailsEditProps = {
className?: string;
@ -21,10 +19,7 @@ type EmailsEditProps = {
};
const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const {
config: { emails },
setEmails,
} = useConfigContext();
const { emails, setEmails, fetchEmails } = useContactStore();
const [open, setOpen] = useState(false);
const { t } = useTranslation();
@ -40,30 +35,21 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
},
});
useEffect(() => {
fetchEmails();
}, []);
const onSubmit = async (data: z.infer<typeof formSchema>) => {
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
if (emails.includes(data.email)) {
form.setError("email", {
message: "common.errmsg.email_duplicate",
});
return;
}
// 保存到 config
const newEmails = [...(emails.content as EmailsSetting).emails, data.email];
try {
const resp = await update({
...emails,
name: "emails",
content: {
emails: newEmails,
},
});
await setEmails([...emails, data.email]);
// 更新本地状态
setEmails(resp);
// 关闭弹窗
form.reset();
form.clearErrors();
@ -115,7 +101,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

View File

@ -75,7 +75,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("common.add")}</div>
<div className="text-sm ">{t("common.button.add")}</div>
</div>
}
onSave={(variable) => {
@ -96,7 +96,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("common.add")}</div>
<div className="text-sm ">{t("common.button.add")}</div>
</div>
}
variable={{
@ -231,7 +231,7 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</div>
</DialogFooter>

View File

@ -3,12 +3,12 @@ import { z } from "zod";
import { useTranslation } from "react-i18next";
import { Edit, Plus, Trash2 } from "lucide-react";
import { cn } from "@/components/ui/utils";
import Show from "@/components/Show";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { FormControl, FormItem, FormLabel } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
type StringListProps = {
className?: string;
@ -79,7 +79,7 @@ const StringList = ({ value, className, onValueChange, valueType = "domain" }: S
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("common.add")}</div>
<div className="text-sm ">{t("common.button.add")}</div>
</div>
}
/>
@ -92,7 +92,7 @@ const StringList = ({ value, className, onValueChange, valueType = "domain" }: S
<div className="border rounded-md p-3 text-sm flex flex-col items-center">
<div className="text-muted-foreground">{t("common.text." + valueType + ".empty")}</div>
<StringEdit value={""} trigger={t("common.add")} onValueChange={addVal} valueType={valueType} />
<StringEdit value={""} trigger={t("common.button.add")} onValueChange={addVal} valueType={valueType} />
</div>
}
>
@ -208,7 +208,7 @@ const StringEdit = ({ trigger, value, onValueChange, op = "add", valueType }: St
onSaveClick();
}}
>
{op === "add" ? t("common.add") : t("common.confirm")}
{op === "add" ? t("common.button.add") : t("common.button.ok")}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -1,9 +1,8 @@
import { useTranslation } from "react-i18next";
import { BookOpen } from "lucide-react";
import { Divider, Space, Typography } from "antd";
import { BookOpen as BookOpenIcon } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { version } from "@/domain/version";
import { cn } from "@/lib/utils";
type VersionProps = {
className?: string;
@ -13,20 +12,19 @@ const Version = ({ className }: VersionProps) => {
const { t } = useTranslation();
return (
<div className={cn("w-full flex pb-5 ", className)}>
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
<a href="https://docs.certimate.me" target="_blank" className="flex items-center">
<BookOpen size={16} />
<div className="ml-1">{t("common.menu.document")}</div>
</a>
<Separator orientation="vertical" className="mx-2" />
<a href="https://github.com/usual2970/certimate/releases" target="_blank">
{version}
</a>
</div>
</div>
<Space className={className} size={4}>
<Typography.Link type="secondary" href="https://docs.certimate.me" target="_blank">
<div className="flex items-center justify-center space-x-1">
<BookOpenIcon size={16} />
<span>{t("common.menu.document")}</span>
</div>
</Typography.Link>
<Divider type="vertical" />
<Typography.Link type="secondary" href="https://github.com/usual2970/certimate/releases" target="_blank">
{version}
</Typography.Link>
</Space>
);
};
export default Version;

View File

@ -1,92 +0,0 @@
import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink } from "@/components/ui/pagination";
type PaginationProps = {
totalPages: number;
currentPage: number;
onPageChange: (page: number) => void;
};
type PageNumber = number | string;
const XPagination = ({ totalPages, currentPage, onPageChange }: PaginationProps) => {
const pageNeighbours = 1; // Number of page numbers to show on either side of the current page
const getPageNumbers = () => {
const totalNumbers = pageNeighbours * 2 + 3; // total pages to display (left + right neighbours + current + 2 for start and end)
const totalBlocks = totalNumbers + 2; // adding 2 for the start and end page numbers
if (totalPages > totalBlocks) {
let pages: PageNumber[] = [];
const leftBound = Math.max(2, currentPage - pageNeighbours);
const rightBound = Math.min(totalPages - 1, currentPage + pageNeighbours);
const beforeLastPage = totalPages - 1;
pages = range(leftBound, rightBound);
if (currentPage > pageNeighbours + 2) {
pages.unshift("...");
}
if (currentPage < beforeLastPage - pageNeighbours) {
pages.push("...");
}
pages.unshift(1);
pages.push(totalPages);
return pages;
}
return range(1, totalPages);
};
const range = (from: number, to: number, step = 1) => {
let i = from;
const range = [];
while (i <= to) {
range.push(i);
i += step;
}
return range;
};
const pages = getPageNumbers();
return (
<>
<Pagination className="dark:text-stone-200 justify-end mt-3">
<PaginationContent>
{pages.map((page, index) => {
if (page === "...") {
return (
<PaginationItem key={index}>
<PaginationEllipsis />
</PaginationItem>
);
}
return (
<PaginationItem key={index}>
<PaginationLink
href="#"
isActive={currentPage == page}
onClick={(e) => {
e.preventDefault();
onPageChange(page as number);
}}
>
{page}
</PaginationLink>
</PaginationItem>
);
})}
</PaginationContent>
</Pagination>
</>
);
};
export default XPagination;

View File

@ -6,9 +6,9 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { getErrMsg } from "@/utils/error";
import { NotifyChannels, NotifyChannelBark } from "@/domain/settings";
import { update } from "@/repository/settings";
import { save } from "@/repository/settings";
import { useNotifyContext } from "@/providers/notify";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
@ -96,7 +96,7 @@ const Bark = () => {
const handleSaveClick = async () => {
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -109,14 +109,14 @@ const Bark = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -137,7 +137,7 @@ const Bark = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -160,7 +160,7 @@ const Bark = () => {
setBark(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -173,10 +173,10 @@ const Bark = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -238,7 +238,7 @@ const Bark = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -6,10 +6,10 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { getErrMsg } from "@/utils/error";
import { NotifyChannelDingTalk, NotifyChannels } from "@/domain/settings";
import { useNotifyContext } from "@/providers/notify";
import { update } from "@/repository/settings";
import { save } from "@/repository/settings";
import Show from "@/components/Show";
import { notifyTest } from "@/api/notify";
@ -96,7 +96,7 @@ const DingTalk = () => {
const handleSaveClick = async () => {
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -109,14 +109,14 @@ const DingTalk = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -139,7 +139,7 @@ const DingTalk = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -160,7 +160,7 @@ const DingTalk = () => {
setDingtalk(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -173,10 +173,10 @@ const DingTalk = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -236,7 +236,7 @@ const DingTalk = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -6,10 +6,10 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { getErrMsg } from "@/utils/error";
import { NotifyChannelEmail, NotifyChannels } from "@/domain/settings";
import { useNotifyContext } from "@/providers/notify";
import { update } from "@/repository/settings";
import { save } from "@/repository/settings";
import Show from "@/components/Show";
import { notifyTest } from "@/api/notify";
@ -119,7 +119,7 @@ const Mail = () => {
const handleSaveClick = async () => {
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -132,14 +132,14 @@ const Mail = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -160,7 +160,7 @@ const Mail = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -183,7 +183,7 @@ const Mail = () => {
setMail(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -196,10 +196,10 @@ const Mail = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -360,7 +360,7 @@ const Mail = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -5,8 +5,8 @@ import { Label } from "@/components/ui/label";
import { useNotifyContext } from "@/providers/notify";
import { NotifyChannelLark, NotifyChannels } from "@/domain/settings";
import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { save } from "@/repository/settings";
import { getErrMsg } from "@/utils/error";
import { useToast } from "@/components/ui/use-toast";
import { useTranslation } from "react-i18next";
import { notifyTest } from "@/api/notify";
@ -92,7 +92,7 @@ const Lark = () => {
const handleSaveClick = async () => {
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -105,14 +105,14 @@ const Lark = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -135,7 +135,7 @@ const Lark = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -156,7 +156,7 @@ const Lark = () => {
setLark(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -169,10 +169,10 @@ const Lark = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -214,7 +214,7 @@ const Lark = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/components/ui/use-toast";
import { defaultNotifyTemplate, NotifyTemplates, NotifyTemplate as NotifyTemplateT } from "@/domain/settings";
import { getSetting, update } from "@/repository/settings";
import { get, save } from "@/repository/settings";
const NotifyTemplate = () => {
const [id, setId] = useState("");
@ -17,7 +17,7 @@ const NotifyTemplate = () => {
useEffect(() => {
const featchData = async () => {
const resp = await getSetting("templates");
const resp = await get("templates");
if (resp.content) {
setTemplates((resp.content as NotifyTemplates).notifyTemplates);
@ -50,7 +50,7 @@ const NotifyTemplate = () => {
};
const handleSaveClick = async () => {
const resp = await update({
const resp = await save({
id: id,
content: {
notifyTemplates: templates,
@ -63,7 +63,7 @@ const NotifyTemplate = () => {
}
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.template.saved.message"),
});
};
@ -88,7 +88,7 @@ const NotifyTemplate = () => {
></Textarea>
<div className="text-muted-foreground text-sm mt-1">{t("settings.notification.template.variables.tips.content")}</div>
<div className="flex justify-end mt-2">
<Button onClick={handleSaveClick}>{t("common.save")}</Button>
<Button onClick={handleSaveClick}>{t("common.button.save")}</Button>
</div>
</div>
);

View File

@ -6,10 +6,10 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { isValidURL } from "@/lib/url";
import { getErrMsg } from "@/utils/error";
import { isValidURL } from "@/utils/url";
import { NotifyChannels, NotifyChannelServerChan } from "@/domain/settings";
import { update } from "@/repository/settings";
import { save } from "@/repository/settings";
import { useNotifyContext } from "@/providers/notify";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
@ -96,14 +96,14 @@ const ServerChan = () => {
serverchan.data.url = serverchan.data.url.trim();
if (!isValidURL(serverchan.data.url)) {
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: t("common.errmsg.url_invalid"),
variant: "destructive",
});
return;
}
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -116,14 +116,14 @@ const ServerChan = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -144,7 +144,7 @@ const ServerChan = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -167,7 +167,7 @@ const ServerChan = () => {
setServerChan(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -180,10 +180,10 @@ const ServerChan = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -225,7 +225,7 @@ const ServerChan = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -6,9 +6,9 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { getErrMsg } from "@/utils/error";
import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings";
import { update } from "@/repository/settings";
import { save } from "@/repository/settings";
import { useNotifyContext } from "@/providers/notify";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
@ -96,7 +96,7 @@ const Telegram = () => {
const handleSaveClick = async () => {
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -109,14 +109,14 @@ const Telegram = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -137,7 +137,7 @@ const Telegram = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -160,7 +160,7 @@ const Telegram = () => {
setTelegram(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -173,10 +173,10 @@ const Telegram = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -238,7 +238,7 @@ const Telegram = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -6,10 +6,10 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { isValidURL } from "@/lib/url";
import { getErrMsg } from "@/utils/error";
import { isValidURL } from "@/utils/url";
import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings";
import { update } from "@/repository/settings";
import { save } from "@/repository/settings";
import { useNotifyContext } from "@/providers/notify";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
@ -96,14 +96,14 @@ const Webhook = () => {
webhook.data.url = webhook.data.url.trim();
if (!isValidURL(webhook.data.url)) {
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: t("common.errmsg.url_invalid"),
variant: "destructive",
});
return;
}
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -116,14 +116,14 @@ const Webhook = () => {
setChannels(resp);
toast({
title: t("common.save.succeeded.message"),
title: t("common.text.operation_succeeded"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -144,7 +144,7 @@ const Webhook = () => {
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
@ -167,7 +167,7 @@ const Webhook = () => {
setWebhook(newData);
try {
const resp = await update({
const resp = await save({
...config,
name: "notifyChannels",
content: {
@ -180,10 +180,10 @@ const Webhook = () => {
setChannels(resp);
} catch (e) {
const msg = getErrMessage(e);
const msg = getErrMsg(e);
toast({
title: t("common.save.failed.message"),
title: t("common.text.operation_failed"),
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
@ -225,7 +225,7 @@ const Webhook = () => {
handleSaveClick();
}}
>
{t("common.save")}
{t("common.button.save")}
</Button>
</Show>

View File

@ -2,7 +2,7 @@ import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Accordion = AccordionPrimitive.Root;

View File

@ -1,94 +0,0 @@
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => <AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />);
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => <AlertDialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />);
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />);
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel ref={ref} className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)} {...props} />
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};

View File

@ -1,7 +1,7 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",

View File

@ -1,29 +0,0 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };

View File

@ -1,57 +0,0 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(({ className, ...props }, ref) => (
<ol ref={ref} className={cn("flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5", className)} {...props} />
));
BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
));
BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return <Comp ref={ref} className={cn("transition-colors hover:text-foreground", className)} {...props} />;
});
BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(({ className, ...props }, ref) => (
<span ref={ref} role="link" aria-disabled="true" aria-current="page" className={cn("font-normal text-foreground", className)} {...props} />
));
BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
<li role="presentation" aria-hidden="true" className={cn("[&>svg]:size-3.5", className)} {...props}>
{children ?? <ChevronRight />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
<span role="presentation" aria-hidden="true" className={cn("flex h-9 w-9 items-center justify-center", className)} {...props}>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
export { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis };

View File

@ -3,7 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",

View File

@ -1,35 +0,0 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
));
CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Collapsible = CollapsiblePrimitive.Root;

View File

@ -3,7 +3,7 @@ import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
import { Dialog, DialogContent } from "@/components/ui/dialog";
const Command = React.forwardRef<React.ElementRef<typeof CommandPrimitive>, React.ComponentPropsWithoutRef<typeof CommandPrimitive>>(

View File

@ -1,122 +1,77 @@
"use client"
"use client";
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils"
import { cn } from "./utils";
const Dialog = DialogPrimitive.Root
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
const DialogOverlay = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>>(
({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
/>
)
);
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogContent = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>>(
({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
)
);
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
);
DialogHeader.displayName = "DialogHeader";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>>(
({ className, ...props }, ref) => (
<DialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
)
);
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
>(({ className, ...props }, ref) => <DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />);
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription };

View File

@ -1,62 +0,0 @@
import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "@/lib/utils";
const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
);
Drawer.displayName = "Drawer";
const DrawerTrigger = DrawerPrimitive.Trigger;
const DrawerPortal = DrawerPrimitive.Portal;
const DrawerClose = DrawerPrimitive.Close;
const DrawerOverlay = React.forwardRef<React.ElementRef<typeof DrawerPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>>(
({ className, ...props }, ref) => <DrawerPrimitive.Overlay ref={ref} className={cn("fixed inset-0 z-50 bg-black/80", className)} {...props} />
);
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<React.ElementRef<typeof DrawerPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>>(
({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn("fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background", className)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
)
);
DrawerContent.displayName = "DrawerContent";
const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} />
);
DrawerHeader.displayName = "DrawerHeader";
const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
);
DrawerFooter.displayName = "DrawerFooter";
const DrawerTitle = React.forwardRef<React.ElementRef<typeof DrawerPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>>(
({ className, ...props }, ref) => (
<DrawerPrimitive.Title ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
)
);
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => <DrawerPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />);
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription };

View File

@ -2,7 +2,7 @@ import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const DropdownMenu = DropdownMenuPrimitive.Root;

View File

@ -4,8 +4,8 @@ import { useTranslation } from "react-i18next";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { cn } from "./utils";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
const Form = FormProvider;

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}

View File

@ -2,7 +2,7 @@ import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");

View File

@ -1,104 +0,0 @@
import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { ChevronDown } from "lucide-react";
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root ref={ref} className={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", className)} {...props}>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List ref={ref} className={cn("group flex flex-1 list-none items-center justify-center space-x-1", className)} {...props} />
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
);
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger ref={ref} className={cn(navigationMenuTriggerStyle(), "group", className)} {...props}>
{children} <ChevronDown className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180" aria-hidden="true" />
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};

View File

@ -1,79 +0,0 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav role="navigation" aria-label="pagination" className={cn("mx-auto flex w-full justify-center", className)} {...props} />
);
Pagination.displayName = "Pagination";
const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<"ul">>(({ className, ...props }, ref) => (
<ul ref={ref} className={cn("flex flex-row items-center gap-1", className)} {...props} />
));
PaginationContent.displayName = "PaginationContent";
const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li">>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
));
PaginationItem.displayName = "PaginationItem";
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">;
const PaginationLink = ({ className, isActive, size = "icon", ...props }: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
);
PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation();
return (
<PaginationLink aria-label="Go to previous page" size="default" className={cn("gap-1 pl-2.5", className)} {...props}>
<ChevronLeft className="h-4 w-4" />
<span>{t("common.pagination.prev")}</span>
</PaginationLink>
);
};
PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation();
return (
<PaginationLink aria-label="Go to next page" size="default" className={cn("gap-1 pr-2.5", className)} {...props}>
<span>{t("common.pagination.next")}</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
};
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => {
const { t } = useTranslation();
return (
<span aria-hidden className={cn("flex h-9 w-9 items-center justify-center", className)} {...props}>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">{t("common.pagination.more")}</span>
</span>
);
};
PaginationEllipsis.displayName = "PaginationEllipsis";
export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious };

View File

@ -1,29 +1,28 @@
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/lib/utils"
import { cn } from "./utils";
const Popover = PopoverPrimitive.Root
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
const PopoverContent = React.forwardRef<React.ElementRef<typeof PopoverPrimitive.Content>, React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>>(
({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
)
);
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent }
export { Popover, PopoverTrigger, PopoverContent };

View File

@ -1,15 +0,0 @@
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "@/lib/utils";
const Progress = React.forwardRef<React.ElementRef<typeof ProgressPrimitive.Root>, React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>>(
({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root ref={ref} className={cn("relative h-4 w-full overflow-hidden rounded-full bg-secondary", className)} {...props}>
<ProgressPrimitive.Indicator className="h-full w-full flex-1 bg-primary transition-all" style={{ transform: `translateX(-${100 - (value || 0)}%)` }} />
</ProgressPrimitive.Root>
)
);
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress };

View File

@ -2,7 +2,7 @@ import * as React from "react";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { Circle } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const RadioGroup = React.forwardRef<React.ElementRef<typeof RadioGroupPrimitive.Root>, React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>>(
({ className, ...props }, ref) => {

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const ScrollArea = React.forwardRef<React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>>(
({ className, children, ...props }, ref) => (

View File

@ -2,7 +2,7 @@ import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Select = SelectPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Separator = React.forwardRef<React.ElementRef<typeof SeparatorPrimitive.Root>, React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>>(
({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (

View File

@ -3,7 +3,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Sheet = SheetPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitives.Root>, React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>>(
({ className, ...props }, ref) => (

View File

@ -1,117 +1,47 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "./utils";
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
))
Table.displayName = "Table"
));
Table.displayName = "Table";
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
));
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
));
TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
<tfoot ref={ref} className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)} {...props} />
));
TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(({ className, ...props }, ref) => (
<tr ref={ref} className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)} {...props} />
));
TableRow.displayName = "TableRow";
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(({ className, ...props }, ref) => (
<th ref={ref} className={cn("h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", className)} {...props} />
));
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(({ className, ...props }, ref) => (
<td ref={ref} className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} {...props} />
));
TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(({ className, ...props }, ref) => (
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
));
TableCaption.displayName = "TableCaption";
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const Tabs = TabsPrimitive.Root;

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}

View File

@ -3,7 +3,7 @@ import * as ToastPrimitives from "@radix-ui/react-toast";
import { X } from "lucide-react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const ToastProvider = ToastPrimitives.Provider;

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
import { cn } from "./utils";
const TooltipProvider = TooltipPrimitive.Provider;

View File

@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select";
import { accessProvidersMap } from "@/domain/access";
import { useTranslation } from "react-i18next";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
import { deployTargetsMap } from "@/domain/domain";
type AccessSelectProps = {
@ -13,9 +13,7 @@ type AccessSelectProps = {
const AccessSelect = ({ value, onValueChange, providerType }: AccessSelectProps) => {
const [localValue, setLocalValue] = React.useState<string>("");
const { t } = useTranslation();
const {
config: { accesses },
} = useConfigContext();
const { accesses } = useAccessStore();
useEffect(() => {
setLocalValue(value);

View File

@ -3,7 +3,7 @@ import { Plus } from "lucide-react";
import { BrandNodeProps, NodeProps } from "./types";
import { newWorkflowNode, workflowNodeDropdownList, WorkflowNodeType } from "@/domain/workflow";
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
import { useShallow } from "zustand/shallow";
import {
DropdownMenu,

View File

@ -1,5 +1,4 @@
import { memo } from "react";
import { memo, useEffect } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import z from "zod";
@ -16,13 +15,12 @@ import EmailsEdit from "@/components/certimate/EmailsEdit";
import StringList from "@/components/certimate/StringList";
import { accessProvidersMap } from "@/domain/access";
import { EmailsSetting } from "@/domain/settings";
import { useConfigContext } from "@/providers/config";
import { useAccessStore } from "@/stores/access";
import { useContactStore } from "@/stores/contact";
import { Switch } from "@/components/ui/switch";
import { TooltipFast } from "@/components/ui/tooltip";
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
import { useWorkflowStore, WorkflowState } from "@/stores/workflow";
import { useShallow } from "zustand/shallow";
import { usePanel } from "./PanelProvider";
@ -35,9 +33,12 @@ const selectState = (state: WorkflowState) => ({
const ApplyForm = ({ data }: ApplyFormProps) => {
const { updateNode } = useWorkflowStore(useShallow(selectState));
const {
config: { accesses, emails },
} = useConfigContext();
const { accesses } = useAccessStore();
const { emails, fetchEmails } = useContactStore();
useEffect(() => {
fetchEmails();
}, []);
const { t } = useTranslation();
@ -122,7 +123,7 @@ const ApplyForm = ({ data }: ApplyFormProps) => {
trigger={
<div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
<Plus size={14} />
{t("common.add")}
{t("common.button.add")}
</div>
}
/>
@ -141,7 +142,7 @@ const ApplyForm = ({ data }: ApplyFormProps) => {
<SelectContent>
<SelectGroup>
<SelectLabel>{t("domain.application.form.email.list")}</SelectLabel>
{(emails.content as EmailsSetting).emails.map((item) => (
{emails.map((item) => (
<SelectItem key={item} value={item}>
<div>{item}</div>
</SelectItem>
@ -168,7 +169,7 @@ const ApplyForm = ({ data }: ApplyFormProps) => {
trigger={
<div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
<Plus size={14} />
{t("common.add")}
{t("common.button.add")}
</div>
}
op="add"
@ -342,7 +343,7 @@ const ApplyForm = ({ data }: ApplyFormProps) => {
</div>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
<Button type="submit">{t("common.button.save")}</Button>
</div>
</form>
</Form>

Some files were not shown because too many files have changed in this diff Show More