From a9db71b234967186d3f66ecc4f4f8b601d2f6aa8 Mon Sep 17 00:00:00 2001 From: fISHIE <83373559+WhoIsFishie@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:40:35 +0500 Subject: [PATCH] added admin dashboard --- Submission.Api/.claude/settings.local.json | 9 + Submission.Api/Controllers/AdminController.cs | 161 ++++++++++ Submission.Api/Controllers/AuthController.cs | 48 +++ Submission.Api/Controllers/DebugController.cs | 159 ---------- Submission.Api/Dto/ApprovalDto.cs | 7 + Submission.Api/Program.cs | 25 ++ Submission.Api/Submission.Api.csproj | 1 + Submission.Api/appsettings.Development.json | 8 + frontend-react/src/App.tsx | 4 + frontend-react/src/lib/adminApi.ts | 117 ++++++++ .../src/pages/AdminDashboardPage.tsx | 278 ++++++++++++++++++ frontend-react/src/pages/AdminLoginPage.tsx | 103 +++++++ .../src/pages/CreatePetitionPage.tsx | 2 +- frontend-react/src/pages/PetitionPage.tsx | 26 +- frontend-react/vite.config.ts | 2 +- global.json | 7 - 16 files changed, 787 insertions(+), 170 deletions(-) create mode 100644 Submission.Api/.claude/settings.local.json create mode 100644 Submission.Api/Controllers/AdminController.cs create mode 100644 Submission.Api/Controllers/AuthController.cs create mode 100644 Submission.Api/Dto/ApprovalDto.cs create mode 100644 frontend-react/src/lib/adminApi.ts create mode 100644 frontend-react/src/pages/AdminDashboardPage.tsx create mode 100644 frontend-react/src/pages/AdminLoginPage.tsx delete mode 100644 global.json diff --git a/Submission.Api/.claude/settings.local.json b/Submission.Api/.claude/settings.local.json new file mode 100644 index 0000000..d50ac72 --- /dev/null +++ b/Submission.Api/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet add package:*)", + "Bash(dotnet build:*)", + "Bash(findstr:*)" + ] + } +} diff --git a/Submission.Api/Controllers/AdminController.cs b/Submission.Api/Controllers/AdminController.cs new file mode 100644 index 0000000..8259821 --- /dev/null +++ b/Submission.Api/Controllers/AdminController.cs @@ -0,0 +1,161 @@ +using Ashi.MongoInterface.Service; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Submission.Api.Configuration; +using Submission.Api.Dto; +using Submission.Api.Models; + +namespace Submission.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + [Authorize] + public class AdminController : ControllerBase + { + private readonly PetitionSettings _petitionSettings; + private readonly IMongoRepository _authorRepository; + private readonly IMongoRepository _petitionRepository; + private readonly IMongoRepository _signatureRepository; + public AdminController(IOptions petitionSettings, IMongoRepository authorRepository, IMongoRepository petitionRepository, IMongoRepository signatureRepository) + { + _petitionSettings = petitionSettings.Value; + _authorRepository = authorRepository; + _petitionRepository = petitionRepository; + _signatureRepository = signatureRepository; + } + + [HttpGet("petitions", Name = "GetPetitions")] + public IActionResult GetPetitions() + { + try + { + var petitions = _petitionRepository.FilterBy(x => x.Id != null); + return Ok(petitions); + } + catch (Exception e) + { + return Problem("Petitions Folder not found"); + } + } + + + [HttpGet("petitions-list", Name = "GetPetitionsList")] + public IActionResult GetPetitionsList() + { + var list = _petitionRepository.FilterBy(x => x.Id != null); + return Ok(list); + } + + [HttpGet("create-petition-folder", Name = "CreatePetitionFolder")] + public IActionResult create_petition_folder() + { + if (Directory.Exists("Petitions")) + { + return Ok("Petitions folder already exists"); + } + + try + { + Directory.CreateDirectory("Petitions"); + return Ok("Petitions folder created"); + } + catch (Exception e) + { + return Problem(e.Message); + } + } + + + + [HttpGet("export/{petition_id}", Name = "ExportSignatures")] + public async Task ExportSignatures([FromRoute] Guid petition_id) + { + var petition = await _petitionRepository.FindByIdAsync(petition_id); + if (petition == null) + return NotFound("Petition not found"); + + var signatures = _signatureRepository.FilterBy(x => x.PetitionId == petition_id) + .OrderBy(x => x.Timestamp) + .ToList(); + + var rows = new System.Text.StringBuilder(); + int index = 1; + foreach (var sig in signatures) + { + rows.Append($@" + + {index++} + {System.Net.WebUtility.HtmlEncode(sig.Name)} + {System.Net.WebUtility.HtmlEncode(sig.IdCard)} + {sig.Timestamp:yyyy-MM-dd} + {sig.Signature_SVG} + "); + } + + var html = $@" + + + + Signatures - {System.Net.WebUtility.HtmlEncode(petition.NameEng)} + + + + +

Signatures for: {System.Net.WebUtility.HtmlEncode(petition.NameEng)}

+

Total signatures: {signatures.Count}

+ + + + + + + + + + + + {rows} + +
#NameID CardDate SignedSignature
+ +"; + + return Content(html, "text/html"); + } + [HttpPatch("petitions/{petition_id}/approve")] + public async Task ApprovePetition([FromRoute] Guid petition_id, [FromBody] ApprovalDto body) + { + var petition = await _petitionRepository.FindByIdAsync(petition_id); + if (petition == null) + return NotFound("Petition not found"); + + petition.isApproved = body.IsApproved; + await _petitionRepository.ReplaceOneAsync(petition); + + return Ok(petition); + } + } +} diff --git a/Submission.Api/Controllers/AuthController.cs b/Submission.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..8a875ff --- /dev/null +++ b/Submission.Api/Controllers/AuthController.cs @@ -0,0 +1,48 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; + +namespace Submission.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class AuthController : ControllerBase + { + private readonly IConfiguration _configuration; + + public AuthController(IConfiguration configuration) + { + _configuration = configuration; + } + + [HttpPost("login")] + public IActionResult Login([FromBody] LoginRequest request) + { + var adminUsername = _configuration["AdminSettings:Username"]; + var adminPassword = _configuration["AdminSettings:Password"]; + + if (request.Username != adminUsername || request.Password != adminPassword) + return Unauthorized("Invalid credentials"); + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!)); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + issuer: _configuration["Jwt:Issuer"], + claims: new[] { new Claim(ClaimTypes.Name, request.Username) }, + expires: DateTime.UtcNow.AddHours(24), + signingCredentials: credentials + ); + + return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) }); + } + } + + public class LoginRequest + { + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + } +} diff --git a/Submission.Api/Controllers/DebugController.cs b/Submission.Api/Controllers/DebugController.cs index 4e6a2eb..c3d6b38 100644 --- a/Submission.Api/Controllers/DebugController.cs +++ b/Submission.Api/Controllers/DebugController.cs @@ -28,167 +28,8 @@ namespace Submission.Api.Controllers _petitionRepository = petitionRepository; } - [HttpGet("petitions", Name = "GetPetitions")] - public IActionResult GetPetitions() - { - try - { - var files = Directory.EnumerateFiles("Petitions"); - return Ok(files); - } - catch (Exception e) - { - return Problem("Petitions Folder not found"); - } - } - [HttpGet("petitions-list", Name = "GetPetitionsList")] - public IActionResult GetPetitionsList() - { - var list = _petitionRepository.FilterBy(x => x.Id != null); - return Ok(list); - } - - - - [HttpGet("create-petition-folder", Name = "CreatePetitionFolder")] - public IActionResult create_petition_folder() - { - if (Directory.Exists("Petitions")) - { - return Ok("Petitions folder already exists"); - } - - try - { - Directory.CreateDirectory("Petitions"); - return Ok("Petitions folder created"); - } - catch (Exception e) - { - return Problem(e.Message); - } - } - - [HttpPost("upload-petition", Name = "UploadPetition")] - public async Task UploadPetition(IFormFile file) - { - // Check if petition creation is allowed - if (!_petitionSettings.AllowPetitionCreation) - { - return StatusCode(403, new { message = "Petition creation is disabled. Set 'PetitionSettings:AllowPetitionCreation' to true in appsettings.json" }); - } - - // Validate file exists - if (file == null || file.Length == 0) - { - return BadRequest(new { message = "No file uploaded" }); - } - - // Validate file extension - if (!file.FileName.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) - { - return BadRequest(new { message = "Only .md files are allowed" }); - } - - try - { - // Read file content - string fileContent; - using (var reader = new StreamReader(file.OpenReadStream())) - { - fileContent = await reader.ReadToEndAsync(); - } - - // Parse frontmatter and body - var (frontmatter, body) = ParseMarkdownFile(fileContent); - - if (frontmatter == null) - { - return BadRequest(new { message = "Invalid markdown format. Frontmatter is required." }); - } - - // Parse YAML frontmatter - var deserializer = new DeserializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - var metadata = deserializer.Deserialize>(frontmatter); - - // Extract values - var petitionId = Guid.NewGuid(); - var startDateStr = metadata["startDate"].ToString(); - var nameDhiv = metadata["nameDhiv"].ToString(); - var nameEng = metadata["nameEng"].ToString(); - var authorData = metadata["author"] as Dictionary; - - var authorName = authorData["name"].ToString(); - var authorNid = authorData["nid"].ToString(); - - // Parse start date (format: dd-MM-yyyy) - var startDate = DateOnly.ParseExact(startDateStr, "dd-MM-yyyy", CultureInfo.InvariantCulture); - - // Parse petition bodies from markdown - var (petitionBodyDhiv, petitionBodyEng) = ParsePetitionBodies(body); - - // Check if petition already exists - var existingPetition = await _petitionRepository.FindByIdAsync(petitionId); - if (existingPetition != null) - { - return Conflict(new { message = $"A petition with ID '{petitionId}' already exists in the database" }); - } - - // Create or get author - var author = await _authorRepository.FindOneAsync(x => x.NID == authorNid); - if (author == null) - { - author = new Author - { - Id = Guid.NewGuid(), - Name = authorName, - NID = authorNid - }; - await _authorRepository.InsertOneAsync(author); - } - - // Create petition - var petition = new PetitionDetail - { - Id = petitionId, - StartDate = startDate, - NameDhiv = nameDhiv, - NameEng = nameEng, - AuthorId = author.Id, - PetitionBodyDhiv = petitionBodyDhiv, - PetitionBodyEng = petitionBodyEng, - SignatureCount = 0 - }; - - await _petitionRepository.InsertOneAsync(petition); - - // Save file with GUID prefix - Directory.CreateDirectory("Petitions"); - var newFileName = $"{Guid.NewGuid()}_{file.FileName}"; - var filePath = Path.Combine("Petitions", newFileName); - - await System.IO.File.WriteAllTextAsync(filePath, fileContent); - - return Ok(new - { - message = "Petition created successfully", - petitionId = petitionId, - fileName = newFileName, - filePath = filePath, - authorId = author.Id - }); - } - catch (Exception e) - { - return Problem(e.Message); - } - } - [HttpPost("svg-debug", Name = "SvgDebug")] public async Task SVG_TEST([FromForm]string svg) diff --git a/Submission.Api/Dto/ApprovalDto.cs b/Submission.Api/Dto/ApprovalDto.cs new file mode 100644 index 0000000..f579969 --- /dev/null +++ b/Submission.Api/Dto/ApprovalDto.cs @@ -0,0 +1,7 @@ +namespace Submission.Api.Dto +{ + public class ApprovalDto + { + public bool IsApproved { get; set; } + } +} diff --git a/Submission.Api/Program.cs b/Submission.Api/Program.cs index 267b5cc..1b93b96 100644 --- a/Submission.Api/Program.cs +++ b/Submission.Api/Program.cs @@ -1,7 +1,10 @@ +using System.Text; using Ashi.MongoInterface; using Ashi.MongoInterface.Service; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.RateLimiting; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; using Submission.Api.Configuration; using Submission.Api.Controllers; using Submission.Api.Services; @@ -28,6 +31,27 @@ builder.Services.AddSwaggerGen(); // Register TurnstileService with typed HttpClient builder.Services.AddHttpClient(); +// Add JWT authentication +var jwtKey = builder.Configuration["Jwt:Key"]!; +var jwtIssuer = builder.Configuration["Jwt:Issuer"]!; +builder.Services.AddAuthentication(options => +{ + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}) +.AddJwtBearer(options => +{ + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtIssuer, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) + }; +}); + // Add rate limiting builder.Services.AddRateLimiter(options => { @@ -53,6 +77,7 @@ app.UseHttpsRedirection(); app.UseRateLimiter(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/Submission.Api/Submission.Api.csproj b/Submission.Api/Submission.Api.csproj index 7c6d60a..4ae3b37 100644 --- a/Submission.Api/Submission.Api.csproj +++ b/Submission.Api/Submission.Api.csproj @@ -8,6 +8,7 @@ + diff --git a/Submission.Api/appsettings.Development.json b/Submission.Api/appsettings.Development.json index 967a15b..af45eb0 100644 --- a/Submission.Api/appsettings.Development.json +++ b/Submission.Api/appsettings.Development.json @@ -15,5 +15,13 @@ "Microsoft.AspNetCore": "Warning" } }, + "AdminSettings": { + "Username": "admin", + "Password": "admin123" + }, + "Jwt": { + "Key": "dev-secret-key-that-is-at-least-32-characters-long!", + "Issuer": "SubmissionApi" + }, "AllowedHosts": "*" } diff --git a/frontend-react/src/App.tsx b/frontend-react/src/App.tsx index 9b38869..6d6978e 100644 --- a/frontend-react/src/App.tsx +++ b/frontend-react/src/App.tsx @@ -2,6 +2,8 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import { HomePage } from "@/pages/HomePage"; import { PetitionPage } from "@/pages/PetitionPage"; import { CreatePetitionPage } from "@/pages/CreatePetitionPage"; +import { AdminLoginPage } from "@/pages/AdminLoginPage"; +import { AdminDashboardPage } from "@/pages/AdminDashboardPage"; import { Layout } from "@/components/layout/Layout"; function App() { @@ -12,6 +14,8 @@ function App() { } /> } /> } /> + } /> + } /> diff --git a/frontend-react/src/lib/adminApi.ts b/frontend-react/src/lib/adminApi.ts new file mode 100644 index 0000000..1874319 --- /dev/null +++ b/frontend-react/src/lib/adminApi.ts @@ -0,0 +1,117 @@ +const API_BASE_URL = ""; + +const TOKEN_KEY = "adminToken"; + +export function getToken(): string | null { + return localStorage.getItem(TOKEN_KEY); +} + +export function setToken(token: string): void { + localStorage.setItem(TOKEN_KEY, token); +} + +export function clearToken(): void { + localStorage.removeItem(TOKEN_KEY); +} + +export async function login( + username: string, + password: string, +): Promise { + const response = await fetch(`${API_BASE_URL}/api/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + + if (response.status === 401) { + throw new Error("Invalid credentials"); + } + + if (!response.ok) { + throw new Error(`Login failed: ${response.status}`); + } + + const data = await response.json(); + setToken(data.token); + return data.token; +} + +export async function adminFetch( + url: string, + options: RequestInit = {}, +): Promise { + const token = getToken(); + if (!token) { + window.location.href = "/admin/login"; + throw new Error("No token"); + } + + const res = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${token}`, + }, + }); + + if (res.status === 401) { + clearToken(); + window.location.href = "/admin/login"; + throw new Error("Unauthorized"); + } + + return res; +} + +export interface AdminPetition { + id: string; + slug: string; + nameEng: string; + nameDhiv: string; + signatureCount: number; + startDate: string; + isApproved: boolean; + authorDetails?: { name: string }; +} + +export async function fetchAdminPetitions(): Promise { + const res = await adminFetch(`${API_BASE_URL}/api/admin/petitions`); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.detail || `HTTP error: ${res.status}`); + } + return res.json(); +} + +export async function createPetitionFolder(): Promise { + const res = await adminFetch( + `${API_BASE_URL}/api/admin/create-petition-folder`, + ); + if (!res.ok) { + throw new Error(`HTTP error: ${res.status}`); + } + return res.text(); +} + +export async function updatePetitionApproval( + petitionId: string, + isApproved: boolean, +): Promise { + const res = await adminFetch( + `${API_BASE_URL}/api/admin/petitions/${petitionId}/approve`, + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ isApproved }), + }, + ); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.detail || `HTTP error: ${res.status}`); + } +} + +export function getExportUrl(petitionId: string): string { + return `${API_BASE_URL}/api/admin/export/${petitionId}`; +} diff --git a/frontend-react/src/pages/AdminDashboardPage.tsx b/frontend-react/src/pages/AdminDashboardPage.tsx new file mode 100644 index 0000000..2bbda97 --- /dev/null +++ b/frontend-react/src/pages/AdminDashboardPage.tsx @@ -0,0 +1,278 @@ +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { + fetchAdminPetitions, + createPetitionFolder, + getExportUrl, + getToken, + clearToken, + updatePetitionApproval, + type AdminPetition, +} from "@/lib/adminApi"; +import { + Loader2, + LogOut, + FileDown, + FolderPlus, + Users, + ShieldCheck, + CheckCircle, + Clock, + Eye, + XCircle, +} from "lucide-react"; + +export function AdminDashboardPage() { + const navigate = useNavigate(); + const [petitions, setPetitions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [folderMsg, setFolderMsg] = useState(null); + const [togglingId, setTogglingId] = useState(null); + + useEffect(() => { + if (!getToken()) { + navigate("/admin/login"); + return; + } + loadPetitions(); + }, [navigate]); + + async function loadPetitions() { + setLoading(true); + setError(null); + try { + const data = await fetchAdminPetitions(); + setPetitions(data); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load petitions"); + } finally { + setLoading(false); + } + } + + async function handleCreateFolder() { + setFolderMsg(null); + try { + const msg = await createPetitionFolder(); + setFolderMsg(msg); + } catch (err) { + setFolderMsg( + err instanceof Error ? err.message : "Failed to create folder", + ); + } + } + + async function handleToggleApproval(petition: AdminPetition) { + setTogglingId(petition.id); + try { + await updatePetitionApproval(petition.id, !petition.isApproved); + await loadPetitions(); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to update approval", + ); + } finally { + setTogglingId(null); + } + } + + function handleLogout() { + clearToken(); + navigate("/admin/login"); + } + + function handleExport(petitionId: string) { + const token = getToken(); + if (!token) return; + fetch(getExportUrl(petitionId), { + headers: { Authorization: `Bearer ${token}` }, + }) + .then((res) => { + if (res.status === 401) { + clearToken(); + navigate("/admin/login"); + return; + } + if (!res.ok) throw new Error("Export failed"); + return res.blob(); + }) + .then((blob) => { + if (!blob) return; + const url = URL.createObjectURL(blob); + window.open(url, "_blank"); + }) + .catch(() => { + setError("Failed to export petition"); + }); + } + + return ( +
+ {/* Header */} +
+
+
+ +

+ Admin Dashboard +

+
+
+ + +
+
+
+ +
+ {folderMsg && ( +
+ {folderMsg} +
+ )} + + {error && ( +
+ {error} +
+ )} + +
+

+ All Petitions +

+ + {petitions.length} total + +
+ + {loading ? ( +
+ +
+ ) : petitions.length === 0 ? ( +
+

No petitions found.

+
+ ) : ( +
+ + + + + + + + + + + + {petitions.map((p) => ( + + + + + + + + ))} + +
+ Petition + + Slug + + Sigs + + Status + + Actions +
+
+ {p.nameEng} +
+
+ {p.nameDhiv} +
+
+ + {p.slug} + + + + + {p.signatureCount} + + + {p.isApproved ? ( + + + Approved + + ) : ( + + + Pending + + )} + +
+ + + +
+
+
+ )} +
+
+ ); +} diff --git a/frontend-react/src/pages/AdminLoginPage.tsx b/frontend-react/src/pages/AdminLoginPage.tsx new file mode 100644 index 0000000..f420764 --- /dev/null +++ b/frontend-react/src/pages/AdminLoginPage.tsx @@ -0,0 +1,103 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { login } from "@/lib/adminApi"; +import { Lock, Loader2 } from "lucide-react"; + +export function AdminLoginPage() { + const navigate = useNavigate(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + setLoading(true); + + try { + await login(username, password); + navigate("/admin"); + } catch (err) { + setError(err instanceof Error ? err.message : "Login failed"); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+
+
+ +
+
+ +

+ Admin Login +

+ + {error && ( +
+ {error} +
+ )} + +
+
+ + setUsername(e.target.value)} + required + autoFocus + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + /> +
+ +
+ + setPassword(e.target.value)} + required + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + /> +
+ + +
+
+
+
+ ); +} diff --git a/frontend-react/src/pages/CreatePetitionPage.tsx b/frontend-react/src/pages/CreatePetitionPage.tsx index d5c63ab..0e8b012 100644 --- a/frontend-react/src/pages/CreatePetitionPage.tsx +++ b/frontend-react/src/pages/CreatePetitionPage.tsx @@ -85,7 +85,7 @@ function GuidelinesModal({ onAccept }: { onAccept: () => void }) { I Understand, Continue (); @@ -58,7 +59,27 @@ export function PetitionPage() { } return ( -
+
+
+
+ + + Home + + {petition && ( + <> + / + + {language === "dv" ? petition.nameDhiv : petition.nameEng} + + + )} +
+
+
{loading ? (
@@ -111,6 +132,7 @@ export function PetitionPage() { )}
+
); } diff --git a/frontend-react/vite.config.ts b/frontend-react/vite.config.ts index f6c38e8..4a1c2cf 100644 --- a/frontend-react/vite.config.ts +++ b/frontend-react/vite.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ server: { proxy: { "/api": { - target: "http://localhost:9755", + target: "http://localhost:5299", changeOrigin: true, }, }, diff --git a/global.json b/global.json deleted file mode 100644 index 93681ff..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "9.0.0", - "rollForward": "latestMinor", - "allowPrerelease": false - } -} \ No newline at end of file