From 0e61ee3516185299598e7971ef24aee9f8fb116c Mon Sep 17 00:00:00 2001 From: Harry Chung <144231383+chungchihhan@users.noreply.github.com> Date: Sun, 23 Feb 2025 13:21:45 +0800 Subject: [PATCH] v0.7.1 (#66) v0.7.1 # Release Notes - TPET v0.7.1 ## Fix - Webhook service page has set required and optional fields correctly. - fix the error of saving template --- .eslintrc.json | 18 +- .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/lint-format.yml | 23 ++- package-lock.json | 101 ++++++++++ package.json | 10 +- src/app/emailHistory/[runId]/page.tsx | 8 +- src/app/emailHistory/page.tsx | 20 +- src/app/ui/aboutUs/top-nav.tsx | 1 - src/app/ui/aboutUs/tpet-footer.tsx | 1 - src/app/ui/attach-dropdown.tsx | 10 +- src/app/ui/create-webhook-form.tsx | 259 ++++++++++++-------------- src/app/ui/drive-upload.tsx | 181 ------------------ src/app/ui/email-details-table.tsx | 10 +- src/app/ui/email-history-card.tsx | 22 +-- src/app/ui/file-table.tsx | 10 +- src/app/ui/file-upload.tsx | 2 +- src/app/ui/select-dropdown.tsx | 11 +- src/app/ui/send-email-form.tsx | 36 +++- src/app/ui/template-dropdown.tsx | 4 +- src/app/ui/tip-tap.tsx | 22 +-- src/app/ui/tool-bar.tsx | 3 +- src/app/ui/webhook-records-card.tsx | 2 +- src/lib/actions.ts | 116 ++---------- 23 files changed, 375 insertions(+), 507 deletions(-) delete mode 100644 src/app/ui/drive-upload.tsx diff --git a/.eslintrc.json b/.eslintrc.json index 91de593..78f6107 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,19 @@ { - "extends": ["next/core-web-vitals", "plugin:prettier/recommended"] + "extends": [ + "next/core-web-vitals", + "plugin:prettier/recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": ["PascalCase"] + } + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/interface-name-prefix": "off" + } } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0ee7501..a58b411 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,9 @@ Thank you for contributing to the project! 🎉 --> + ## Description + ## Type of Change + + - [ ] 🐛 Bug Fix (fixes an issue) - [ ] ✨ New Feature (introduces a new feature) - [ ] 💄 UI/UX (improves interface or user experience) @@ -26,12 +30,14 @@ Select the type(s) of changes made (you can select multiple): - [ ] 🔒 Security (addresses security concerns) ## Related Issues + ## Testing + ## Screenshots/Videos + ## Checklist + + - [ ] I have tested these changes. - [ ] I have updated the relevant documentation. - [ ] My code adheres to the project’s code standards. @@ -57,6 +66,7 @@ Ensure the following items are checked before submitting the PR: - [ ] All existing tests pass. ## Additional Notes + @@ -66,4 +76,4 @@ Reminder: Before submitting your PR, please review the **Coding Guidelines**: https://aws-educate-tw.notion.site/TPET-Backend-Coding-Guidelines-12e6bfee681780ac89c3cf43854380d4?pvs=4 --> - \ No newline at end of file + diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml index 5acdc6e..31c677b 100644 --- a/.github/workflows/lint-format.yml +++ b/.github/workflows/lint-format.yml @@ -9,6 +9,10 @@ on: - "**/*.tsx" - "**/*.css" - "**/*.scss" + - "package.json" + - "package-lock.json" + - ".eslintrc*" + - ".prettierrc*" jobs: lint-format: @@ -16,18 +20,25 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18.17.0 + cache: "npm" - name: Install dependencies - run: npm install + run: npm ci - - name: Run Prettier - run: npm run format -- --check + - name: Type check + run: npm run type-check - - name: Run ESLint + - name: Check formatting + run: npm run format:check + + - name: Check linting run: npm run lint + + - name: Run all checks + run: npm run check diff --git a/package-lock.json b/package-lock.json index a260433..0473b4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,8 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", "eslint": "^8", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", @@ -981,6 +983,12 @@ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1019,6 +1027,47 @@ "@types/react": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", @@ -1064,6 +1113,33 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/types": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", @@ -1129,6 +1205,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", diff --git a/package.json b/package.json index 4dc5b31..115c2ff 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,13 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "eslint . --ext .ts,.tsx,.js,.jsx", + "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix", "format": "prettier --write .", - "fix": "npm run format && npm run lint --fix" + "format:check": "prettier --check .", + "type-check": "tsc --noEmit", + "fix": "npm run type-check && npm run format && npm run lint:fix", + "check": "npm run type-check && npm run format:check && npm run lint" }, "dependencies": { "@heroicons/react": "^2.1.3", @@ -43,6 +47,8 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", "eslint": "^8", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", diff --git a/src/app/emailHistory/[runId]/page.tsx b/src/app/emailHistory/[runId]/page.tsx index d7b9ccd..f00b301 100644 --- a/src/app/emailHistory/[runId]/page.tsx +++ b/src/app/emailHistory/[runId]/page.tsx @@ -10,11 +10,11 @@ interface PageProps { }; } -interface rowDataType { +interface RowDataType { [key: string]: string; } -interface dataType { +interface DataType { bcc: string[]; subject: string; cc: string[]; @@ -24,7 +24,7 @@ interface dataType { sender_local_part: string; status: string; spreadsheet_file_id: string; - row_data: rowDataType; + row_data: RowDataType; atatachment_file_ids: string[]; is_generated_certficate: boolean; sender_username: string; @@ -38,7 +38,7 @@ interface dataType { } export default function Page({ params }: PageProps) { - const [data, setData] = useState([]); + const [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(true); const [previousLastEvaluatedKey, setPreviousLastEvaluatedKey] = useState(null); const [currentLastEvaluatedKey, setCurrentLastEvaluatedKey] = useState(null); diff --git a/src/app/emailHistory/page.tsx b/src/app/emailHistory/page.tsx index 231b300..ca5ada2 100644 --- a/src/app/emailHistory/page.tsx +++ b/src/app/emailHistory/page.tsx @@ -4,7 +4,7 @@ import EmailHistoryCardLoading from "@/app/ui/skeleton/email-history-card-skelet import { ChevronRight, ChevronLeft } from "lucide-react"; import EmailHistoryCard from "../ui/email-history-card"; -interface attachmentFilesType { +interface AttachmentFilesType { file_url: string; uploaded_id: string; updated_at: string; @@ -16,7 +16,7 @@ interface attachmentFilesType { file_size: number; } -interface spreadsheetFileType { +interface SpreadsheetFileType { file_url: string; uploaded_id: string; updated_at: string; @@ -28,7 +28,7 @@ interface spreadsheetFileType { file_size: number; } -interface templateFileType { +interface TemplateFileType { file_url: string; uploaded_id: string; updated_at: string; @@ -40,18 +40,18 @@ interface templateFileType { file_size: number; } -interface senderType { +interface SenderType { user_id: string; email: string; username: string; } -interface dataType { +interface DataType { bcc: string[]; subject: string; cc: string[]; run_id: string; - attachment_files: attachmentFilesType[]; + attachment_files: AttachmentFilesType[]; recipient_source: "DIRECT" | "SPREADSHEET"; created_at: string; sender_local_part: string; @@ -60,21 +60,21 @@ interface dataType { recipients: Array<{ email: string; template_variables: Record }>; attachment_file_ids: string[]; is_generate_certificate: boolean; - spreadsheet_file: spreadsheetFileType | null; + spreadsheet_file: SpreadsheetFileType | null; display_name: string; sender_id: string | null; - sender: senderType; + sender: SenderType; template_file_id: string; success_email_count: number; expected_email_send_count: number; reply_to: string; - template_file: templateFileType; + template_file: TemplateFileType; created_year_month_day: string; created_year: string; } export default function Page() { - const [data, setData] = useState(null); + const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [previousLastEvaluatedKey, setPreviousLastEvaluatedKey] = useState(null); const [currentLastEvaluatedKey, setCurrentLastEvaluatedKey] = useState(null); diff --git a/src/app/ui/aboutUs/top-nav.tsx b/src/app/ui/aboutUs/top-nav.tsx index 23351e0..27a11a5 100644 --- a/src/app/ui/aboutUs/top-nav.tsx +++ b/src/app/ui/aboutUs/top-nav.tsx @@ -17,7 +17,6 @@ export default function TopNav() { useEffect(() => { const handleScroll = () => { const scrollPosition = window.scrollY + 120; // Offset for the fixed header - const viewportHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight; const scrolledToBottom = window.innerHeight + window.scrollY >= documentHeight - 50; // Added tolerance diff --git a/src/app/ui/aboutUs/tpet-footer.tsx b/src/app/ui/aboutUs/tpet-footer.tsx index 37bab22..537ef3e 100644 --- a/src/app/ui/aboutUs/tpet-footer.tsx +++ b/src/app/ui/aboutUs/tpet-footer.tsx @@ -1,4 +1,3 @@ -import { EnvelopeIcon } from "@heroicons/react/24/outline"; import { Instagram, Mail } from "lucide-react"; export default function TpetFooter() { diff --git a/src/app/ui/attach-dropdown.tsx b/src/app/ui/attach-dropdown.tsx index 1246d20..2c8ff44 100644 --- a/src/app/ui/attach-dropdown.tsx +++ b/src/app/ui/attach-dropdown.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from "react"; import { convertToTaipeiTime, formatFileSize } from "@/lib/utils/dataUtils"; import { ChevronRight, ChevronLeft } from "lucide-react"; -interface fileDataType { +interface FileDataType { file_id: string; created_at: string; updated_at: string; @@ -19,12 +19,12 @@ interface AttachDropdownProps { } export default function AttachDropdown({ onSelect }: AttachDropdownProps) { - const [options, setOptions] = useState(null); - const [filteredOptions, setFilteredOptions] = useState(null); + const [options, setOptions] = useState(null); + const [filteredOptions, setFilteredOptions] = useState(null); const [searchTerm, setSearchTerm] = useState(""); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [selectedFiles, setSelectedFiles] = useState([]); + const [selectedFiles, setSelectedFiles] = useState([]); const [previousLastEvaluatedKey, setPreviousLastEvaluatedKey] = useState(null); const [currentLastEvaluatedKey, setCurrentLastEvaluatedKey] = useState(null); const [nextLastEvaluatedKey, setNextLastEvaluatedKey] = useState(null); @@ -92,7 +92,7 @@ export default function AttachDropdown({ onSelect }: AttachDropdownProps) { setIsOpen(!isOpen); }; - const handleSelect = (file: fileDataType | null) => { + const handleSelect = (file: FileDataType | null) => { if (!file) { setSelectedFiles([]); onSelect([]); diff --git a/src/app/ui/create-webhook-form.tsx b/src/app/ui/create-webhook-form.tsx index 4d082a7..b7eadca 100644 --- a/src/app/ui/create-webhook-form.tsx +++ b/src/app/ui/create-webhook-form.tsx @@ -37,7 +37,6 @@ export default function CreateWebhookForm() { // 首先加入 state const [showAttachUpload, setShowAttachUpload] = useState(false); const [attachment_file_ids, setAttachment_file_ids] = useState([]); - const [isGenerateCertificate, setIsGenerateCertificate] = useState(false); const [showSuccessToast, setShowSuccessToast] = useState(false); const [showFailedToast, setShowFailedToast] = useState(false); const [isCopied, setIsCopied] = useState(false); @@ -67,24 +66,28 @@ export default function CreateWebhookForm() { if (!ref.current) return; // Extract form data - const formData = { + const formData: any = { subject: (ref.current.querySelector("[id='subject']") as HTMLInputElement).value, display_name: (ref.current.querySelector("[id='display_name']") as HTMLInputElement).value, template_file_id: selectedHTML, - webhook_name: (ref.current.querySelector("[id='webhook_name']") as HTMLInputElement).value, - hash_key: (ref.current.querySelector("[id='hash_key']") as HTMLInputElement).value, - iv_key: (ref.current.querySelector("[id='iv_key']") as HTMLInputElement).value, surveycake_link: (ref.current.querySelector("[id='surveycake_link']") as HTMLInputElement) .value, - attachment_file_ids: attachment_file_ids, - reply_to: replyToEmail, - sender_local_part: localPart, - bcc: bccEmails, - cc: ccEmails, - is_generate_certificate: isGenerateCertificate, + hash_key: (ref.current.querySelector("[id='hash_key']") as HTMLInputElement).value, + iv_key: (ref.current.querySelector("[id='iv_key']") as HTMLInputElement).value, webhook_type: webhookType, }; + if ((ref.current.querySelector("[id='webhook_name']") as HTMLInputElement).value) { + formData.webhook_name = ( + ref.current.querySelector("[id='webhook_name']") as HTMLInputElement + ).value; + } + if (localPart) formData.sender_local_part = localPart; + if (replyToEmail) formData.reply_to = replyToEmail; + if (bccEmails.length > 0) formData.bcc = bccEmails; + if (ccEmails.length > 0) formData.cc = ccEmails; + if (attachment_file_ids.length > 0) formData.attachment_file_ids = attachment_file_ids; + try { const response = await submitWebhookForm( JSON.stringify(formData), @@ -232,33 +235,6 @@ export default function CreateWebhookForm() { {errors.display_name &&

{errors.display_name}

} -
- -
- setLocalPart(e.target.value)} - placeholder="Enter the local part of email" - disabled={isSubmitting} - className={`rounded-l-md border py-2 pl-4 text-sm outline-2 placeholder:text-gray-500 w-full ${ - errors.sender_local_part ? "border-red-500" : "border-gray-200" - }`} - /> -
@aws-educate.tw
-
- {errors.sender_local_part && ( -

{errors.sender_local_part}

- )} -
-
- +
-
+
-
-
- setIsGenerateCertificate(true)} - name="inline-radio-group" - className="w-4 h-4 text-blue-600 bg-white border-gray-300 focus:ring-blue-500" - /> - -
-
- setIsGenerateCertificate(false)} - name="inline-radio-group" - defaultChecked - className="w-4 h-4 text-blue-600 bg-white border-gray-300 focus:ring-blue-500" - /> - -
-
+ + {errors.surveycake_link && ( +

{errors.surveycake_link}

+ )} +
+ +
+ + + {errors.hash_key &&

{errors.hash_key}

}
+
+ + + {errors.iv_key &&

{errors.iv_key}

} +
+ +
+ + +
+
+

+ Optional + {/* */} +

+
+
+ +
+ setLocalPart(e.target.value)} + placeholder="Enter the local part of email" + disabled={isSubmitting} + className={`rounded-l-md border py-2 pl-4 text-sm outline-2 placeholder:text-gray-500 w-full ${ + errors.sender_local_part ? "border-red-500" : "border-gray-200" + }`} + /> +
@aws-educate.tw
+
+ {errors.sender_local_part && ( +

{errors.sender_local_part}

+ )} +
+
- -
- - - {errors.surveycake_link && ( -

{errors.surveycake_link}

- )} -
- -
- - - {errors.hash_key &&

{errors.hash_key}

} -
- -
- - - {errors.iv_key &&

{errors.iv_key}

} -
-
- - -
diff --git a/src/app/ui/drive-upload.tsx b/src/app/ui/drive-upload.tsx deleted file mode 100644 index e4b01a7..0000000 --- a/src/app/ui/drive-upload.tsx +++ /dev/null @@ -1,181 +0,0 @@ -"use client"; -import React, { useState, FormEvent, ChangeEvent } from "react"; -import { convertToTaipeiTime } from "@/lib/utils/dataUtils"; - -interface FileDataType { - file_id: string; - created_at: string; - updated_at: string; - file_url: string; - file_name: string; - file_extension: string; - file_size: number; - uploader_id: string; -} - -interface FileUploadProps { - onFileUploadSuccess: (newFiles: FileDataType[]) => void; -} - -export default function DriveUpload({ onFileUploadSuccess }: FileUploadProps) { - const [isSubmitting, setIsSubmitting] = useState(false); - const [files, setFiles] = useState([]); - const [fileData, setFileData] = useState([]); - - const handleFileChange = (event: ChangeEvent) => { - if (event.target.files) { - setFiles(Array.from(event.target.files)); - } - }; - - const onSubmit = async (event: FormEvent) => { - event.preventDefault(); - if (files.length === 0) { - alert("Please select a file to upload."); - return; - } - - const formData = new FormData(); - files.forEach(file => { - formData.append("file", file); - }); - // console.log("Form Data:", formData); - - setIsSubmitting(true); - - try { - const response = await fetch( - "https://sojek1stci.execute-api.ap-northeast-1.amazonaws.com/dev/upload-multiple-file", - { - method: "POST", - body: formData, - } - ); - - if (!response.ok) { - const errorMessage = `Upload failed: ${response.status} - ${response.statusText}`; - throw new Error(errorMessage); - } - const result = await response.json(); - - setFileData(result.files); - result.files.forEach((file: FileDataType) => { - if (file.file_extension === "xlsx") { - localStorage.setItem("xlsx_key", file.file_id); - } else if (file.file_extension === "html") { - localStorage.setItem("html_key", file.file_id); - } - }); - onFileUploadSuccess(result.files); - alert("File uploaded successfully!"); - } catch (error: any) { - alert("Failed to send form data: " + error.message); - } finally { - setIsSubmitting(false); - } - }; - - return ( - <> -
-

Upload new files

-

- Upload your participants sheet and email template at - once. -

-
-
-
- - - -

- .docx .html .xlsx and .pdf -

-
-
- -
-
- {fileData && ( -
- -
-
- - - - - - - - - - {fileData.map((file: FileDataType, index: number) => ( - - - - - - - - - ))} -
- FILE NAME - - FILE SIZE - - CREATED AT - - UPDATED AT -
- - {file.file_name} - - {file.file_size}{convertToTaipeiTime(file.created_at)}{convertToTaipeiTime(file.updated_at)}
-
-
-
- )} - {/*
- -
*/} - - ); -} diff --git a/src/app/ui/email-details-table.tsx b/src/app/ui/email-details-table.tsx index db8d4b1..be3e312 100644 --- a/src/app/ui/email-details-table.tsx +++ b/src/app/ui/email-details-table.tsx @@ -1,10 +1,10 @@ import { convertToTaipeiTime } from "@/lib/utils/dataUtils"; -interface rowDataType { +interface RowDataType { [key: string]: string; } -interface dataType { +interface DataType { bcc: string[]; subject: string; cc: string[]; @@ -14,7 +14,7 @@ interface dataType { sender_local_part: string; status: string; spreadsheet_file_id: string; - row_data: rowDataType; + row_data: RowDataType; atatachment_file_ids: string[]; is_generated_certficate: boolean; sender_username: string; @@ -27,7 +27,7 @@ interface dataType { email_id: string; } -export default function EmailDetailsTable({ data }: { data: dataType[] }) { +export default function EmailDetailsTable({ data }: { data: DataType[] }) { return (
@@ -51,7 +51,7 @@ export default function EmailDetailsTable({ data }: { data: dataType[] }) { - {data.map((item: dataType, index: number) => ( + {data.map((item: DataType, index: number) => ( - {files.map((file: fileDataType, index: number) => ( + {files.map((file: FileDataType, index: number) => (
{item.recipient_email} diff --git a/src/app/ui/email-history-card.tsx b/src/app/ui/email-history-card.tsx index 9e986e8..6dac7fc 100644 --- a/src/app/ui/email-history-card.tsx +++ b/src/app/ui/email-history-card.tsx @@ -2,7 +2,7 @@ import { CalendarDays, User, FileText, Clock, Send, Sheet, Mail } from "lucide-r import { convertToTaipeiTime } from "@/lib/utils/dataUtils"; import Link from "next/link"; -interface fileType { +interface FileType { file_url: string; uploaded_id: string | null; updated_at: string; @@ -14,19 +14,19 @@ interface fileType { file_size: number; } -interface senderType { +interface SenderType { user_id: string; email: string; username: string; message?: string; } -interface dataType { +interface DataType { bcc: string[]; subject: string; cc: string[]; run_id: string; - attachment_files: fileType[]; + attachment_files: FileType[]; recipient_source: "DIRECT" | "SPREADSHEET"; created_at: string; sender_local_part: string; @@ -35,30 +35,30 @@ interface dataType { recipients: Array<{ email: string; template_variables: Record }>; attachment_file_ids: string[]; is_generate_certificate: boolean; - spreadsheet_file: fileType | null; + spreadsheet_file: FileType | null; display_name: string; sender_id: string | null; - sender: senderType; + sender: SenderType; template_file_id: string; success_email_count: number; expected_email_send_count: number; reply_to: string; - template_file: fileType; + template_file: FileType; created_year_month_day: string; created_year: string; } -interface propsType { - data: dataType[] | null; +interface PropsType { + data: DataType[] | null; } -export default function EmailHistoryCard({ data }: propsType) { +export default function EmailHistoryCard({ data }: PropsType) { // Early return for empty or undefined data if (!data || data.length === 0) { return
No email history found
; } - const renderRecipientInfo = (item: dataType) => { + const renderRecipientInfo = (item: DataType) => { const isDirect = item.recipient_source === "DIRECT"; return ( diff --git a/src/app/ui/file-table.tsx b/src/app/ui/file-table.tsx index 48f00fc..79ea2df 100644 --- a/src/app/ui/file-table.tsx +++ b/src/app/ui/file-table.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { convertToTaipeiTime } from "@/lib/utils/dataUtils"; -interface fileDataType { +interface FileDataType { file_id: string; created_at: string; updated_at: string; @@ -13,16 +13,16 @@ interface fileDataType { } interface FileTableProps { - files: fileDataType[] | null; + files: FileDataType[] | null; title: string; file_extension: string; loading: boolean; fetchFiles: ( file_extension: string, - setFiles: React.Dispatch>, + setFiles: React.Dispatch>, setLastEvaluatedKey: React.Dispatch> ) => void; - setFiles: React.Dispatch>; + setFiles: React.Dispatch>; setLastEvaluatedKey: React.Dispatch>; lastEvaluatedKey: string | null; } @@ -88,7 +88,7 @@ export default function FileTable({
diff --git a/src/app/ui/file-upload.tsx b/src/app/ui/file-upload.tsx index 42278d5..8e1dcfb 100644 --- a/src/app/ui/file-upload.tsx +++ b/src/app/ui/file-upload.tsx @@ -100,7 +100,7 @@ export default function FileUpload({ OnFileExtension }: { OnFileExtension: strin multiple accept={OnFileExtension} /> -