diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..872f40c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +## Contribution +We welcome contributions from developers of all skill levels! To contribute: +1. Fork the Repository +Click the "Fork" button at the top right of this page to create your own copy. + +2. Clone Your Fork + ```bash + git clone https://github.com//mail-service.git + cd mail-service + ``` + +3. Create a New Branch + ```bash + git checkout -b feature/ + ``` + +4. Make Your Changes + Implement your feature or bug fix. Be sure to follow the existing code style and naming conventions. + +5. Test Your Changes + Run the relevant tests (cargo test, yarn storybook) and ensure everything passes. + +6. Commit and Push + ```bash + git add . + git commit -m "Add " + git push origin feature/ + ``` + +7. Open a Pull Request + Go to the original repository on GitHub and open a pull request from your fork. + +## Guidelines +- Keep pull requests small and focused. +- Write clear, descriptive commit messages. +- If your change introduces a new feature or configuration, update the documentation accordingly. +- Discuss major changes with maintainers before starting work. +- Please run `yarn lint` and ensure no ESLint errors before committing (if applicable). \ No newline at end of file diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 0000000..fd12919 --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,187 @@ +# Developers Note + +## Prerequisites +- **Node.js v20+** — for the frontend +- **Yarn v4+** — package manager for the frontend +- **Rust & Cargo** — for building and running the backend +- **Diesel CLI** — for database migrations (`cargo install diesel_cli --no-default-features --features postgres`) +- **Docker & Docker Compose** — optional, used to run Postgres locally +- **PostgreSQL** — required if not using Docker + +## Version used during the development +- yarn 4.6.0 +- rustc 1.83.0 (90b35a623 2024-11-26) +- node v20.14.0 +- npm 10.7.0 + +## Getting Started +### Clone the repository +> Using HTTPS: (recommended) +```bash +https://github.com/sireto/mail-service.git +``` + +> Using SSH: +```bash +git@github.com:sireto/mail-service.git +``` + +### Setup .env file on the root of the project mail-service/ +``` +DATABASE_URL="postgresql://:@localhost:5432/mail_service" +DATABASE_URL_TEST="postgresql://:@localhost:5432/__test_mail_service" +SERVER_ADDRESS="0.0.0.0:8000" + +POSTGRES_HOST=127.0.0.1 +POSTGRES_PORT=5432 +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_DB=mail_service + +ORIGINS=http://localhost:3000 + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_SES_CONFIGURATION_SET_NAME="mail-service" + +NEXT_PUBLIC_BASE_URL=http://localhost:8000/api +NEXT_PUBLIC_NAMESPACE_ID=e3bda5cf-760e-43ea-8e9a-c2c3c5f95b82 +``` + +### Backend +1. Navigate to the backend directory: + ``` + cd backend + ``` + +2. Install dependencies: + ```bash + cargo build + ``` + +3. Start the Postgres Database + ```bash + docker compose -f docker-compose.yml up + ``` + >**NOTE**: You need to update the environment variables placeholder in docker-compose.yml file _(if there are any)_ + +4. Install Diesel CLI + You’ll need the Diesel CLI to run database migrations locally. Install it with: + ```bash + cargo install diesel_cli --no-default-features --features postgres + ``` + +5. Run the Rust server: + ```bash + cargo run + ``` + +6. Swagger UI: + ```bash + http://localhost:8000/swagger-ui/ + ``` + +Test to see the server is working with Swagger UI. + +### Frontend +1. Navigate to the frontend directory: + ``` + cd frontend + ``` + +2. Install dependencies: + ```bash + yarn install + ``` + +4. Run the development server: + ```bash + yarn dev + ``` + +Check out the frontend server on `http://localhost:3000/` + +### Database Migrations +Mail-service uses the diesel-ORM for migrations and all the pending migrations starts when the server starts so you won't need to run the migration on your own. However, if you intend to tweak your local db you might want to run some db migrations cmd. + +Some useful commands: +1. Generate a new migration file + ```bash + diesel migration generate + ``` + +2. Run a migration file + ```bash + diesel migration run + ``` + +3. Rollback + ```bash + diesel migration revert + ``` + +4. Redo + ```bash + diesel migration redo + ``` + +If you want to know more about diesel, you can see its documentation [here](https://diesel.rs/guides/getting-started) + +### Tests +Backend tests: +1. Navigate to the backend directory: + ``` + cd backend + ``` +2. Run the following cmd: + ``` + cargo test + ``` + +Frontend tests: +1. Navigate to the frontend directory: + ``` + cd frontend + ``` +2. Run the following cmd: + ``` + yarn storybook + ``` + +### Project Structure +```bash +sireto-mail-service/ +├── README.md +├── docker-compose-qa.yml +├── docker-compose.yml +├── LICENSE +├── backend/ +│ ├── README.md +│ ├── Cargo.lock +│ ├── Cargo.toml +│ ├── docker-compose.yml +│ ├── Dockerfile +│ ├── migrations/ # contains Diesel database migration folders +│ ├── src/ +│ ├── tests/ +│ └── .cargo/ +├── frontend/ +│ ├── README.md +│ ├── components.json +│ ├── Dockerfile +│ ├── Dockerfile.storybook +│ ├── eslint.config.mjs +│ ├── next.config.ts +│ ├── package.json +│ ├── postcss.config.cjs +│ ├── tailwind.config.ts +│ ├── tsconfig.json +│ ├── yarn.lock +│ ├── .dockerignore +│ ├── .gitignore +│ ├── .yarnrc.yml +│ └── app/ +``` + +## Contribution +Want to contribute? Please check out our [CONTRIBUTING.md](https://github.com/sireto/mail-service/blob/develop/CONTRIBUTING.md) guide for instructions on how to get started. \ No newline at end of file diff --git a/README.md b/README.md index a3a9f06..2dfa2dd 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,83 @@ ## Overview -The Mail Service is a comprehensive email management platform designed to simplify and enhance bulk mailing operations for organizations. This service streamlines the process of creating, managing, and sending email campaigns at scale. It comes equipped with features to design reusable email templates, manage contact lists, and gain valuable insights into email performance through detailed analytics. By leveraging reliable email delivery services like Amazon SES, this platform ensures seamless, scalable, and efficient communication with subscribers. - -## Why This Service Is Essential - -### Analytics and Optimization -- Gain access to critical metrics such as open rates, click rates, and bounce rates. These insights enable organizations to evaluate the effectiveness of their email campaigns and make data-driven decisions to optimize performance. - -### Efficient Template Utilization -- Save time and maintain consistency by creating, editing, and reusing pre-designed email templates tailored for bulk email campaigns. +The Mail Service is a comprehensive email management platform designed to simplify and enhance bulk mailing operations for organizations. This service streamlines the process of creating, managing, and sending email campaigns at scale. It comes equipped with features to design reusable email templates, manage contact lists, and gain valuable insights into email performance through detailed analytics. By leveraging reliable email delivery services like Amazon SES and SMTP server, this platform ensures seamless, scalable, and efficient communication with subscribers. + +![mail_service_img](https://github.com/user-attachments/assets/2a71f828-a17e-4114-b1ee-350f8c409b93) + +## Running with Docker +The latest images are available at: [here](https://github.com/sireto/mail-service). + +Or, you can also run the docker-compose.yml file of the root by setting up your own environment variables by simply running: +```bash +docker compose up -d +``` + +Here is the sample docker-compose.yml file: +```bash +services: + postgres: + image: postgres:latest + container_name: postgres_mailservice + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mail_service + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + api: + image: ghcr.io/sireto/mail-service-backend:nightly + ports: + - "8000:8000" + depends_on: + - postgres + environment: + DATABASE_URL: postgresql://postgres:postgres@postgres_mailservice:5432/mail_service + AWS_ACCESS_KEY_ID: + AWS_SECRET_ACCESS_KEY: + AWS_SES_CONFIGURATION_SET_NAME: + ORIGINS: http://localhost:3000,http://172.31.0.6:3600 + + webapp: + build: + context: ./frontend + dockerfile: Dockerfile + args: + NEXT_PUBLIC_BASE_URL: http://localhost:8000/api + depends_on: + - postgres + - api + environment: + NEXT_PUBLIC_NAMESPACE_ID: e3bda5cf-760e-43ea-8e9a-c2c3c5f95b82 + + ports: + - "3000:3000" + +volumes: + postgres_data: +``` ## Features and Scope +- **Email Campaign Management** +- **Contact Management** +- **Template Management** +- **Integration with Email Delivery Services** +- **Analytics and Reporting** +- **Dashboard** +- **Efficient Template Utilization** -**Email Campaign Management:** - -- **Draft, Send, and Schedule:** Easily create campaigns and schedule them for later delivery or send immediately to a targeted audience. -- **Bulk and Individual Emails:** Whether sending to a large audience or just a single contact, the platform accommodates both. - -**Contact Management:** - -- **Contact Handling:** Add, edit, and organize subscriber lists to ensure accurate targeting for email campaigns. - -**Template Management:** - -- **Dynamic Template Creation:** Design and customize reusable email templates to match your campaign's tone. -- **HTML and Plain Text Support:** Flexibility to craft visually appealing HTML emails or simple plain text messages. - -**Integration with Email Delivery Services:** - -- **Seamless Integration:** Reliably send emails through third-party services like Amazon SES, eliminating the need for building or maintaining an SMTP server. - -**Analytics and Reporting:** - -- **Performance Tracking:** Monitor email campaign performance with metrics like open rates, click-through rates, and delivery statuses. -- **Visual Reports:** Generate detailed, easy-to-interpret visual reports to track campaign trends and outcomes over time. - -**Dashboard:** - -- **Unified Interface:** Manage all aspects of your campaigns, from templates to analytics, in one user-friendly dashboard. - -## Out of Scope +## Limitations -- **SMTP Infrastructure:** This service does not include its own SMTP server for email delivery. Instead, it relies on external providers such as Amazon SES to handle email sending and delivery +- **SMTP Infrastructure:** This service includes the SMTP server implementation. However, emails sent via the SMTP server are not tracked for opens, clicks, bounces, or delivery status. -## Developers Note +## Developers +Mail-service is free and open-source software licensed under Apache 2.0 License. If you're interested in contributing, please refer to the [Developer README](https://github.com/sireto/mail-service/blob/develop/DEVELOPER.md) for setup instructions and [Contribution README](https://github.com/sireto/mail-service/blob/develop/CONTRIBUTING.md) contribution guidelines. ## License - [Apache 2.0 License](LICENSE) -By using the Mail Service, organizations can efficiently manage email communication, optimize campaign performance, and achieve their outreach goals without the complexities of managing email infrastructure. \ No newline at end of file +By using the Mail Service, organizations can efficiently manage email communication, optimize campaign performance, and achieve their outreach goals without the complexities of managing email infrastructure. diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 48f2332..5b8facb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2369,9 +2369,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.69" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -2401,9 +2401,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -2953,15 +2953,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index bf68188..91ef3f1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -2,6 +2,7 @@ name = "backend" version = "0.1.0" edition = "2021" +resolver = "2" [dependencies] axum = "0.8.1" diff --git a/backend/rustfmt.toml b/backend/rustfmt.toml new file mode 100644 index 0000000..86ee802 --- /dev/null +++ b/backend/rustfmt.toml @@ -0,0 +1,18 @@ +newline_style = "unix" +use_field_init_shorthand = true +use_try_shorthand = true + +# Unstable features below (remove if using stable) +# unstable_features = true +style_edition = "2021" # Stick to stable 2021 edition +max_width = 120 +comment_width = 120 +error_on_line_overflow = true +format_code_in_doc_comments = true +format_macro_bodies = true +format_macro_matchers = true +format_strings = true +imports_granularity = "Module" +group_imports = "StdExternalCrate" +normalize_doc_attributes = true +wrap_comments = true \ No newline at end of file diff --git a/backend/src/handlers/bounce_logs_handler.rs b/backend/src/handlers/bounce_logs_handler.rs index 1bfda59..519551b 100644 --- a/backend/src/handlers/bounce_logs_handler.rs +++ b/backend/src/handlers/bounce_logs_handler.rs @@ -1,5 +1,3 @@ -// https://f2d0-2400-74e0-0-6aae-be17-ceca-c77e-bb91.ngrok-free.app - use std::sync::Arc; use crate::{error::AppError, models::{bounce_logs:: diff --git a/backend/src/main.rs b/backend/src/main.rs index 4c2a826..61b99f0 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,24 +1,19 @@ -use axum::error_handling::HandleErrorLayer; -use axum::handler::HandlerService; use axum::middleware; -use axum::response::Response; -use axum::BoxError; use backend::error::AppError; use backend::repositories::mail_repository; use backend::route::create_router; use backend::services::mail_service::{self, MailServiceTrait}; use backend::servers::{ servers_repo, servers_services }; use diesel::PgConnection; -use diesel::Connection; // Import the Connection trait +use diesel::Connection; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use dotenv::dotenv; use axum::http::{ header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}, - HeaderValue, Method, status::StatusCode, Request + HeaderValue, Method }; use tower_http::cors::CorsLayer; -use std::convert::Infallible; use std::{env, net::SocketAddr, sync::Arc}; use backend::middleware::error_handling_middleware; @@ -73,17 +68,6 @@ async fn main() { }); } - // Worker for retrying submitted but not delivered mails... - { - let mail_service = Arc::clone(&mail_service); - let server_service = Arc::clone(&server_service); - tokio::spawn(async move { - if let Err(err) = mail_service.process_submitted_mails(server_service.into()).await.map_err(|err| AppError::InternalServerError(Some(format!("Mail retry worker error: {:?}", err.to_string())))) { - eprintln!("Error occurred in mail retry worker: {:?}", err); - } - }); - } - // Address configuration let addr = env::var("SERVER_ADDRESS").unwrap_or_else(|_| "0.0.0.0:8000".to_string()); let addr: SocketAddr = addr.parse().expect("Invalid server address"); diff --git a/backend/src/models/bounce_logs.rs b/backend/src/models/bounce_logs.rs index 1413d4d..d076949 100644 --- a/backend/src/models/bounce_logs.rs +++ b/backend/src/models/bounce_logs.rs @@ -78,9 +78,6 @@ pub struct SnsNotification { pub message: String, // Actual bounce data in JSON string format... #[serde(rename = "MessageId")] pub message_id: String, - // #[serde(rename = "SubscribeURL")] - // pub subscribe_url: String, - } #[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] diff --git a/backend/src/models/campaign.rs b/backend/src/models/campaign.rs index ec9cd0d..b9d0fda 100644 --- a/backend/src/models/campaign.rs +++ b/backend/src/models/campaign.rs @@ -1,6 +1,4 @@ -use axum::http::status::StatusCode; use chrono::{ DateTime, NaiveDateTime, Utc }; -use serde_json::Value; use serde::{ Serialize, Deserialize }; use utoipa::{openapi::schema, ToSchema}; use diesel::{prelude::*, sql_types::Integer}; diff --git a/backend/src/models/template.rs b/backend/src/models/template.rs index 99b942f..a40831b 100644 --- a/backend/src/models/template.rs +++ b/backend/src/models/template.rs @@ -1,12 +1,9 @@ -/** - * The following model was the schema for the template table in the prisma-client-rust which was in the backend/src/schema.rs file... - */ - -use chrono::{ DateTime, NaiveDateTime, Utc }; +use chrono::{ DateTime, Utc }; use serde_json::Value; - use serde::{ Serialize, Deserialize }; use utoipa::ToSchema; +use diesel::prelude::*; +use uuid::Uuid; #[derive(Debug, Default, Serialize, Deserialize, ToSchema, Clone, PartialEq)] #[derive(Queryable, Selectable, Insertable)] @@ -56,54 +53,6 @@ pub struct GetTemplateResponse{ pub updated_at: DateTime, } -// #[derive(Debug, Default, Serialize, Deserialize, ToSchema)] -// pub struct UpdateTemplateRequest { -// pub name: Option, -// pub template_data: Option, -// pub content_plaintext: Option, -// pub content_html: Option, -// } - - -// the following might change... -// #[derive(Debug, Default, Serialize, Deserialize, ToSchema)] -// pub struct UpdateTemplateResponse { -// pub id: String, -// pub name: String, - -// #[schema(value_type = String, example = "2023-01-01T00:00:00Z")] -// pub updated_at: DateTime, -// } - -// #[derive(Debug, Default, Serialize, Deserialize, ToSchema)] -// pub struct DeleteTemplateResponse { -// pub id: Uuid, -// pub name: String, -// pub updated_at: NaiveDateTime, -// } - -/* - <==== Here the following models are for the diesel ORM... ======> -*/ -use diesel::prelude::*; -use crate::schema::templates; -// use diesel::pg::sql_types::Uuid; -use uuid::Uuid; - -// #[derive(Queryable, Selectable)] -// #[diesel(table_name = templates)] -// #[diesel(check_for_backend(diesel::pg::Pg))] -// pub struct Template { -// pub id: Uuid, -// pub namespace_id: Uuid, -// pub name: String, -// pub template_data: Value, -// pub content_plaintext: Option, -// pub content_html: String, -// pub created_at: NaiveDateTime, -// pub updated_at: NaiveDateTime, -// } - #[derive(Debug, Queryable, Selectable, Identifiable, Clone, PartialEq)] #[diesel(table_name = crate::schema::templates)] #[diesel(check_for_backend(diesel::pg::Pg))] @@ -149,7 +98,7 @@ pub struct DeleteTemplateResponse { #[derive(Debug, Default, Serialize, Deserialize, ToSchema, Queryable, Clone, PartialEq)] pub struct SendMailRequest { - pub receiver: Option, // this should be a list of emails seperated by commas or the list name for now (later to be changed to the list_id)... + pub receiver: Option, // string that may be a single email or emails separated by comma... pub cc: Option, pub bcc: Option, pub from: String, diff --git a/backend/src/repositories/bounce_logs_repo.rs b/backend/src/repositories/bounce_logs_repo.rs index 65cccf6..86ecba0 100644 --- a/backend/src/repositories/bounce_logs_repo.rs +++ b/backend/src/repositories/bounce_logs_repo.rs @@ -3,8 +3,7 @@ use crate::schema::bounce_logs::dsl::*; use diesel::prelude::*; use crate::models::bounce_logs::{ BounceLog, - CreateBounceLogRequest, - CreateBounceLogResponse + CreateBounceLogRequest }; use uuid::Uuid; use mockall::{ automock, predicate::* }; diff --git a/backend/src/repositories/campaign_lists_repo.rs b/backend/src/repositories/campaign_lists_repo.rs index 659cbf4..da1582e 100644 --- a/backend/src/repositories/campaign_lists_repo.rs +++ b/backend/src/repositories/campaign_lists_repo.rs @@ -68,12 +68,6 @@ impl CampaignListRepository for CampaignListRepositoryImpl { use crate::schema::campaign_lists::dsl::*; let mut conn = get_connection_pool().await; - // Query to fetch list_ids for the given campaign_id - // campaign_lists - // .filter(campaign_id.eq(id)) - // .select(list_id) - // .load::(&mut conn) - let result = campaign_lists .filter(campaign_id.eq(c_id)) .inner_join(lists.on(list_id.eq(id))) diff --git a/backend/src/repositories/contact.rs b/backend/src/repositories/contact.rs index 2d5d189..23bada6 100644 --- a/backend/src/repositories/contact.rs +++ b/backend/src/repositories/contact.rs @@ -181,32 +181,4 @@ impl ContactRepository for ContactRepositoryImpl { result } -} - -// #[automock] -// pub async fn create_contact ( -// payload: CreateContactRequest -// ) -> Result { -// let mut conn = get_connection_pool().await; - -// diesel::insert_into(contacts) -// .values(&payload) -// .returning(Contact::as_returning()) -// .get_result::(&mut conn) -// } - -// pub async fn get_all_contacts() -> Result, diesel::result::Error> { -// let mut conn = get_connection_pool().await; - -// contacts -// .select(( -// id, -// first_name, -// last_name, -// email, -// attribute, -// created_at, -// updated_at, -// )) -// .load::(&mut conn) -// } \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/repositories/list_contact_repo.rs b/backend/src/repositories/list_contact_repo.rs index da74d41..6e14d50 100644 --- a/backend/src/repositories/list_contact_repo.rs +++ b/backend/src/repositories/list_contact_repo.rs @@ -57,7 +57,7 @@ impl ListContactRepository for ListContactRepositoryImpl { async fn delete_contacts_from_list( &self, - listId: Uuid, + list_uuid: Uuid, contact_ids: Vec, ) -> Result { use crate::schema::list_contacts::dsl::*; @@ -65,7 +65,7 @@ impl ListContactRepository for ListContactRepositoryImpl { diesel::delete( list_contacts - .filter(list_id.eq(listId)) + .filter(list_id.eq(list_uuid)) .filter(contact_id.eq_any(contact_ids)) ) .execute(&mut conn) diff --git a/backend/src/services/list_service.rs b/backend/src/services/list_service.rs index 526e81a..8ace6b4 100644 --- a/backend/src/services/list_service.rs +++ b/backend/src/services/list_service.rs @@ -8,8 +8,6 @@ use crate::repositories::list_contact_repo::{ListContactRepository, ListContactR use crate::repositories::contact::ContactRepository; -// use anyhow::{anyhow, Result}; - pub struct ListContactService { repository: Arc } diff --git a/backend/src/services/mail_service.rs b/backend/src/services/mail_service.rs index e47e1b2..d30c676 100644 --- a/backend/src/services/mail_service.rs +++ b/backend/src/services/mail_service.rs @@ -303,4 +303,4 @@ pub async fn process_one_batch( update(mail.id.clone()); } } -} +} \ No newline at end of file diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts index 9945360..3f927b7 100644 --- a/frontend/.storybook/main.ts +++ b/frontend/.storybook/main.ts @@ -2,16 +2,28 @@ import type { StorybookConfig } from "@storybook/nextjs"; const config: StorybookConfig = { stories: ["../**/*.mdx", "../**/*.stories.@(js|jsx|mjs|ts|tsx)"], + addons: [ "@storybook/addon-onboarding", "@storybook/addon-essentials", "@chromatic-com/storybook", "@storybook/addon-interactions", + "@storybook/addon-mdx-gfm" ], + framework: { name: "@storybook/nextjs", options: {}, }, + staticDirs: ["../public"], + + docs: { + autodocs: true + }, + + typescript: { + reactDocgen: "react-docgen-typescript" + } }; export default config; diff --git a/frontend/app/dashboard/campaigns/[id]/_components/CampaignForm.tsx b/frontend/app/dashboard/campaigns/[id]/_components/CampaignForm.tsx index b0de0ed..0281d43 100644 --- a/frontend/app/dashboard/campaigns/[id]/_components/CampaignForm.tsx +++ b/frontend/app/dashboard/campaigns/[id]/_components/CampaignForm.tsx @@ -15,19 +15,16 @@ import { import { AddCampaignFormSchemaDTO, CampaignSenderDTO, - ListDTO, TemplateDTO, } from "@/lib/type"; import { Input } from "@/components/ui/input"; -import React, { useState, useEffect, useRef, Dispatch, SetStateAction } from "react"; +import React, { useRef } from "react"; import { UseFormReturn } from "react-hook-form"; import { z } from "zod"; -import { useGetTemplatesQuery } from "@/app/services/TemplateApi"; import { ClipboardX } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useGetListsQuery } from "@/app/services/ListApi"; import DropdownItemList from "./DropdownItemList"; -import { useGetCampaignSendersQuery } from "@/app/services/CampaignSenderApi"; import { MultiSelect } from "@/components/common/Multiselect"; type Template = z.infer; @@ -46,12 +43,6 @@ type SenderItem = { name: string; }; -type List = z.infer; -type MultiSelectItemType = { - label: string; - value: string; -}; - const namespaceId: string | undefined = process.env.NEXT_PUBLIC_NAMESPACE_ID; const CampaignForm = (props: CampaignFormProps) => { @@ -115,7 +106,7 @@ const CampaignForm = (props: CampaignFormProps) => { ( + render={({ field }) => ( Sender @@ -141,7 +132,7 @@ const CampaignForm = (props: CampaignFormProps) => { ( + render={({ field }) => ( Template @@ -166,7 +157,7 @@ const CampaignForm = (props: CampaignFormProps) => { { + render={({ field }) => { return ( Lists diff --git a/frontend/app/dashboard/campaigns/[id]/_components/SendTestMailForm.tsx b/frontend/app/dashboard/campaigns/[id]/_components/SendTestMailForm.tsx index 5a7a718..b5070d7 100644 --- a/frontend/app/dashboard/campaigns/[id]/_components/SendTestMailForm.tsx +++ b/frontend/app/dashboard/campaigns/[id]/_components/SendTestMailForm.tsx @@ -22,7 +22,7 @@ const SendTestMailForm = ({ defaultValues: { email: "" }, }) - const [ sendTemplatedMail, { isLoading: isSending, error: sendError }] = useSendTemplatedEmailMutation(); + const [ sendTemplatedMail ] = useSendTemplatedEmailMutation(); // onSubmit handler const onSubmit = (data: z.infer) => { diff --git a/frontend/app/dashboard/campaigns/[id]/analytics/_columns.tsx b/frontend/app/dashboard/campaigns/[id]/analytics/_columns.tsx index 35bb8c6..6b79e04 100644 --- a/frontend/app/dashboard/campaigns/[id]/analytics/_columns.tsx +++ b/frontend/app/dashboard/campaigns/[id]/analytics/_columns.tsx @@ -6,6 +6,7 @@ import { formatDateWithoutDay } from '@/lib/utils'; import Link from 'next/link'; import ActionsColumn from './_components/ActionsColumn'; import ToolTip from '@/components/common/ToolTip'; +import { Mail } from '@/lib/type/mail'; const statusTagStyleMap = { draft: 'bg-gray-100 text-gray-800', @@ -18,7 +19,7 @@ const statusTagStyleMap = { export const columns = ( deleteMailHandler: (id: string) => void, -): ColumnDef[] => [ +): ColumnDef[] => [ { accessorKey: "email", header: "Email", diff --git a/frontend/app/dashboard/campaigns/[id]/analytics/_components/ActionsColumn.tsx b/frontend/app/dashboard/campaigns/[id]/analytics/_components/ActionsColumn.tsx index ab1331a..cf0f503 100644 --- a/frontend/app/dashboard/campaigns/[id]/analytics/_components/ActionsColumn.tsx +++ b/frontend/app/dashboard/campaigns/[id]/analytics/_components/ActionsColumn.tsx @@ -3,9 +3,10 @@ import { Row } from '@tanstack/react-table'; import { Trash2 } from 'lucide-react'; import ConfirmationPopup from '@/components/common/ConfirmationPopup'; import { Button } from '@/components/ui/button'; +import { Mail } from '@/lib/type/mail'; interface ActionsColumnProps { - row: Row, + row: Row, deleteMailHandler: (id: string) => void, }; diff --git a/frontend/app/dashboard/campaigns/[id]/analytics/page.tsx b/frontend/app/dashboard/campaigns/[id]/analytics/page.tsx index 48a8e7b..209642e 100644 --- a/frontend/app/dashboard/campaigns/[id]/analytics/page.tsx +++ b/frontend/app/dashboard/campaigns/[id]/analytics/page.tsx @@ -3,7 +3,7 @@ import { useDeleteMailMutation, useGetMailsQuery } from '@/app/services/MailApi'; import DataTable from '@/components/DataTable'; import columns from './_columns'; -import React, { useState, useRef } from 'react'; +import React, { useState } from 'react'; import { useParams } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { Search } from 'lucide-react'; @@ -21,11 +21,13 @@ const SearchCampaignAnalyticsDTO = z.object({ to: z.union([z.string().nonempty("To date is required"), z.date()]) }); +export type SearchCampaignAnalytics = z.infer; + const Page = () => { const { id } = useParams(); // Get campaign ID from URL parameters - const [ deleteMail, { isLoading: isDeleting, error: deletionError }] = useDeleteMailMutation(); + const [ deleteMail, { error: deletionError }] = useDeleteMailMutation(); const [isGraphView, setIsGraphView] = useState(false); const today = new Date(); @@ -57,13 +59,11 @@ const Page = () => { { skip: !form.formState.isValid || (form.formState.isDirty && !datesChanged) } ); - const multiSelectRef = useRef(null); - if (error) { return
There was an error fetching campaign mails data...
} - const searchHandler = async (value: any) => { + const searchHandler = async (value: SearchCampaignAnalytics) => { const { campaigns, from, to } = value; setSearchParams({ diff --git a/frontend/app/dashboard/campaigns/[id]/content/page.tsx b/frontend/app/dashboard/campaigns/[id]/content/page.tsx index ae2bb6a..182dcf7 100644 --- a/frontend/app/dashboard/campaigns/[id]/content/page.tsx +++ b/frontend/app/dashboard/campaigns/[id]/content/page.tsx @@ -5,33 +5,41 @@ import { Textarea } from '@/components/ui/textarea'; import { AddTemplateFormSchemaDTO } from '@/lib/type'; import { zodResolver } from '@hookform/resolvers/zod'; import { Input } from '@/components/ui/input'; -import React from 'react' +import React, { useEffect } from 'react' import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Save, ClipboardX } from 'lucide-react'; -import { useGetTemplateByIdQuery, useGetTemplatesQuery, useUpdateTemplateMutation } from '@/app/services/TemplateApi'; +import { useGetTemplateByIdQuery, useUpdateTemplateMutation } from '@/app/services/TemplateApi'; import { useParams, useRouter } from 'next/navigation'; import { useGetCampaignByIdQuery } from '@/app/services/CampaignApi'; const Page = () => { const { id } = useParams<{ id: string }>(); - const { data: currentCampaign, error, isLoading } = useGetCampaignByIdQuery(id); - const { data: currentTemplate, error: templateError, isLoading: templateLoading } = useGetTemplateByIdQuery(currentCampaign?.template_id ?? ''); - const [updateTemplate, { isLoading: isUpdating, error: updateError }] = useUpdateTemplateMutation(); + const { data: currentCampaign } = useGetCampaignByIdQuery(id); + const { data: currentTemplate } = useGetTemplateByIdQuery(currentCampaign?.template_id ?? ''); + const [updateTemplate] = useUpdateTemplateMutation(); const router = useRouter(); - - const form = useForm>({ resolver: zodResolver(AddTemplateFormSchemaDTO), defaultValues: { - name: currentTemplate?.name ?? "", - raw_mjml_content: currentTemplate?.content_html ?? "Hi, {{name}}", + name: "", + raw_mjml_content: "", } }); + useEffect(() => { + if (currentTemplate) { + form.reset({ + name: currentTemplate.name, + raw_mjml_content: currentTemplate.content_html + }) + } + }, [currentTemplate, form]); + + const editTemplate = async (value: z.infer) => { const updatedTemplate = { name: value.name.trim(), diff --git a/frontend/app/dashboard/campaigns/[id]/layout.tsx b/frontend/app/dashboard/campaigns/[id]/layout.tsx index 3256276..7f6392f 100644 --- a/frontend/app/dashboard/campaigns/[id]/layout.tsx +++ b/frontend/app/dashboard/campaigns/[id]/layout.tsx @@ -21,7 +21,7 @@ export default function NewLayout({ const pathname = usePathname(); const isEditing = id !== "new"; // if not new this Page is opened in the editing mode... - const { data: campaignData, error, isLoading } = useGetCampaignByIdQuery(id); + const { data: campaignData } = useGetCampaignByIdQuery(id); return (
diff --git a/frontend/app/dashboard/campaigns/[id]/page.tsx b/frontend/app/dashboard/campaigns/[id]/page.tsx index 580613f..1fd05b6 100644 --- a/frontend/app/dashboard/campaigns/[id]/page.tsx +++ b/frontend/app/dashboard/campaigns/[id]/page.tsx @@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useCreateCampaignMutation, useGetCampaignByIdQuery, - useGetCampaignsQuery, useUpdateCampaignMutation, } from "@/app/services/CampaignApi"; import { Save } from "lucide-react"; @@ -36,12 +35,10 @@ const Page = () => { const { data: campaignData, - error, - isLoading, } = useGetCampaignByIdQuery(id, { skip: !isEditing }); - const [createCampaign, { isLoading: isCreating, error: creationError }] = + const [createCampaign] = useCreateCampaignMutation(); - const [updateCampaign, { isLoading: isUpdating, error: updateError }] = + const [updateCampaign] = useUpdateCampaignMutation(); const { @@ -61,7 +58,6 @@ const Page = () => { ); useEffect(() => { - console.warn("THE template id ====> ", campaignData?.template_id, "The campaign_sender id =====> ", campaignData?.campaign_senders); if ( isEditing && campaignData && @@ -78,9 +74,7 @@ const Page = () => { list_ids: campaignData.lists.map((list) => list.id), }); } - - console.warn("THE FORM STATE ===> ", form.getValues()); - }, [campaignData, isEditing, isTemplateLoading, isSenderLoading]); + }, [campaignData, isEditing, isTemplateLoading, isSenderLoading, form, campaignSenders, templates]); const saveCampaignChanges = async ( value: z.infer @@ -95,8 +89,6 @@ const Page = () => { list_ids: value.list_ids, }; - console.warn("IS EDITING WITH VALUE ===> ", updatedCampaign); - const updatedCampaignData = { campaignId: id, updatedCampaign: updatedCampaign, diff --git a/frontend/app/dashboard/campaigns/_columns.tsx b/frontend/app/dashboard/campaigns/_columns.tsx index 3519f33..8414f57 100644 --- a/frontend/app/dashboard/campaigns/_columns.tsx +++ b/frontend/app/dashboard/campaigns/_columns.tsx @@ -7,11 +7,12 @@ import Link from "next/link"; import ActionsColumn from "./_components/ActionsColumn"; import { ListDTO } from "@/lib/type"; import { z } from "zod"; +import { Campaign } from "@/lib/type/campaign"; export const columns = ( deleteCampaignHandler: (id: string) => void, startCampaignHandler: (id: string) => void -): ColumnDef[] => [ +): ColumnDef[] => [ { accessorKey: "campaign_name", header: "Name", diff --git a/frontend/app/dashboard/campaigns/_components/ActionsColumn.tsx b/frontend/app/dashboard/campaigns/_components/ActionsColumn.tsx index 263ef1c..856a979 100644 --- a/frontend/app/dashboard/campaigns/_components/ActionsColumn.tsx +++ b/frontend/app/dashboard/campaigns/_components/ActionsColumn.tsx @@ -4,9 +4,10 @@ import { Edit3, Trash2, Rocket } from 'lucide-react'; import Link from 'next/link'; import ConfirmationPopup from '@/components/common/ConfirmationPopup'; import { Button } from '@/components/ui/button'; +import { Campaign } from '@/lib/type/campaign'; interface ActionsColumnProps { - row: Row, + row: Row, startCampaignHandler: (id: string) => void, deleteCampaignHandler: (id: string) => void, }; diff --git a/frontend/app/dashboard/campaigns/page.tsx b/frontend/app/dashboard/campaigns/page.tsx index bf4a24d..f946559 100644 --- a/frontend/app/dashboard/campaigns/page.tsx +++ b/frontend/app/dashboard/campaigns/page.tsx @@ -11,8 +11,8 @@ import AddButton from '@/components/common/AddButton'; export default function CampaignPage() { const { data: campaigns, error, isLoading } = useGetCampaignsQuery(); - const [ deleteCampaign, { isLoading: isDeleting, error: deletionError }] = useDeleteCampaignMutation(); - const [ startCampaign, { isLoading: isStarting, error: startingError }] = useStartCampaignMutation(); + const [ deleteCampaign, { error: deletionError }] = useDeleteCampaignMutation(); + const [ startCampaign, { error: startingError }] = useStartCampaignMutation(); if (error) { diff --git a/frontend/app/dashboard/campaigns/senders/_columns.tsx b/frontend/app/dashboard/campaigns/senders/_columns.tsx index 7add007..f748ca6 100644 --- a/frontend/app/dashboard/campaigns/senders/_columns.tsx +++ b/frontend/app/dashboard/campaigns/senders/_columns.tsx @@ -47,7 +47,14 @@ const CampaignSenderActions = ({ senderData }: CampaignSenderActionsProps) => { ); }; -export const columns: ColumnDef[] = [ +export const columns: ColumnDef<{ + id: string; + created_at: string; + updated_at: string; + server_id: string; + from_name: string; + from_email: string; +}>[] = [ { accessorKey: "from_name", header: "From Name", @@ -72,7 +79,9 @@ export const columns: ColumnDef[] = [ id: "actions", enableSorting: false, header: () => "Actions", - cell: ({ row }) => , + cell: ({ row }) => ( + + ), }, ]; diff --git a/frontend/app/dashboard/campaigns/senders/_components/AddCampaignSender.tsx b/frontend/app/dashboard/campaigns/senders/_components/AddCampaignSender.tsx index be2c6c9..ded679f 100644 --- a/frontend/app/dashboard/campaigns/senders/_components/AddCampaignSender.tsx +++ b/frontend/app/dashboard/campaigns/senders/_components/AddCampaignSender.tsx @@ -16,7 +16,7 @@ import { CampaignSenderFields } from "./CampaignSenderFields"; import { Server } from "@/lib/type/server"; // Schema for adding a campaign sender -const AddCampaignSenderSchema = EditCampaignSenderFormSchemaDTO.extend({ +export const AddCampaignSenderSchema = EditCampaignSenderFormSchemaDTO.extend({ server_id: z.string().uuid("Invalid server ID"), }); diff --git a/frontend/app/dashboard/campaigns/senders/_components/CampaignSenderFields.tsx b/frontend/app/dashboard/campaigns/senders/_components/CampaignSenderFields.tsx index 572a67b..2534786 100644 --- a/frontend/app/dashboard/campaigns/senders/_components/CampaignSenderFields.tsx +++ b/frontend/app/dashboard/campaigns/senders/_components/CampaignSenderFields.tsx @@ -17,9 +17,13 @@ import { SelectValue, } from "@/components/ui/select"; import { Server } from "@/lib/type/server"; +import { AddCampaignSenderSchema } from "./AddCampaignSender"; +import { z } from "zod"; + +type AddCampaignSenderSchemaType = z.infer; interface CampaignSenderFieldsProps { - form: UseFormReturn; + form: UseFormReturn; servers?: Server[]; isLoadingServers?: boolean; onFieldChange?: () => void; diff --git a/frontend/app/dashboard/campaigns/senders/page.tsx b/frontend/app/dashboard/campaigns/senders/page.tsx index 310828a..f80df0c 100644 --- a/frontend/app/dashboard/campaigns/senders/page.tsx +++ b/frontend/app/dashboard/campaigns/senders/page.tsx @@ -8,7 +8,7 @@ import { AddCampaignSender } from "./_components/AddCampaignSender"; const CampaignSenderPage = () => { const [isOpen, setIsOpen] = useState(false); - const { data: senders, error, isLoading } = useGetCampaignSendersQuery(); + const { data: senders, isLoading } = useGetCampaignSendersQuery(); if (isLoading) { return
Loading...
; diff --git a/frontend/app/dashboard/contacts/[id]/mails/_columns.tsx b/frontend/app/dashboard/contacts/[id]/mails/_columns.tsx index 2a3d51c..05aa5d5 100644 --- a/frontend/app/dashboard/contacts/[id]/mails/_columns.tsx +++ b/frontend/app/dashboard/contacts/[id]/mails/_columns.tsx @@ -6,17 +6,23 @@ import { formatDateWithoutDay } from '@/lib/utils'; import ActionsColumn from '@/app/dashboard/campaigns/[id]/analytics/_components/ActionsColumn'; import ToolTip from '@/components/common/ToolTip'; import TemplateName from './components/TemplateName'; +import { MailDTO } from '@/lib/type'; +import { z } from 'zod'; + +type Mail = z.infer; const statusTagStyleMap = { draft: 'bg-gray-100 text-gray-800', queued: 'bg-yellow-100 text-yellow-800', - delivered: 'bg-blue-100 text-blue-800', + submitted: 'bg-blue-100 text-blue-800', + delivered: 'bg-green-100 text-green-800', bounced: 'bg-red-100 text-red-800', + failed: 'border border-red-800 text-red-800', } export const columns = ( deleteMailHandler: (id: string) => void, -): ColumnDef[] => [ +): ColumnDef[] => [ { accessorKey: "template", header: "Template", diff --git a/frontend/app/dashboard/contacts/[id]/mails/page.tsx b/frontend/app/dashboard/contacts/[id]/mails/page.tsx index 7d196d6..1a859c9 100644 --- a/frontend/app/dashboard/contacts/[id]/mails/page.tsx +++ b/frontend/app/dashboard/contacts/[id]/mails/page.tsx @@ -6,12 +6,13 @@ import DataTable from "@/components/DataTable"; import { useParams } from "next/navigation"; import columns from "@/app/dashboard/contacts/[id]/mails/_columns"; import { User } from "lucide-react"; +import Link from "next/link"; -const page = () => { +const Page = () => { const { id } : { id: string } = useParams(); const { data: mails, isLoading } = useGetMailsForContactQuery(id); - const { data: contact, isLoading: isContactLoading } = useGetContactByIdQuery(id); - const [deleteMail, { isLoading: isDeleting, error: deletionError }] = useDeleteMailMutation(); + const { data: contact } = useGetContactByIdQuery(id); + const [deleteMail, { error: deletionError }] = useDeleteMailMutation(); const deleteMailHandler = async (id: string) => { if (deletionError) { @@ -40,9 +41,9 @@ const page = () => {
{/* breadcrumb */}
- Dashboard + Dashboard / - Contacts + Contacts / {contact?.first_name} {contact?.last_name} / @@ -60,4 +61,4 @@ const page = () => { ) } -export default page \ No newline at end of file +export default Page \ No newline at end of file diff --git a/frontend/app/dashboard/contacts/bounces/_columns.tsx b/frontend/app/dashboard/contacts/bounces/_columns.tsx index ec791c8..3e0ca39 100644 --- a/frontend/app/dashboard/contacts/bounces/_columns.tsx +++ b/frontend/app/dashboard/contacts/bounces/_columns.tsx @@ -7,13 +7,14 @@ import ActionsColumn from '@/app/dashboard/campaigns/[id]/analytics/_components/ import ToolTip from '@/components/common/ToolTip'; import CampaignName from './_components/CampaignName'; import { Checkbox } from '@/components/ui/checkbox'; +import { Mail } from '@/lib/type/mail'; export const columns = ( selectedBounces: Record, setSelectedBounces: React.Dispatch>>, deleteMailHandler: (id: string) => void, -): ColumnDef[] => [ +): ColumnDef[] => [ { id: "select", enableSorting: false, diff --git a/frontend/app/dashboard/contacts/bounces/page.tsx b/frontend/app/dashboard/contacts/bounces/page.tsx index c42a165..59a321c 100644 --- a/frontend/app/dashboard/contacts/bounces/page.tsx +++ b/frontend/app/dashboard/contacts/bounces/page.tsx @@ -8,11 +8,10 @@ import columns from './_columns'; import { useDeleteMailMutation, useGetBouncedMailQuery } from '@/app/services/MailApi'; import ConfirmationPopup from '@/components/common/ConfirmationPopup'; -const page = () => { - const { data: bouncedMails, error, isLoading } = useGetBouncedMailQuery(); +const Page = () => { + const { data: bouncedMails } = useGetBouncedMailQuery(); const [ deleteMail, - { isLoading: isDeleting, error: deletionError }, ] = useDeleteMailMutation(); const [selectedBounces, setSelectedBounces] = useState>({}); @@ -79,4 +78,4 @@ const page = () => { ) } -export default page \ No newline at end of file +export default Page diff --git a/frontend/app/dashboard/contacts/page.tsx b/frontend/app/dashboard/contacts/page.tsx index b2c7490..3384e4f 100644 --- a/frontend/app/dashboard/contacts/page.tsx +++ b/frontend/app/dashboard/contacts/page.tsx @@ -2,7 +2,7 @@ import DataTable from "@/components/DataTable"; import { Button } from "@/components/ui/button"; -import { useState, useMemo, Suspense } from "react"; +import { useState, Suspense } from "react"; import { useGetContactsQuery, useDeleteContactMutation, diff --git a/frontend/app/dashboard/lists/_columns.tsx b/frontend/app/dashboard/lists/_columns.tsx index 06715ed..2bc660c 100644 --- a/frontend/app/dashboard/lists/_columns.tsx +++ b/frontend/app/dashboard/lists/_columns.tsx @@ -5,10 +5,11 @@ import { ColumnDef } from "@tanstack/react-table"; import { formatDate } from "@/lib/utils"; import ActionsColumn from "./_components/ActionsColumn"; import Link from "next/link"; +import { List } from "@/lib/type/list"; const namespaceId = "e3bda5cf-760e-43ea-8e9a-c2c3c5f95b82"; -export const columns: ColumnDef[] = [ +export const columns: ColumnDef[] = [ { accessorKey: "name", header: "Name", diff --git a/frontend/app/dashboard/lists/_components/ActionsColumn.tsx b/frontend/app/dashboard/lists/_components/ActionsColumn.tsx index e894771..c0cdbb2 100644 --- a/frontend/app/dashboard/lists/_components/ActionsColumn.tsx +++ b/frontend/app/dashboard/lists/_components/ActionsColumn.tsx @@ -6,9 +6,10 @@ import { Edit3, Trash2 } from 'lucide-react'; import React from 'react' import ConfirmationPopup from '@/components/common/ConfirmationPopup'; import { Button } from '@/components/ui/button'; +import { List } from '@/lib/type/list'; interface ActionsColumnProps { - row: Row, + row: Row, namespaceId: string, }; @@ -18,7 +19,7 @@ const ActionsColumn = ({ }: ActionsColumnProps) => { const listId = row.original.id; - const [ deleteList, { isLoading: isDeleting, error: deletionError } ] = useDeleteListMutation(); + const [ deleteList, { error: deletionError } ] = useDeleteListMutation(); const deleteListHandler = async (id: string) => { if (deletionError) { diff --git a/frontend/app/dashboard/lists/_components/listForms/EditListForm.tsx b/frontend/app/dashboard/lists/_components/listForms/EditListForm.tsx index 7e933ea..634fa94 100644 --- a/frontend/app/dashboard/lists/_components/listForms/EditListForm.tsx +++ b/frontend/app/dashboard/lists/_components/listForms/EditListForm.tsx @@ -1,5 +1,5 @@ 'use client' -/* eslint-disable @typescript-eslint/no-unused-vars */ + import React, { useEffect, useRef } from 'react'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -39,7 +39,7 @@ const EditListForm = ({ listId }: { listId: string }) => { form.setValue("description", list.description); } } - }, [data]); + }, [data, form, listId]); if (updateError) { return
There was an error updating the list...
diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 761a7d7..acee8cd 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -5,8 +5,8 @@ import DataTable from "@/components/DataTable"; import columns from "@/app/dashboard/campaigns/[id]/analytics/_columns"; function Dashboard() { - const { data: mails, isLoading, error } = useGetMailsQuery({}); - const [deleteMail, { isLoading: isDeleting, error: deletionError }] = + const { data: mails } = useGetMailsQuery({}); + const [deleteMail, { error: deletionError }] = useDeleteMailMutation(); const deleteMailHandler = async (id: string) => { diff --git a/frontend/app/dashboard/servers/_components/SmtpServerForm.tsx b/frontend/app/dashboard/servers/_components/SmtpServerForm.tsx index efd8688..a4b3322 100644 --- a/frontend/app/dashboard/servers/_components/SmtpServerForm.tsx +++ b/frontend/app/dashboard/servers/_components/SmtpServerForm.tsx @@ -11,6 +11,7 @@ import { UseFormTrigger, Control, FieldErrors, + UseFormSetValue, } from "react-hook-form"; import { Select, @@ -28,7 +29,7 @@ interface SmtpServerFormProps { control: Control; trigger: UseFormTrigger; watch: UseFormWatch; - setValue: any; + setValue: UseFormSetValue; } export default function SmtpServerForm({ @@ -40,10 +41,6 @@ export default function SmtpServerForm({ watch, }: SmtpServerFormProps) { const { handlePortChange } = usePortControls(watch, setValue, trigger); - const currentPort = watch("port"); - const currentDefaultFromEmail = watch("default_from_email"); - - // console.warn("THE DEFAULT from email ===> ", currentDefaultFromEmail); return ( <> diff --git a/frontend/app/dashboard/templates/_columns.tsx b/frontend/app/dashboard/templates/_columns.tsx index 0f8a6a3..280d5e3 100644 --- a/frontend/app/dashboard/templates/_columns.tsx +++ b/frontend/app/dashboard/templates/_columns.tsx @@ -4,9 +4,10 @@ import React from 'react' import { ColumnDef } from '@tanstack/react-table'; import { formatDate } from '@/lib/utils'; import ActionsColumn from './_components/ActionsColumn'; +import { Template } from '@/lib/type/template'; -export const columns: ColumnDef[] = [ +export const columns: ColumnDef