[REFACTOR] remove all <dialog> modals and replace with custom ModalWrapper component (#641)

* add useModal hook and ModalWrapper component that will be used to replace all <dialog> modals for better browser support

* implement useModal hook and ModalWrapper component to replace all exisiting <dialog>
This commit is contained in:
Sean Hatfield 2024-01-23 14:19:08 -08:00 committed by GitHub
parent 2f3db0e63a
commit 62da5c9933
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 732 additions and 755 deletions

View File

@ -6,7 +6,6 @@ export default function ChangeWarningModal({
onConfirm,
}) {
return (
<dialog id="confirmation-modal" className="bg-transparent outline-none">
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -44,6 +43,5 @@ export default function ChangeWarningModal({
</div>
</div>
</div>
</dialog>
);
}

View File

@ -0,0 +1,9 @@
export default function ModalWrapper({ children, isOpen }) {
if (!isOpen) return null;
return (
<div className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center z-30">
{children}
</div>
);
}

View File

@ -193,7 +193,7 @@ function AccountModal({ user, hideModal }) {
return (
<div
id="account-modal"
className="bg-black/20 fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center"
className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center"
>
<div className="relative w-[500px] max-w-2xl max-h-full bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">

View File

@ -1,9 +1,10 @@
import { memo, useState, useEffect, useRef } from "react";
import { memo, useState } from "react";
import { X } from "@phosphor-icons/react";
import { v4 } from "uuid";
import { decode as HTMLDecode } from "he";
import { CaretRight, FileText } from "@phosphor-icons/react";
import truncate from "truncate";
import ModalWrapper from "@/components/ModalWrapper";
function combineLikeSources(sources) {
const combined = {};
@ -98,27 +99,10 @@ function SkeletonLine() {
function CitationDetailModal({ source, onClose }) {
const { references, title, text } = source;
const dialogRef = useRef(null);
useEffect(() => {
if (source && dialogRef.current) {
dialogRef.current.showModal();
}
}, [source]);
const handleModalClose = () => {
if (dialogRef.current) {
dialogRef.current.close();
}
onClose();
};
return (
<dialog
ref={dialogRef}
className="bg-transparent outline-none fixed top-0 left-0 w-full h-full flex items-center justify-center z-10"
>
<div className="relative w-full max-w-2xl bg-main-gradient rounded-lg shadow border border-white/10 overflow-hidden">
<ModalWrapper isOpen={source}>
<div className="w-full max-w-2xl bg-main-gradient rounded-lg shadow border border-white/10 overflow-hidden">
<div className="relative p-6 border-b rounded-t border-gray-500/50">
<h3 className="text-xl font-semibold text-white overflow-hidden overflow-ellipsis whitespace-nowrap">
{truncate(title, 45)}
@ -129,7 +113,7 @@ function CitationDetailModal({ source, onClose }) {
</p>
)}
<button
onClick={handleModalClose}
onClick={onClose}
type="button"
className="absolute top-6 right-6 transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
>
@ -153,7 +137,7 @@ function CitationDetailModal({ source, onClose }) {
</div>
</div>
</div>
</dialog>
</ModalWrapper>
);
}

View File

@ -3,6 +3,7 @@ import Workspace from "@/models/workspace";
import LoadingChat from "./LoadingChat";
import ChatContainer from "./ChatContainer";
import paths from "@/utils/paths";
import ModalWrapper from "../ModalWrapper";
export default function WorkspaceChat({ loading, workspace }) {
const [history, setHistory] = useState([]);
@ -28,11 +29,7 @@ export default function WorkspaceChat({ loading, workspace }) {
return (
<>
{loading === false && !workspace && (
<dialog
open={true}
style={{ zIndex: 100 }}
className="fixed top-0 flex bg-black bg-opacity-50 w-full md:w-[100vw] h-full items-center justify-center"
>
<ModalWrapper isOpen={true}>
<div className="relative w-full md:max-w-2xl max-h-full bg-main-gradient rounded-lg shadow p-4">
<div className="flex flex-col gap-y-4 w-full p-6 text-center">
<p className="font-semibold text-red-500 text-xl">
@ -52,7 +49,7 @@ export default function WorkspaceChat({ loading, workspace }) {
</div>
</div>
</div>
</dialog>
</ModalWrapper>
)}
<LoadingChat />
</>

View File

@ -0,0 +1,10 @@
import { useState } from "react";
export function useModal() {
const [isOpen, setIsOpen] = useState(false);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
return { isOpen, openModal, closeModal };
}

View File

@ -2,14 +2,7 @@ import React, { useEffect, useState } from "react";
import { X } from "@phosphor-icons/react";
import Admin from "@/models/admin";
const DIALOG_ID = `new-invite-modal`;
function hideModal() {
document.getElementById(DIALOG_ID)?.close();
}
export const NewInviteModalId = DIALOG_ID;
export default function NewInviteModal() {
export default function NewInviteModal({ closeModal }) {
const [invite, setInvite] = useState(null);
const [error, setError] = useState(null);
const [copied, setCopied] = useState(false);
@ -39,7 +32,6 @@ export default function NewInviteModal() {
}, [copied]);
return (
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
<div className="relative w-[500px] max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -47,7 +39,7 @@ export default function NewInviteModal() {
Create new invite
</h3>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
@ -58,9 +50,7 @@ export default function NewInviteModal() {
<form onSubmit={handleCreate}>
<div className="p-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
{invite && (
<input
type="url"
@ -80,7 +70,7 @@ export default function NewInviteModal() {
{!invite ? (
<>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
>
@ -107,6 +97,5 @@ export default function NewInviteModal() {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -7,9 +7,12 @@ import { EnvelopeSimple } from "@phosphor-icons/react";
import usePrefersDarkMode from "@/hooks/usePrefersDarkMode";
import Admin from "@/models/admin";
import InviteRow from "./InviteRow";
import NewInviteModal, { NewInviteModalId } from "./NewInviteModal";
import NewInviteModal from "./NewInviteModal";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function AdminInvites() {
const { isOpen, openModal, closeModal } = useModal();
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
{!isMobile && <Sidebar />}
@ -23,9 +26,7 @@ export default function AdminInvites() {
<div className="items-center flex gap-x-4">
<p className="text-2xl font-semibold text-white">Invitations</p>
<button
onClick={() =>
document?.getElementById(NewInviteModalId)?.showModal()
}
onClick={openModal}
className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
>
<EnvelopeSimple className="h-4 w-4" /> Create Invite Link
@ -38,7 +39,9 @@ export default function AdminInvites() {
</div>
<InvitationsContainer />
</div>
<NewInviteModal />
<ModalWrapper isOpen={isOpen}>
<NewInviteModal closeModal={closeModal} />
</ModalWrapper>
</div>
</div>
);

View File

@ -4,14 +4,7 @@ import Admin from "@/models/admin";
import { userFromStorage } from "@/utils/request";
import { RoleHintDisplay } from "..";
const DIALOG_ID = `new-user-modal`;
function hideModal() {
document.getElementById(DIALOG_ID)?.close();
}
export const NewUserModalId = DIALOG_ID;
export default function NewUserModal() {
export default function NewUserModal({ closeModal }) {
const [error, setError] = useState(null);
const [role, setRole] = useState("default");
const handleCreate = async (e) => {
@ -28,7 +21,6 @@ export default function NewUserModal() {
const user = userFromStorage();
return (
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -36,7 +28,7 @@ export default function NewUserModal() {
Add user to instance
</h3>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
@ -102,18 +94,16 @@ export default function NewUserModal() {
</select>
<RoleHintDisplay role={role} />
</div>
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
<p className="text-white text-xs md:text-sm">
After creating a user they will need to login with their
initial login to get access.
After creating a user they will need to login with their initial
login to get access.
</p>
</div>
</div>
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
>
@ -129,6 +119,5 @@ export default function NewUserModal() {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -3,16 +3,10 @@ import { X } from "@phosphor-icons/react";
import Admin from "@/models/admin";
import { RoleHintDisplay } from "../..";
export const EditUserModalId = (user) => `edit-user-${user.id}-modal`;
export default function EditUserModal({ currentUser, user }) {
export default function EditUserModal({ currentUser, user, closeModal }) {
const [role, setRole] = useState(user.role);
const [error, setError] = useState(null);
const hideModal = () => {
document.getElementById(EditUserModalId(user)).close();
};
const handleUpdate = async (e) => {
setError(null);
e.preventDefault();
@ -28,7 +22,6 @@ export default function EditUserModal({ currentUser, user }) {
};
return (
<dialog id={EditUserModalId(user)} className="bg-transparent outline-none">
<div className="relative w-[500px] max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -36,7 +29,7 @@ export default function EditUserModal({ currentUser, user }) {
Edit {user.username}
</h3>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
@ -102,14 +95,12 @@ export default function EditUserModal({ currentUser, user }) {
</select>
<RoleHintDisplay role={role} />
</div>
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
</div>
</div>
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
>
@ -125,6 +116,5 @@ export default function EditUserModal({ currentUser, user }) {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -1,9 +1,11 @@
import { useRef, useState } from "react";
import { titleCase } from "text-case";
import Admin from "@/models/admin";
import EditUserModal, { EditUserModalId } from "./EditUserModal";
import EditUserModal from "./EditUserModal";
import { DotsThreeOutline } from "@phosphor-icons/react";
import showToast from "@/utils/toast";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
const ModMap = {
admin: ["admin", "manager", "default"],
@ -15,6 +17,7 @@ export default function UserRow({ currUser, user }) {
const rowRef = useRef(null);
const canModify = ModMap[currUser?.role || "default"].includes(user.role);
const [suspended, setSuspended] = useState(user.suspended === 1);
const { isOpen, openModal, closeModal } = useModal();
const handleSuspend = async () => {
if (
!window.confirm(
@ -65,9 +68,7 @@ export default function UserRow({ currUser, user }) {
<td className="px-6 py-4 flex items-center gap-x-6">
{canModify && (
<button
onClick={() =>
document?.getElementById(EditUserModalId(user))?.showModal()
}
onClick={openModal}
className="font-medium text-white text-opacity-80 rounded-lg hover:text-white px-2 py-1 hover:text-opacity-60 hover:bg-white hover:bg-opacity-10"
>
<DotsThreeOutline weight="fill" className="h-5 w-5" />
@ -91,7 +92,13 @@ export default function UserRow({ currUser, user }) {
)}
</td>
</tr>
<EditUserModal currentUser={currUser} user={user} />
<ModalWrapper isOpen={isOpen}>
<EditUserModal
currentUser={currUser}
user={user}
closeModal={closeModal}
/>
</ModalWrapper>
</>
);
}

View File

@ -7,9 +7,12 @@ import { UserPlus } from "@phosphor-icons/react";
import Admin from "@/models/admin";
import UserRow from "./UserRow";
import useUser from "@/hooks/useUser";
import NewUserModal, { NewUserModalId } from "./NewUserModal";
import NewUserModal from "./NewUserModal";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function AdminUsers() {
const { isOpen, openModal, closeModal } = useModal();
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
{!isMobile && <Sidebar />}
@ -23,9 +26,7 @@ export default function AdminUsers() {
<div className="items-center flex gap-x-4">
<p className="text-2xl font-semibold text-white">Users</p>
<button
onClick={() =>
document?.getElementById(NewUserModalId)?.showModal()
}
onClick={openModal}
className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
>
<UserPlus className="h-4 w-4" /> Add user
@ -39,7 +40,9 @@ export default function AdminUsers() {
</div>
<UsersContainer />
</div>
<NewUserModal />
<ModalWrapper isOpen={isOpen}>
<NewUserModal closeModal={closeModal} />
</ModalWrapper>
</div>
</div>
);

View File

@ -1,14 +1,8 @@
import React, { useState } from "react";
import { X } from "@phosphor-icons/react";
import Admin from "@/models/admin";
const DIALOG_ID = `new-workspace-modal`;
function hideModal() {
document.getElementById(DIALOG_ID)?.close();
}
export const NewWorkspaceModalId = DIALOG_ID;
export default function NewWorkspaceModal() {
export default function NewWorkspaceModal({ closeModal }) {
const [error, setError] = useState(null);
const handleCreate = async (e) => {
setError(null);
@ -20,7 +14,6 @@ export default function NewWorkspaceModal() {
};
return (
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
<div className="relative w-[500px] max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
@ -28,7 +21,7 @@ export default function NewWorkspaceModal() {
Create new workspace
</h3>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
@ -56,9 +49,7 @@ export default function NewWorkspaceModal() {
autoComplete="off"
/>
</div>
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
<p className="text-white text-opacity-60 text-xs md:text-sm">
After creating this workspace only admins will be able to see
it. You can add users after it has been created.
@ -67,7 +58,7 @@ export default function NewWorkspaceModal() {
</div>
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-600">
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
>
@ -83,6 +74,5 @@ export default function NewWorkspaceModal() {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -6,13 +6,13 @@ import { titleCase } from "text-case";
export const EditWorkspaceUsersModalId = (workspace) =>
`edit-workspace-${workspace.id}-modal`;
export default function EditWorkspaceUsersModal({ workspace, users }) {
export default function EditWorkspaceUsersModal({
workspace,
users,
closeModal,
}) {
const [error, setError] = useState(null);
const hideModal = () => {
document.getElementById(EditWorkspaceUsersModalId(workspace)).close();
};
const handleUpdate = async (e) => {
setError(null);
e.preventDefault();
@ -35,10 +35,6 @@ export default function EditWorkspaceUsersModal({ workspace, users }) {
};
return (
<dialog
id={EditWorkspaceUsersModalId(workspace)}
className="bg-transparent outline-none"
>
<div className="relative w-[500px] max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -46,7 +42,7 @@ export default function EditWorkspaceUsersModal({ workspace, users }) {
Edit {workspace.name}
</h3>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
@ -128,14 +124,12 @@ export default function EditWorkspaceUsersModal({ workspace, users }) {
Deselect All
</button>
</div>
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
</div>
</div>
<div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
>
@ -151,6 +145,5 @@ export default function EditWorkspaceUsersModal({ workspace, users }) {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -1,13 +1,14 @@
import { useRef } from "react";
import Admin from "@/models/admin";
import paths from "@/utils/paths";
import EditWorkspaceUsersModal, {
EditWorkspaceUsersModalId,
} from "./EditWorkspaceUsersModal";
import EditWorkspaceUsersModal from "./EditWorkspaceUsersModal";
import { DotsThreeOutline, LinkSimple, Trash } from "@phosphor-icons/react";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function WorkspaceRow({ workspace, users }) {
const rowRef = useRef(null);
const { isOpen, openModal, closeModal } = useModal();
const handleDelete = async () => {
if (
!window.confirm(
@ -32,6 +33,7 @@ export default function WorkspaceRow({ workspace, users }) {
<a
href={paths.workspace.chat(workspace.slug)}
target="_blank"
rel="noreferrer"
className="text-white flex items-center hover:underline"
>
<LinkSimple className="mr-2 w-5 h-5" /> {workspace.slug}
@ -41,11 +43,7 @@ export default function WorkspaceRow({ workspace, users }) {
<td className="px-6 py-4">{workspace.createdAt}</td>
<td className="px-6 py-4 flex items-center gap-x-6">
<button
onClick={() =>
document
?.getElementById(EditWorkspaceUsersModalId(workspace))
?.showModal()
}
onClick={openModal}
className="font-medium rounded-lg hover:text-white hover:text-opacity-60 px-2 py-1 hover:bg-white hover:bg-opacity-10"
>
<DotsThreeOutline weight="fill" className="h-5 w-5" />
@ -58,7 +56,13 @@ export default function WorkspaceRow({ workspace, users }) {
</button>
</td>
</tr>
<EditWorkspaceUsersModal workspace={workspace} users={users} />
<ModalWrapper isOpen={isOpen}>
<EditWorkspaceUsersModal
workspace={workspace}
users={users}
closeModal={closeModal}
/>
</ModalWrapper>
</>
);
}

View File

@ -7,9 +7,12 @@ import { BookOpen } from "@phosphor-icons/react";
import usePrefersDarkMode from "@/hooks/usePrefersDarkMode";
import Admin from "@/models/admin";
import WorkspaceRow from "./WorkspaceRow";
import NewWorkspaceModal, { NewWorkspaceModalId } from "./NewWorkspaceModal";
import NewWorkspaceModal from "./NewWorkspaceModal";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function AdminWorkspaces() {
const { isOpen, openModal, closeModal } = useModal();
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
{!isMobile && <Sidebar />}
@ -25,9 +28,7 @@ export default function AdminWorkspaces() {
Instance workspaces
</p>
<button
onClick={() =>
document?.getElementById(NewWorkspaceModalId)?.showModal()
}
onClick={openModal}
className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
>
<BookOpen className="h-4 w-4" /> New Workspace
@ -40,7 +41,9 @@ export default function AdminWorkspaces() {
</div>
<WorkspacesContainer />
</div>
<NewWorkspaceModal />
<ModalWrapper isOpen={isOpen}>
<NewWorkspaceModal closeModal={closeModal} />
</ModalWrapper>
</div>
</div>
);

View File

@ -5,14 +5,7 @@ import paths from "@/utils/paths";
import { userFromStorage } from "@/utils/request";
import System from "@/models/system";
const DIALOG_ID = `new-api-key-modal`;
function hideModal() {
document.getElementById(DIALOG_ID)?.close();
}
export const NewApiKeyModalId = DIALOG_ID;
export default function NewApiKeyModal() {
export default function NewApiKeyModal({ closeModal }) {
const [apiKey, setApiKey] = useState(null);
const [error, setError] = useState(null);
const [copied, setCopied] = useState(false);
@ -43,7 +36,6 @@ export default function NewApiKeyModal() {
}, [copied]);
return (
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
<div className="relative w-[500px] max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -51,7 +43,7 @@ export default function NewApiKeyModal() {
Create new API key
</h3>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
@ -62,9 +54,7 @@ export default function NewApiKeyModal() {
<form onSubmit={handleCreate}>
<div className="p-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
{apiKey && (
<input
type="text"
@ -74,12 +64,13 @@ export default function NewApiKeyModal() {
/>
)}
<p className="text-white text-xs md:text-sm">
Once created the API key can be used to programmatically
access and configure this AnythingLLM instance.
Once created the API key can be used to programmatically access
and configure this AnythingLLM instance.
</p>
<a
href={paths.apiDocs()}
target="_blank"
rel="noreferrer"
className="text-blue-400 hover:underline"
>
Read the API documentation &rarr;
@ -90,7 +81,7 @@ export default function NewApiKeyModal() {
{!apiKey ? (
<>
<button
onClick={hideModal}
onClick={closeModal}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-stone-900 transition-all duration-300"
>
@ -117,6 +108,5 @@ export default function NewApiKeyModal() {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -6,12 +6,15 @@ import "react-loading-skeleton/dist/skeleton.css";
import { PlusCircle } from "@phosphor-icons/react";
import Admin from "@/models/admin";
import ApiKeyRow from "./ApiKeyRow";
import NewApiKeyModal, { NewApiKeyModalId } from "./NewApiKeyModal";
import NewApiKeyModal from "./NewApiKeyModal";
import paths from "@/utils/paths";
import { userFromStorage } from "@/utils/request";
import System from "@/models/system";
import ModalWrapper from "@/components/ModalWrapper";
import { useModal } from "@/hooks/useModal";
export default function AdminApiKeys() {
const { isOpen, openModal, closeModal } = useModal();
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
{!isMobile && <Sidebar />}
@ -25,9 +28,7 @@ export default function AdminApiKeys() {
<div className="items-center flex gap-x-4">
<p className="text-2xl font-semibold text-white">API Keys</p>
<button
onClick={() =>
document?.getElementById(NewApiKeyModalId)?.showModal()
}
onClick={openModal}
className="border border-slate-200 px-4 py-1 rounded-lg text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800"
>
<PlusCircle className="h-4 w-4" /> Generate New API Key
@ -40,6 +41,7 @@ export default function AdminApiKeys() {
<a
href={paths.apiDocs()}
target="_blank"
rel="noreferrer"
className="text-sm font-base text-blue-300 hover:underline"
>
Read the API documentation &rarr;
@ -47,7 +49,9 @@ export default function AdminApiKeys() {
</div>
<ApiKeysContainer />
</div>
<NewApiKeyModal />
<ModalWrapper isOpen={isOpen}>
<NewApiKeyModal closeModal={closeModal} />
</ModalWrapper>
</div>
</div>
);

View File

@ -2,9 +2,22 @@ import { useRef } from "react";
import truncate from "truncate";
import { X, Trash } from "@phosphor-icons/react";
import System from "@/models/system";
import ModalWrapper from "@/components/ModalWrapper";
import { useModal } from "@/hooks/useModal";
export default function ChatRow({ chat }) {
const rowRef = useRef(null);
const {
isOpen: isPromptOpen,
openModal: openPromptModal,
closeModal: closePromptModal,
} = useModal();
const {
isOpen: isResponseOpen,
openModal: openResponseModal,
closeModal: closeResponseModal,
} = useModal();
const handleDelete = async () => {
if (
!window.confirm(
@ -30,17 +43,13 @@ export default function ChatRow({ chat }) {
</td>
<td className="px-6 py-4">{chat.workspace?.name}</td>
<td
onClick={() => {
document.getElementById(`chat-${chat.id}-prompt`)?.showModal();
}}
onClick={openPromptModal}
className="px-6 py-4 border-transparent cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
>
{truncate(chat.prompt, 40)}
</td>
<td
onClick={() => {
document.getElementById(`chat-${chat.id}-response`)?.showModal();
}}
onClick={openResponseModal}
className="px-6 py-4 cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
>
{truncate(JSON.parse(chat.response)?.text, 40)}
@ -55,31 +64,28 @@ export default function ChatRow({ chat }) {
</button>
</td>
</tr>
<TextPreview text={chat.prompt} modalName={`chat-${chat.id}-prompt`} />
<ModalWrapper isOpen={isPromptOpen}>
<TextPreview text={chat.prompt} closeModal={closePromptModal} />
</ModalWrapper>
<ModalWrapper isOpen={isResponseOpen}>
<TextPreview
text={JSON.parse(chat.response)?.text}
modalName={`chat-${chat.id}-response`}
closeModal={closeResponseModal}
/>
</ModalWrapper>
</>
);
}
function hideModal(modalName) {
document.getElementById(modalName)?.close();
}
const TextPreview = ({ text, modalName }) => {
const TextPreview = ({ text, closeModal }) => {
return (
<dialog id={modalName} className="bg-transparent outline-none w-full">
<div className="relative w-full md:max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
<h3 className="text-xl font-semibold text-white">Viewing Text</h3>
<button
onClick={() => hideModal(modalName)}
onClick={closeModal}
type="button"
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
data-modal-hide="staticModal"
>
<X className="text-gray-300 text-lg" />
</button>
@ -91,6 +97,5 @@ const TextPreview = ({ text, modalName }) => {
</div>
</div>
</div>
</dialog>
);
};

View File

@ -15,6 +15,8 @@ import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions";
import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions";
import EmbedderItem from "@/components/EmbeddingSelection/EmbedderItem";
import { MagnifyingGlass } from "@phosphor-icons/react";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function GeneralEmbeddingPreference() {
const [saving, setSaving] = useState(false);
@ -25,6 +27,7 @@ export default function GeneralEmbeddingPreference() {
const [searchQuery, setSearchQuery] = useState("");
const [filteredEmbedders, setFilteredEmbedders] = useState([]);
const [selectedEmbedder, setSelectedEmbedder] = useState(null);
const { isOpen, openModal, closeModal } = useModal();
const handleSubmit = async (e) => {
e.preventDefault();
@ -33,7 +36,7 @@ export default function GeneralEmbeddingPreference() {
hasChanges &&
hasEmbeddings
) {
document.getElementById("confirmation-modal")?.showModal();
openModal();
} else {
await handleSaveSettings();
}
@ -56,7 +59,7 @@ export default function GeneralEmbeddingPreference() {
setHasChanges(false);
}
setSaving(false);
document.getElementById("confirmation-modal")?.close();
closeModal();
};
const updateChoice = (selection) => {
@ -116,11 +119,13 @@ export default function GeneralEmbeddingPreference() {
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
<ModalWrapper isOpen={isOpen}>
<ChangeWarningModal
warningText=" Switching the embedder may affect previously embedded documents and future similarity search results."
onClose={() => document.getElementById("confirmation-modal")?.close()}
warningText="Switching the vector database will ignore previously embedded documents and future similarity search results. They will need to be re-added to each workspace."
onClose={closeModal}
onConfirm={handleSaveSettings}
/>
</ModalWrapper>
{!isMobile && <Sidebar />}
{loading ? (
<div

View File

@ -21,6 +21,8 @@ import WeaviateDBOptions from "@/components/VectorDBSelection/WeaviateDBOptions"
import VectorDBItem from "@/components/VectorDBSelection/VectorDBItem";
import MilvusDBOptions from "@/components/VectorDBSelection/MilvusDBOptions";
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
import { useModal } from "@/hooks/useModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function GeneralVectorDatabase() {
const [saving, setSaving] = useState(false);
@ -31,6 +33,7 @@ export default function GeneralVectorDatabase() {
const [searchQuery, setSearchQuery] = useState("");
const [filteredVDBs, setFilteredVDBs] = useState([]);
const [selectedVDB, setSelectedVDB] = useState(null);
const { isOpen, openModal, closeModal } = useModal();
useEffect(() => {
async function fetchKeys() {
@ -107,7 +110,7 @@ export default function GeneralVectorDatabase() {
const handleSubmit = async (e) => {
e.preventDefault();
if (selectedVDB !== settings?.VectorDB && hasChanges && hasEmbeddings) {
document.getElementById("confirmation-modal")?.showModal();
openModal();
} else {
await handleSaveSettings();
}
@ -130,7 +133,7 @@ export default function GeneralVectorDatabase() {
setHasChanges(false);
}
setSaving(false);
document.getElementById("confirmation-modal")?.close();
closeModal();
};
useEffect(() => {
@ -142,11 +145,13 @@ export default function GeneralVectorDatabase() {
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
<ModalWrapper isOpen={isOpen}>
<ChangeWarningModal
warningText="Switching the vector database will ignore previously embedded documents and future similarity search results. They will need to be re-added to each workspace."
onClose={() => document.getElementById("confirmation-modal")?.close()}
onClose={closeModal}
onConfirm={handleSaveSettings}
/>
</ModalWrapper>
{!isMobile && <Sidebar />}
{loading ? (
<div

View File

@ -31,7 +31,6 @@ export default function NewUserModal() {
};
return (
<dialog open={true} className="bg-transparent outline-none">
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-main-gradient rounded-lg shadow">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
@ -76,12 +75,10 @@ export default function NewUserModal() {
autoComplete="off"
/>
</div>
{error && (
<p className="text-red-400 text-sm">Error: {error}</p>
)}
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
<p className="text-slate-200 text-xs md:text-sm">
After creating your account you will be able to login with
these credentials and start using workspaces.
After creating your account you will be able to login with these
credentials and start using workspaces.
</p>
</div>
</div>
@ -96,6 +93,5 @@ export default function NewUserModal() {
</form>
</div>
</div>
</dialog>
);
}

View File

@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
import { FullScreenLoader } from "@/components/Preloader";
import Invite from "@/models/invite";
import NewUserModal from "./NewUserModal";
import ModalWrapper from "@/components/ModalWrapper";
export default function InvitePage() {
const { code } = useParams();
@ -47,7 +48,9 @@ export default function InvitePage() {
return (
<div className="w-screen h-screen overflow-hidden bg-sidebar flex items-center justify-center">
<ModalWrapper isOpen={true}>
<NewUserModal />
</ModalWrapper>
</div>
);
}