From 95679bc27f7bf964935b0236a76573893409d975 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 6 May 2024 02:17:24 +0530 Subject: [PATCH 1/3] chore: add email security dropdown and remove SMTP username and password validation. --- admin/app/ai/components/ai-config-form.tsx | 2 +- .../email/components/email-config-form.tsx | 145 +++++++++++++----- admin/components/common/controller-input.tsx | 8 +- admin/package.json | 1 + packages/ui/src/dropdowns/custom-select.tsx | 19 ++- yarn.lock | 4 +- 6 files changed, 125 insertions(+), 54 deletions(-) diff --git a/admin/app/ai/components/ai-config-form.tsx b/admin/app/ai/components/ai-config-form.tsx index 5290ed1e2..d61eb9ed9 100644 --- a/admin/app/ai/components/ai-config-form.tsx +++ b/admin/app/ai/components/ai-config-form.tsx @@ -96,7 +96,7 @@ export const InstanceAIForm: FC = (props) => {
OpenAI
If you use ChatGPT, this is for you.
-
+
{aiFormFields.map((field) => ( ; +type TEmailSecurityKeys = "EMAIL_USE_TLS" | "EMAIL_USE_SSL" | "NONE"; + +const EMAIL_SECURITY_OPTIONS: { [key in TEmailSecurityKeys]: string } = { + EMAIL_USE_TLS: "TLS", + EMAIL_USE_SSL: "SSL", + NONE: "No email security", +}; + export const InstanceEmailForm: FC = (props) => { const { config } = props; // states @@ -26,8 +34,9 @@ export const InstanceEmailForm: FC = (props) => { const { handleSubmit, watch, + setValue, control, - formState: { errors, isSubmitting }, + formState: { errors, isValid, isDirty, isSubmitting }, } = useForm({ defaultValues: { EMAIL_HOST: config["EMAIL_HOST"], @@ -35,7 +44,7 @@ export const InstanceEmailForm: FC = (props) => { EMAIL_HOST_USER: config["EMAIL_HOST_USER"], EMAIL_HOST_PASSWORD: config["EMAIL_HOST_PASSWORD"], EMAIL_USE_TLS: config["EMAIL_USE_TLS"], - // EMAIL_USE_SSL: config["EMAIL_USE_SSL"], + EMAIL_USE_SSL: config["EMAIL_USE_SSL"], EMAIL_FROM: config["EMAIL_FROM"], }, }); @@ -57,13 +66,26 @@ export const InstanceEmailForm: FC = (props) => { error: Boolean(errors.EMAIL_PORT), required: true, }, + { + key: "EMAIL_FROM", + type: "text", + label: "Sender email address", + description: + "This is the email address your users will see when getting emails from this instance. You will need to verify this address.", + placeholder: "no-reply@projectplane.so", + error: Boolean(errors.EMAIL_FROM), + required: true, + }, + ]; + + const OptionalEmailFormFields: TControllerInputFormField[] = [ { key: "EMAIL_HOST_USER", type: "text", label: "Username", placeholder: "getitdone@projectplane.so", error: Boolean(errors.EMAIL_HOST_USER), - required: true, + required: false, }, { key: "EMAIL_HOST_PASSWORD", @@ -71,17 +93,7 @@ export const InstanceEmailForm: FC = (props) => { label: "Password", placeholder: "Password", error: Boolean(errors.EMAIL_HOST_PASSWORD), - required: true, - }, - { - key: "EMAIL_FROM", - type: "text", - label: "From address", - description: - "This is the email address your users will see when getting emails from this instance. You will need to verify this address.", - placeholder: "no-reply@projectplane.so", - error: Boolean(errors.EMAIL_FROM), - required: true, + required: false, }, ]; @@ -99,11 +111,34 @@ export const InstanceEmailForm: FC = (props) => { .catch((err) => console.error(err)); }; + const useTLSValue = watch("EMAIL_USE_TLS"); + const useSSLValue = watch("EMAIL_USE_SSL"); + const emailSecurityKey: TEmailSecurityKeys = useMemo(() => { + if (useTLSValue === "1") return "EMAIL_USE_TLS"; + if (useSSLValue === "1") return "EMAIL_USE_SSL"; + return "NONE"; + }, [useTLSValue, useSSLValue]); + + const handleEmailSecurityChange = (key: TEmailSecurityKeys) => { + if (key === "EMAIL_USE_SSL") { + setValue("EMAIL_USE_TLS", "0"); + setValue("EMAIL_USE_SSL", "1"); + } + if (key === "EMAIL_USE_TLS") { + setValue("EMAIL_USE_TLS", "1"); + setValue("EMAIL_USE_SSL", "0"); + } + if (key === "NONE") { + setValue("EMAIL_USE_TLS", "0"); + setValue("EMAIL_USE_SSL", "0"); + } + }; + return (
setIsSendTestEmailModalOpen(false)} /> -
+
{emailFormFields.map((field) => ( = (props) => { required={field.required} /> ))} +
+

Email security

+ + {Object.entries(EMAIL_SECURITY_OPTIONS).map(([key, value]) => ( + + {value} + + ))} + +
-
-
-
-
- Turn TLS {Boolean(parseInt(watch("EMAIL_USE_TLS"))) ? "off" : "on"} -
-
- Use this if your email domain supports TLS. +
+
+
+
+
Authentication (optional)
+
+ We recommend setting up a username password for your SMTP server +
-
- +
+ {OptionalEmailFormFields.map((field) => ( + ( - { - Boolean(parseInt(value)) === true ? onChange("0") : onChange("1"); - }} - size="sm" - /> - )} + type={field.type} + name={field.key} + label={field.label} + description={field.description} + placeholder={field.placeholder} + error={field.error} + required={field.required} /> -
+ ))}
-
- -
diff --git a/admin/components/common/controller-input.tsx b/admin/components/common/controller-input.tsx index c386e7374..d47fe43f9 100644 --- a/admin/components/common/controller-input.tsx +++ b/admin/components/common/controller-input.tsx @@ -6,6 +6,8 @@ import { Controller, Control } from "react-hook-form"; import { Input } from "@plane/ui"; // icons import { Eye, EyeOff } from "lucide-react"; +// helpers +import { cn } from "@/helpers/common.helper"; type Props = { control: Control; @@ -51,7 +53,9 @@ export const ControllerInput: React.FC = (props) => { ref={ref} hasError={error} placeholder={placeholder} - className="w-full rounded-md font-medium" + className={cn("w-full rounded-md font-medium", { + "pr-10": type === "password", + })} /> )} /> @@ -72,7 +76,7 @@ export const ControllerInput: React.FC = (props) => { ))}
- {description &&

{description}

} + {description &&

{description}

}
); }; diff --git a/admin/package.json b/admin/package.json index e0913d094..956d39c1a 100644 --- a/admin/package.json +++ b/admin/package.json @@ -11,6 +11,7 @@ "lint": "next lint" }, "dependencies": { + "@headlessui/react": "^1.7.19", "@plane/types": "*", "@plane/ui": "*", "@tailwindcss/typography": "^0.5.9", diff --git a/packages/ui/src/dropdowns/custom-select.tsx b/packages/ui/src/dropdowns/custom-select.tsx index 2d669cc05..17933da42 100644 --- a/packages/ui/src/dropdowns/custom-select.tsx +++ b/packages/ui/src/dropdowns/custom-select.tsx @@ -67,7 +67,7 @@ const CustomSelect = (props: ICustomSelectProps) => { className={`flex items-center justify-between gap-1 text-xs ${ disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80" } ${customButtonClassName}`} - onClick={openDropdown} + onClick={isOpen ? closeDropdown : openDropdown} > {customButton} @@ -77,12 +77,17 @@ const CustomSelect = (props: ICustomSelectProps) => { - - + + + + {!isSidebarCollapsed && "Redirect to plane"} + + + + + + + +
From b9422bc94a4fd5f3ab4a4cd54751507efcac45ea Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 6 May 2024 02:27:08 +0530 Subject: [PATCH 3/3] chore: add dropdown to collapsed admin sidebar. --- .../admin-sidebar/sidebar-dropdown.tsx | 101 +++++++++++------- 1 file changed, 65 insertions(+), 36 deletions(-) diff --git a/admin/components/admin-sidebar/sidebar-dropdown.tsx b/admin/components/admin-sidebar/sidebar-dropdown.tsx index e2328d576..68212464e 100644 --- a/admin/components/admin-sidebar/sidebar-dropdown.tsx +++ b/admin/components/admin-sidebar/sidebar-dropdown.tsx @@ -9,7 +9,7 @@ import { Avatar } from "@plane/ui"; // hooks import { useTheme, useUser } from "@/hooks"; // helpers -import { API_BASE_URL } from "@/helpers/common.helper"; +import { API_BASE_URL, cn } from "@/helpers/common.helper"; // services import { AuthService } from "@/services"; @@ -32,6 +32,45 @@ export const SidebarDropdown = observer(() => { const handleSignOut = () => signOut(); + const getSidebarMenuItems = () => ( + +
+ {currentUser?.email} +
+
+ + + Switch to {resolvedTheme === "dark" ? "light" : "dark"} mode + +
+
+
+ + + + Sign out + +
+
+
+ ); + useEffect(() => { if (csrfToken === undefined) authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); @@ -45,9 +84,30 @@ export const SidebarDropdown = observer(() => { isSidebarCollapsed ? "justify-center" : "" }`} > -
- -
+ + +
+ +
+
+ {isSidebarCollapsed && ( + + {getSidebarMenuItems()} + + )} +
{!isSidebarCollapsed && (
@@ -78,38 +138,7 @@ export const SidebarDropdown = observer(() => { leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - -
- {currentUser?.email} -
-
- - - Switch to {resolvedTheme === "dark" ? "light" : "dark"} mode - -
-
-
- - - - Sign out - -
-
-
+ {getSidebarMenuItems()} )}