Skip to content

Commit

Permalink
feat(v2): add all settings pages
Browse files Browse the repository at this point in the history
  • Loading branch information
moonrailgun committed Apr 7, 2024
1 parent 20e1963 commit fa7534a
Show file tree
Hide file tree
Showing 17 changed files with 925 additions and 46 deletions.
19 changes: 14 additions & 5 deletions src/client/components/CommonHeader.tsx
Expand Up @@ -3,18 +3,27 @@ import { TipIcon } from './TipIcon';

interface CommonHeaderProps {
title: string;
desc?: React.ReactNode;
desc?: string;
tip?: React.ReactNode;
actions?: React.ReactNode;
}
export const CommonHeader: React.FC<CommonHeaderProps> = React.memo((props) => {
return (
<>
<h1 className="text-xl font-bold">{props.title}</h1>
<div className="flex w-full items-center">
<div className="flex flex-1 items-center">
<h1 className="text-xl font-bold">{props.title}</h1>

{props.desc && <TipIcon className="ml-1" content={props.desc} />}
{props.desc && (
<span className="text-muted-foreground ml-2 self-end text-sm">
{props.desc}
</span>
)}

{props.tip && <TipIcon className="ml-1" content={props.tip} />}
</div>

{props.actions && <div className="ml-auto">{props.actions}</div>}
</>
</div>
);
});
CommonHeader.displayName = 'CommonHeader';
14 changes: 9 additions & 5 deletions src/client/components/CommonList.tsx
Expand Up @@ -48,7 +48,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
return (
<div className="flex h-full flex-col">
{props.hasSearch && (
<div className="bg-background/95 supports-[backdrop-filter]:bg-background/60 p-4 backdrop-blur">
<div className="bg-background/95 supports-[backdrop-filter]:bg-background/60 px-4 pt-4 backdrop-blur">
<form>
<div className="relative">
<LuSearch className="text-muted-foreground absolute left-2 top-2.5 h-4 w-4" />
Expand All @@ -64,7 +64,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
)}

<ScrollArea className="flex-1">
<div className="flex flex-col gap-2 p-4 pt-0">
<div className="flex flex-col gap-2 p-4">
{finalList.map((item) => {
const isSelected = item.href === location.pathname;

Expand All @@ -88,9 +88,13 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
</div>
</div>
</div>
<div className="text-muted-foreground line-clamp-2 w-full text-xs">
{item.content}
</div>

{item.content && (
<div className="text-muted-foreground line-clamp-2 w-full text-xs">
{item.content}
</div>
)}

{Array.isArray(item.tags) && item.tags.length > 0 ? (
<div className="flex items-center gap-2">
{item.tags.map((tag) => (
Expand Down
4 changes: 3 additions & 1 deletion src/client/components/ui/button.tsx
Expand Up @@ -60,7 +60,9 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
) => {
const Comp = asChild ? Slot : 'button';

const icon = Icon ? <Icon className="mr-1" /> : undefined;
const icon = Icon ? (
<Icon className={cn(props.children && 'mr-1')} />
) : undefined;
const children = (
<>
{loading ? <Spinner className="mr-1" /> : icon}
Expand Down
151 changes: 151 additions & 0 deletions src/client/components/website/WebsiteConfig.tsx
@@ -0,0 +1,151 @@
import { Button, Form, Input, message, Popconfirm, Tabs } from 'antd';
import React from 'react';
import { useNavigate, useParams } from 'react-router';
import { deleteWorkspaceWebsite } from '../../api/model/website';
import { useRequest } from '../../hooks/useRequest';
import { useCurrentWorkspaceId } from '../../store/user';
import { ErrorTip } from '../ErrorTip';
import { Loading } from '../Loading';
import { NoWorkspaceTip } from '../NoWorkspaceTip';
import { MonitorPicker } from '../monitor/MonitorPicker';
import {
defaultErrorHandler,
defaultSuccessHandler,
getQueryKey,
trpc,
} from '../../api/trpc';
import { useQueryClient } from '@tanstack/react-query';
import { useEvent } from '../../hooks/useEvent';
import { hostnameValidator } from '../../utils/validator';
import { useTranslation } from '@i18next-toolkit/react';

export const WebsiteConfig: React.FC<{ websiteId: string }> = React.memo(
(props) => {
const { websiteId } = props;
const { t } = useTranslation();
const workspaceId = useCurrentWorkspaceId();
const navigate = useNavigate();
const queryClient = useQueryClient();

const { data: website, isLoading } = trpc.website.info.useQuery({
workspaceId,
websiteId,
});

const updateMutation = trpc.website.updateInfo.useMutation({
onSuccess: () => {
queryClient.resetQueries(getQueryKey(trpc.website.info));
defaultSuccessHandler();
},
onError: defaultErrorHandler,
});

const handleSave = useEvent(
async (values: { name: string; domain: string; monitorId: string }) => {
await updateMutation.mutateAsync({
workspaceId,
websiteId,
name: values.name,
domain: values.domain,
monitorId: values.monitorId,
});
}
);

const [, handleDeleteWebsite] = useRequest(async () => {
await deleteWorkspaceWebsite(workspaceId, websiteId!);

message.success(t('Delete Success'));

navigate('/settings/websites');
});

if (!workspaceId) {
return <NoWorkspaceTip />;
}

if (!websiteId) {
return <ErrorTip />;
}

if (isLoading) {
return <Loading />;
}

if (!website) {
return <ErrorTip />;
}

return (
<div>
<div>
<Tabs>
<Tabs.TabPane key={'detail'} tab={'Detail'}>
<Form
layout="vertical"
initialValues={{
id: website.id,
name: website.name,
domain: website.domain,
monitorId: website.monitorId,
}}
onFinish={handleSave}
>
<Form.Item label={t('Website ID')} name="id">
<Input size="large" disabled={true} />
</Form.Item>
<Form.Item
label={t('Name')}
name="name"
rules={[{ required: true }]}
>
<Input size="large" />
</Form.Item>
<Form.Item
label={t('Domain')}
name="domain"
rules={[
{ required: true },
{
validator: hostnameValidator,
},
]}
>
<Input size="large" />
</Form.Item>

<Form.Item
label={t('Monitor')}
name="monitorId"
tooltip={t(
'You can bind a monitor which will display health status in website overview'
)}
>
<MonitorPicker size="large" allowClear={true} />
</Form.Item>

<Form.Item>
<Button size="large" htmlType="submit">
{t('Save')}
</Button>
</Form.Item>
</Form>
</Tabs.TabPane>

<Tabs.TabPane key={'data'} tab={'Data'}>
<Popconfirm
title={t('Delete Website')}
onConfirm={() => handleDeleteWebsite()}
>
<Button type="primary" danger={true}>
{t('Delete Website')}
</Button>
</Popconfirm>
</Tabs.TabPane>
</Tabs>
</div>
</div>
);
}
);
WebsiteConfig.displayName = 'WebsiteConfig';
63 changes: 51 additions & 12 deletions src/client/pages/Layout/UserConfig.tsx
Expand Up @@ -13,9 +13,12 @@ import {
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
} from '@/components/ui/dropdown-menu';
import { useEvent } from '@/hooks/useEvent';
import { useSettingsStore } from '@/store/settings';
import { useUserInfo } from '@/store/user';
import { languages } from '@/utils/constants';
import { useTranslation, setLanguage } from '@i18next-toolkit/react';
import { useNavigate } from '@tanstack/react-router';
import React from 'react';
import { LuMoreVertical } from 'react-icons/lu';

Expand All @@ -24,7 +27,15 @@ interface UserConfigProps {
}
export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
const userInfo = useUserInfo();
const { i18n } = useTranslation();
const { i18n, t } = useTranslation();
const navigate = useNavigate();
const colorScheme = useSettingsStore((state) => state.colorScheme);

const handleChangeColorSchema = useEvent((colorScheme) => {
useSettingsStore.setState({
colorScheme,
});
});

const avatar = (
<Avatar>
Expand Down Expand Up @@ -64,10 +75,28 @@ export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
)}

<DropdownMenuContent>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
navigate({
to: '/settings/profile',
})
}
>
{t('Profile')}
</DropdownMenuItem>

<DropdownMenuItem
onClick={() =>
navigate({
to: '/settings/notifications',
})
}
>
{t('Notifications')}
</DropdownMenuItem>

<DropdownMenuSub>
<DropdownMenuSubTrigger>Language</DropdownMenuSubTrigger>
<DropdownMenuSubTrigger>{t('Language')}</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
Expand All @@ -87,14 +116,24 @@ export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
</DropdownMenuPortal>
</DropdownMenuSub>

<DropdownMenuItem
className="cursor-default"
onSelect={(e) => {
e.preventDefault();
}}
>
<ColorSchemeSwitcher />
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>{t('Theme')}</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
value={colorScheme}
onValueChange={handleChangeColorSchema}
>
<DropdownMenuRadioItem value={'dark'}>
{t('Dark')}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value={'light'}>
{t('Light')}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down

0 comments on commit fa7534a

Please sign in to comment.