From b643bdae5874de2181fd1cc73edb214666cff704 Mon Sep 17 00:00:00 2001 From: Bernt Christian Egeland Date: Sat, 3 Aug 2024 09:47:58 +0000 Subject: [PATCH 01/15] organization settings menu --- src/components/elements/inputField.tsx | 39 +++-- src/components/organization/orgNavBar.tsx | 8 +- src/locales/en/common.json | 1 + src/locales/es/common.json | 1 + src/locales/fr/common.json | 1 + src/locales/no/common.json | 1 + src/locales/pl/common.json | 1 + src/locales/zh-tw/common.json | 1 + src/locales/zh/common.json | 1 + .../organization/[orgid]/admin/index.tsx | 75 +++++++++ .../[orgid]/{ => admin}/invite/index.tsx | 0 .../admin/organization-setting/index.tsx | 138 +++++++++++++++++ .../[orgid]/{ => admin}/webhooks/index.tsx | 0 src/pages/organization/[orgid]/meta/index.tsx | 144 ------------------ src/server/api/routers/organizationRouter.ts | 3 +- 15 files changed, 251 insertions(+), 163 deletions(-) create mode 100644 src/pages/organization/[orgid]/admin/index.tsx rename src/pages/organization/[orgid]/{ => admin}/invite/index.tsx (100%) create mode 100644 src/pages/organization/[orgid]/admin/organization-setting/index.tsx rename src/pages/organization/[orgid]/{ => admin}/webhooks/index.tsx (100%) delete mode 100644 src/pages/organization/[orgid]/meta/index.tsx diff --git a/src/components/elements/inputField.tsx b/src/components/elements/inputField.tsx index fd3f1b9d..55097d7d 100644 --- a/src/components/elements/inputField.tsx +++ b/src/components/elements/inputField.tsx @@ -1,5 +1,5 @@ import { useTranslations } from "next-intl"; -import React, { ChangeEvent } from "react"; +import React, { ChangeEvent, useMemo } from "react"; import { useState, useRef, useEffect } from "react"; import Input from "~/components/elements/input"; import cn from "classnames"; @@ -82,21 +82,33 @@ const InputField = ({ const inputRef = useRef(null); const selectRef = useRef(null); + // Extract primitive values from fields + const fieldsDependency = useMemo(() => { + return fields + .map((field) => `${field.name}:${field.type}:${field.value}:${field.initialValue}`) + .join("|"); + }, [fields]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { setFormValues( - fields.reduce((acc, field) => { - let value; - if (field.type === "checkbox") { - value = !!field.value || !!field.initialValue; - } else { - value = field.value || field.initialValue || ""; - } - acc[field.name] = value; - return acc; - }, {}), + fields.reduce( + (acc, field) => { + let value; + if (field.type === "checkbox") { + value = !!field.value || !!field.initialValue; + } else { + value = field.value || field.initialValue || ""; + } + acc[field.name] = value; + return acc; + }, + {} as Record, + ), ); - }, [fields]); + }, [fieldsDependency]); + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { // When showInputs is true, focus the appropriate field based on its type if (showInputs) { @@ -106,7 +118,7 @@ const InputField = ({ inputRef.current?.focus(); } } - }, [showInputs, fields]); + }, [showInputs, fieldsDependency]); const handleEditClick = () => !disabled && setShowInputs(!showInputs); @@ -116,7 +128,6 @@ const InputField = ({ | { target: { name: string; value: string[] } }, ) => { const { name, value } = "target" in e ? e.target : e; - setFormValues((prevValues) => ({ ...prevValues, [name]: value, diff --git a/src/components/organization/orgNavBar.tsx b/src/components/organization/orgNavBar.tsx index a37310ed..e8d99d6c 100644 --- a/src/components/organization/orgNavBar.tsx +++ b/src/components/organization/orgNavBar.tsx @@ -48,7 +48,7 @@ const AdminNavMenu = ({ organization }) => {
{b("addWebhooks")}
@@ -56,7 +56,7 @@ const AdminNavMenu = ({ organization }) => {
{b("inviteUser")}
@@ -64,10 +64,10 @@ const AdminNavMenu = ({ organization }) => {
-
{b("meta")}
+
{b("settings")}
diff --git a/src/locales/en/common.json b/src/locales/en/common.json index e53aa256..236a6742 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -29,6 +29,7 @@ "userActions": "User Actions", "addWebhooks": "Add Webhooks", "options": "Options", + "settings": "Settings", "resend": "Resend" }, "commonTable": { diff --git a/src/locales/es/common.json b/src/locales/es/common.json index 35ab54f7..617a1aec 100644 --- a/src/locales/es/common.json +++ b/src/locales/es/common.json @@ -29,6 +29,7 @@ "userActions": "Acciones del usuario", "addWebhooks": "Añadir Webhooks", "options": "Opciones", + "settings": "Configuraciones", "resend": "Reenviar" }, "commonTable": { diff --git a/src/locales/fr/common.json b/src/locales/fr/common.json index 353b211e..73370fda 100644 --- a/src/locales/fr/common.json +++ b/src/locales/fr/common.json @@ -29,6 +29,7 @@ "userActions": "Gérer l'utilisateur", "addWebhooks": "Ajouter des Webhooks", "options": "Options", + "settings": "Paramètres", "resend": "Renvoyer" }, "commonTable": { diff --git a/src/locales/no/common.json b/src/locales/no/common.json index 89bd2022..e2695a52 100644 --- a/src/locales/no/common.json +++ b/src/locales/no/common.json @@ -29,6 +29,7 @@ "userActions": "Brukerhandlinger", "addWebhooks": "Legg til Webhooks", "options": "Alternativer", + "settings": "Innstillinger", "resend": "Send på nytt" }, "commonTable": { diff --git a/src/locales/pl/common.json b/src/locales/pl/common.json index 23aa8c25..bf6097f0 100644 --- a/src/locales/pl/common.json +++ b/src/locales/pl/common.json @@ -29,6 +29,7 @@ "userActions": "Akcje", "addWebhooks": "Dodaj Webhooki", "options": "Opcje", + "settings": "Ustawienia", "resend": "Wyślij ponownie" }, "commonTable": { diff --git a/src/locales/zh-tw/common.json b/src/locales/zh-tw/common.json index 5c71eb5a..58b21a77 100644 --- a/src/locales/zh-tw/common.json +++ b/src/locales/zh-tw/common.json @@ -29,6 +29,7 @@ "userActions": "使用者操作", "addWebhooks": "新增 Webhooks", "options": "選項", + "settings": "設定", "resend": "重新發送" }, "commonTable": { diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json index 00a843bb..9a9f5eff 100644 --- a/src/locales/zh/common.json +++ b/src/locales/zh/common.json @@ -29,6 +29,7 @@ "userActions": "用户操作", "addWebhooks": "添加 Webhooks", "options": "选项", + "settings": "设置", "resend": "重新发送" }, "commonTable": { diff --git a/src/pages/organization/[orgid]/admin/index.tsx b/src/pages/organization/[orgid]/admin/index.tsx new file mode 100644 index 00000000..1d857944 --- /dev/null +++ b/src/pages/organization/[orgid]/admin/index.tsx @@ -0,0 +1,75 @@ +import React, { type ReactElement } from "react"; +import { useRouter } from "next/router"; +import { LayoutOrganizationAuthenticated } from "~/components/layouts/layout"; +import { globalSiteTitle } from "~/utils/global"; +import { useTranslations } from "next-intl"; +import { getServerSideProps } from "~/server/getServerSideProps"; +import useOrganizationWebsocket from "~/hooks/useOrganizationWebsocket"; +import MetaTags from "~/components/shared/metaTags"; +import OrganizationSettings from "./organization-setting"; +import OrganizationWebhook from "./webhooks"; + +const OrganizationAdminSettings = ({ orgIds }) => { + const title = `${globalSiteTitle} - Admin Settings`; + + const router = useRouter(); + const { tab = "members" } = router.query; + const t = useTranslations("admin"); + + useOrganizationWebsocket(orgIds); + interface ITab { + name: string; + value: string; + component: ReactElement; + } + + const tabs: ITab[] = [ + { + name: "Settings", + value: "organization-setting", + component: , + }, + { + name: "Webhooks", + value: "webhook-setting", + component: , + }, + ]; + + const changeTab = async (tab: ITab) => { + await router.push({ + pathname: "/admin/organization-setting", + query: { tab: tab.value }, + }); + }; + return ( + + ); +}; + +OrganizationAdminSettings.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; +export { getServerSideProps }; +export default OrganizationAdminSettings; diff --git a/src/pages/organization/[orgid]/invite/index.tsx b/src/pages/organization/[orgid]/admin/invite/index.tsx similarity index 100% rename from src/pages/organization/[orgid]/invite/index.tsx rename to src/pages/organization/[orgid]/admin/invite/index.tsx diff --git a/src/pages/organization/[orgid]/admin/organization-setting/index.tsx b/src/pages/organization/[orgid]/admin/organization-setting/index.tsx new file mode 100644 index 00000000..0029bae2 --- /dev/null +++ b/src/pages/organization/[orgid]/admin/organization-setting/index.tsx @@ -0,0 +1,138 @@ +import React, { ReactElement } from "react"; +import { api } from "~/utils/api"; +import toast from "react-hot-toast"; +import { useModalStore } from "~/utils/store"; +import { useTranslations } from "next-intl"; +import { ErrorData } from "~/types/errorHandling"; +import { useRouter } from "next/router"; +import { LayoutOrganizationAuthenticated } from "~/components/layouts/layout"; +import { getServerSideProps } from "~/server/getServerSideProps"; +import HeadSection from "~/components/shared/metaTags"; +import { globalSiteTitle } from "~/utils/global"; +import InputField from "~/components/elements/inputField"; + +const OrganizationSettings = () => { + const router = useRouter(); + const organizationId = router.query.orgid as string; + + const b = useTranslations("commonButtons"); + const t = useTranslations("admin"); + + const { closeModal } = useModalStore((state) => state); + const { refetch: refecthAllOrg } = api.org.getAllOrg.useQuery(); + + const { data: orgData, refetch: refecthOrgById } = api.org.getOrgById.useQuery( + { + organizationId, + }, + { + enabled: !!organizationId, + }, + ); + const { mutate: updateOrg, isLoading: loadingUpdate } = api.org.updateMeta.useMutation({ + onSuccess: () => { + toast.success("Organization updated successfully"); + refecthAllOrg(); + refecthOrgById(); + closeModal(); + }, + onError: (error) => { + if ((error.data as ErrorData)?.zodError) { + const fieldErrors = (error.data as ErrorData)?.zodError.fieldErrors; + for (const field in fieldErrors) { + toast.error(`${fieldErrors[field].join(", ")}`); + } + } else if (error.message) { + toast.error(error.message); + } else { + toast.error("An unknown error occurred"); + } + }, + }); + + const pageTitle = `${globalSiteTitle} - Meta`; + return ( +
+
+

Organization Settings

+
+
+
+ { + return new Promise((resolve, reject) => + updateOrg( + { + organizationId, + ...params, + }, + { + onSuccess: () => { + resolve(true); + }, + onError: (error) => { + reject(error); + }, + }, + ), + ); + }} + /> + { + return new Promise((resolve, reject) => + updateOrg( + { + organizationId, + ...params, + }, + { + onSuccess: () => { + resolve(true); + }, + onError: (error) => { + reject(error); + }, + }, + ), + ); + }} + /> +
+ +
+ ); +}; + +OrganizationSettings.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export { getServerSideProps }; + +export default OrganizationSettings; diff --git a/src/pages/organization/[orgid]/webhooks/index.tsx b/src/pages/organization/[orgid]/admin/webhooks/index.tsx similarity index 100% rename from src/pages/organization/[orgid]/webhooks/index.tsx rename to src/pages/organization/[orgid]/admin/webhooks/index.tsx diff --git a/src/pages/organization/[orgid]/meta/index.tsx b/src/pages/organization/[orgid]/meta/index.tsx deleted file mode 100644 index 922f85bc..00000000 --- a/src/pages/organization/[orgid]/meta/index.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, { ReactElement, useEffect, useState } from "react"; -import { api } from "~/utils/api"; -import toast from "react-hot-toast"; -import { useModalStore } from "~/utils/store"; -import { useTranslations } from "next-intl"; -import { ErrorData } from "~/types/errorHandling"; -import Input from "~/components/elements/input"; -import { useRouter } from "next/router"; -import { LayoutOrganizationAuthenticated } from "~/components/layouts/layout"; -import { getServerSideProps } from "~/server/getServerSideProps"; -import HeadSection from "~/components/shared/metaTags"; -import { globalSiteTitle } from "~/utils/global"; - -const EditOrganizationMeta = () => { - const router = useRouter(); - const organizationId = router.query.orgid as string; - - const b = useTranslations("commonButtons"); - const t = useTranslations("admin"); - - const [input, setInput] = useState({ orgDescription: "", orgName: "" }); - const { closeModal } = useModalStore((state) => state); - const { refetch: refecthAllOrg } = api.org.getAllOrg.useQuery(); - - const { data: orgData, refetch: refecthOrgById } = api.org.getOrgById.useQuery( - { - organizationId, - }, - { - enabled: !!organizationId, - }, - ); - const { mutate: updateOrg } = api.org.updateMeta.useMutation({ - onSuccess: () => { - toast.success("Organization updated successfully"); - refecthAllOrg(); - refecthOrgById(); - closeModal(); - }, - onError: (error) => { - if ((error.data as ErrorData)?.zodError) { - const fieldErrors = (error.data as ErrorData)?.zodError.fieldErrors; - for (const field in fieldErrors) { - toast.error(`${fieldErrors[field].join(", ")}`); - } - } else if (error.message) { - toast.error(error.message); - } else { - toast.error("An unknown error occurred"); - } - }, - }); - useEffect(() => { - if (orgData) { - setInput({ - orgDescription: orgData.description, - orgName: orgData.orgName, - }); - } - }, [orgData]); - - const inputHandler = (e: React.ChangeEvent) => { - setInput({ - ...input, - [e.target.name]: e.target.value, - }); - }; - - const pageTitle = `${globalSiteTitle} - Meta`; - return ( -
- - -
-

Organization Meta

-
-

- Give your organization a name and description to help others understand what - it is about. -

-
-

- {t("organization.listOrganization.organizationName")} -

-
- -
-