mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-10 06:29:52 +00:00
support email update
This commit is contained in:
parent
7f6549bdf3
commit
b912c5e688
49
ui/src/components/certimate/DeployState.tsx
Normal file
49
ui/src/components/certimate/DeployState.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { Deployment } from "@/domain/deployment";
|
||||
import { CircleCheck, CircleX } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "../ui/tooltip";
|
||||
|
||||
type DeployStateProps = {
|
||||
deployment: Deployment;
|
||||
};
|
||||
|
||||
const DeployState = ({ deployment }: DeployStateProps) => {
|
||||
// 获取指定阶段的错误信息
|
||||
const error = (state: "check" | "apply" | "deploy") => {
|
||||
if (!deployment.log[state]) {
|
||||
return "";
|
||||
}
|
||||
return deployment.log[state][deployment.log[state].length - 1].error;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{deployment.phase === "deploy" && deployment.phaseSuccess ? (
|
||||
<CircleCheck size={16} className="text-green-700" />
|
||||
) : (
|
||||
<>
|
||||
{error(deployment.phase).length ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild className="cursor-pointer">
|
||||
<CircleX size={16} className="text-red-700" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[35em]">
|
||||
{error(deployment.phase)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<CircleX size={16} className="text-red-700" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployState;
|
@ -46,12 +46,12 @@ export default function Dashboard() {
|
||||
};
|
||||
|
||||
const handleSettingClick = () => {
|
||||
navigate("/setting/password");
|
||||
navigate("/setting/account");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ConfigProvider>
|
||||
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
|
||||
<div className="grid min-h-screen w-full md:grid-cols-[180px_1fr] lg:grid-cols-[200px_1fr] 2xl:md:grid-cols-[280px_1fr] ">
|
||||
<div className="hidden border-r dark:border-stone-500 bg-muted/40 md:block">
|
||||
<div className="flex h-full max-h-screen flex-col gap-2">
|
||||
<div className="flex h-14 items-center border-b dark:border-stone-500 px-4 lg:h-[60px] lg:px-6">
|
||||
|
@ -1,20 +1,57 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { KeyRound, UserRound } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
const SettingLayout = () => {
|
||||
const location = useLocation();
|
||||
const [tabValue, setTabValue] = useState("account");
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const pathname = location.pathname;
|
||||
const tabValue = pathname.split("/")[2];
|
||||
setTabValue(tabValue);
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Toaster />
|
||||
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
|
||||
设置密码
|
||||
偏好设置
|
||||
</div>
|
||||
<div className="w-full sm:w-[35em] mt-10 flex flex-col p-3 mx-auto">
|
||||
{/* <div className="text-muted-foreground">
|
||||
<span className="transition-all text-sm bg-gray-400 px-3 py-1 rounded-sm text-white cursor-pointer">
|
||||
密码
|
||||
</span>
|
||||
</div> */}
|
||||
<Outlet />
|
||||
<div className="w-full mt-5 p-3 flex justify-center">
|
||||
<Tabs defaultValue="account" className="" value={tabValue}>
|
||||
<TabsList>
|
||||
<TabsTrigger
|
||||
value="account"
|
||||
onClick={() => {
|
||||
navigate("/setting/account");
|
||||
}}
|
||||
className="px-5"
|
||||
>
|
||||
<UserRound size={14} />
|
||||
<div className="ml-1">账户</div>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="password"
|
||||
onClick={() => {
|
||||
navigate("/setting/password");
|
||||
}}
|
||||
className="px-5"
|
||||
>
|
||||
<KeyRound size={14} />
|
||||
<div className="ml-1">密码</div>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={tabValue}>
|
||||
<div className="mt-5 w-full md:w-[45em]">
|
||||
<Outlet />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import DeployProgress from "@/components/certimate/DeployProgress";
|
||||
import DeployState from "@/components/certimate/DeployState";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@ -204,11 +205,7 @@ const Dashboard = () => {
|
||||
{deployment.expand.domain?.domain}
|
||||
</div>
|
||||
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{deployment.phase === "deploy" && deployment.phaseSuccess ? (
|
||||
<CircleCheck size={16} className="text-green-700" />
|
||||
) : (
|
||||
<CircleX size={16} className="text-red-700" />
|
||||
)}
|
||||
<DeployState deployment={deployment} />
|
||||
</div>
|
||||
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<DeployProgress
|
||||
|
@ -1,4 +1,5 @@
|
||||
import DeployProgress from "@/components/certimate/DeployProgress";
|
||||
import DeployState from "@/components/certimate/DeployState";
|
||||
import XPagination from "@/components/certimate/XPagination";
|
||||
import Show from "@/components/Show";
|
||||
import {
|
||||
@ -196,12 +197,12 @@ const Home = () => {
|
||||
) : (
|
||||
<>
|
||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||
<div className="w-40">域名</div>
|
||||
<div className="w-48">有效期限</div>
|
||||
<div className="w-36">域名</div>
|
||||
<div className="w-40">有效期限</div>
|
||||
<div className="w-32">最近执行状态</div>
|
||||
<div className="w-64">最近执行阶段</div>
|
||||
<div className="w-40 sm:ml-2">最近执行时间</div>
|
||||
<div className="w-32">是否启用</div>
|
||||
<div className="w-24">是否启用</div>
|
||||
<div className="grow">操作</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
@ -213,10 +214,10 @@ const Home = () => {
|
||||
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
|
||||
key={domain.id}
|
||||
>
|
||||
<div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<div className="sm:w-36 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{domain.domain}
|
||||
</div>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<div>
|
||||
{domain.expiredAt ? (
|
||||
<>
|
||||
@ -231,12 +232,7 @@ const Home = () => {
|
||||
<div className="sm:w-32 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{domain.lastDeployedAt && domain.expand?.lastDeployment ? (
|
||||
<>
|
||||
{domain.expand.lastDeployment?.phase === "deploy" &&
|
||||
domain.expand.lastDeployment?.phaseSuccess ? (
|
||||
<CircleCheck size={16} className="text-green-700" />
|
||||
) : (
|
||||
<CircleX size={16} className="text-red-700" />
|
||||
)}
|
||||
<DeployState deployment={domain.expand.lastDeployment} />
|
||||
</>
|
||||
) : (
|
||||
"---"
|
||||
@ -257,7 +253,7 @@ const Home = () => {
|
||||
? convertZulu2Beijing(domain.lastDeployedAt)
|
||||
: "---"}
|
||||
</div>
|
||||
<div className="sm:w-32 flex items-center">
|
||||
<div className="sm:w-24 flex items-center">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import DeployProgress from "@/components/certimate/DeployProgress";
|
||||
import DeployState from "@/components/certimate/DeployState";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
@ -88,11 +89,7 @@ const History = () => {
|
||||
{deployment.expand.domain?.domain}
|
||||
</div>
|
||||
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{deployment.phase === "deploy" && deployment.phaseSuccess ? (
|
||||
<CircleCheck size={16} className="text-green-700" />
|
||||
) : (
|
||||
<CircleX size={16} className="text-red-700" />
|
||||
)}
|
||||
<DeployState deployment={deployment} />
|
||||
</div>
|
||||
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
|
||||
<DeployProgress
|
||||
|
109
ui/src/pages/setting/Account.tsx
Normal file
109
ui/src/pages/setting/Account.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { getPb } from "@/repository/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email("请输入正确的邮箱"),
|
||||
});
|
||||
|
||||
const Account = () => {
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [changed, setChanged] = useState(false);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
email: getPb().authStore.model?.email,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
try {
|
||||
await getPb().admins.update(getPb().authStore.model?.id, {
|
||||
email: values.email,
|
||||
});
|
||||
|
||||
getPb().authStore.clear();
|
||||
toast({
|
||||
title: "修改账户邮箱功",
|
||||
description: "请重新登录",
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate("/login");
|
||||
}, 500);
|
||||
} catch (e) {
|
||||
const message = getErrMessage(e);
|
||||
toast({
|
||||
title: "修改账户邮箱失败",
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full md:max-w-[35em]">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 dark:text-stone-200"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>邮箱</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="请输入邮箱"
|
||||
{...field}
|
||||
type="email"
|
||||
onChange={(e) => {
|
||||
setChanged(true);
|
||||
form.setValue("email", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
{changed ? (
|
||||
<Button type="submit">确认修改</Button>
|
||||
) : (
|
||||
<Button type="submit" disabled variant={"secondary"}>
|
||||
确认修改
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Account;
|
@ -84,64 +84,70 @@ const Password = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 dark:text-stone-200"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="oldPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>当前密码</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="当前密码" {...field} type="password" />
|
||||
</FormControl>
|
||||
<div className="w-full md:max-w-[35em]">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 dark:text-stone-200"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="oldPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>当前密码</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="当前密码" {...field} type="password" />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="newPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>新密码</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="newPassword" {...field} type="password" />
|
||||
</FormControl>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="newPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>新密码</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="newPassword"
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>确认密码</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="confirmPassword"
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>确认密码</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="confirmPassword"
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">确认修改</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">确认修改</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import LoginLayout from "./pages/LoginLayout";
|
||||
import Password from "./pages/setting/Password";
|
||||
import SettingLayout from "./pages/SettingLayout";
|
||||
import Dashboard from "./pages/dashboard/Dashboard";
|
||||
import Account from "./pages/setting/Account";
|
||||
|
||||
export const router = createHashRouter([
|
||||
{
|
||||
@ -44,6 +45,10 @@ export const router = createHashRouter([
|
||||
path: "/setting/password",
|
||||
element: <Password />,
|
||||
},
|
||||
{
|
||||
path: "/setting/account",
|
||||
element: <Account />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user