From 6e44d9ea2e673402dbcaa24d8fb1f8690bcddd87 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 3 Jun 2025 18:52:17 +0800 Subject: [PATCH] [Mail] Copied. Now can send + set. Mail template need to update. --- package.json | 1 + src/app/(main)/settings/mail/page.tsx | 41 +++ src/app/api/mail/actions.ts | 50 +++ src/app/api/mail/index.ts | 42 +++ src/components/MailField/MailField.css | 61 ++++ src/components/MailField/MailField.tsx | 90 +++++ src/components/MailField/MailFieldWrapper.tsx | 21 ++ src/components/MailField/MailToolbar.tsx | 319 ++++++++++++++++++ src/components/MailField/index.ts | 1 + src/components/MailSetting/MailSetting.tsx | 197 +++++++++++ .../MailSetting/MailSettingLoading.tsx | 38 +++ .../MailSetting/MailSettingWrapper.tsx | 40 +++ src/components/MailSetting/SettingDetails.tsx | 143 ++++++++ .../MailSetting/TimesheetMailDetails.tsx | 96 ++++++ src/components/MailSetting/index.ts | 1 + .../NavigationContent/NavigationContent.tsx | 5 + 16 files changed, 1146 insertions(+) create mode 100644 src/app/(main)/settings/mail/page.tsx create mode 100644 src/app/api/mail/actions.ts create mode 100644 src/app/api/mail/index.ts create mode 100644 src/components/MailField/MailField.css create mode 100644 src/components/MailField/MailField.tsx create mode 100644 src/components/MailField/MailFieldWrapper.tsx create mode 100644 src/components/MailField/MailToolbar.tsx create mode 100644 src/components/MailField/index.ts create mode 100644 src/components/MailSetting/MailSetting.tsx create mode 100644 src/components/MailSetting/MailSettingLoading.tsx create mode 100644 src/components/MailSetting/MailSettingWrapper.tsx create mode 100644 src/components/MailSetting/SettingDetails.tsx create mode 100644 src/components/MailSetting/TimesheetMailDetails.tsx create mode 100644 src/components/MailSetting/index.ts diff --git a/package.json b/package.json index 8687e20..dc9d7f4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@mui/material-nextjs": "^5.15.0", "@mui/x-data-grid": "^6.18.7", "@mui/x-date-pickers": "^6.18.7", + "@tiptap/react": "^2.12.0", "@unly/universal-language-detector": "^2.0.3", "apexcharts": "^3.45.2", "axios": "^1.9.0", diff --git a/src/app/(main)/settings/mail/page.tsx b/src/app/(main)/settings/mail/page.tsx new file mode 100644 index 0000000..6eefa37 --- /dev/null +++ b/src/app/(main)/settings/mail/page.tsx @@ -0,0 +1,41 @@ +import { getServerI18n } from "@/i18n"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { Metadata } from "next"; +import { Suspense } from "react"; +import { I18nProvider } from "@/i18n"; +// import { fetchUserAbilities } from "@/app/utils/fetchUtil"; +import { preloadMails } from "@/app/api/mail"; +import MailSetting from "@/components/MailSetting"; + +export const metadata: Metadata = { + title: "Mail", +}; + +const Customer: React.FC = async () => { + const { t } = await getServerI18n("mail"); + preloadMails(); + // const abilities = await fetchUserAbilities() + + return ( + <> + + + {t("Mail")} + + + + }> + + + + + ); +}; + +export default Customer; \ No newline at end of file diff --git a/src/app/api/mail/actions.ts b/src/app/api/mail/actions.ts new file mode 100644 index 0000000..eb6a7a1 --- /dev/null +++ b/src/app/api/mail/actions.ts @@ -0,0 +1,50 @@ +"use server"; + +import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { MailSetting } from "."; + +export interface MailSave { + settings: MailSetting[]; + // template: MailTemplate; +} + +export const saveMail = async (data: MailSave) => { + return serverFetchJson(`${BASE_API_URL}/mails/save`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; + +export const testSendMail = async () => { + return serverFetchWithNoContent(`${BASE_API_URL}/mails/test-send`, { + method: "GET", + // body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; + +export const testEveryone = async () => { + return serverFetchWithNoContent(`${BASE_API_URL}/mails/testEveryone`, { + method: "GET", + // body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; + +export const test7th = async () => { + return serverFetchWithNoContent(`${BASE_API_URL}/mails/test7th`, { + method: "GET", + // body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; + +export const test15th = async () => { + return serverFetchWithNoContent(`${BASE_API_URL}/mails/test15th`, { + method: "GET", + // body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; \ No newline at end of file diff --git a/src/app/api/mail/index.ts b/src/app/api/mail/index.ts new file mode 100644 index 0000000..0ab8e7e --- /dev/null +++ b/src/app/api/mail/index.ts @@ -0,0 +1,42 @@ +import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { cache } from "react"; + +export interface MailSMTP { + host: string; + port: number; + username: string; + password: string; +} + +export interface MailSetting { + id: number; + name: string; + value: string; + category: string; + type: string; +} + +// export interface MailTemplate { +// cc?: string; +// bcc?: string; +// subject?: string; +// template?: string; +// } + +export const preloadMails = () => { + fetchMailSetting(); + // fetchMailTimesheetTemplate(); +}; + +export const fetchMailSetting = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/mails/setting`, { + next: { tags: ["mailSetting"] }, + }); +}); + +// export const fetchMailTimesheetTemplate = cache(async () => { +// return serverFetchJson(`${BASE_API_URL}/mails/timesheet-template`, { +// next: { tags: ["mailTimesheetTemplate"] }, +// }); +// }); diff --git a/src/components/MailField/MailField.css b/src/components/MailField/MailField.css new file mode 100644 index 0000000..bfa2515 --- /dev/null +++ b/src/components/MailField/MailField.css @@ -0,0 +1,61 @@ +/* Root styles */ +:not(.tiptap-error) .tiptap { + padding-left: 15px; + padding-right: 15px; + background-color: transparent; + border-radius: 8px; + border-style: solid; + border-width: 1px; + overflow: hidden; + border-color: #e0e0e0; + /* palette.neutral[200] */ + transition: border-color 0.3s, box-shadow 0.3s; + /* Assuming muiTheme.transitions.create(["border-color", "box-shadow"]) translates to 0.3s for both */ +} + +.tiptap-error { + background-color: transparent; + border-radius: 8px; + border-style: solid; + border-width: 1px; + overflow: hidden; + /* palette.neutral[200] */ + transition: border-color 0.3s, box-shadow 0.3s; + + border-color: #F04438; + /* palette.primary.main */ + box-shadow: #F04438 0 0 0 2px; + /* palette.primary.main */ +} + +.tiptap:hover { + background-color: #f5f5f5; + /* palette.action.hover */ +} + +.tiptap::before { + display: none; +} + +.tiptap::after { + display: none; +} + +:not(.tiptap-error) > .tiptap:focus { + background-color: transparent; + border-color: #8dba00; + /* palette.primary.main */ + box-shadow: #8dba00 0 0 0 2px; + /* palette.primary.main */ +} + +.ProseMirror:focus { + outline: none; +} + +/* Input styles */ +/* .tiptap-input { + font-size: 14px; + font-weight: 500; + line-height: 12px; +} */ \ No newline at end of file diff --git a/src/components/MailField/MailField.tsx b/src/components/MailField/MailField.tsx new file mode 100644 index 0000000..915e3ec --- /dev/null +++ b/src/components/MailField/MailField.tsx @@ -0,0 +1,90 @@ +"use client"; + +import "./MailField.css" +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' +import Underline from '@tiptap/extension-underline' +import { useEditor, EditorContent, Extension } from "@tiptap/react" +import StarterKit from "@tiptap/starter-kit" +import MailToolbar from "./MailToolbar"; +import { Grid } from "@mui/material"; +import Highlight from '@tiptap/extension-highlight' +import { Color } from '@tiptap/extension-color' +import ListItem from "@tiptap/extension-list-item"; +import TextStyle from "@tiptap/extension-text-style"; +import TextAlign from '@tiptap/extension-text-align' + +interface Props { + content?: string, + onChange?: (richText: string) => void, + error?: boolean, +} + +const MailField: React.FC = ({ + content, + onChange, + error +}) => { + + const TAB_CHAR = '\u0009'; + + const TabHandler = Extension.create({ + name: 'tabHandler', + addKeyboardShortcuts() { + return { + Tab: ({ editor }) => { + // Sinks a list item / inserts a tab character + editor + .chain() + .sinkListItem('listItem') + .command(({ tr }) => { + tr.insertText(TAB_CHAR); + return true; + }) + .run(); + // Prevent default behavior (losing focus) + return true; + }, + }; + }, + }); + + const editor = useEditor({ + extensions: [ + StarterKit.configure(), + Document, + Paragraph, + Text, + TextStyle, + TextAlign.configure({ + types: ['heading', 'paragraph'] + }), + Underline, + Highlight.configure({ multicolor: true }), + Color, + ListItem, + TabHandler + ], + content: content, + onUpdate({ editor }) { + if (onChange) { + onChange(editor.getHTML()) + } + console.log(editor.getHTML()) + }, + }) + + return ( + + + + + + + + + ); +}; + +export default MailField; \ No newline at end of file diff --git a/src/components/MailField/MailFieldWrapper.tsx b/src/components/MailField/MailFieldWrapper.tsx new file mode 100644 index 0000000..e5d2bf3 --- /dev/null +++ b/src/components/MailField/MailFieldWrapper.tsx @@ -0,0 +1,21 @@ +"use client"; + +import React from "react"; +import MailField from "./MailField"; + +export interface Props { + content?: string, + onChange?: (richText: string) => void, + error?: boolean, +} + +const TransferListWrapper: React.FC = ({ + content, + onChange, + error +}) => { + + return ; +}; + +export default TransferListWrapper; diff --git a/src/components/MailField/MailToolbar.tsx b/src/components/MailField/MailToolbar.tsx new file mode 100644 index 0000000..efaadbe --- /dev/null +++ b/src/components/MailField/MailToolbar.tsx @@ -0,0 +1,319 @@ +"use client"; + +import { Button, ButtonGroup, Grid, IconButton, ToggleButton, ToggleButtonGroup } from "@mui/material"; +import "./MailField.css" +import { useEditor, EditorContent, Editor } from "@tiptap/react" +import FormatBoldIcon from '@mui/icons-material/FormatBold'; +import React, { useCallback } from "react"; +import { FormatItalic, FormatUnderlined } from "@mui/icons-material"; +import { MuiColorInput, MuiColorInputColors, MuiColorInputValue } from 'mui-color-input' +import BorderColorIcon from '@mui/icons-material/BorderColor'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import FormatColorTextIcon from '@mui/icons-material/FormatColorText'; +import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft'; +import FormatAlignJustifyIcon from '@mui/icons-material/FormatAlignJustify'; +import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight'; + +interface Props { + editor: Editor | null; +} + +const colorInputSx = { + width: 150, + height: 25, + ".MuiInputBase-colorPrimary": { + margin: -1, + borderRadius: "0px 5px 5px 0px", + borderColor: "rgba(0, 0, 0, 0)", + backgroundColor: "rgba(0, 0, 0, 0)", + }, + ".Mui-focused": { + borderColor: "rgba(0, 0, 0, 0)", + }, + ".MuiColorInput-Button": { + marginBottom: 2, + borderColor: "rgba(0, 0, 0, 0)", + backgroundColor: "rgba(0, 0, 0, 0)", + }, + ".MuiInputBase-input": { + marginBottom: 1.5, + }, +} + +const fontFamily = [ + { + label: 'Arial', + value: 'Arial', + }, + { + label: 'Times New Roman', + value: 'Times New Roman', + }, + { + label: 'Courier New', + value: 'Courier New', + }, + { + label: 'Georgia', + value: 'Georgia', + }, + { + + } +] + +const MailToolbar: React.FC = ({ + editor +}) => { + + if (editor == null) { + return null + } + + const [fontStyle, setFontStyle] = React.useState(() => ["alignLeft"]); + const [colorHighlightValue, setColorHighlightValue] = React.useState("red"); + const [colorTextValue, setColorTextValue] = React.useState("black"); + const colorHighlightValueInputRef = React.useRef(null); + const colorTextValueInputRef = React.useRef(null); + + const handleFontStyle = useCallback(( + event: React.MouseEvent, + newFontStyles: string[], + ) => { + + setFontStyle((prev) => { + const id = event.currentTarget?.id + const include = prev.includes(id) + + if (include) { + return prev.filter(ele => ele !== id) + } else { + prev = prev.filter(ele => !ele.includes("align")) + prev.push(id) + return prev + } + }); + }, []); + + const handleColorHighlightValue = useCallback((value: string, colors: MuiColorInputColors) => { + // console.log(colors) + setColorHighlightValue(() => value) + // editor.chain().focus().toggleHighlight({ color: value }).run() + }, []) + + const handleColorHighlightValueClick = useCallback((event: React.MouseEvent) => { + editor.chain().focus().toggleHighlight({ color: colorHighlightValue.toString() }).run() + }, [colorHighlightValue]) + + const handleColorHighlightValueClose = useCallback((event: {}, reason: "backdropClick" | "escapeKeyDown") => { + // console.log(event) + editor.chain().focus().toggleHighlight({ color: colorHighlightValue.toString() }).run() + }, [colorHighlightValue]) + + const handleColorHighlightValueBlur = useCallback((event: React.FocusEvent) => { + editor.chain().focus().toggleHighlight({ color: colorHighlightValue.toString() }).run() + }, [colorHighlightValue]) + + const handleColorTextValue = useCallback((value: string, colors: MuiColorInputColors) => { + // console.log(colors) + setColorTextValue(() => value) + // if (editor.isActive("textStyle")) { + // editor.chain().focus().unsetColor().run() + // } else { + // editor.chain().focus().setColor(value).run() + // } + }, []) + + const handleColorTextValueClick = useCallback((event: React.MouseEvent) => { + if (editor.isActive("textStyle", { color: colorTextValue.toString() })) { + editor.chain().focus().unsetColor().run() + } else { + editor.chain().focus().setColor(colorTextValue.toString()).run() + } + }, [colorTextValue]) + + const handleColorTextValueClose = useCallback((event: {}, reason: "backdropClick" | "escapeKeyDown") => { + if (editor.isActive("textStyle", { color: colorTextValue.toString() })) { + editor.chain().focus().unsetColor().run() + } else { + editor.chain().focus().setColor(colorTextValue.toString()).run() + } + }, [colorTextValue]) + + const handleColorTextValueBlur = useCallback((event: React.FocusEvent) => { + if (editor.isActive("textStyle", { color: colorTextValue.toString() })) { + editor.chain().focus().unsetColor().run() + } else { + editor.chain().focus().setColor(colorTextValue.toString()).run() + } + }, [colorTextValue]) + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + if (colorHighlightValueInputRef.current !== null) { + colorHighlightValueInputRef.current.blur(); + } + + if (colorTextValueInputRef.current !== null) { + colorTextValueInputRef.current.blur(); + } + } + } + + const handleTextAlign = useCallback((event: React.MouseEvent, value: any) => { + console.log(value) + switch (value) { + case "alignLeft": + if (editor.isActive({ textAlign: 'left' })) { + editor.chain().focus().unsetTextAlign().run() + } else { + editor.chain().focus().setTextAlign('left').run() + } + break; + case "alignCenter": + if (editor.isActive({ textAlign: 'center' })) { + editor.chain().focus().unsetTextAlign().run() + } else { + editor.chain().focus().setTextAlign('center').run() + } + break; + case "alignRight": + if (editor.isActive({ textAlign: 'right' })) { + editor.chain().focus().unsetTextAlign().run() + } else { + editor.chain().focus().setTextAlign('right').run() + } + break; + default: + break; + } + }, []) + + React.useEffect(() => { + editor.on('selectionUpdate', ({ editor }) => { + const currentFormatList: string[] = [] + if (editor.isActive("bold")) { + currentFormatList.push("bold") + } + + if (editor.isActive("italic")) { + currentFormatList.push("italic") + } + + if (editor.isActive("underline")) { + currentFormatList.push("underline") + } + + if (editor.isActive("highlight", { color: colorHighlightValue.toString() })) { + currentFormatList.push("highlight") + } + + if (editor.isActive("textStyle", { color: colorTextValue.toString() })) { + currentFormatList.push("textStyle") + } + + if (editor.isActive({ textAlign: 'left' })) { + currentFormatList.push("alignLeft") + } + + if (editor.isActive({ textAlign: 'center' })) { + currentFormatList.push("alignCenter") + } + + if (editor.isActive({ textAlign: 'right' })) { + currentFormatList.push("alignRight") + } + + console.log(currentFormatList) + setFontStyle(() => currentFormatList) + }) + }, [editor]) + + return ( + + + editor.chain().focus().toggleBold().run()}> + + + editor.chain().focus().toggleItalic().run()}> + + + editor.chain().focus().toggleUnderline().run()}> + + + + + + + + + {/* console.log("Expand more")}> + + */} + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default MailToolbar; \ No newline at end of file diff --git a/src/components/MailField/index.ts b/src/components/MailField/index.ts new file mode 100644 index 0000000..ab7f59d --- /dev/null +++ b/src/components/MailField/index.ts @@ -0,0 +1 @@ +export { default } from "./MailFieldWrapper"; \ No newline at end of file diff --git a/src/components/MailSetting/MailSetting.tsx b/src/components/MailSetting/MailSetting.tsx new file mode 100644 index 0000000..9cffb2d --- /dev/null +++ b/src/components/MailSetting/MailSetting.tsx @@ -0,0 +1,197 @@ +"use client"; + +import Check from "@mui/icons-material/Check"; +import Close from "@mui/icons-material/Close"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import { useRouter } from "next/navigation"; +import React, { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + FieldErrors, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, +} from "react-hook-form"; +import { Tab, Tabs, TabsProps, Typography } from "@mui/material"; +import TimesheetMailDetails from "./TimesheetMailDetails"; +import { Error } from "@mui/icons-material"; +import { MailSave, saveMail, testEveryone, test7th, test15th, testSendMail } from "@/app/api/mail/actions"; +import SettingDetails from "./SettingDetails"; +import { errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts"; + +export interface Props { + defaultInputs?: MailSave, +} + +const hasErrorsInTab = ( + tabIndex: number, + errors: FieldErrors, +) => { + switch (tabIndex) { + case 0: + return ( + errors.settings + ); + // case 1: + // return ( + // errors.template + // ); + default: + false; + } +}; + +const MailSetting: React.FC = ({ + defaultInputs, +}) => { + const [serverError, setServerError] = useState(""); + const { t } = useTranslation(); + const router = useRouter(); + const [tabIndex, setTabIndex] = useState(0); + const [test, setTest] = useState(false) + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [], + ); + + const formProps = useForm({ + defaultValues: defaultInputs + }); + + const handleCancel = () => { + router.back(); + }; + + const onSubmit = useCallback>( + async (data) => { + try { + console.log(data); + + // let haveError = false + // if (data.name.length === 0) { + // haveError = true + // formProps.setError("name", { message: "Name is empty", type: "required" }) + // } + + // if (haveError) { + // return false + // } + + + setServerError(""); + + submitDialog(async () => { + const response = await saveMail(data); + + console.log(response) + if (response !== null) { + if (test) { + await testSendMail() + } + // if (test) { + // let msg = "" + // try { + // msg = "testEveryone" + // await testEveryone() + // msg = "test7th" + // await test7th() + // msg = "test15th" + // await test15th() + // } catch (error) { + // console.log(error) + // console.log(msg) + // } + // } + + successDialog(t("Save Success"), t) + } else { + errorDialog(t("Save Fail"), t).then(() => { + // formProps.setError("code", { message: response.message, type: "custom" }) + // setTabIndex(0) + return false + }) + } + }, t) + } catch (e) { + console.log(e) + setServerError(t("An error has occurred. Please try again later.")); + } + }, + [router, t, test], + ); + + const onSubmitError = useCallback>( + (errors) => { + console.log(errors) + }, + [], + ); + + const errors = formProps.formState.errors; + + return ( + + + {serverError && ( + + {serverError} + + )} + + + ) : undefined + } + iconPosition="end" + /> + {/* + ) : undefined + } + iconPosition="end" + /> */} + + + {/* */} + + + + + + + + + ); +}; + +export default MailSetting; \ No newline at end of file diff --git a/src/components/MailSetting/MailSettingLoading.tsx b/src/components/MailSetting/MailSettingLoading.tsx new file mode 100644 index 0000000..9cd157b --- /dev/null +++ b/src/components/MailSetting/MailSettingLoading.tsx @@ -0,0 +1,38 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +// Can make this nicer +export const MailSettingLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + ); +}; + +export default MailSettingLoading; \ No newline at end of file diff --git a/src/components/MailSetting/MailSettingWrapper.tsx b/src/components/MailSetting/MailSettingWrapper.tsx new file mode 100644 index 0000000..91ba265 --- /dev/null +++ b/src/components/MailSetting/MailSettingWrapper.tsx @@ -0,0 +1,40 @@ +// import { fetchUserAbilities } from "@/app/utils/fetchUtil"; +import MailSetting from "./MailSetting"; +import MailSettingLoading from "./MailSettingLoading"; +import { fetchMailSetting } from "@/app/api/mail"; + +interface SubComponents { + Loading: typeof MailSettingLoading; +} + +const MailSettingWrapper: React.FC & SubComponents = async () => { + const [ + // abilities, + settings, + // timesheetTemplate, + ] = await Promise.all([ + // fetchUserAbilities(), + fetchMailSetting(), + // fetchMailTimesheetTemplate() + ]); + + // const tempTimesheetTemplate: MailTemplate = { + // cc: timesheetTemplate.find(template => template.name.includes(".cc"))?.value, + // bcc: timesheetTemplate.find(template => template.name.includes(".bcc"))?.value, + // subject: timesheetTemplate.find(template => template.name.includes(".subject"))?.value, + // template: timesheetTemplate.find(template => template.name.includes(".template"))?.value, + // } + return ( + + ); +}; + +MailSettingWrapper.Loading = MailSettingLoading; + + +export default MailSettingWrapper; diff --git a/src/components/MailSetting/SettingDetails.tsx b/src/components/MailSetting/SettingDetails.tsx new file mode 100644 index 0000000..9f81765 --- /dev/null +++ b/src/components/MailSetting/SettingDetails.tsx @@ -0,0 +1,143 @@ +"use client"; + +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useTranslation } from "react-i18next"; +import { Controller, useFieldArray, useFormContext } from "react-hook-form"; +import Link from "next/link"; +import React from "react"; +import MailField from "../MailField/MailField"; +import { MailSetting } from "@/app/api/mail"; +import { MailSave } from "@/app/api/mail/actions"; +import { Checkbox, IconButton, InputAdornment } from "@mui/material"; +import { Visibility, VisibilityOff } from "@mui/icons-material"; + +interface Props { + isActive: boolean; +} + +const SettingDetails: React.FC = ({ isActive }) => { + const requiredFields = ["host", "port", "username"] + const { t } = useTranslation(); + const { + register, + formState: { errors }, + control, + watch + } = useFormContext(); + + const { fields } = useFieldArray({ + control, + name: "settings" + }) + + const [showSMTPPassword, setShowSMTPPassword] = React.useState(false) + + const handleClickShowPassword = () => setShowSMTPPassword((show) => !show); + + const handleMouseDownPassword = (event: React.MouseEvent) => { + event.preventDefault(); + }; + + return ( + + + + + {t("Settings")} + + { + fields.map((field, index) => ( + + + {t(field.name)} + + { + field.name.toLowerCase().includes("password") === true ? + + + + {showSMTPPassword ? : } + + + ), + }} + fullWidth + {...register(`settings.${index}.value`)} + /> + + : + field.type.toLowerCase() === "boolean" ? + + + + : + field.type.toLowerCase() === "integer" ? + + field.name.toLowerCase().includes(name)) + })} + error={Boolean( + errors.settings + && errors.settings.length + && errors.settings.length > index + && errors.settings[index]?.value + )} + /> + + : + + field.name.toLowerCase().includes(name)) + })} + error={Boolean( + errors.settings + && errors.settings.length + && errors.settings.length > index + && errors.settings[index]?.value + )} + /> + + } + + )) + } + + + + ); +}; + +export default SettingDetails; \ No newline at end of file diff --git a/src/components/MailSetting/TimesheetMailDetails.tsx b/src/components/MailSetting/TimesheetMailDetails.tsx new file mode 100644 index 0000000..c38f8c7 --- /dev/null +++ b/src/components/MailSetting/TimesheetMailDetails.tsx @@ -0,0 +1,96 @@ +"use client"; + +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useTranslation } from "react-i18next"; +import { Controller, useFormContext } from "react-hook-form"; +import Link from "next/link"; +import React from "react"; +import MailField from "../MailField/MailField"; +import { MailSave } from "@/app/api/mail/actions"; + +interface Props { + isActive: boolean; +} + +const TimesheetMailDetails: React.FC = ({ isActive }) => { + const { t } = useTranslation(); + const { + register, + formState: { errors }, + control + } = useFormContext(); + + return ( + + + + + {t("Timesheet Template")} + + + + + + + + + + + + + + + + ( + + )} + rules={{ + required: true, + validate: value => value?.includes("${date}") + }} + /> + + + + + + ); +}; + +export default TimesheetMailDetails; diff --git a/src/components/MailSetting/index.ts b/src/components/MailSetting/index.ts new file mode 100644 index 0000000..fadb1d2 --- /dev/null +++ b/src/components/MailSetting/index.ts @@ -0,0 +1 @@ +export { default } from "./MailSettingWrapper"; \ No newline at end of file diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 71100c7..87c6ebd 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -258,6 +258,11 @@ const NavigationContent: React.FC = () => { label: "QC Check Template", path: "/settings/user", }, + { + icon: , + label: "Mail", + path: "/settings/mail", + }, ], }, ];