Compare commits
No commits in common. "7faac8b448ed736b87488f535e919bf3bbd59d77" and "3bd334f2398359858036c562def00745b9518da2" have entirely different histories.
7faac8b448
...
3bd334f239
@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.MassEmailReact.API.Controllers;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
@ -8,7 +7,6 @@ using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.API.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class AuthenticationController : BaseController
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
@ -28,13 +26,6 @@ namespace Surge365.MassEmailReact.API.Controllers
|
||||
SameSite = SameSiteMode.Strict,
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(-1) // Expire immediately
|
||||
});
|
||||
Response.Cookies.Append("accessToken", "", new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = true,
|
||||
SameSite = SameSiteMode.Strict,
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(-1) // Expire immediately
|
||||
});
|
||||
|
||||
return Ok(new { message = "Logged out successfully" });
|
||||
}
|
||||
@ -57,7 +48,6 @@ namespace Surge365.MassEmailReact.API.Controllers
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
||||
};
|
||||
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
||||
Response.Cookies.Append("accessToken", authResponse.data.Value.accessToken, cookieOptions);
|
||||
|
||||
//TODO: Store user in session
|
||||
return Ok(new { success = true, authResponse.data.Value.accessToken, authResponse.data.Value.user });
|
||||
@ -84,7 +74,6 @@ namespace Surge365.MassEmailReact.API.Controllers
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
||||
};
|
||||
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
||||
Response.Cookies.Append("accessToken", authResponse.data.Value.accessToken, cookieOptions);
|
||||
|
||||
return Ok(new { accessToken = authResponse.data.Value.accessToken });
|
||||
}
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Surge365.MassEmailReact.API.Controllers
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
@ -6,19 +6,13 @@ using Surge365.MassEmailReact.Infrastructure;
|
||||
using Surge365.MassEmailReact.Infrastructure.DapperMaps;
|
||||
using Surge365.MassEmailReact.Infrastructure.Repositories;
|
||||
using Surge365.MassEmailReact.Infrastructure.Services;
|
||||
using Surge365.MassEmailReact.Infrastructure.Middleware;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
WebApplication? app = null;
|
||||
try
|
||||
{
|
||||
|
||||
var keyVaultName = builder.Configuration["KeyVaultName"] ?? "";
|
||||
if (!string.IsNullOrEmpty(keyVaultName))
|
||||
{
|
||||
@ -29,41 +23,6 @@ try
|
||||
new SurgeKeyVaultSecretManager()
|
||||
);
|
||||
}
|
||||
builder.Services.AddSingleton<ILoggingService, LoggingService>();
|
||||
|
||||
var jwtKey = Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]!);
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false,
|
||||
ValidateLifetime = true, // Enforce expiration
|
||||
ValidateIssuerSigningKey = true,
|
||||
//ValidIssuer = "your_issuer", // ADD back if admin api updated to include
|
||||
//ValidAudience = "your_audience", // ADD back if admin api updated to include
|
||||
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
|
||||
ClockSkew = TimeSpan.Zero // Optional: no grace period for expiration
|
||||
};
|
||||
// Read JWT from accessToken cookie
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var token = context.Request.Cookies["accessToken"];
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
context.Token = token;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddHttpClient("SendGridClient", client =>
|
||||
{
|
||||
@ -95,10 +54,8 @@ try
|
||||
builder.Services.AddScoped<IMailingService, MailingService>();
|
||||
builder.Services.AddScoped<IMailingRepository, MailingRepository>();
|
||||
|
||||
app = builder.Build();
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
app.UseCustomExceptionHandler();
|
||||
app.UseDefaultFiles();
|
||||
app.MapStaticAssets();
|
||||
|
||||
@ -116,12 +73,5 @@ try
|
||||
|
||||
|
||||
DapperConfiguration.ConfigureMappings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggingService appLoggingService = new LoggingService(builder.Configuration);
|
||||
appLoggingService.LogError(ex).Wait();
|
||||
return;
|
||||
}
|
||||
if (app != null)
|
||||
|
||||
app.Run();
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
||||
<Version>9.*-*</Version>
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
using Surge365.MassEmailReact.Application.DTOs.AuthApi;
|
||||
|
||||
namespace Surge365.MassEmailReact.Application.Interfaces
|
||||
{
|
||||
public enum LogLevels //TODO: Move all this to Surge365.Core (new project)
|
||||
{
|
||||
Fatal = 1,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug
|
||||
}
|
||||
public interface ILoggingService
|
||||
{
|
||||
Task<bool> InsertLog(LogLevels level, string logger, int? userKey, string task, string message,
|
||||
string exceptionStack, string exceptionMessage, string exceptionInnerMessage,
|
||||
string customMessage1, string customMessage2, string customMessage3,
|
||||
string customMessage4, string customMessage5);
|
||||
Task<bool> LogError(Exception ex);
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Middleware
|
||||
{
|
||||
public class CustomException
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILoggingService _loggingService;
|
||||
|
||||
public CustomException(RequestDelegate next, ILoggingService loggingService)
|
||||
{
|
||||
_next = next;
|
||||
_loggingService = loggingService;
|
||||
}
|
||||
/// <summary>
|
||||
/// Invokes the middleware peforming session start
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/>.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
if (!(ex is ThreadAbortException))
|
||||
{
|
||||
/* Guid? loginId = null;
|
||||
|
||||
if (context.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
// Adjust the claim type based on your JWT configuration (e.g., "sub", "nameid", or custom)
|
||||
var loginIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? context.User.FindFirst("sub")?.Value;
|
||||
if (!string.IsNullOrEmpty(loginIdClaim) && Guid.TryParse(loginIdClaim, out var parsedUserId))
|
||||
{
|
||||
loginId = parsedUserId;
|
||||
}
|
||||
}*/
|
||||
|
||||
await _loggingService.LogError(ex);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class CustomExceptionMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<CustomException>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,7 +67,6 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
parameters.Add("@unsubscribe", bouncedEmail.Unsubscribe, DbType.Boolean);
|
||||
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
parameters.Add("@bounced_email_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
||||
|
||||
await conn.ExecuteAsync("mem_save_bounced_email", parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||
{
|
||||
public class LoggingService(IConfiguration configuration) : ILoggingService
|
||||
{
|
||||
private IConfiguration _configuration = configuration;
|
||||
public async Task<bool> InsertLog(LogLevels level, string logger, int? userId, string task, string message,
|
||||
string exceptionStack, string exceptionMessage, string exceptionInnerMessage = "",
|
||||
string customMessage1 = "", string customMessage2 = "", string customMessage3 = "",
|
||||
string customMessage4 = "", string customMessage5 = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
string applicationCode = _configuration["AppCode"] ?? "";
|
||||
List<SqlParameter> pms = new List<SqlParameter> {
|
||||
new SqlParameter("@ApplicationCode", applicationCode),
|
||||
new SqlParameter("@level", level),
|
||||
new SqlParameter("@logger", (string.IsNullOrWhiteSpace(logger) ? this.GetType().Name : logger)),
|
||||
new SqlParameter("@UserKey", userId),
|
||||
new SqlParameter("@Task", task),
|
||||
new SqlParameter("@Message", message),
|
||||
new SqlParameter("@ExceptionStack", exceptionStack),
|
||||
new SqlParameter("@ExceptionMessage", exceptionMessage),
|
||||
new SqlParameter("@ExceptionInnerMessage", exceptionInnerMessage),
|
||||
new SqlParameter("@CustomMessage1", customMessage1),
|
||||
new SqlParameter("@CustomMessage2", customMessage2),
|
||||
new SqlParameter("@CustomMessage3", customMessage3),
|
||||
new SqlParameter("@CustomMessage4", customMessage4),
|
||||
new SqlParameter("@CustomMessage5", customMessage5)
|
||||
};
|
||||
DataAccess da = new DataAccess(_configuration, "YTBLog.ConnectionString");
|
||||
await da.CallActionProcedureAsync(pms, "usp_InsertLog");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public Task<bool> LogError(Exception ex)
|
||||
{
|
||||
string exceptionMessage = "";
|
||||
string exceptionInnerMessage = "";
|
||||
string exceptionStack = "";
|
||||
|
||||
exceptionMessage = ex.Message;
|
||||
exceptionStack = ex.StackTrace ?? "";
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
exceptionInnerMessage = ex.InnerException.Message;
|
||||
}
|
||||
|
||||
return InsertLog(LogLevels.Error, "", null, "LogError", ex.Message,
|
||||
exceptionStack, exceptionMessage, exceptionInnerMessage, "", "", "", "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
|
||||
<PackageReference Include="Dapper.FluentMap" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.4" />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.2191419">
|
||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.2191419">
|
||||
<PropertyGroup>
|
||||
<StartupCommand>npm run dev</StartupCommand>
|
||||
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
||||
|
||||
2897
Surge365.MassEmailReact.Web/package-lock.json
generated
2897
Surge365.MassEmailReact.Web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,8 +14,6 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@fontsource/roboto": "^5.1.1",
|
||||
"@grafana/faro-web-sdk": "^1.18.0",
|
||||
"@grafana/faro-web-tracing": "^1.18.0",
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@mui/icons-material": "^6.4.5",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import utils from '@/utils/utils';
|
||||
import utils from '@/ts/utils';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
|
||||
const AuthCheck: React.FC = () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import utils from '@/utils/utils';
|
||||
import utils from '@/ts/utils';
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
|
||||
interface AuthContextType {
|
||||
@ -7,7 +7,6 @@ interface AuthContextType {
|
||||
userRoles: string[];
|
||||
setAuth: (token: string | null) => void;
|
||||
isLoading: boolean; // Add loading state
|
||||
refreshToken: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
@ -15,7 +14,6 @@ const AuthContext = createContext<AuthContextType>({
|
||||
userRoles: [],
|
||||
setAuth: () => { },
|
||||
isLoading: true, // Default to loading
|
||||
refreshToken: async () => false,
|
||||
});
|
||||
|
||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
@ -35,26 +33,6 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const refreshToken = async (): Promise<boolean> => {
|
||||
try {
|
||||
const response = await fetch('/api/authentication/refreshtoken', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAuth(data.accessToken);
|
||||
return true;
|
||||
} else {
|
||||
setAuth(null);
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
setAuth(null);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Check auth on mount
|
||||
useEffect(() => {
|
||||
const initializeAuth = async () => {
|
||||
@ -77,16 +55,17 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
}
|
||||
} catch {
|
||||
setAuth(null);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false); // Done loading regardless of outcome
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initializeAuth();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ accessToken, userRoles, setAuth, isLoading, refreshToken }}>
|
||||
<AuthContext.Provider value={{ accessToken, userRoles, setAuth, isLoading }}>
|
||||
{isLoading ? (
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@ -45,7 +45,6 @@ import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
|
||||
// Constants
|
||||
@ -80,7 +79,6 @@ interface LayoutProps {
|
||||
}
|
||||
|
||||
const Layout = ({ children }: LayoutProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); //TODO: Move this to shared utils?
|
||||
const [open, setOpen] = React.useState(!isMobile);
|
||||
@ -120,7 +118,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
const handleRefreshUser = async () => {
|
||||
handleCloseProfileMenu();
|
||||
try {
|
||||
const response = await customFetch('/api/authentication/refreshtoken', {
|
||||
const response = await fetch('/api/authentication/refreshtoken', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
|
||||
@ -140,7 +138,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
|
||||
const handleLogout = async () => {
|
||||
setAuth(null); // Clear context
|
||||
await customFetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
||||
await fetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@ import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||
import { useForm, Resolver, Controller } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type BouncedEmailEditProps = {
|
||||
open: boolean;
|
||||
@ -49,7 +48,6 @@ const defaultBouncedEmail: BouncedEmail = {
|
||||
};
|
||||
|
||||
const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = bouncedEmail == null;
|
||||
const setupData: SetupData = useSetupData();
|
||||
const originalBouncedEmail: BouncedEmail | null = bouncedEmail ? { ...bouncedEmail } : null;
|
||||
@ -75,7 +73,7 @@ const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailE
|
||||
const method = isNew ? "POST" : "PUT"
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
@ -95,7 +93,7 @@ const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailE
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{isNew ? "Add Blocked Email" : "Edit Blocked Email"}</DialogTitle>
|
||||
<DialogTitle>{isNew ? "Add Bounced Email" : "Edit Bounced Email"}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
{...register("emailAddress")}
|
||||
|
||||
@ -14,7 +14,6 @@ import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||
import { useForm, Controller, Resolver } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type EmailDomainEditProps = {
|
||||
open: boolean;
|
||||
@ -57,7 +56,6 @@ const defaultEmailDomain: EmailDomain = {
|
||||
};
|
||||
|
||||
const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !emailDomain || emailDomain.id === 0;
|
||||
const setupData: SetupData = useSetupData();
|
||||
const originalEmailDomain: EmailDomain | null = emailDomain ? { ...emailDomain } : null;
|
||||
@ -82,7 +80,7 @@ const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEdit
|
||||
const method = isNew ? "POST" : "PUT";
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { useState, FormEvent } from 'react';
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Typography, Box, IconButton } from '@mui/material';
|
||||
import { Close as CloseIcon } from '@mui/icons-material';
|
||||
import utils from '@/utils/utils';
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
import utils from '@/ts/utils';
|
||||
|
||||
type FormErrors = Record<string, string>;
|
||||
|
||||
@ -12,7 +11,6 @@ type ForgotPasswordModalProps = {
|
||||
};
|
||||
|
||||
const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose }) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const [username, setUsername] = useState('');
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||
const [usernameNotFound, setUsernameNotFound] = useState(false);
|
||||
@ -38,7 +36,7 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose
|
||||
if (validate()) {
|
||||
console.log('Processing forgot password for', username);
|
||||
const apiUrl = "/api/authentication/generatepasswordrecovery";
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username}),
|
||||
|
||||
@ -40,7 +40,6 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import dayjs, { Dayjs } from 'dayjs'; // Import Dayjs for date handling
|
||||
import utc from 'dayjs/plugin/utc'; // Import the UTC plugin
|
||||
import { toast } from "react-toastify";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
@ -67,75 +66,6 @@ const recurringTypeOptions = [
|
||||
{ code: 'W', name: 'Weekly' },
|
||||
];
|
||||
|
||||
const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !mailing || mailing.id === 0;
|
||||
const setupData: SetupData = useSetupData();
|
||||
const [approved, setApproved] = useState<boolean>(false);
|
||||
const [recurring, setRecurring] = useState<boolean>(false);
|
||||
const [scheduleForLater, setScheduleForLater] = useState<boolean>(false);
|
||||
const [testEmailListId, setTestEmailListId] = useState<number | null>(null);
|
||||
const [emails, setEmails] = useState<string[]>([]); // State for email array
|
||||
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
||||
const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null);
|
||||
const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
||||
const [currentTarget, setCurrentTarget] = useState<Target | null>(null);
|
||||
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
||||
const [targetSampleLoading, setTargetSampleLoading] = useState(false);
|
||||
|
||||
const defaultMailing: Mailing = {
|
||||
id: 0,
|
||||
name: "",
|
||||
description: "",
|
||||
templateId: 0,
|
||||
targetId: 0,
|
||||
statusCode: "ED",
|
||||
scheduleDate: null,
|
||||
sentDate: null,
|
||||
sessionActivityId: null,
|
||||
recurringTypeCode: null,
|
||||
recurringStartDate: null,
|
||||
template: {
|
||||
id: 0,
|
||||
mailingId: 0,
|
||||
name: "",
|
||||
domainId: 0,
|
||||
description: "",
|
||||
htmlBody: "",
|
||||
subject: "",
|
||||
toName: "",
|
||||
fromName: "",
|
||||
fromEmail: "",
|
||||
replyToEmail: "",
|
||||
clickTracking: false,
|
||||
openTracking: false,
|
||||
categoryXml: ""
|
||||
},
|
||||
target: {
|
||||
id: 0,
|
||||
mailingId: 0,
|
||||
serverId: 0,
|
||||
name: "",
|
||||
databaseName: "",
|
||||
viewName: "",
|
||||
filterQuery: "",
|
||||
allowWriteBack: false,
|
||||
}
|
||||
,
|
||||
};
|
||||
|
||||
|
||||
const nameIsAvailable = async (id: number, name: string) => {
|
||||
const response = await customFetch(`/api/mailings/available?${id > 0 ? "id=" + id + "&" : ""}name=${name}`);
|
||||
const data = await response.json();
|
||||
return data.available;
|
||||
};
|
||||
const getNextAvailableName = async (id: number, name: string) => {
|
||||
const response = await customFetch(`/api/mailings/nextavailablename?${id > 0 ? "id=" + id + "&" : ""}name=${name}`);
|
||||
const data = await response.json();
|
||||
return data.name;
|
||||
};
|
||||
|
||||
const schema = yup.object().shape({
|
||||
id: yup.number().nullable(),
|
||||
name: yup.string().required("Name is required")
|
||||
@ -243,6 +173,73 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
}).nullable(),
|
||||
});
|
||||
|
||||
const nameIsAvailable = async (id: number, name: string) => {
|
||||
const response = await fetch(`/api/mailings/available?${id > 0 ? "id=" + id + "&" : ""}name=${name}`);
|
||||
const data = await response.json();
|
||||
return data.available;
|
||||
};
|
||||
const getNextAvailableName = async (id: number, name: string) => {
|
||||
const response = await fetch(`/api/mailings/nextavailablename?${id > 0 ? "id=" + id + "&" : ""}name=${name}`);
|
||||
const data = await response.json();
|
||||
return data.name;
|
||||
};
|
||||
|
||||
const defaultMailing: Mailing = {
|
||||
id: 0,
|
||||
name: "",
|
||||
description: "",
|
||||
templateId: 0,
|
||||
targetId: 0,
|
||||
statusCode: "ED",
|
||||
scheduleDate: null,
|
||||
sentDate: null,
|
||||
sessionActivityId: null,
|
||||
recurringTypeCode: null,
|
||||
recurringStartDate: null,
|
||||
template: {
|
||||
id: 0,
|
||||
mailingId: 0,
|
||||
name: "",
|
||||
domainId: 0,
|
||||
description: "",
|
||||
htmlBody: "",
|
||||
subject: "",
|
||||
toName: "",
|
||||
fromName: "",
|
||||
fromEmail: "",
|
||||
replyToEmail: "",
|
||||
clickTracking: false,
|
||||
openTracking: false,
|
||||
categoryXml: ""
|
||||
},
|
||||
target: {
|
||||
id: 0,
|
||||
mailingId: 0,
|
||||
serverId: 0,
|
||||
name: "",
|
||||
databaseName: "",
|
||||
viewName: "",
|
||||
filterQuery: "",
|
||||
allowWriteBack: false,
|
||||
}
|
||||
,
|
||||
};
|
||||
|
||||
const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
const isNew = !mailing || mailing.id === 0;
|
||||
const setupData: SetupData = useSetupData();
|
||||
const [approved, setApproved] = useState<boolean>(false);
|
||||
const [recurring, setRecurring] = useState<boolean>(false);
|
||||
const [scheduleForLater, setScheduleForLater] = useState<boolean>(false);
|
||||
const [testEmailListId, setTestEmailListId] = useState<number | null>(null);
|
||||
const [emails, setEmails] = useState<string[]>([]); // State for email array
|
||||
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
||||
const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null);
|
||||
const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
||||
const [currentTarget, setCurrentTarget] = useState<Target | null>(null);
|
||||
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
||||
const [targetSampleLoading, setTargetSampleLoading] = useState(false);
|
||||
|
||||
const { register, trigger, control, handleSubmit, reset, setValue, formState: { errors } } = useForm<Mailing>({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
@ -254,7 +251,6 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const initializeMailingEdit = async () => {
|
||||
if (open) {
|
||||
@ -318,7 +314,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
}
|
||||
try {
|
||||
const jsonPayload = JSON.stringify(formData);
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: jsonPayload,
|
||||
@ -361,7 +357,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
};
|
||||
|
||||
// Make the API call to /api/mailings/test
|
||||
const response = await customFetch("/api/mailings/test", {
|
||||
const response = await fetch("/api/mailings/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@ -391,7 +387,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
||||
|
||||
setTargetSampleLoading(true);
|
||||
try {
|
||||
const response = await customFetch(`/api/targets/${currentTarget.id}/sample`);
|
||||
const response = await fetch(`/api/targets/${currentTarget.id}/sample`);
|
||||
if (!response.ok) throw new Error("Failed to fetch sample data");
|
||||
const data: TargetSample = await response.json();
|
||||
setTargetSample(data);
|
||||
|
||||
@ -28,7 +28,6 @@ import TemplateViewer from "@/components/modals/TemplateViewer";
|
||||
import TargetSampleModal from "@/components/modals/TargetSampleModal";
|
||||
import Target from "@/types/target";
|
||||
import Template from "@/types/template";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
interface MailingViewProps {
|
||||
open: boolean;
|
||||
@ -49,7 +48,6 @@ interface MailingEmail {
|
||||
}
|
||||
|
||||
function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
||||
const customFetch = useCustomFetch();
|
||||
const setupData = useSetupData();
|
||||
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
||||
const [targetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
||||
@ -67,7 +65,7 @@ function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const emailsResponse = await customFetch(`/api/mailings/${mailing.id}/emails`);
|
||||
const emailsResponse = await fetch(`/api/mailings/${mailing.id}/emails`);
|
||||
const emailsData = await emailsResponse.json();
|
||||
|
||||
if (emailsData) {
|
||||
|
||||
@ -12,7 +12,6 @@ import Server from "@/types/server";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type ServerEditProps = {
|
||||
open: boolean;
|
||||
@ -52,7 +51,6 @@ const defaultServer: Server = {
|
||||
password: "",
|
||||
};
|
||||
const ServerEdit = ({ open, server, onClose, onSave }: ServerEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !server || server.id === 0;
|
||||
const originalServer: Server | null = server ? { ...server } : null;
|
||||
//const [formData, setFormData] = useState<Server>({ ...server });
|
||||
@ -75,7 +73,7 @@ const ServerEdit = ({ open, server, onClose, onSave }: ServerEditProps) => {
|
||||
const method = isNew ? "POST" : "PUT";
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
|
||||
@ -24,7 +24,6 @@ import TargetColumn from "@/types/targetColumn";
|
||||
import TargetSampleColumn from "@/types/targetSampleColumn";
|
||||
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
||||
import { GridColDef } from '@mui/x-data-grid';
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type TargetEditProps = {
|
||||
open: boolean;
|
||||
@ -97,7 +96,6 @@ const useColumnErrors = (errors: any, columns: TargetColumn[]) => {
|
||||
};
|
||||
|
||||
const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !target || target.id === 0;
|
||||
const setupData: SetupData = useSetupData();
|
||||
const [targetTested, setTargetTested] = useState<boolean>(false);
|
||||
@ -138,7 +136,7 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
||||
const method = isNew ? "POST" : "PUT";
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
@ -174,7 +172,7 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
||||
const handleTestTarget = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(`/api/targets/test`, {
|
||||
const response = await fetch(`/api/targets/test`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
|
||||
@ -13,7 +13,6 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import TargetSample from "@/types/targetSample";
|
||||
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
||||
import { GridColDef } from '@mui/x-data-grid';
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type TargetSampleModalProps = {
|
||||
open: boolean;
|
||||
@ -23,7 +22,6 @@ type TargetSampleModalProps = {
|
||||
};
|
||||
|
||||
const TargetSampleModal = ({ open, target, targetSample, onClose }: TargetSampleModalProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const [localTargetSample, setLocalTargetSample] = useState<TargetSample | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@ -36,7 +34,7 @@ const TargetSampleModal = ({ open, target, targetSample, onClose }: TargetSample
|
||||
else if (target.id) {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(`/api/targets/${target.id}/sample`);
|
||||
const response = await fetch(`/api/targets/${target.id}/sample`);
|
||||
if (!response.ok) throw new Error("Failed to fetch sample data");
|
||||
const data: TargetSample = await response.json();
|
||||
setLocalTargetSample(data);
|
||||
|
||||
@ -21,7 +21,6 @@ import Editor from "@monaco-editor/react";
|
||||
// Assuming these types and context are defined elsewhere
|
||||
import Template from "@/types/template";
|
||||
import { useSetupData } from "@/context/SetupDataContext";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type TemplateEditProps = {
|
||||
open: boolean;
|
||||
@ -65,7 +64,6 @@ const defaultTemplate: Template = {
|
||||
};
|
||||
|
||||
const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !template || template.id === 0;
|
||||
const setupData = useSetupData();
|
||||
|
||||
@ -102,7 +100,7 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
|
||||
const method = isNew ? "POST" : "PUT";
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
|
||||
@ -13,7 +13,6 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import TestEmailList from "@/types/testEmailList";
|
||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type TestEmailListEditProps = {
|
||||
open: boolean;
|
||||
@ -57,7 +56,6 @@ const defaultTestEmailList: TestEmailListForm = {
|
||||
};
|
||||
|
||||
const TestEmailListEdit = ({ open, testEmailList, onClose, onSave }: TestEmailListEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !testEmailList || testEmailList.id === 0;
|
||||
const setupData: SetupData = useSetupData();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -92,7 +90,7 @@ const TestEmailListEdit = ({ open, testEmailList, onClose, onSave }: TestEmailLi
|
||||
const method = isNew ? "POST" : "PUT";
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(testEmailListDto),
|
||||
|
||||
@ -11,7 +11,6 @@ import UnsubscribeUrl from "@/types/unsubscribeUrl";
|
||||
import { useForm, Resolver } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
type UnsubscribeUrlEditProps = {
|
||||
open: boolean;
|
||||
@ -33,7 +32,6 @@ const defaultUnsubscribeUrl: UnsubscribeUrl = {
|
||||
};
|
||||
|
||||
const UnsubscribeUrlEdit = ({ open, unsubscribeUrl, onClose, onSave }: UnsubscribeUrlEditProps) => {
|
||||
const customFetch = useCustomFetch();
|
||||
const isNew = !unsubscribeUrl || unsubscribeUrl.id === 0;
|
||||
|
||||
const { register, handleSubmit, reset, formState: { errors } } = useForm<UnsubscribeUrl>({
|
||||
@ -55,7 +53,7 @@ const UnsubscribeUrlEdit = ({ open, unsubscribeUrl, onClose, onSave }: Unsubscri
|
||||
const method = isNew ? "POST" : "PUT";
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(formData),
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, CircularProgress, IconButton, FormControlLabel } from '@mui/material';
|
||||
import { DataGrid, GridColDef, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||
import MailingStatistic from '@/types/mailingStatistic';
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
import MailingStatistic from '@/types/mailingStatistic'; // Assuming you'll create this type based on the C# class
|
||||
|
||||
function ActiveMailings() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -42,9 +38,8 @@ function ActiveMailings() {
|
||||
|
||||
isFetchingRef.current = true;
|
||||
setMailingsLoading(true);
|
||||
setupData.reloadSetupData();
|
||||
try {
|
||||
const response = await customFetch("/api/mailings/status/SD/stats");
|
||||
const response = await fetch("/api/mailings/status/SD/stats");
|
||||
const statsData = await response.json();
|
||||
if (statsData) {
|
||||
setMailingStats(statsData);
|
||||
|
||||
@ -7,25 +7,6 @@ import { TitleProvider } from "@/context/TitleContext";
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
||||
|
||||
import { getWebInstrumentations, initializeFaro } from '@grafana/faro-web-sdk';
|
||||
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
|
||||
|
||||
initializeFaro({
|
||||
url: 'https://faro-collector-prod-us-west-0.grafana.net/collect/4be6b1cb063deee9f9f54fc46da6c6e7',
|
||||
app: {
|
||||
name: 'Surge365 Mass Email',
|
||||
version: '1.0.0',
|
||||
environment: 'production'
|
||||
},
|
||||
|
||||
instrumentations: [
|
||||
// Mandatory, omits default instrumentations otherwise.
|
||||
...getWebInstrumentations(),
|
||||
|
||||
// Tracing package to get end-to-end visibility for HTTP requests.
|
||||
new TracingInstrumentation(),
|
||||
],
|
||||
});
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (rootElement) {
|
||||
|
||||
@ -7,10 +7,8 @@ import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, Circ
|
||||
import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton, GridDeleteIcon } from '@mui/x-data-grid';
|
||||
import BouncedEmail from '@/types/bouncedEmail';
|
||||
import BouncedEmailEdit from "@/components/modals/BouncedEmailEdit";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
function BouncedEmails() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
@ -56,7 +54,7 @@ function BouncedEmails() {
|
||||
const method = "DELETE";
|
||||
//setLoading(true);
|
||||
try {
|
||||
const response = await customFetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
|
||||
@ -7,11 +7,9 @@ import { Box, useTheme, CircularProgress, IconButton } from '@mui/material';
|
||||
import { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||
import Mailing from '@/types/mailing';
|
||||
import MailingEdit from "@/components/modals/MailingEdit";
|
||||
import MailingView from "@/components/modals/MailingView";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
import MailingView from "@/components/modals/MailingView"; // Assume this is a new read-only view component
|
||||
|
||||
function CancelledMailings() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
|
||||
@ -53,8 +51,7 @@ function CancelledMailings() {
|
||||
|
||||
const reloadMailings = async () => {
|
||||
setMailingsLoading(true);
|
||||
setupData.reloadSetupData();
|
||||
const mailingsResponse = await customFetch("/api/mailings/status/c");
|
||||
const mailingsResponse = await fetch("/api/mailings/status/c");
|
||||
const mailingsData = await mailingsResponse.json();
|
||||
if (mailingsData) {
|
||||
setMailings(mailingsData);
|
||||
|
||||
@ -9,10 +9,8 @@ import MailingStatistic from '@/types/mailingStatistic';
|
||||
import Mailing from '@/types/mailing';
|
||||
import MailingView from "@/components/modals/MailingView";
|
||||
import MailingEdit from "@/components/modals/MailingEdit";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
function CompletedMailings() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
@ -95,7 +93,7 @@ function CompletedMailings() {
|
||||
if (params.length > 0) {
|
||||
url += `?${params.join('&')}`;
|
||||
}
|
||||
const response = await customFetch(url);
|
||||
const response = await fetch(url);
|
||||
const statsData = await response.json();
|
||||
if (statsData) {
|
||||
setMailingStats(statsData);
|
||||
@ -111,7 +109,7 @@ function CompletedMailings() {
|
||||
|
||||
const fetchMailingDetails = async (mailingId: number) => {
|
||||
try {
|
||||
const response = await customFetch(`/api/mailings/${mailingId}`);
|
||||
const response = await fetch(`/api/mailings/${mailingId}`);
|
||||
const mailingData = await response.json();
|
||||
if (mailingData) {
|
||||
return mailingData;
|
||||
|
||||
@ -8,10 +8,8 @@ import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, Butt
|
||||
import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||
import EmailDomain from '@/types/emailDomain';
|
||||
import EmailDomainEdit from "@/components/modals/EmailDomainEdit";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
function EmailDomains() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
@ -71,7 +69,7 @@ function EmailDomains() {
|
||||
}
|
||||
};
|
||||
const loadEmailDomainsWithPasswords = async () => {
|
||||
const response = await customFetch("/api/emailDomains/GetAll?activeOnly=false&returnPassword=true");
|
||||
const response = await fetch("/api/emailDomains/GetAll?activeOnly=false&returnPassword=true");
|
||||
const data = await response.json();
|
||||
setEmailDomainsWithPasswords(data);
|
||||
}
|
||||
|
||||
@ -10,10 +10,8 @@ import Mailing from '@/types/mailing';
|
||||
//import Template from '@/types/template';
|
||||
import MailingEdit from "@/components/modals/MailingEdit";
|
||||
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
function NewMailings() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
@ -57,9 +55,8 @@ function NewMailings() {
|
||||
|
||||
const reloadMailings = async () => {
|
||||
setMailingsLoading(true);
|
||||
setupData.reloadSetupData();
|
||||
|
||||
const mailingsResponse = await customFetch("/api/mailings/status/ed");
|
||||
const mailingsResponse = await fetch("/api/mailings/status/ed");
|
||||
const mailingsData = await mailingsResponse.json();
|
||||
if (mailingsData) {
|
||||
setMailings(mailingsData);
|
||||
@ -97,7 +94,7 @@ function NewMailings() {
|
||||
if (!mailingToCancel) return;
|
||||
|
||||
try {
|
||||
const response = await customFetch(`/api/mailings/${mailingToCancel.id}/cancel`, { method: 'POST' });
|
||||
const response = await fetch(`/api/mailings/${mailingToCancel.id}/cancel`, { method: 'POST' });
|
||||
if (response.ok) {
|
||||
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@ -10,12 +9,9 @@ import Mailing from '@/types/mailing';
|
||||
import MailingEdit from "@/components/modals/MailingEdit";
|
||||
import MailingView from "@/components/modals/MailingView";
|
||||
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
function ScheduleMailings() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -83,8 +79,7 @@ function ScheduleMailings() {
|
||||
|
||||
const reloadMailings = async () => {
|
||||
setMailingsLoading(true);
|
||||
setupData.reloadSetupData();
|
||||
const mailingsResponse = await customFetch("/api/mailings/status/sc"); // Adjust endpoint as needed
|
||||
const mailingsResponse = await fetch("/api/mailings/status/sc"); // Adjust endpoint as needed
|
||||
const mailingsData = await mailingsResponse.json();
|
||||
if (mailingsData) {
|
||||
setMailings(mailingsData);
|
||||
@ -116,7 +111,7 @@ function ScheduleMailings() {
|
||||
if (!mailingToCancel) return;
|
||||
|
||||
try {
|
||||
const response = await customFetch(`/api/mailings/${mailingToCancel.id}/cancel`, { method: 'POST' });
|
||||
const response = await fetch(`/api/mailings/${mailingToCancel.id}/cancel`, { method: 'POST' });
|
||||
if (response.ok) {
|
||||
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
|
||||
} else {
|
||||
|
||||
@ -7,14 +7,12 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, CircularProgress, IconButton } from '@mui/material';
|
||||
import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||
import { Lock, LockOpen } from "@mui/icons-material";
|
||||
//import utils from '@/utils/utils';
|
||||
//import utils from '@/ts/utils';
|
||||
import Server from '@/types/server';
|
||||
import ServerEdit from "@/components/modals/ServerEdit";
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
|
||||
function Servers() {
|
||||
const customFetch = useCustomFetch();
|
||||
const theme = useTheme();
|
||||
const setupData: SetupData = useSetupData();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
@ -33,7 +31,7 @@ function Servers() {
|
||||
else {
|
||||
try {
|
||||
setIsPasswordVisible(true);
|
||||
const serversResponse = await customFetch("/api/servers/GetAll?activeOnly=false&returnPassword=true");
|
||||
const serversResponse = await fetch("/api/servers/GetAll?activeOnly=false&returnPassword=true");
|
||||
const serversData = await serversResponse.json();
|
||||
setServersWithPasswords(serversData);
|
||||
}
|
||||
|
||||
@ -5,13 +5,11 @@ import dayjs from 'dayjs'; // Import Dayjs for date handling
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import MailingStatistic from '@/types/mailingStatistic';
|
||||
import { useCustomFetch } from "@/utils/customFetch";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export default function RecentMailingStatsChart({ days = 7 }: { days?: number }) {
|
||||
const customFetch = useCustomFetch();
|
||||
const [stats, setStats] = useState<MailingStatistic[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@ -24,7 +22,7 @@ export default function RecentMailingStatsChart({ days = 7 }: { days?: number })
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = await customFetch(`/api/mailings/status/s%2Csd/stats?startDate=${startDateString}&endDate=${endDateString}`);
|
||||
const response = await fetch(`/api/mailings/status/s%2Csd/stats?startDate=${startDateString}&endDate=${endDateString}`);
|
||||
const data: MailingStatistic[] = await response.json();
|
||||
setStats(data);
|
||||
} catch (error) {
|
||||
|
||||
@ -28,7 +28,7 @@ const utils = {
|
||||
|
||||
/*The following may not be needed any longer?
|
||||
**TODO: WebMethod should be changed to mimic fetch command but add in auth headers?
|
||||
customFetch('/api/protected-endpoint', {
|
||||
fetch('/api/protected-endpoint', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
|
||||
}
|
||||
@ -98,7 +98,7 @@ const utils = {
|
||||
// //const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
// setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
// const response = await customFetch(url, {
|
||||
// const response = await fetch(url, {
|
||||
// method: httpMethod,
|
||||
// headers,
|
||||
// body: (httpMethod.toUpperCase() == "GET" ? null : JSON.stringify(parameters)),
|
||||
@ -1,55 +0,0 @@
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface FetchOptions extends RequestInit {
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
const customFetch = async (
|
||||
url: string,
|
||||
options: FetchOptions = {},
|
||||
authContext: ReturnType<typeof useAuth>,
|
||||
navigate: ReturnType<typeof useNavigate>
|
||||
): Promise<Response> => {
|
||||
const { accessToken, refreshToken } = authContext;
|
||||
|
||||
const headers = {
|
||||
...options.headers,
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||
};
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
const refreshSuccess = await refreshToken();
|
||||
if (refreshSuccess) {
|
||||
const newAccessToken = authContext.accessToken;
|
||||
const retryResponse = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
...(newAccessToken ? { Authorization: `Bearer ${newAccessToken}` } : {}),
|
||||
},
|
||||
});
|
||||
return retryResponse;
|
||||
} else {
|
||||
navigate('/login');
|
||||
throw new Error('Authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export const useCustomFetch = () => {
|
||||
const authContext = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (url: string, options: FetchOptions = {}) =>
|
||||
customFetch(url, options, authContext, navigate);
|
||||
};
|
||||
|
||||
export default customFetch;
|
||||
Loading…
x
Reference in New Issue
Block a user