[FEAT]: Allow user to set support email (#726)

* implement custom support email for usermenu support button

* small refactor

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2024-02-19 10:30:41 -08:00 committed by GitHub
parent b636a07e52
commit 17c1913ccc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 161 additions and 1 deletions

View File

@ -7,6 +7,7 @@
"hljs",
"Langchain",
"Milvus",
"Mintplex",
"Ollama",
"openai",
"Qdrant",

View File

@ -52,6 +52,7 @@ function UserButton() {
const { user } = useUser();
const [showMenu, setShowMenu] = useState(false);
const [showAccountSettings, setShowAccountSettings] = useState(false);
const [supportEmail, setSupportEmail] = useState("");
const mode = useLoginMode();
const menuRef = useRef();
const buttonRef = useRef();
@ -77,6 +78,18 @@ function UserButton() {
return () => document.removeEventListener("mousedown", handleClose);
}, [showMenu]);
useEffect(() => {
const fetchSupportEmail = async () => {
const supportEmail = await System.fetchSupportEmail();
if (supportEmail.email) {
setSupportEmail(`mailto:${supportEmail.email}`);
} else {
setSupportEmail(paths.mailToMintplex());
}
};
fetchSupportEmail();
}, []);
if (mode === null) return null;
return (
@ -105,7 +118,7 @@ function UserButton() {
</button>
)}
<a
href={paths.mailToMintplex()}
href={supportEmail}
className="text-white hover:bg-slate-200/20 w-full text-left px-4 py-1.5 rounded-md"
>
Support

View File

@ -5,6 +5,7 @@ import DataConnector from "./dataConnector";
const System = {
cacheKeys: {
footerIcons: "anythingllm_footer_links",
supportEmail: "anythingllm_support_email",
},
ping: async function () {
return await fetch(`${API_BASE}/ping`)
@ -225,6 +226,36 @@ const System = {
);
return { footerData: newData, error: null };
},
fetchSupportEmail: async function () {
const cache = window.localStorage.getItem(this.cacheKeys.supportEmail);
const { email, lastFetched } = cache
? safeJsonParse(cache, { email: "", lastFetched: 0 })
: { email: "", lastFetched: 0 };
if (!!email && Date.now() - lastFetched < 3_600_000)
return { email: email, error: null };
const { supportEmail, error } = await fetch(
`${API_BASE}/system/support-email`,
{
method: "GET",
cache: "no-cache",
headers: baseHeaders(),
}
)
.then((res) => res.json())
.catch((e) => {
console.log(e);
return { email: "", error: e.message };
});
if (!supportEmail || !!error) return { email: "", error: null };
window.localStorage.setItem(
this.cacheKeys.supportEmail,
JSON.stringify({ email: supportEmail, lastFetched: Date.now() })
);
return { email: supportEmail, error: null };
},
fetchLogo: async function () {
return await fetch(`${API_BASE}/system/logo`, {
method: "GET",

View File

@ -0,0 +1,94 @@
import useUser from "@/hooks/useUser";
import Admin from "@/models/admin";
import System from "@/models/system";
import showToast from "@/utils/toast";
import { useEffect, useState } from "react";
export default function SupportEmail() {
const { user } = useUser();
const [loading, setLoading] = useState(true);
const [hasChanges, setHasChanges] = useState(false);
const [supportEmail, setSupportEmail] = useState("");
const [originalEmail, setOriginalEmail] = useState("");
useEffect(() => {
const fetchSupportEmail = async () => {
const supportEmail = await System.fetchSupportEmail();
setSupportEmail(supportEmail.email || "");
setOriginalEmail(supportEmail.email || "");
setLoading(false);
};
fetchSupportEmail();
}, []);
const updateSupportEmail = async (e, newValue = null) => {
e.preventDefault();
let support_email = newValue;
if (newValue === null) {
const form = new FormData(e.target);
support_email = form.get("supportEmail");
}
const { success, error } = await Admin.updateSystemPreferences({
support_email,
});
if (!success) {
showToast(`Failed to update support email: ${error}`, "error");
return;
} else {
showToast("Successfully updated support email.", "success");
window.localStorage.removeItem(System.cacheKeys.supportEmail);
setSupportEmail(support_email);
setOriginalEmail(support_email);
setHasChanges(false);
}
};
const handleChange = (e) => {
setSupportEmail(e.target.value);
setHasChanges(true);
};
if (loading || !user?.role) return null;
return (
<form className="mb-6" onSubmit={updateSupportEmail}>
<div className="flex flex-col gap-y-2">
<h2 className="leading-tight font-medium text-white">Support Email</h2>
<p className="text-sm font-base text-white/60">
Set the support email address that shows up in the user menu while
logged into this instance.
</p>
</div>
<div className="flex items-center gap-x-4">
<input
name="supportEmail"
type="email"
className="bg-zinc-900 mt-4 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 max-w-[275px]"
placeholder="support@mycompany.com"
required={true}
autoComplete="off"
onChange={handleChange}
value={supportEmail}
/>
{originalEmail !== "" && (
<button
type="button"
onClick={(e) => updateSupportEmail(e, "")}
className="mt-4 text-white text-base font-medium hover:text-opacity-60"
>
Clear
</button>
)}
</div>
{hasChanges && (
<button
type="submit"
className="transition-all mt-6 w-fit duration-300 border border-slate-200 px-5 py-2.5 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
>
Save
</button>
)}
</form>
);
}

View File

@ -8,6 +8,7 @@ import EditingChatBubble from "@/components/EditingChatBubble";
import showToast from "@/utils/toast";
import { Plus } from "@phosphor-icons/react";
import FooterCustomization from "./FooterCustomization";
import SupportEmail from "./SupportEmail";
export default function Appearance() {
const { logo: _initLogo, setLogo: _setLogo } = useLogo();
@ -250,6 +251,7 @@ export default function Appearance() {
)}
</div>
<FooterCustomization />
<SupportEmail />
</div>
</div>
</div>

View File

@ -307,6 +307,9 @@ function adminEndpoints(app) {
footer_data:
(await SystemSettings.get({ label: "footer_data" }))?.value ||
JSON.stringify([]),
support_email:
(await SystemSettings.get({ label: "support_email" }))?.value ||
null,
};
response.status(200).json({ settings });
} catch (e) {

View File

@ -469,6 +469,21 @@ function systemEndpoints(app) {
}
});
app.get("/system/support-email", [validatedRequest], async (_, response) => {
try {
const supportEmail =
(
await SystemSettings.get({
label: "support_email",
})
)?.value ?? null;
response.status(200).json({ supportEmail: supportEmail });
} catch (error) {
console.error("Error fetching support email:", error);
response.status(500).json({ message: "Internal server error" });
}
});
app.get(
"/system/pfp/:id",
[validatedRequest, flexUserRoleValid([ROLES.all])],

View File

@ -13,6 +13,7 @@ const SystemSettings = {
"logo_filename",
"telemetry_id",
"footer_data",
"support_email",
],
validations: {
footer_data: (updates) => {