diff --git a/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx b/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx index 102200dc6cd..8340ef083c6 100644 --- a/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx +++ b/frontend/src/pages/Admin/Invitations/NewInviteModal/index.jsx @@ -1,9 +1,10 @@ import React, { useEffect, useState } from "react"; -import { X } from "@phosphor-icons/react"; +import { X, Copy, Check } from "@phosphor-icons/react"; import Admin from "@/models/admin"; import Workspace from "@/models/workspace"; +import showToast from "@/utils/toast"; -export default function NewInviteModal({ closeModal }) { +export default function NewInviteModal({ closeModal, onSuccess }) { const [invite, setInvite] = useState(null); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); @@ -18,15 +19,22 @@ export default function NewInviteModal({ closeModal }) { role: null, workspaceIds: selectedWorkspaceIds, }); - if (!!newInvite) setInvite(newInvite); + if (!!newInvite) { + setInvite(newInvite); + onSuccess(); + } setError(error); }; + const copyInviteLink = () => { if (!invite) return false; window.navigator.clipboard.writeText( `${window.location.origin}/accept-invite/${invite.code}` ); setCopied(true); + showToast("Invite link copied to clipboard", "success", { + clear: true, + }); }; const handleWorkspaceSelection = (workspaceId) => { @@ -79,12 +87,30 @@ export default function NewInviteModal({ closeModal }) {
{error &&

Error: {error}

} {invite && ( - +
+ + +
)}

After creation you will be able to copy the invite and send it @@ -126,13 +152,13 @@ export default function NewInviteModal({ closeModal }) {

)} -
+
{!invite ? ( <> @@ -145,12 +171,11 @@ export default function NewInviteModal({ closeModal }) { ) : ( )}
diff --git a/frontend/src/pages/Admin/Invitations/index.jsx b/frontend/src/pages/Admin/Invitations/index.jsx index b79c55d5fe8..d7f9d5ed28a 100644 --- a/frontend/src/pages/Admin/Invitations/index.jsx +++ b/frontend/src/pages/Admin/Invitations/index.jsx @@ -4,7 +4,6 @@ import { isMobile } from "react-device-detect"; import * as Skeleton from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; import { EnvelopeSimple } from "@phosphor-icons/react"; -import usePrefersDarkMode from "@/hooks/usePrefersDarkMode"; import Admin from "@/models/admin"; import InviteRow from "./InviteRow"; import NewInviteModal from "./NewInviteModal"; @@ -14,6 +13,18 @@ import CTAButton from "@/components/lib/CTAButton"; export default function AdminInvites() { const { isOpen, openModal, closeModal } = useModal(); + const [loading, setLoading] = useState(true); + const [invites, setInvites] = useState([]); + + const fetchInvites = async () => { + const _invites = await Admin.invites(); + setInvites(_invites); + setLoading(false); + }; + + useEffect(() => { + fetchInvites(); + }, []); return (
@@ -44,71 +55,58 @@ export default function AdminInvites() {
- + {loading ? ( + + ) : ( + + + + + + + + + + + + {invites.length === 0 ? ( + + + + ) : ( + invites.map((invite) => ( + + )) + )} + +
+ Status + + Accepted By + + Created By + + Created + + {" "} +
+ No invitations found +
+ )}
- + ); } - -function InvitationsContainer() { - const darkMode = usePrefersDarkMode(); - const [loading, setLoading] = useState(true); - const [invites, setInvites] = useState([]); - - useEffect(() => { - async function fetchInvites() { - const _invites = await Admin.invites(); - setInvites(_invites); - setLoading(false); - } - fetchInvites(); - }, []); - - if (loading) { - return ( - - ); - } - - return ( - - - - - - - - - - - - {invites.map((invite) => ( - - ))} - -
- Status - - Accepted By - - Created By - - Created - - {" "} -
- ); -} diff --git a/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx b/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx index 8ca766d49f6..8e0248b5553 100644 --- a/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx +++ b/frontend/src/pages/GeneralSettings/ApiKeys/NewApiKeyModal/index.jsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from "react"; -import { X } from "@phosphor-icons/react"; +import { X, Copy, Check } from "@phosphor-icons/react"; import Admin from "@/models/admin"; import paths from "@/utils/paths"; import { userFromStorage } from "@/utils/request"; import System from "@/models/system"; +import showToast from "@/utils/toast"; -export default function NewApiKeyModal({ closeModal }) { +export default function NewApiKeyModal({ closeModal, onSuccess }) { const [apiKey, setApiKey] = useState(null); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); @@ -17,14 +18,22 @@ export default function NewApiKeyModal({ closeModal }) { const Model = !!user ? Admin : System; const { apiKey: newApiKey, error } = await Model.generateApiKey(); - if (!!newApiKey) setApiKey(newApiKey); + if (!!newApiKey) { + setApiKey(newApiKey); + onSuccess(); + } setError(error); }; + const copyApiKey = () => { if (!apiKey) return false; window.navigator.clipboard.writeText(apiKey.secret); setCopied(true); + showToast("API key copied to clipboard", "success", { + clear: true, + }); }; + useEffect(() => { function resetStatus() { if (!copied) return false; @@ -57,12 +66,30 @@ export default function NewApiKeyModal({ closeModal }) {
{error &&

Error: {error}

} {apiKey && ( - +
+ + +
)}

Once created the API key can be used to programmatically access @@ -77,13 +104,13 @@ export default function NewApiKeyModal({ closeModal }) { Read the API documentation →

-
+
{!apiKey ? ( <> @@ -91,17 +118,16 @@ export default function NewApiKeyModal({ closeModal }) { type="submit" className="transition-all duration-300 bg-white text-black hover:opacity-60 px-4 py-2 rounded-lg text-sm" > - Create API key + Create API Key ) : ( )}
diff --git a/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx b/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx index b3bffdfcf16..9c2e3c2a61c 100644 --- a/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx +++ b/frontend/src/pages/GeneralSettings/ApiKeys/index.jsx @@ -18,6 +18,25 @@ import { useTranslation } from "react-i18next"; export default function AdminApiKeys() { const { isOpen, openModal, closeModal } = useModal(); const { t } = useTranslation(); + const [loading, setLoading] = useState(true); + const [apiKeys, setApiKeys] = useState([]); + + const fetchExistingKeys = async () => { + const user = userFromStorage(); + const Model = !!user ? Admin : System; + const { apiKeys: foundKeys } = await Model.getApiKeys(); + setApiKeys(foundKeys); + setLoading(false); + }; + + useEffect(() => { + fetchExistingKeys(); + }, []); + + const removeApiKey = (id) => { + setApiKeys((prevKeys) => prevKeys.filter((apiKey) => apiKey.id !== id)); + }; + return (
@@ -54,70 +73,62 @@ export default function AdminApiKeys() {
- + {loading ? ( + + ) : ( + + + + + + + + + + + {apiKeys.length === 0 ? ( + + + + ) : ( + apiKeys.map((apiKey) => ( + + )) + )} + +
+ {t("api.table.key")} + + {t("api.table.by")} + + {t("api.table.created")} + + {" "} +
+ No API keys found +
+ )}
- + ); } - -function ApiKeysContainer() { - const [loading, setLoading] = useState(true); - const [apiKeys, setApiKeys] = useState([]); - const { t } = useTranslation(); - - useEffect(() => { - async function fetchExistingKeys() { - const user = userFromStorage(); - const Model = !!user ? Admin : System; - const { apiKeys: foundKeys } = await Model.getApiKeys(); - setApiKeys(foundKeys); - setLoading(false); - } - fetchExistingKeys(); - }, []); - - if (loading) { - return ( - - ); - } - - return ( - - - - - - - - - - - {apiKeys.map((apiKey) => ( - - ))} - -
- {t("api.table.key")} - - {t("api.table.by")} - - {t("api.table.created")} - - {" "} -
- ); -}