feat: Use separate /setting route page instead of modal

Better extensibility as we may overlay additional modal in setting
This commit is contained in:
Tianzhou Chen
2023-04-30 23:45:05 +08:00
parent 620ebdeb0d
commit fa0cbfa381
9 changed files with 285 additions and 97 deletions

View File

@ -29,5 +29,6 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
}
},
"i18n-ally.keystyle": "nested"
}

View File

@ -12,6 +12,8 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.17",
"@mui/material": "^5.11.14",
"@mui/styled-engine-sc": "^5.11.11",
"@prisma/client": "4.13.0",

26
pnpm-lock.yaml generated
View File

@ -7,6 +7,12 @@ dependencies:
'@emotion/styled':
specifier: ^11.10.6
version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0)
'@headlessui/react':
specifier: ^1.7.14
version: 1.7.14(react-dom@18.2.0)(react@18.2.0)
'@heroicons/react':
specifier: ^2.0.17
version: 2.0.17(react@18.2.0)
'@mui/material':
specifier: ^5.11.14
version: 5.11.14(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
@ -645,6 +651,26 @@ packages:
- '@types/react'
dev: false
/@headlessui/react@1.7.14(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-znzdq9PG8rkwcu9oQ2FwIy0ZFtP9Z7ycS+BAqJ3R5EIqC/0bJGvhT7193rFf+45i9nnPsYvCQVW4V/bB9Xc+gA==}
engines: {node: '>=10'}
peerDependencies:
react: ^16 || ^17 || ^18
react-dom: ^16 || ^17 || ^18
dependencies:
client-only: 0.0.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@heroicons/react@2.0.17(react@18.2.0):
resolution: {integrity: sha512-90GMZktkA53YbNzHp6asVEDevUQCMtxWH+2UK2S8OpnLEu7qckTJPhNxNQG52xIR1WFTwFqtH6bt7a60ZNcLLA==}
peerDependencies:
react: '>= 16'
dependencies:
react: 18.2.0
dev: false
/@humanwhocodes/config-array@0.9.5:
resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==}
engines: {node: '>=10.10.0'}

View File

@ -2,25 +2,20 @@ import { Drawer } from "@mui/material";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store";
import Link from "next/link";
import Select from "./kit/Select";
import Tooltip from "./kit/Tooltip";
import Icon from "./Icon";
import DarkModeSwitch from "./DarkModeSwitch";
import SettingModal from "./SettingModal";
import ConversationList from "./Sidebar/ConversationList";
import ConnectionList from "./Sidebar/ConnectionList";
interface State {
showSettingModal: boolean;
}
interface State {}
const ConnectionSidebar = () => {
const { t } = useTranslation();
const layoutStore = useLayoutStore();
const connectionStore = useConnectionStore();
const [state, setState] = useState<State>({
showSettingModal: false,
});
const [isRequestingDatabase, setIsRequestingDatabase] =
useState<boolean>(false);
const currentConnectionCtx = connectionStore.currentConnectionCtx;
@ -60,13 +55,6 @@ const ConnectionSidebar = () => {
}
}, [currentConnectionCtx?.connection]);
const toggleSettingModal = (show = true) => {
setState({
...state,
showSettingModal: show,
});
};
const handleDatabaseNameSelect = async (databaseName: string) => {
if (!currentConnectionCtx?.connection) {
return;
@ -101,13 +89,13 @@ const ConnectionSidebar = () => {
<div className="w-full flex flex-col justify-end items-center">
<DarkModeSwitch />
<Tooltip title={t("common.setting")} side="right">
<button
<Link
className=" w-10 h-10 p-1 rounded-full flex flex-row justify-center items-center hover:bg-gray-100 dark:hover:bg-zinc-700"
data-tip={t("common.setting")}
onClick={() => toggleSettingModal(true)}
href="/setting"
>
<Icon.IoMdSettings className="text-gray-600 dark:text-gray-300 w-6 h-auto" />
</button>
</Link>
</Tooltip>
</div>
</div>
@ -179,10 +167,6 @@ const ConnectionSidebar = () => {
</div>
</div>
</Drawer>
{state.showSettingModal && (
<SettingModal close={() => toggleSettingModal(false)} />
)}
</>
);
};

View File

@ -1,8 +1,7 @@
import { useTranslation } from "react-i18next";
import { useLocalStorage } from "react-use";
import Link from "next/link";
import Icon from "./Icon";
import SettingModal from "./SettingModal";
import { useState } from "react";
interface Props {
className?: string;
@ -15,7 +14,6 @@ const QuotaOverflowBanner = (props: Props) => {
"hide-quota-overflow-banner",
false
);
const [showSettingModal, setShowSettingModal] = useState(false);
const show = !hideBanner;
return (
@ -27,12 +25,9 @@ const QuotaOverflowBanner = (props: Props) => {
>
<div className="text-sm leading-6 pr-4 cursor-pointer">
{t("banner.quota-overflow")}{" "}
<button
className="ml-1 underline hover:opacity-80"
onClick={() => setShowSettingModal(true)}
>
<Link className="ml-1 underline hover:opacity-80" href="/setting">
{t("banner.use-my-key")}
</button>
</Link>
</div>
<button
className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100"
@ -41,10 +36,6 @@ const QuotaOverflowBanner = (props: Props) => {
<Icon.BiX className="w-6 h-auto" />
</button>
</div>
{showSettingModal && (
<SettingModal close={() => setShowSettingModal(false)} />
)}
</>
);
};

View File

@ -1,61 +0,0 @@
import { useTranslation } from "react-i18next";
import Modal from "./kit/Modal";
import Icon from "./Icon";
import WeChatQRCodeView from "./WeChatQRCodeView";
import ClearDataButton from "./ClearDataButton";
import LocaleSelector from "./LocaleSelector";
import ThemeSelector from "./ThemeSelector";
import OpenAIApiConfigView from "./OpenAIApiConfigView";
interface Props {
close: () => void;
}
const SettingModal = (props: Props) => {
const { close } = props;
const { t } = useTranslation();
return (
<Modal title={t("setting.self")} onClose={close}>
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
<div className="w-full flex flex-row justify-start items-start flex-wrap gap-2">
<a
href="https://discord.gg/z6kakemDjm"
className="w-auto px-4 py-2 rounded-full bg-indigo-600 text-white text-sm font-medium flex flex-row justify-center items-center hover:underline hover:shadow"
target="_blank"
>
<Icon.BsDiscord className="w-4 h-auto mr-1" />
{t("social.join-discord-channel")}
</a>
<WeChatQRCodeView />
</div>
<h3 className="pl-4 text-sm text-gray-500">
{t("setting.basic.self")}
</h3>
<div className="w-full border border-gray-200 dark:border-zinc-700 p-4 rounded-lg space-y-2">
<div className="w-full flex flex-row justify-between items-center gap-2">
<span>{t("setting.basic.language")}</span>
<LocaleSelector />
</div>
<div className="w-full flex flex-row justify-between items-center gap-2">
<span>{t("setting.theme.self")}</span>
<ThemeSelector />
</div>
</div>
<OpenAIApiConfigView />
<h3 className="pl-4 text-sm text-gray-500">{t("setting.data.self")}</h3>
<div className="w-full border border-red-200 dark:border-zinc-700 p-4 rounded-lg">
<div className="w-full flex flex-row justify-between items-center gap-2">
<span>{t("setting.data.clear-all-data")}</span>
<ClearDataButton />
</div>
</div>
</div>
</Modal>
);
};
export default SettingModal;

View File

@ -0,0 +1,52 @@
import React from "react";
import { useTranslation } from "react-i18next";
import Icon from "./Icon";
import WeChatQRCodeView from "./WeChatQRCodeView";
import ClearDataButton from "./ClearDataButton";
import LocaleSelector from "./LocaleSelector";
import ThemeSelector from "./ThemeSelector";
import OpenAIApiConfigView from "./OpenAIApiConfigView";
const SettingView = () => {
const { t } = useTranslation();
return (
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
<div className="w-full flex flex-row justify-start items-start flex-wrap gap-2">
<a
href="https://discord.gg/z6kakemDjm"
className="w-auto px-4 py-2 rounded-full bg-indigo-600 text-white text-sm font-medium flex flex-row justify-center items-center hover:underline hover:shadow"
target="_blank"
>
<Icon.BsDiscord className="w-4 h-auto mr-1" />
{t("social.join-discord-channel")}
</a>
<WeChatQRCodeView />
</div>
<h3 className="pl-4 text-sm text-gray-500">{t("setting.basic.self")}</h3>
<div className="w-full border border-gray-200 dark:border-zinc-700 p-4 rounded-lg space-y-2">
<div className="w-full flex flex-row justify-between items-center gap-2">
<span>{t("setting.basic.language")}</span>
<LocaleSelector />
</div>
<div className="w-full flex flex-row justify-between items-center gap-2">
<span>{t("setting.theme.self")}</span>
<ThemeSelector />
</div>
</div>
<OpenAIApiConfigView />
<h3 className="pl-4 text-sm text-gray-500">{t("setting.data.self")}</h3>
<div className="w-full border border-red-200 dark:border-zinc-700 p-4 rounded-lg">
<div className="w-full flex flex-row justify-between items-center gap-2">
<span>{t("setting.data.clear-all-data")}</span>
<ClearDataButton />
</div>
</div>
</div>
);
};
export default SettingView;

View File

@ -10,7 +10,6 @@ import CreateConnectionModal from "../CreateConnectionModal";
interface State {
showCreateConnectionModal: boolean;
showSettingModal: boolean;
showUpdateConversationModal: boolean;
}
@ -19,7 +18,6 @@ const ConnectionList = () => {
const connectionStore = useConnectionStore();
const [state, setState] = useState<State>({
showCreateConnectionModal: false,
showSettingModal: false,
showUpdateConversationModal: false,
});
const [editConnectionModalContext, setEditConnectionModalContext] =

195
src/pages/setting/index.tsx Normal file
View File

@ -0,0 +1,195 @@
import { NextPage } from "next";
import { Fragment, useState } from "react";
import Link from "next/link";
import { Dialog, Transition } from "@headlessui/react";
import {
ArrowUturnLeftIcon,
Bars3Icon,
Cog6ToothIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import SettingView from "../../components/SettingView";
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(" ");
}
const SettingPage: NextPage = () => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const navigation = [
{ name: "Back", href: "/", icon: ArrowUturnLeftIcon, current: false },
{ name: "General", href: "/setting", icon: Cog6ToothIcon, current: true },
];
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-white">
<body class="h-full">
```
*/}
<div>
<Transition.Root show={sidebarOpen} as={Fragment}>
<Dialog
as="div"
className="relative z-50 lg:hidden"
onClose={setSidebarOpen}
>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-900/80" />
</Transition.Child>
<div className="fixed inset-0 flex">
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<Dialog.Panel className="relative mr-16 flex w-full max-w-xs flex-1">
<Transition.Child
as={Fragment}
enter="ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute left-full top-0 flex w-16 justify-center pt-5">
<button
type="button"
className="-m-2.5 p-2.5"
onClick={() => setSidebarOpen(false)}
>
<span className="sr-only">Close sidebar</span>
<XMarkIcon
className="h-6 w-6 text-white"
aria-hidden="true"
/>
</button>
</div>
</Transition.Child>
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2">
<Link className="flex pt-4 shrink-0 items-center" href="/">
<img className="w-auto" src="/chat-logo.webp" />
</Link>
<nav className="flex flex-1 flex-col">
<ul role="list" className="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" className="-mx-2 space-y-1">
{navigation.map((item) => (
<li key={item.name}>
<Link
href={item.href}
className={classNames(
item.current
? "bg-gray-50 text-indigo-600"
: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
>
<item.icon
className={classNames(
item.current
? "text-indigo-600"
: "text-gray-400 group-hover:text-indigo-600",
"h-6 w-6 shrink-0"
)}
aria-hidden="true"
/>
{item.name}
</Link>
</li>
))}
</ul>
</li>
</ul>
</nav>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
{/* Static sidebar for desktop */}
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6">
<Link className="flex pt-4 shrink-0 items-center" href="/">
<img className="" src="/chat-logo.webp" />
</Link>
<nav className="flex flex-1 flex-col">
<ul role="list" className="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" className="-mx-2 space-y-1">
{navigation.map((item) => (
<li key={item.name}>
<Link
href={item.href}
className={classNames(
item.current
? "bg-gray-50 text-indigo-600"
: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
>
<item.icon
className={classNames(
item.current
? "text-indigo-600"
: "text-gray-400 group-hover:text-indigo-600",
"h-6 w-6 shrink-0"
)}
aria-hidden="true"
/>
{item.name}
</Link>
</li>
))}
</ul>
</li>
</ul>
</nav>
</div>
</div>
<div className="sticky top-0 z-40 flex items-center gap-x-6 bg-white px-4 py-4 shadow-sm sm:px-6 lg:hidden">
<button
type="button"
className="-m-2.5 p-2.5 text-gray-700 lg:hidden"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<main className="py-10 lg:pl-72">
<div className="px-4 sm:px-6 lg:px-8">
<SettingView />
</div>
</main>
</div>
</>
);
};
export default SettingPage;