-
+
Slash
{profile.plan === PlanType.PRO && (
diff --git a/frontend/web/src/components/Logo.tsx b/frontend/web/src/components/Logo.tsx
new file mode 100644
index 0000000..b2bff10
--- /dev/null
+++ b/frontend/web/src/components/Logo.tsx
@@ -0,0 +1,23 @@
+import classNames from "classnames";
+import { useWorkspaceStore } from "@/stores";
+import Icon from "./Icon";
+
+interface Props {
+ className?: string;
+}
+
+const Logo = ({ className }: Props) => {
+ const workspaceStore = useWorkspaceStore();
+ const branding = workspaceStore.setting.branding ? new TextDecoder().decode(workspaceStore.setting.branding) : "";
+ return (
+
+ {branding ? (
+

+ ) : (
+
+ )}
+
+ );
+};
+
+export default Logo;
diff --git a/frontend/web/src/components/setting/WorkspaceSection.tsx b/frontend/web/src/components/setting/WorkspaceSection.tsx
index 86a9a4c..0a7887e 100644
--- a/frontend/web/src/components/setting/WorkspaceSection.tsx
+++ b/frontend/web/src/components/setting/WorkspaceSection.tsx
@@ -1,5 +1,5 @@
import { Button, Option, Select, Textarea } from "@mui/joy";
-import { isEqual } from "lodash-es";
+import { head, isEqual } from "lodash-es";
import { useRef, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
@@ -7,6 +7,7 @@ import { workspaceServiceClient } from "@/grpcweb";
import { useWorkspaceStore } from "@/stores";
import { Visibility } from "@/types/proto/api/v1/common";
import { WorkspaceSetting } from "@/types/proto/api/v1/workspace_service";
+import Icon from "../Icon";
const getDefaultVisibility = (visibility?: Visibility) => {
if (!visibility || [Visibility.VISIBILITY_UNSPECIFIED, Visibility.UNRECOGNIZED].includes(visibility)) {
@@ -16,12 +17,32 @@ const getDefaultVisibility = (visibility?: Visibility) => {
return visibility;
};
+const convertFileToBase64 = (file: File) =>
+ new Promise
((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => resolve(reader.result as string);
+ reader.onerror = (error) => reject(error);
+ });
+
const WorkspaceSection = () => {
const { t } = useTranslation();
const workspaceStore = useWorkspaceStore();
const [workspaceSetting, setWorkspaceSetting] = useState(workspaceStore.setting);
const originalWorkspaceSetting = useRef(workspaceStore.setting);
const allowSave = !isEqual(originalWorkspaceSetting.current, workspaceSetting);
+ const branding = workspaceSetting.branding ? new TextDecoder().decode(workspaceSetting.branding) : "";
+
+ const onBrandingChange = async (event: React.ChangeEvent) => {
+ const files: File[] = Array.from(event.target.files || []);
+ const file = head(files);
+ if (!file) {
+ return;
+ }
+
+ const base64 = await convertFileToBase64(file);
+ setWorkspaceSetting({ ...workspaceSetting, branding: new TextEncoder().encode(base64) });
+ };
const handleCustomStyleChange = async (value: string) => {
setWorkspaceSetting({
@@ -39,6 +60,9 @@ const WorkspaceSection = () => {
const handleSaveWorkspaceSetting = async () => {
const updateMask: string[] = [];
+ if (!isEqual(originalWorkspaceSetting.current.branding, workspaceSetting.branding)) {
+ updateMask.push("branding");
+ }
if (!isEqual(originalWorkspaceSetting.current.customStyle, workspaceSetting.customStyle)) {
updateMask.push("custom_style");
}
@@ -70,6 +94,26 @@ const WorkspaceSection = () => {
{t("settings.workspace.self")}
+
+
+
Custom branding
+
Recommand logo ratio: 1:1
+
+
+ {branding ? (
+
+

+
setWorkspaceSetting({ ...workspaceSetting, branding: new TextEncoder().encode("") })}
+ />
+
+ ) : (
+
+ )}
+
+
+
{t("settings.workspace.default-visibility")}
diff --git a/frontend/web/src/pages/SignIn.tsx b/frontend/web/src/pages/SignIn.tsx
index d864135..27f882d 100644
--- a/frontend/web/src/pages/SignIn.tsx
+++ b/frontend/web/src/pages/SignIn.tsx
@@ -3,7 +3,7 @@ import React, { FormEvent, useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
-import Icon from "@/components/Icon";
+import Logo from "@/components/Logo";
import { authServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo";
@@ -64,7 +64,7 @@ const SignIn: React.FC = () => {