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 Microsoft.AspNetCore.Mvc;
|
||||||
using Surge365.MassEmailReact.API.Controllers;
|
using Surge365.MassEmailReact.API.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
@ -8,7 +7,6 @@ using Surge365.MassEmailReact.Domain.Entities;
|
|||||||
|
|
||||||
namespace Surge365.MassEmailReact.API.Controllers
|
namespace Surge365.MassEmailReact.API.Controllers
|
||||||
{
|
{
|
||||||
[AllowAnonymous]
|
|
||||||
public class AuthenticationController : BaseController
|
public class AuthenticationController : BaseController
|
||||||
{
|
{
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
@ -28,13 +26,6 @@ namespace Surge365.MassEmailReact.API.Controllers
|
|||||||
SameSite = SameSiteMode.Strict,
|
SameSite = SameSiteMode.Strict,
|
||||||
Expires = DateTimeOffset.UtcNow.AddDays(-1) // Expire immediately
|
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" });
|
return Ok(new { message = "Logged out successfully" });
|
||||||
}
|
}
|
||||||
@ -57,7 +48,6 @@ namespace Surge365.MassEmailReact.API.Controllers
|
|||||||
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
||||||
};
|
};
|
||||||
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
||||||
Response.Cookies.Append("accessToken", authResponse.data.Value.accessToken, cookieOptions);
|
|
||||||
|
|
||||||
//TODO: Store user in session
|
//TODO: Store user in session
|
||||||
return Ok(new { success = true, authResponse.data.Value.accessToken, authResponse.data.Value.user });
|
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)
|
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
||||||
};
|
};
|
||||||
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
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 });
|
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
|
namespace Surge365.MassEmailReact.API.Controllers
|
||||||
{
|
{
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize]
|
|
||||||
public class BaseController : ControllerBase
|
public class BaseController : ControllerBase
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,19 +6,13 @@ using Surge365.MassEmailReact.Infrastructure;
|
|||||||
using Surge365.MassEmailReact.Infrastructure.DapperMaps;
|
using Surge365.MassEmailReact.Infrastructure.DapperMaps;
|
||||||
using Surge365.MassEmailReact.Infrastructure.Repositories;
|
using Surge365.MassEmailReact.Infrastructure.Repositories;
|
||||||
using Surge365.MassEmailReact.Infrastructure.Services;
|
using Surge365.MassEmailReact.Infrastructure.Services;
|
||||||
using Surge365.MassEmailReact.Infrastructure.Middleware;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
WebApplication? app = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var keyVaultName = builder.Configuration["KeyVaultName"] ?? "";
|
var keyVaultName = builder.Configuration["KeyVaultName"] ?? "";
|
||||||
if (!string.IsNullOrEmpty(keyVaultName))
|
if (!string.IsNullOrEmpty(keyVaultName))
|
||||||
{
|
{
|
||||||
@ -29,41 +23,6 @@ try
|
|||||||
new SurgeKeyVaultSecretManager()
|
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 =>
|
builder.Services.AddHttpClient("SendGridClient", client =>
|
||||||
{
|
{
|
||||||
@ -95,10 +54,8 @@ try
|
|||||||
builder.Services.AddScoped<IMailingService, MailingService>();
|
builder.Services.AddScoped<IMailingService, MailingService>();
|
||||||
builder.Services.AddScoped<IMailingRepository, MailingRepository>();
|
builder.Services.AddScoped<IMailingRepository, MailingRepository>();
|
||||||
|
|
||||||
app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
||||||
app.UseCustomExceptionHandler();
|
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.MapStaticAssets();
|
app.MapStaticAssets();
|
||||||
|
|
||||||
@ -116,12 +73,5 @@ try
|
|||||||
|
|
||||||
|
|
||||||
DapperConfiguration.ConfigureMappings();
|
DapperConfiguration.ConfigureMappings();
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggingService appLoggingService = new LoggingService(builder.Configuration);
|
|
||||||
appLoggingService.LogError(ex).Wait();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (app != null)
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
||||||
<Version>9.*-*</Version>
|
<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("@unsubscribe", bouncedEmail.Unsubscribe, DbType.Boolean);
|
||||||
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
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);
|
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>
|
<ItemGroup>
|
||||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
|
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
|
||||||
<PackageReference Include="Dapper.FluentMap" Version="2.0.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.Data.SqlClient" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" 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>
|
<PropertyGroup>
|
||||||
<StartupCommand>npm run dev</StartupCommand>
|
<StartupCommand>npm run dev</StartupCommand>
|
||||||
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
<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/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@fontsource/roboto": "^5.1.1",
|
"@fontsource/roboto": "^5.1.1",
|
||||||
"@grafana/faro-web-sdk": "^1.18.0",
|
|
||||||
"@grafana/faro-web-tracing": "^1.18.0",
|
|
||||||
"@hookform/resolvers": "^4.1.2",
|
"@hookform/resolvers": "^4.1.2",
|
||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@mui/icons-material": "^6.4.5",
|
"@mui/icons-material": "^6.4.5",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import utils from '@/utils/utils';
|
import utils from '@/ts/utils';
|
||||||
import { useAuth } from '@/components/auth/AuthContext';
|
import { useAuth } from '@/components/auth/AuthContext';
|
||||||
|
|
||||||
const AuthCheck: React.FC = () => {
|
const AuthCheck: React.FC = () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
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';
|
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
@ -7,7 +7,6 @@ interface AuthContextType {
|
|||||||
userRoles: string[];
|
userRoles: string[];
|
||||||
setAuth: (token: string | null) => void;
|
setAuth: (token: string | null) => void;
|
||||||
isLoading: boolean; // Add loading state
|
isLoading: boolean; // Add loading state
|
||||||
refreshToken: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType>({
|
const AuthContext = createContext<AuthContextType>({
|
||||||
@ -15,7 +14,6 @@ const AuthContext = createContext<AuthContextType>({
|
|||||||
userRoles: [],
|
userRoles: [],
|
||||||
setAuth: () => { },
|
setAuth: () => { },
|
||||||
isLoading: true, // Default to loading
|
isLoading: true, // Default to loading
|
||||||
refreshToken: async () => false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
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
|
// Check auth on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeAuth = async () => {
|
const initializeAuth = async () => {
|
||||||
@ -77,16 +55,17 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setAuth(null);
|
setAuth(null);
|
||||||
}
|
} finally {
|
||||||
setIsLoading(false); // Done loading regardless of outcome
|
setIsLoading(false); // Done loading regardless of outcome
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeAuth();
|
initializeAuth();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ accessToken, userRoles, setAuth, isLoading, refreshToken }}>
|
<AuthContext.Provider value={{ accessToken, userRoles, setAuth, isLoading }}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@ -45,7 +45,6 @@ import Menu from '@mui/material/Menu';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import InputLabel from '@mui/material/InputLabel';
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
@ -80,7 +79,6 @@ interface LayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Layout = ({ children }: LayoutProps) => {
|
const Layout = ({ children }: LayoutProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); //TODO: Move this to shared utils?
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); //TODO: Move this to shared utils?
|
||||||
const [open, setOpen] = React.useState(!isMobile);
|
const [open, setOpen] = React.useState(!isMobile);
|
||||||
@ -120,7 +118,7 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
const handleRefreshUser = async () => {
|
const handleRefreshUser = async () => {
|
||||||
handleCloseProfileMenu();
|
handleCloseProfileMenu();
|
||||||
try {
|
try {
|
||||||
const response = await customFetch('/api/authentication/refreshtoken', {
|
const response = await fetch('/api/authentication/refreshtoken', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
|
||||||
@ -140,7 +138,7 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
setAuth(null); // Clear context
|
setAuth(null); // Clear context
|
||||||
await customFetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
await fetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
|||||||
import { useForm, Resolver, Controller } from "react-hook-form";
|
import { useForm, Resolver, Controller } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type BouncedEmailEditProps = {
|
type BouncedEmailEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -49,7 +48,6 @@ const defaultBouncedEmail: BouncedEmail = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailEditProps) => {
|
const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = bouncedEmail == null;
|
const isNew = bouncedEmail == null;
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const originalBouncedEmail: BouncedEmail | null = bouncedEmail ? { ...bouncedEmail } : null;
|
const originalBouncedEmail: BouncedEmail | null = bouncedEmail ? { ...bouncedEmail } : null;
|
||||||
@ -75,7 +73,7 @@ const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailE
|
|||||||
const method = isNew ? "POST" : "PUT"
|
const method = isNew ? "POST" : "PUT"
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
@ -95,7 +93,7 @@ const BouncedEmailEdit = ({ open, bouncedEmail, onClose, onSave }: BouncedEmailE
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
<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>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
{...register("emailAddress")}
|
{...register("emailAddress")}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
|||||||
import { useForm, Controller, Resolver } from "react-hook-form";
|
import { useForm, Controller, Resolver } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type EmailDomainEditProps = {
|
type EmailDomainEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -57,7 +56,6 @@ const defaultEmailDomain: EmailDomain = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEditProps) => {
|
const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = !emailDomain || emailDomain.id === 0;
|
const isNew = !emailDomain || emailDomain.id === 0;
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const originalEmailDomain: EmailDomain | null = emailDomain ? { ...emailDomain } : null;
|
const originalEmailDomain: EmailDomain | null = emailDomain ? { ...emailDomain } : null;
|
||||||
@ -82,7 +80,7 @@ const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEdit
|
|||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { useState, FormEvent } from 'react';
|
import { useState, FormEvent } from 'react';
|
||||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Typography, Box, IconButton } from '@mui/material';
|
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Typography, Box, IconButton } from '@mui/material';
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
import utils from '@/utils/utils';
|
import utils from '@/ts/utils';
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type FormErrors = Record<string, string>;
|
type FormErrors = Record<string, string>;
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ type ForgotPasswordModalProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose }) => {
|
const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose }) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||||
const [usernameNotFound, setUsernameNotFound] = useState(false);
|
const [usernameNotFound, setUsernameNotFound] = useState(false);
|
||||||
@ -38,7 +36,7 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose
|
|||||||
if (validate()) {
|
if (validate()) {
|
||||||
console.log('Processing forgot password for', username);
|
console.log('Processing forgot password for', username);
|
||||||
const apiUrl = "/api/authentication/generatepasswordrecovery";
|
const apiUrl = "/api/authentication/generatepasswordrecovery";
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username}),
|
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 dayjs, { Dayjs } from 'dayjs'; // Import Dayjs for date handling
|
||||||
import utc from 'dayjs/plugin/utc'; // Import the UTC plugin
|
import utc from 'dayjs/plugin/utc'; // Import the UTC plugin
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@ -67,75 +66,6 @@ const recurringTypeOptions = [
|
|||||||
{ code: 'W', name: 'Weekly' },
|
{ 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({
|
const schema = yup.object().shape({
|
||||||
id: yup.number().nullable(),
|
id: yup.number().nullable(),
|
||||||
name: yup.string().required("Name is required")
|
name: yup.string().required("Name is required")
|
||||||
@ -243,6 +173,73 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
}).nullable(),
|
}).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>({
|
const { register, trigger, control, handleSubmit, reset, setValue, formState: { errors } } = useForm<Mailing>({
|
||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -254,7 +251,6 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeMailingEdit = async () => {
|
const initializeMailingEdit = async () => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@ -318,7 +314,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const jsonPayload = JSON.stringify(formData);
|
const jsonPayload = JSON.stringify(formData);
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: jsonPayload,
|
body: jsonPayload,
|
||||||
@ -361,7 +357,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Make the API call to /api/mailings/test
|
// Make the API call to /api/mailings/test
|
||||||
const response = await customFetch("/api/mailings/test", {
|
const response = await fetch("/api/mailings/test", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@ -391,7 +387,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
|
|
||||||
setTargetSampleLoading(true);
|
setTargetSampleLoading(true);
|
||||||
try {
|
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");
|
if (!response.ok) throw new Error("Failed to fetch sample data");
|
||||||
const data: TargetSample = await response.json();
|
const data: TargetSample = await response.json();
|
||||||
setTargetSample(data);
|
setTargetSample(data);
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import TemplateViewer from "@/components/modals/TemplateViewer";
|
|||||||
import TargetSampleModal from "@/components/modals/TargetSampleModal";
|
import TargetSampleModal from "@/components/modals/TargetSampleModal";
|
||||||
import Target from "@/types/target";
|
import Target from "@/types/target";
|
||||||
import Template from "@/types/template";
|
import Template from "@/types/template";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
interface MailingViewProps {
|
interface MailingViewProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -49,7 +48,6 @@ interface MailingEmail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const setupData = useSetupData();
|
const setupData = useSetupData();
|
||||||
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
||||||
const [targetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
const [targetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
||||||
@ -67,7 +65,7 @@ function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
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();
|
const emailsData = await emailsResponse.json();
|
||||||
|
|
||||||
if (emailsData) {
|
if (emailsData) {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import Server from "@/types/server";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type ServerEditProps = {
|
type ServerEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -52,7 +51,6 @@ const defaultServer: Server = {
|
|||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
const ServerEdit = ({ open, server, onClose, onSave }: ServerEditProps) => {
|
const ServerEdit = ({ open, server, onClose, onSave }: ServerEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = !server || server.id === 0;
|
const isNew = !server || server.id === 0;
|
||||||
const originalServer: Server | null = server ? { ...server } : null;
|
const originalServer: Server | null = server ? { ...server } : null;
|
||||||
//const [formData, setFormData] = useState<Server>({ ...server });
|
//const [formData, setFormData] = useState<Server>({ ...server });
|
||||||
@ -75,7 +73,7 @@ const ServerEdit = ({ open, server, onClose, onSave }: ServerEditProps) => {
|
|||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import TargetColumn from "@/types/targetColumn";
|
|||||||
import TargetSampleColumn from "@/types/targetSampleColumn";
|
import TargetSampleColumn from "@/types/targetSampleColumn";
|
||||||
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
||||||
import { GridColDef } from '@mui/x-data-grid';
|
import { GridColDef } from '@mui/x-data-grid';
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type TargetEditProps = {
|
type TargetEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -97,7 +96,6 @@ const useColumnErrors = (errors: any, columns: TargetColumn[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = !target || target.id === 0;
|
const isNew = !target || target.id === 0;
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const [targetTested, setTargetTested] = useState<boolean>(false);
|
const [targetTested, setTargetTested] = useState<boolean>(false);
|
||||||
@ -138,7 +136,7 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
@ -174,7 +172,7 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
const handleTestTarget = async () => {
|
const handleTestTarget = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(`/api/targets/test`, {
|
const response = await fetch(`/api/targets/test`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import CloseIcon from '@mui/icons-material/Close';
|
|||||||
import TargetSample from "@/types/targetSample";
|
import TargetSample from "@/types/targetSample";
|
||||||
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
||||||
import { GridColDef } from '@mui/x-data-grid';
|
import { GridColDef } from '@mui/x-data-grid';
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type TargetSampleModalProps = {
|
type TargetSampleModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -23,7 +22,6 @@ type TargetSampleModalProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TargetSampleModal = ({ open, target, targetSample, onClose }: TargetSampleModalProps) => {
|
const TargetSampleModal = ({ open, target, targetSample, onClose }: TargetSampleModalProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const [localTargetSample, setLocalTargetSample] = useState<TargetSample | null>(null);
|
const [localTargetSample, setLocalTargetSample] = useState<TargetSample | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
@ -36,7 +34,7 @@ const TargetSampleModal = ({ open, target, targetSample, onClose }: TargetSample
|
|||||||
else if (target.id) {
|
else if (target.id) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
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");
|
if (!response.ok) throw new Error("Failed to fetch sample data");
|
||||||
const data: TargetSample = await response.json();
|
const data: TargetSample = await response.json();
|
||||||
setLocalTargetSample(data);
|
setLocalTargetSample(data);
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import Editor from "@monaco-editor/react";
|
|||||||
// Assuming these types and context are defined elsewhere
|
// Assuming these types and context are defined elsewhere
|
||||||
import Template from "@/types/template";
|
import Template from "@/types/template";
|
||||||
import { useSetupData } from "@/context/SetupDataContext";
|
import { useSetupData } from "@/context/SetupDataContext";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type TemplateEditProps = {
|
type TemplateEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -65,7 +64,6 @@ const defaultTemplate: Template = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) => {
|
const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = !template || template.id === 0;
|
const isNew = !template || template.id === 0;
|
||||||
const setupData = useSetupData();
|
const setupData = useSetupData();
|
||||||
|
|
||||||
@ -102,7 +100,7 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
|
|||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method,
|
method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import TestEmailList from "@/types/testEmailList";
|
import TestEmailList from "@/types/testEmailList";
|
||||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type TestEmailListEditProps = {
|
type TestEmailListEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -57,7 +56,6 @@ const defaultTestEmailList: TestEmailListForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TestEmailListEdit = ({ open, testEmailList, onClose, onSave }: TestEmailListEditProps) => {
|
const TestEmailListEdit = ({ open, testEmailList, onClose, onSave }: TestEmailListEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = !testEmailList || testEmailList.id === 0;
|
const isNew = !testEmailList || testEmailList.id === 0;
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -92,7 +90,7 @@ const TestEmailListEdit = ({ open, testEmailList, onClose, onSave }: TestEmailLi
|
|||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method,
|
method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(testEmailListDto),
|
body: JSON.stringify(testEmailListDto),
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import UnsubscribeUrl from "@/types/unsubscribeUrl";
|
|||||||
import { useForm, Resolver } from "react-hook-form";
|
import { useForm, Resolver } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
type UnsubscribeUrlEditProps = {
|
type UnsubscribeUrlEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -33,7 +32,6 @@ const defaultUnsubscribeUrl: UnsubscribeUrl = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UnsubscribeUrlEdit = ({ open, unsubscribeUrl, onClose, onSave }: UnsubscribeUrlEditProps) => {
|
const UnsubscribeUrlEdit = ({ open, unsubscribeUrl, onClose, onSave }: UnsubscribeUrlEditProps) => {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const isNew = !unsubscribeUrl || unsubscribeUrl.id === 0;
|
const isNew = !unsubscribeUrl || unsubscribeUrl.id === 0;
|
||||||
|
|
||||||
const { register, handleSubmit, reset, formState: { errors } } = useForm<UnsubscribeUrl>({
|
const { register, handleSubmit, reset, formState: { errors } } = useForm<UnsubscribeUrl>({
|
||||||
@ -55,7 +53,7 @@ const UnsubscribeUrlEdit = ({ open, unsubscribeUrl, onClose, onSave }: Unsubscri
|
|||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
|
|||||||
@ -1,16 +1,12 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import Switch from '@mui/material/Switch';
|
import Switch from '@mui/material/Switch';
|
||||||
import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, CircularProgress, IconButton, FormControlLabel } from '@mui/material';
|
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 { DataGrid, GridColDef, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||||
import MailingStatistic from '@/types/mailingStatistic';
|
import MailingStatistic from '@/types/mailingStatistic'; // Assuming you'll create this type based on the C# class
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function ActiveMailings() {
|
function ActiveMailings() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -42,9 +38,8 @@ function ActiveMailings() {
|
|||||||
|
|
||||||
isFetchingRef.current = true;
|
isFetchingRef.current = true;
|
||||||
setMailingsLoading(true);
|
setMailingsLoading(true);
|
||||||
setupData.reloadSetupData();
|
|
||||||
try {
|
try {
|
||||||
const response = await customFetch("/api/mailings/status/SD/stats");
|
const response = await fetch("/api/mailings/status/SD/stats");
|
||||||
const statsData = await response.json();
|
const statsData = await response.json();
|
||||||
if (statsData) {
|
if (statsData) {
|
||||||
setMailingStats(statsData);
|
setMailingStats(statsData);
|
||||||
|
|||||||
@ -7,25 +7,6 @@ import { TitleProvider } from "@/context/TitleContext";
|
|||||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
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');
|
const rootElement = document.getElementById('root');
|
||||||
if (rootElement) {
|
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 { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton, GridDeleteIcon } from '@mui/x-data-grid';
|
||||||
import BouncedEmail from '@/types/bouncedEmail';
|
import BouncedEmail from '@/types/bouncedEmail';
|
||||||
import BouncedEmailEdit from "@/components/modals/BouncedEmailEdit";
|
import BouncedEmailEdit from "@/components/modals/BouncedEmailEdit";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function BouncedEmails() {
|
function BouncedEmails() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
@ -56,7 +54,7 @@ function BouncedEmails() {
|
|||||||
const method = "DELETE";
|
const method = "DELETE";
|
||||||
//setLoading(true);
|
//setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: { "Content-Type": "application/json" }
|
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 { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||||
import Mailing from '@/types/mailing';
|
import Mailing from '@/types/mailing';
|
||||||
import MailingEdit from "@/components/modals/MailingEdit";
|
import MailingEdit from "@/components/modals/MailingEdit";
|
||||||
import MailingView from "@/components/modals/MailingView";
|
import MailingView from "@/components/modals/MailingView"; // Assume this is a new read-only view component
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function CancelledMailings() {
|
function CancelledMailings() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
|
|
||||||
@ -53,8 +51,7 @@ function CancelledMailings() {
|
|||||||
|
|
||||||
const reloadMailings = async () => {
|
const reloadMailings = async () => {
|
||||||
setMailingsLoading(true);
|
setMailingsLoading(true);
|
||||||
setupData.reloadSetupData();
|
const mailingsResponse = await fetch("/api/mailings/status/c");
|
||||||
const mailingsResponse = await customFetch("/api/mailings/status/c");
|
|
||||||
const mailingsData = await mailingsResponse.json();
|
const mailingsData = await mailingsResponse.json();
|
||||||
if (mailingsData) {
|
if (mailingsData) {
|
||||||
setMailings(mailingsData);
|
setMailings(mailingsData);
|
||||||
|
|||||||
@ -9,10 +9,8 @@ import MailingStatistic from '@/types/mailingStatistic';
|
|||||||
import Mailing from '@/types/mailing';
|
import Mailing from '@/types/mailing';
|
||||||
import MailingView from "@/components/modals/MailingView";
|
import MailingView from "@/components/modals/MailingView";
|
||||||
import MailingEdit from "@/components/modals/MailingEdit";
|
import MailingEdit from "@/components/modals/MailingEdit";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function CompletedMailings() {
|
function CompletedMailings() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
@ -95,7 +93,7 @@ function CompletedMailings() {
|
|||||||
if (params.length > 0) {
|
if (params.length > 0) {
|
||||||
url += `?${params.join('&')}`;
|
url += `?${params.join('&')}`;
|
||||||
}
|
}
|
||||||
const response = await customFetch(url);
|
const response = await fetch(url);
|
||||||
const statsData = await response.json();
|
const statsData = await response.json();
|
||||||
if (statsData) {
|
if (statsData) {
|
||||||
setMailingStats(statsData);
|
setMailingStats(statsData);
|
||||||
@ -111,7 +109,7 @@ function CompletedMailings() {
|
|||||||
|
|
||||||
const fetchMailingDetails = async (mailingId: number) => {
|
const fetchMailingDetails = async (mailingId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await customFetch(`/api/mailings/${mailingId}`);
|
const response = await fetch(`/api/mailings/${mailingId}`);
|
||||||
const mailingData = await response.json();
|
const mailingData = await response.json();
|
||||||
if (mailingData) {
|
if (mailingData) {
|
||||||
return 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 { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||||
import EmailDomain from '@/types/emailDomain';
|
import EmailDomain from '@/types/emailDomain';
|
||||||
import EmailDomainEdit from "@/components/modals/EmailDomainEdit";
|
import EmailDomainEdit from "@/components/modals/EmailDomainEdit";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function EmailDomains() {
|
function EmailDomains() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
@ -71,7 +69,7 @@ function EmailDomains() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const loadEmailDomainsWithPasswords = async () => {
|
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();
|
const data = await response.json();
|
||||||
setEmailDomainsWithPasswords(data);
|
setEmailDomainsWithPasswords(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,8 @@ import Mailing from '@/types/mailing';
|
|||||||
//import Template from '@/types/template';
|
//import Template from '@/types/template';
|
||||||
import MailingEdit from "@/components/modals/MailingEdit";
|
import MailingEdit from "@/components/modals/MailingEdit";
|
||||||
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function NewMailings() {
|
function NewMailings() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
@ -57,9 +55,8 @@ function NewMailings() {
|
|||||||
|
|
||||||
const reloadMailings = async () => {
|
const reloadMailings = async () => {
|
||||||
setMailingsLoading(true);
|
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();
|
const mailingsData = await mailingsResponse.json();
|
||||||
if (mailingsData) {
|
if (mailingsData) {
|
||||||
setMailings(mailingsData);
|
setMailings(mailingsData);
|
||||||
@ -97,7 +94,7 @@ function NewMailings() {
|
|||||||
if (!mailingToCancel) return;
|
if (!mailingToCancel) return;
|
||||||
|
|
||||||
try {
|
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) {
|
if (response.ok) {
|
||||||
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
|
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
|
||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@ -10,12 +9,9 @@ import Mailing from '@/types/mailing';
|
|||||||
import MailingEdit from "@/components/modals/MailingEdit";
|
import MailingEdit from "@/components/modals/MailingEdit";
|
||||||
import MailingView from "@/components/modals/MailingView";
|
import MailingView from "@/components/modals/MailingView";
|
||||||
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
function ScheduleMailings() {
|
function ScheduleMailings() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -83,8 +79,7 @@ function ScheduleMailings() {
|
|||||||
|
|
||||||
const reloadMailings = async () => {
|
const reloadMailings = async () => {
|
||||||
setMailingsLoading(true);
|
setMailingsLoading(true);
|
||||||
setupData.reloadSetupData();
|
const mailingsResponse = await fetch("/api/mailings/status/sc"); // Adjust endpoint as needed
|
||||||
const mailingsResponse = await customFetch("/api/mailings/status/sc"); // Adjust endpoint as needed
|
|
||||||
const mailingsData = await mailingsResponse.json();
|
const mailingsData = await mailingsResponse.json();
|
||||||
if (mailingsData) {
|
if (mailingsData) {
|
||||||
setMailings(mailingsData);
|
setMailings(mailingsData);
|
||||||
@ -116,7 +111,7 @@ function ScheduleMailings() {
|
|||||||
if (!mailingToCancel) return;
|
if (!mailingToCancel) return;
|
||||||
|
|
||||||
try {
|
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) {
|
if (response.ok) {
|
||||||
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
|
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
|
||||||
} else {
|
} 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 { 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 { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||||
import { Lock, LockOpen } from "@mui/icons-material";
|
import { Lock, LockOpen } from "@mui/icons-material";
|
||||||
//import utils from '@/utils/utils';
|
//import utils from '@/ts/utils';
|
||||||
import Server from '@/types/server';
|
import Server from '@/types/server';
|
||||||
import ServerEdit from "@/components/modals/ServerEdit";
|
import ServerEdit from "@/components/modals/ServerEdit";
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
|
|
||||||
function Servers() {
|
function Servers() {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
@ -33,7 +31,7 @@ function Servers() {
|
|||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
setIsPasswordVisible(true);
|
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();
|
const serversData = await serversResponse.json();
|
||||||
setServersWithPasswords(serversData);
|
setServersWithPasswords(serversData);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,11 @@ import dayjs from 'dayjs'; // Import Dayjs for date handling
|
|||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc';
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
import MailingStatistic from '@/types/mailingStatistic';
|
import MailingStatistic from '@/types/mailingStatistic';
|
||||||
import { useCustomFetch } from "@/utils/customFetch";
|
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
export default function RecentMailingStatsChart({ days = 7 }: { days?: number }) {
|
export default function RecentMailingStatsChart({ days = 7 }: { days?: number }) {
|
||||||
const customFetch = useCustomFetch();
|
|
||||||
const [stats, setStats] = useState<MailingStatistic[]>([]);
|
const [stats, setStats] = useState<MailingStatistic[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
@ -24,7 +22,7 @@ export default function RecentMailingStatsChart({ days = 7 }: { days?: number })
|
|||||||
|
|
||||||
const fetchStats = async () => {
|
const fetchStats = async () => {
|
||||||
try {
|
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();
|
const data: MailingStatistic[] = await response.json();
|
||||||
setStats(data);
|
setStats(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const utils = {
|
|||||||
|
|
||||||
/*The following may not be needed any longer?
|
/*The following may not be needed any longer?
|
||||||
**TODO: WebMethod should be changed to mimic fetch command but add in auth headers?
|
**TODO: WebMethod should be changed to mimic fetch command but add in auth headers?
|
||||||
customFetch('/api/protected-endpoint', {
|
fetch('/api/protected-endpoint', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
|
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ const utils = {
|
|||||||
// //const timeoutId = setTimeout(() => controller.abort(), timeout);
|
// //const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
// setTimeout(() => controller.abort(), timeout);
|
// setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
// const response = await customFetch(url, {
|
// const response = await fetch(url, {
|
||||||
// method: httpMethod,
|
// method: httpMethod,
|
||||||
// headers,
|
// headers,
|
||||||
// body: (httpMethod.toUpperCase() == "GET" ? null : JSON.stringify(parameters)),
|
// 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