diff --git a/README.md b/README.md
index d9fe6f9..9dc8c71 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,226 @@
-### Feature in the Development
-- Add refresh expired token in the cache ( adding the logic for cache )
\ No newline at end of file
+The refactoring for the new version of the software altogether
+backend/
+│
+├── cmd/
+│ └── server/
+│ └── main.go # Application entrypoint
+│
+├── internal/
+│ │
+│ ├── domain/ # Core domain layer
+│ │ │
+│ │ ├── entities/ # Database models (shared)
+│ │ │ ├── user.go
+│ │ │ ├── tenant.go
+│ │ │ ├── role.go
+│ │ │ ├── route_role.go
+│ │ │ ├── token.go
+│ │ │ ├── message.go
+│ │ │ ├── login.go
+│ │ │ └── reset_token.go
+│ │ │
+│ │ ├── dto/ # Data Transfer Objects
+│ │ │ │
+│ │ │ ├── customer/ # Customer API contracts
+│ │ │ │ ├── requests/
+│ │ │ │ │ ├── auth.go # RegisterUserRequest, LoginRequest
+│ │ │ │ │ ├── user.go # UpdateProfileRequest
+│ │ │ │ │ ├── role.go # RequestRoleRequest
+│ │ │ │ │ └── message.go # CreateMessageRequest
+│ │ │ │ │
+│ │ │ │ └── responses/
+│ │ │ │ ├── auth.go # LoginResponse, RegisterResponse
+│ │ │ │ ├── user.go # UserResponse, UserListResponse
+│ │ │ │ ├── role.go # RoleResponse, RoleListResponse
+│ │ │ │ └── message.go # MessageResponse, MessageStatusResponse
+│ │ │ │
+│ │ │ └── tenant/ # Tenant/Admin API contracts
+│ │ │ ├── requests/
+│ │ │ │ ├── tenant.go # CreateTenantRequest, LoginTenantRequest
+│ │ │ │ ├── user_admin.go # DisableUserRequest, AssignRoleRequest
+│ │ │ │ ├── role_admin.go # CreateRoleRequest, UpdatePermissionsRequest
+│ │ │ │ ├── token.go # CreateTokenRequest, RevokeTokenRequest
+│ │ │ │ ├── message.go # ApproveMessageRequest, RejectMessageRequest
+│ │ │ │ └── dashboard.go # DashboardFiltersRequest
+│ │ │ │
+│ │ │ └── responses/
+│ │ │ ├── tenant.go # TenantResponse, TenantDetailsResponse
+│ │ │ ├── user_admin.go # UserAdminResponse, UserListAdminResponse
+│ │ │ ├── role_admin.go # RoleAdminResponse, PermissionsResponse
+│ │ │ ├── token.go # TokenResponse, TokenListResponse
+│ │ │ ├── message.go # MessageAdminResponse
+│ │ │ └── dashboard.go # DashboardStatsResponse
+│ │ │
+│ │ └── errors/ # Custom error types
+│ │ ├── errors.go # AppError, ValidationError, etc.
+│ │ └── codes.go # Error codes
+│ │
+│ ├── repository/ # Data access layer
+│ │ │
+│ │ ├── customer/ # Customer-context repositories
+│ │ │ ├── user_repository.go
+│ │ │ ├── role_repository.go
+│ │ │ ├── message_repository.go
+│ │ │ ├── login_repository.go
+│ │ │ ├── token_repository.go
+│ │ │ └── interfaces.go
+│ │ │
+│ │ ├── tenant/ # Tenant-context repositories
+│ │ │ ├── tenant_repository.go
+│ │ │ ├── user_repository.go
+│ │ │ ├── role_repository.go
+│ │ │ ├── message_repository.go
+│ │ │ ├── token_repository.go
+│ │ │ ├── tenant_login_repository.go
+│ │ │ ├── dashboard_repository.go
+│ │ │ └── interfaces.go
+│ │ │
+│ │ └── shared/ # Shared repository utilities
+│ │ ├── base_repository.go
+│ │ ├── reset_token_repository.go
+│ │ └── route_role_repository.go
+│ │
+│ ├── service/ # Business logic layer
+│ │ │
+│ │ ├── customer/ # Customer business logic
+│ │ │ ├── auth_service.go
+│ │ │ ├── user_service.go
+│ │ │ ├── role_service.go
+│ │ │ ├── message_service.go
+│ │ │ └── interfaces.go
+│ │ │
+│ │ ├── tenant/ # Tenant business logic
+│ │ │ ├── tenant_service.go
+│ │ │ ├── user_admin_service.go
+│ │ │ ├── role_admin_service.go
+│ │ │ ├── token_service.go
+│ │ │ ├── message_admin_service.go
+│ │ │ ├── dashboard_service.go
+│ │ │ └── interfaces.go
+│ │ │
+│ │ └── shared/ # Shared services
+│ │ ├── mail_service.go
+│ │ ├── cache_service.go
+│ │ └── password_service.go
+│ │
+│ ├── api/ # HTTP API layer
+│ │ │
+│ │ ├── customer/ # Customer-facing API (Port 8080)
+│ │ │ ├── handlers/
+│ │ │ │ ├── auth_handler.go
+│ │ │ │ ├── user_handler.go
+│ │ │ │ ├── role_handler.go
+│ │ │ │ ├── message_handler.go
+│ │ │ │ └── common.go # Shared response helpers
+│ │ │ │
+│ │ │ ├── middleware/
+│ │ │ │ ├── app_key.go
+│ │ │ │ ├── jwt_auth.go
+│ │ │ │ ├── permissions.go
+│ │ │ │ ├── ratelimit.go
+│ │ │ │ └── chain.go # Middleware chains
+│ │ │ │
+│ │ │ └── routes.go # Customer API routes
+│ │ │
+│ │ └── tenant/ # Tenant-facing API (Port 8081)
+│ │ ├── handlers/
+│ │ │ ├── tenant_handler.go
+│ │ │ ├── user_admin_handler.go
+│ │ │ ├── role_admin_handler.go
+│ │ │ ├── token_handler.go
+│ │ │ ├── message_admin_handler.go
+│ │ │ ├── dashboard_handler.go
+│ │ │ └── common.go # Shared response helpers
+│ │ │
+│ │ ├── middleware/
+│ │ │ ├── tenant_auth.go
+│ │ │ ├── ratelimit.go
+│ │ │ └── chain.go
+│ │ │
+│ │ └── routes.go # Tenant API routes
+│ │
+│ ├── bootstrap/ # Application initialization
+│ │ ├── app.go # Main app bootstrap
+│ │ ├── database.go # Database setup & migrations
+│ │ ├── cache.go # Redis setup
+│ │ ├── queue.go # RabbitMQ setup
+│ │ ├── server.go # HTTP servers setup
+│ │ └── dependencies.go # Dependency injection
+│ │
+│ └── pkg/ # Internal shared packages
+│ │
+│ ├── cache/ # Cache utilities
+│ │ ├── cache.go
+│ │ └── redis.go
+│ │
+│ ├── queue/ # Message queue utilities
+│ │ ├── queue.go
+│ │ ├── consumer.go
+│ │ └── producer.go
+│ │
+│ ├── mailer/ # Email service
+│ │ ├── mailer.go
+│ │ └── smtp.go
+│ │
+│ ├── logger/ # Logging utilities
+│ │ └── logger.go
+│ │
+│ ├── validator/ # Validation utilities
+│ │ └── validator.go
+│ │
+│ ├── crypto/ # Cryptography utilities
+│ │ ├── hash.go
+│ │ └── jwt.go
+│ │
+│ └── pagination/ # Pagination utilities
+│ ├── pagination.go
+│ └── response.go
+│
+├── config/ # Configuration files
+│ ├── permissions/ # Role permission JSON files
+│ │ ├── admin.json
+│ │ ├── user.json
+│ │ ├── moderator.json
+│ │ └── guest.json
+│ │
+│ └── database/ # Database migrations
+│ └── migrations/
+│ ├── 001_create_users_table.sql
+│ ├── 002_create_roles_table.sql
+│ └── ...
+│
+├── scripts/ # Utility scripts
+│ ├── migrate.sh # Run database migrations
+│ ├── seed.sh # Seed initial data
+│ └── test.sh # Run tests
+│
+├── test/ # Tests
+│ ├── integration/
+│ │ ├── customer_api_test.go
+│ │ ├── tenant_api_test.go
+│ │ └── fixtures/
+│ │ └── test_data.sql
+│ │
+│ └── unit/
+│ ├── service/
+│ │ └── user_service_test.go
+│ │
+│ └── repository/
+│ └── user_repository_test.go
+│
+├── docs/ # API documentation
+│ ├── swagger/
+│ │ ├── docs.go
+│ │ ├── swagger.json
+│ │ └── swagger.yaml
+│ │
+│ ├── API_CUSTOMER.md # Customer API docs
+│ └── API_TENANT.md # Tenant API docs
+│
+├── .env.example # Environment variables template
+├── .gitignore
+├── Dockerfile # Multi-stage build
+├── Makefile # Build automation
+├── go.mod
+├── go.sum
+└── README.md
\ No newline at end of file
diff --git a/backend/Makefile b/backend/Makefile
new file mode 100644
index 0000000..6ac9e84
--- /dev/null
+++ b/backend/Makefile
@@ -0,0 +1,62 @@
+# Makefile
+
+.PHONY: help
+help: ## Show this help message
+ @echo 'Usage: make [target]'
+ @echo ''
+ @echo 'Available targets:'
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
+
+## Development Commands
+
+compose-build: ## Build Docker images
+ docker-compose build
+
+compose-with-debug: compose-build ## Start with debug logs
+ @echo "Starting in the debug mode for container"
+ @docker compose up
+
+compose-without-app: compose-build ## Start without app container
+ @echo "Starting in the debug mode for container"
+ @docker compose up --scale app=0 -d
+
+compose-up: compose-build ## Start all containers in background
+ @docker compose up -d
+
+compose-stop: ## Stop all containers
+ @echo "stopping docker compose in background"
+ @docker compose down
+
+compose-clean: compose-stop ## Stop and remove containers
+ docker-compose rm -f
+
+compose-build-no-cache: ## Build without cache
+ docker-compose build --no-cache
+
+## Testing Commands
+
+test-setup: ## Setup test environment (first time only)
+ @cd test-suite && chmod +x setup.sh && ./setup.sh
+
+test-isolated: ## Run tests with isolated database environment
+ @cd test-suite && chmod +x run_tests.sh && ./run_tests.sh
+
+test-quick: ## Run tests using existing containers (faster)
+ @cd test-suite && go test -v -timeout 3m ./...
+
+test-start: ## Start test containers without running tests
+ @cd test-suite && docker-compose -f test-suite/docker-compose.test.yml up -d
+
+test-stop: ## Stop test containers
+ @cd test-suite && docker-compose -f test-suite/docker-compose.test.yml down
+
+test-clean: ## Remove test containers and volumes
+ @cd test-suite && docker-compose -f test-suite/docker-compose.test.yml down -v
+
+test-logs: ## Show test container logs
+ @cd test-suite && docker-compose -f test-suite/docker-compose.test.yml logs -f
+
+test-status: ## Show test container status
+ @cd test-suite && docker-compose -f test-suite/docker-compose.test.yml ps
+
+.DEFAULT_GOAL := help
\ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..7059a96
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,12 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/frontend/contact.txt b/frontend/contact.txt
new file mode 100644
index 0000000..2a62bc8
--- /dev/null
+++ b/frontend/contact.txt
@@ -0,0 +1,35 @@
+
+
+
setSidebarOpen(!sidebarOpen)}
+ aria-label="Open sidebar"
+ >
+
+
+
+
+ {/* Sidebar: collapsible only on small screens, static on md+ */}
+
+
+ {
+ sessionStorage.setItem("activeSection", "dashboard");
+ pageReload();
+ }}
+ className="font-sans cursor-pointer antialiased text-base md:text-lg text-stone-800 block py-1 font-semibold"
+ >
+ TrustKit
+
+
+
+
+ handleSectionClick("dashboard")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 font-sans transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "dashboard" ? "" : ""
+ }`}
+ >
+
+ Dashboard
+
+
+
+ handleSectionClick("tokens")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 font-sans transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "tokens" ? "" : ""
+ }`}
+ >
+
+ Tokens
+
+
+
+ handleSectionClick("users")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer font-sans ${
+ activeSection === "users" ? "" : ""
+ }`}
+ >
+
+ User Management
+
+
+
+ handleSectionClick("docs")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 font-sans transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "docs" ? "" : ""
+ }`}
+ >
+
+ Docs
+
+
+
+ handleSectionClick("roles")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 font-sans transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "roles" ? "" : ""
+ }`}
+ >
+
+ Roles
+
+
+
+ handleSectionClick("messages")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 font-sans transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "messages" ? "" : ""
+ }`}
+ >
+
+ Messages
+
+
+
+ handleSectionClick("profile")}
+ className={`flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 font-sans transition-transform ease-in duration-200 text-stone-800 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "profile" ? "" : ""
+ }`}
+ >
+
+ Profile
+
+
+
+ {
+ sessionStorage.clear();
+ navigate("/");
+ }}
+ className={`group flex flex-row items-center h-12 px-4 gap-2 transform hover:translate-x-2 transition-transform ease-in duration-200 text-red-400 hover:text-gray-800 w-full cursor-pointer ${
+ activeSection === "logout" ? "" : ""
+ }`}
+ >
+
+
+ Logout
+
+
+
+
+
+ {/* Overlay for mobile when sidebar is open */}
+ {sidebarOpen && (
+
setSidebarOpen(false)}
+ />
+ )}
+ {/* Main content */}
+
+ {activeSection === "tokens" &&
}
+ {activeSection === "users" &&
}
+ {activeSection === "profile" &&
}
+ {activeSection === "dashboard" &&
}
+ {activeSection === "roles" &&
}
+ {activeSection === "docs" &&
}
+ {activeSection === "messages" &&
}
+
+
+ );
+};
+
+export default Home;
diff --git a/frontend/src/components/homePages/dashboard.jsx b/frontend/src/components/homePages/dashboard.jsx
new file mode 100644
index 0000000..75fdaf9
--- /dev/null
+++ b/frontend/src/components/homePages/dashboard.jsx
@@ -0,0 +1,342 @@
+import React, { useState, useEffect } from "react";
+import {
+ FiKey,
+ FiShield,
+ FiEdit2,
+ FiChevronRight,
+ FiRefreshCw,
+ FiUsers,
+} from "react-icons/fi";
+import { GetDashBoardDetails, GetActiveRoles } from "../services/dashboard";
+const Dashboard = () => {
+ function pageReload() {
+ location.reload();
+ }
+ const [loading, setLoading] = useState(true);
+ const [stats, setStats] = useState({
+ users: 0,
+ tokens: 0,
+ roles: 0,
+ });
+ const [users, setUsers] = useState([]);
+ const [ActiveToken, setActiveToken] = useState(1)
+
+ // Simulate data loading
+ useEffect(() => {
+ const fetchData = async () => {
+ // In a real app, you'd fetch from your API
+ await new Promise((resolve) => setTimeout(resolve, 800));
+ const response = await GetDashBoardDetails()
+ console.log(response)
+ setStats({
+ users: response.data.user_count,
+ tokens: response.data.token_count,
+ roles: response.data.role_count,
+ });
+
+ const TokensResponse = await GetActiveRoles(1,5)
+ console.log("the token response: ",TokensResponse.data.pagination.total_items)
+ setActiveToken(TokensResponse.data.pagination.total_items)
+ setUsers([
+ {
+ id: 1,
+ name: "John Doe",
+ email: "john@example.com",
+ title: "Software Engineer",
+ department: "Web Development",
+ status: "active",
+ role: "Owner",
+ lastActive: "2 hours ago",
+ },
+ {
+ id: 2,
+ name: "Jane Smith",
+ email: "jane@example.com",
+ title: "Product Manager",
+ department: "Product",
+ status: "active",
+ role: "Admin",
+ lastActive: "30 minutes ago",
+ },
+ {
+ id: 3,
+ name: "Robert Johnson",
+ email: "robert@example.com",
+ title: "QA Engineer",
+ department: "Testing",
+ status: "inactive",
+ role: "Member",
+ lastActive: "3 days ago",
+ },
+ ]);
+
+ setLoading(false);
+ };
+
+ fetchData();
+ }, []);
+
+ const navigateTo = (section) => {
+ sessionStorage.setItem("activeSection", section);
+ window.location.reload();
+ };
+
+ const refreshData = () => {
+ setLoading(true);
+ // In a real app, you would refetch data here
+ setTimeout(() => setLoading(false), 500);
+ };
+
+ const formatNumber = (num) => {
+ return new Intl.NumberFormat().format(num);
+ };
+
+ return (
+
+
+
+
+ {/* Header with refresh button */}
+
+
Dashboard
+
+
+ Refresh
+
+
+
+ {/* Stats Cards */}
+
+
+ {/* Users Card */}
+
+
+
{
+ sessionStorage.setItem("activeSection", "users");
+ pageReload();
+ }}
+ className="absolute top-3 cursor-pointer right-3 flex items-center text-stone-400 hover:text-stone-700 transition"
+ aria-label="View users"
+ >
+ View users
+
+
+
+
+
+
+
+ {loading ? "--" : formatNumber(stats.users)}
+
+
Total Users
+
+ {loading ? "Loading..." : "Last updated just now"}
+
+
+
+
+
+ {/* Tokens Card */}
+
+
+
{
+ sessionStorage.setItem("activeSection", "tokens");
+ pageReload();
+ }}
+ className="absolute top-3 right-3 cursor-pointer flex items-center text-stone-400 hover:text-stone-700 transition"
+ aria-label="View tokens"
+ >
+ View tokens
+
+
+
+
+
+
+
+ {loading ? "--" : formatNumber(stats.tokens)}
+
+
Total Tokens
+
+ {loading ? "Loading..." : `Active:${ActiveToken}`}
+
+
+
+
+
+ {/* Roles Card */}
+
+
+
{
+ sessionStorage.setItem("activeSection", "roles");
+ pageReload();
+ }}
+ className="absolute top-3 right-3 flex cursor-pointer items-center text-stone-400 hover:text-stone-700 transition"
+ aria-label="View roles"
+ >
+ View roles
+
+
+
+
+
+
+
+ {loading ? "--" : stats.roles}
+
+
Custom Roles
+
+ {loading ? "Loading..." : "5 system roles"}
+
+
+
+
+
+
+
+ {/* Recent Users Table */}
+
+
+
+
+
+ Recent Users
+
+ navigateTo("users")}
+ className="text-sm text-stone-600 hover:text-stone-900"
+ >
+ View All
+
+
+
+
+
+
+
+
+ User
+
+
+ Details
+
+
+ Status
+
+
+ Role
+
+
+ Last Active
+
+
+
+
+
+
+ {loading ? (
+
+
+ Loading user data...
+
+
+ ) : (
+ users.map((user) => (
+
+
+
+
+
+ {user.name.charAt(0)}
+
+
+
+
+ {user.name}
+
+
+ {user.email}
+
+
+
+
+
+
+
+ {user.title}
+
+
+ {user.department}
+
+
+
+
+
+ {user.status.charAt(0).toUpperCase() +
+ user.status.slice(1)}
+
+
+
+
+ {user.role}
+
+
+
+ {user.lastActive}
+
+
+
+ {
+ sessionStorage.setItem(
+ "activeSection",
+ "profile"
+ );
+ sessionStorage.setItem(
+ "selectedUserId",
+ user.id
+ );
+ window.location.reload();
+ }}
+ className="flex items-center text-stone-600 hover:text-stone-900"
+ aria-label={`Edit ${user.name}`}
+ >
+
+ Edit
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Dashboard;
diff --git a/frontend/src/components/homePages/docs.jsx b/frontend/src/components/homePages/docs.jsx
new file mode 100644
index 0000000..256c248
--- /dev/null
+++ b/frontend/src/components/homePages/docs.jsx
@@ -0,0 +1,990 @@
+import { FiLock, FiUser, FiShield } from "react-icons/fi";
+
+const Docs = () => {
+ // Base URL for Swagger UI
+ const swaggerBaseUrl = "http://localhost:8080/swagger/index.html";
+
+ return (
+
+ {/* Header */}
+
+
+ {/* Sidebar Navigation */}
+
+
+
API Endpoints
+
+
+
+ {
+ const el = document.getElementById("authentication");
+ if (el) el.scrollIntoView({ behavior: "smooth" });
+ }}
+ className="flex items-center text-stone-500 hover:text-stone-900 cursor-pointer"
+ >
+ Authentication
+
+
+
+ {
+ const el = document.getElementById("user-management");
+ if (el) el.scrollIntoView({ behavior: "smooth" });
+ }}
+ className="flex items-center text-stone-500 hover:text-stone-800 cursor-pointer"
+ >
+ User Management
+
+
+
+ {
+ const el = document.getElementById("role-management");
+ if (el) el.scrollIntoView({ behavior: "smooth" });
+ }}
+ className="flex items-center text-stone-500 hover:text-stone-800 cursor-pointer"
+ >
+ Role Management
+
+
+
+
+ View Swagger Docs
+
+
+
+
+
+
+
System Features
+
+
+ ✓ JWT
+ Authentication
+
+
+ ✓ Role-Based
+ Access Control
+
+
+ ✓ Redis Caching
+ Layer
+
+
+ ✓ Password Reset
+ Flow
+
+
+ ✓ Custom Role
+ Creation
+
+
+
+
+
+
+ {/* Documentation Content */}
+
+ {/* Introduction */}
+
+ TrustKit API
+
+ TrustKit provides enterprise-grade authentication and
+ authorization services that can be easily integrated into any
+ application.
+
+
+
+
+ {/* Authentication Section */}
+
+
+ Authentication
+
+
+
+
+
+
+
POST /auth/login
+
+ Authenticate user credentials and receive JWT tokens
+
+
+
+ Public
+
+
+
+
+ Example Request
+
+
+ {`{
+ "email": "user@example.com",
+ "password": "securePassword123"
+}`}
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
PUT /auth/refresh
+
+ Refresh expired access tokens using a valid refresh token
+
+
+
+ JWT Required
+
+
+
+
Headers
+
+ Authorization: Bearer [refresh_token]
+
+
+
+ View in Swagger →
+
+
+
+
+
+ {/* User Management Section */}
+
+
+ User Management
+
+
+
+
+
+
+
GET /user/me
+
+ Retrieve comprehensive profile information for the
+ authenticated user
+
+
+
+ JWT Required
+
+
+
+
+ Explanation
+
+
+
+ Returns complete user profile details including personal
+ information, assigned roles, and account status
+
+
+ Automatically validates JWT token and retrieves current
+ user context from the authorization header
+
+
+ Ideal for populating user profiles, dashboards, and
+ personalization features
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
PUT /user/me
+
+ Update profile information for the currently authenticated
+ user
+
+
+
+ JWT Required
+
+
+
+
+ Explanation
+
+
+
+ Allows users to modify their own profile information
+ including name, contact details, and preferences
+
+
+ Supports partial updates - only included fields will be
+ modified
+
+
+ Automatically validates input data and applies business
+ logic constraints
+
+
+ Returns the updated user object with applied changes
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
GET /user/{'{id}'}
+
+ Retrieve detailed user information by unique identifier
+
+
+
+ Admin | Moderator
+
+
+
+
+ Explanation
+
+
+
+ Provides comprehensive user details for administrative
+ purposes
+
+
+ Includes sensitive information such as account status,
+ role assignments, and audit trail data
+
+
+ Validates user permissions to ensure authorized access to
+ user records
+
+
+ Returns 404 error if the specified user ID does not exist
+ in the system
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
DELETE /user/{'{id}'}
+
+ Permanently remove a user account from the system
+
+
+
+ Admin | Moderator
+
+
+
+
+ Explanation
+
+
+
+ Completely removes user account and associated data from
+ the database
+
+
+ Performs cascading deletion of user-related records while
+ maintaining referential integrity
+
+
+ Includes safety checks to prevent accidental deletion of
+ critical system accounts
+
+
+ Returns confirmation message with details of the deletion
+ operation
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ PUT /user/{'{id}'}/roles
+
+
+ Manage role assignments for specific users within the
+ system
+
+
+
+ Admin
+
+
+
+
+ Request Body Example
+
+
+ {`{
+ "role": "content-manager"
+}`}
+
+
+
+
+ Explanation
+
+
+
+ Enables administrators to assign or modify roles for
+ specific users identified by their unique ID
+
+
+ Validates role existence and user permissions before
+ applying changes
+
+
+ Supports atomic role updates with immediate effect across
+ the system
+
+
+ Returns the updated user object reflecting the new role
+ assignments
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ POST /user/resetpassword
+
+
+ Initiate password reset process for user accounts
+
+
+
+ Public
+
+
+
+
+ Request Body Example
+
+
+ {`{
+ "email": "user@TrustKits.com"
+}`}
+
+
+
+
+ Explanation
+
+
+
+ Initiates secure password reset workflow by generating and
+ delivering a time-sensitive OTP to the registered email
+ address
+
+
+ Implements rate limiting and security measures to prevent
+ abuse
+
+
+ Validates email existence while maintaining user privacy
+ through generic success responses
+
+
+ OTP tokens are securely handled and include expiration
+ timestamps
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ PUT /user/setpassword
+
+
+ Complete password reset process with OTP verification
+
+
+
+ Public
+
+
+
+
+ Request Body Example
+
+
+ {`{
+ "email": "user@TrustKits.com",
+ "otp": "123456",
+ "new_password": "new_secure_password",
+ "confirm_password": "new_secure_password"
+}`}
+
+
+
+
+ Explanation
+
+
+
+ Verifies OTP authenticity and validity before allowing
+ password change
+
+
+ Enforces strong password policies and matches confirmation
+ fields
+
+
+ Invalidates the used OTP immediately after successful
+ password update
+
+
+ Securely hashes new password and updates user
+ authentication credentials
+
+
+ Can be combined with session invalidation for higher
+ security
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+ {/* Role Management Section */}
+
+
+ Role Management
+
+
+
+
+
+
+
POST /roles/
+
+ Create custom roles with granular permissions
+
+
+
+ Admin
+
+
+
+
+ Request Body Example
+
+
+ {`{
+ "name": "content-manager",
+ "display_name": "Content Manager",
+ "description": "Manages content creation and moderation",
+ "permissions": [
+ {
+ "route": "/api/content",
+ "methods": ["GET", "POST", "PUT"],
+ "description": "Full content management access"
+ }
+ ]
+}`}
+
+
+
+
+ Explanation
+
+
+
+ Creates a custom role with precisely defined permissions
+ for API endpoints
+
+
+ Users assigned to this role inherit all specified
+ permissions automatically
+
+
+ Returns the newly created role object with
+ system-generated ID
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
GET /roles
+
+ Retrieve all roles with detailed information
+
+
+
+ Admin | Moderator | User
+
+
+
+
+ Query Parameters
+
+
+ {`?type=custom // Filter by role type (system|custom)
+?status=enabled // Filter by status (enabled|disabled)
+?page=1 // Pagination page number
+?limit=20 // Results per page`}
+
+
+
+
+ Explanation
+
+
+
+ Returns a paginated list of roles with comprehensive
+ details including permissions
+
+
+ System roles can be distinguished from custom roles using
+ filters
+
+
+ Response can include metadata for pagination (total count,
+ current page, etc.)
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ PUT /roles/{'{id}'}/permissions
+
+
+ Modify permissions for a specific role
+
+
+
+ Admin | Moderator
+
+
+
+
+ Request Body Example
+
+
+ {`{
+ "add_permissions": [
+ {
+ "route": "/api/reports",
+ "methods": ["GET"],
+ "description": "Access to reporting dashboard"
+ }
+ ],
+ "remove_permissions": [
+ {
+ "route": "/api/content",
+ "methods": ["DELETE"]
+ }
+ ]
+}`}
+
+
+
+
+ Explanation
+
+
+
+ Atomically adds and removes permissions from the specified
+ role
+
+
+ Changes take effect immediately for all users assigned to
+ this role
+
+
+ Returns the updated role object with the modified
+ permissions set
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ GET /roles/{'{id}'}/permissions
+
+
+ List all permissions for a specific role
+
+
+
+ Admin | Moderator | User
+
+
+
+
+ Explanation
+
+
+
+ Returns a comprehensive list of all permissions granted to
+ the specified role
+
+
+ Includes detailed information about each permission
+ (route, HTTP methods, description)
+
+
+ Useful for auditing role permissions or displaying them in
+ administration interfaces
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ PUT /roles/enable/{'{id}'}
+
+
+ Activate a disabled role
+
+
+
+ Admin | Moderator
+
+
+
+
+ Explanation
+
+
+
+ Changes the status of a role from 'disabled' to
+ 'enabled'
+
+
+ Once enabled, the role can be assigned to users and its
+ permissions become active
+
+ Returns the updated role object with the new status
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
+ PUT /roles/disable/{'{id}'}
+
+
+ Deactivate a role without deleting it
+
+
+
+ Admin | Moderator
+
+
+
+
+ Explanation
+
+
+
+ Changes the status of a role from 'enabled' to
+ 'disabled'
+
+
+ Disabled roles cannot be assigned to new users, and
+ existing assignments become inactive
+
+
+ Useful for temporarily restricting access without removing
+ role definitions
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+
+
DELETE /roles/{'{id}'}
+
+ Permanently remove a custom role
+
+
+
+ Admin
+
+
+
+
+ Explanation
+
+
+ Completely removes a custom role from the system
+
+ Automatically revokes this role from all users who had it
+ assigned
+
+
+ System roles cannot be deleted - this endpoint only works
+ for custom roles
+
+
+ Returns a confirmation message upon successful deletion
+
+
+
+
+ View in Swagger →
+
+
+
+
+
+ {/* Integration Guide */}
+
+ Implementation Guide
+
+
Getting Started
+
+
+ Configure your application to point to our TrustKit endpoints
+
+ Implement the login flow to obtain JWT tokens
+
+ Store refresh tokens securely (httpOnly cookies recommended)
+
+
+ Add the Authorization header to all authenticated requests
+
+
+
+
+ Why Choose TrustKit?
+
+
+
+ Production-ready design: Built with patterns
+ inspired by real-world SaaS systems
+
+
+ High Performance: Redis caching layer reduces
+ database load
+
+
+ Flexible: Custom roles and permissions for
+ a wide range of use cases
+
+
+ Secure by design: JWT-based authentication,
+ strong password flows, and rate limiting
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Docs;
diff --git a/frontend/src/components/homePages/messages.jsx b/frontend/src/components/homePages/messages.jsx
new file mode 100644
index 0000000..457d98d
--- /dev/null
+++ b/frontend/src/components/homePages/messages.jsx
@@ -0,0 +1,388 @@
+import React, { useState, useEffect } from "react";
+import { FiCheck, FiX } from "react-icons/fi";
+import { ListMessages, ApproveMessage, RejectMessage } from "../services/messages";
+
+const Messages = () => {
+ const [requests, setRequests] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [searchQuery, setSearchQuery] = useState("");
+ const [statusFilter, setStatusFilter] = useState("All");
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalMessages, setTotalMessages] = useState(0);
+
+ // Fetch messages from API
+ useEffect(() => {
+ fetchMessages();
+ }, [currentPage, rowsPerPage, statusFilter]);
+
+ const fetchMessages = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ // Convert status filter to API format
+ let apiStatus = "";
+ if (statusFilter === "Pending") {
+ apiStatus = "pending";
+ } else if (statusFilter === "Approved") {
+ apiStatus = "approved";
+ } else if (statusFilter === "Rejected") {
+ apiStatus = "rejected";
+ }
+
+ const response = await ListMessages(currentPage, rowsPerPage, apiStatus);
+
+ // Extract data from response
+ const messageData = response.data?.data || [];
+ const paginationData = response.data?.pagination || {};
+
+ // Transform API response to match component structure
+ const transformedMessages = messageData.map((msg) => ({
+ id: msg.message_id,
+ email: msg.email || msg.user_email,
+ currentRole: msg.current_role || msg.currentRole,
+ requestedRole: msg.requested_role || msg.requestedRole,
+ status: msg.status.charAt(0).toUpperCase() + msg.status.slice(1),
+ requestedAt: msg.request_at || msg.requested_at || msg.requestedAt || msg.created_at,
+ }));
+
+ setRequests(transformedMessages);
+ setTotalMessages(paginationData.total_items || transformedMessages.length);
+ } catch (err) {
+ console.error("Error fetching messages:", err);
+ setError("Failed to load messages. Please try again.");
+ setRequests([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleApprove = async (requestId) => {
+ try {
+ // Optimistically update UI
+ setRequests(
+ requests.map((request) =>
+ request.id === requestId
+ ? { ...request, status: "Approved" }
+ : request
+ )
+ );
+
+ // Call API
+ await ApproveMessage(requestId);
+ } catch (err) {
+ console.error("Error approving message:", err);
+ // Revert on error
+ await fetchMessages();
+ alert("Failed to approve request. Please try again.");
+ }
+ };
+
+ const handleReject = async (requestId) => {
+ try {
+ // Optimistically update UI
+ setRequests(
+ requests.map((request) =>
+ request.id === requestId
+ ? { ...request, status: "Rejected" }
+ : request
+ )
+ );
+
+ // Call API
+ await RejectMessage(requestId);
+ } catch (err) {
+ console.error("Error rejecting message:", err);
+ // Revert on error
+ await fetchMessages();
+ alert("Failed to reject request. Please try again.");
+ }
+ };
+
+ // Client-side search filtering
+ const filteredRequests = requests.filter((request) => {
+ if (searchQuery === "") return true;
+ const matchesSearch =
+ request.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ request.requestedRole.toLowerCase().includes(searchQuery.toLowerCase());
+ return matchesSearch;
+ });
+
+ const totalPages = Math.ceil(totalMessages / rowsPerPage);
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case "Pending":
+ return "text-yellow-900 bg-yellow-200";
+ case "Approved":
+ return "text-green-900 bg-green-200";
+ case "Rejected":
+ return "text-red-900 bg-red-200";
+ default:
+ return "text-gray-900 bg-gray-200";
+ }
+ };
+
+ return (
+
+
+
+ {/* Header */}
+
+
+ Messages
+
+
+
+ {/* Filters and Search */}
+
+
+
+
{
+ setRowsPerPage(Number(e.target.value));
+ setCurrentPage(1);
+ }}
+ >
+ 5 per page
+ 10 per page
+ 20 per page
+
+
+
+
+
{
+ setStatusFilter(e.target.value);
+ setCurrentPage(1);
+ }}
+ >
+ All Status
+ Pending
+ Approved
+ Rejected
+
+
+
+
+
+
+
+
+
+
+
setSearchQuery(e.target.value)}
+ />
+
+
+
+ {/* Requests Table */}
+
+
+
+
+
+
+ User
+
+
+ Current Role
+
+
+ Requested Role
+
+
+ Status
+
+
+ Requested At
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+
+
+
Loading requests...
+
+
+
+ ) : error ? (
+
+
+
+
+
+ ) : filteredRequests.length > 0 ? (
+ filteredRequests.map((request) => (
+
+
+
+
+
+ {request.email.charAt(0).toUpperCase()}
+
+
+
+
+
+
+
+ {request.currentRole}
+
+
+
+
+ {request.requestedRole}
+
+
+
+
+
+ {request.status}
+
+
+
+
+ {new Date(request.requestedAt).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ })}
+
+
+
+ {request.status === "Pending" ? (
+
+ handleApprove(request.id)}
+ className="flex items-center text-sm text-green-600 hover:text-green-800 transition-colors cursor-pointer"
+ >
+
+ Approve
+
+ handleReject(request.id)}
+ className="flex items-center text-sm text-red-600 hover:text-red-800 transition-colors cursor-pointer"
+ >
+
+ Reject
+
+
+ ) : (
+ —
+ )}
+
+
+ ))
+ ) : (
+
+
+ No requests found matching your criteria
+
+
+ )}
+
+
+
+ {/* Pagination */}
+
+
+ Showing {Math.min((currentPage - 1) * rowsPerPage + 1, totalMessages)} to{" "}
+ {Math.min(currentPage * rowsPerPage, totalMessages)} of{" "}
+ {totalMessages} requests
+
+
+ setCurrentPage((prev) => Math.max(prev - 1, 1))}
+ disabled={currentPage === 1}
+ className={`text-sm py-2 px-4 rounded-l cursor-pointer ${
+ currentPage === 1
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Prev
+
+ setCurrentPage((prev) => Math.min(prev + 1, totalPages))}
+ disabled={currentPage === totalPages || totalPages === 0}
+ className={`text-sm py-2 px-4 rounded-r cursor-pointer ${
+ currentPage === totalPages || totalPages === 0
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Next
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Messages;
\ No newline at end of file
diff --git a/frontend/src/components/homePages/profile.jsx b/frontend/src/components/homePages/profile.jsx
new file mode 100644
index 0000000..628da9a
--- /dev/null
+++ b/frontend/src/components/homePages/profile.jsx
@@ -0,0 +1,417 @@
+import React, { useEffect, useState } from "react";
+import { CgOrganisation } from "react-icons/cg";
+import { GetProfile, DeleteProfile } from "../services/profile";
+import { useNavigate } from "react-router-dom";
+import {
+ FiMail,
+ FiUser,
+ FiLock,
+ FiRefreshCw,
+ FiTrash2,
+ FiEdit2,
+ FiCheck,
+ FiEye,
+ FiEyeOff,
+ FiX,
+} from "react-icons/fi";
+
+const Profile = () => {
+ const navigate = useNavigate();
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
+ const [showPassword, setShowPassword] = useState(false);
+ const [password, setPassword] = useState("");
+ const [deletingAccount, setDeletingAccount] = useState(false);
+ const [userDetails, setUserDetails] = useState({
+ name: "",
+ email: "",
+ organization: "",
+ });
+ const [passwordRequirements, setPasswordRequirements] = useState({
+ hasMinLength: false,
+ hasUppercase: false,
+ hasLowercase: false,
+ hasNumber: false,
+ hasSpecialChar: false,
+ });
+
+ const checkPasswordRequirements = (password) => {
+ const requirements = {
+ hasMinLength: password.length >= 8,
+ hasUppercase: /[A-Z]/.test(password),
+ hasLowercase: /[a-z]/.test(password),
+ hasNumber: /[0-9]/.test(password),
+ hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(password),
+ };
+ setPasswordRequirements(requirements);
+ return Object.values(requirements).every((req) => req);
+ };
+
+ const handlePasswordChange = (e) => {
+ const newPassword = e.target.value;
+ setPassword(newPassword);
+ checkPasswordRequirements(newPassword);
+ };
+
+ const isPasswordValid = Object.values(passwordRequirements).every(
+ (req) => req
+ );
+
+ useEffect(() => {
+ const fetchProfile = async () => {
+ const response = await GetProfile();
+ setUserDetails({
+ name: response.data.name,
+ email: response.data.email,
+ organization: response.data.organisation
+ })
+ console.log(response);
+ };
+ fetchProfile();
+ }, []);
+
+ // Handle account deletion
+ const handleDeleteAccount = async () => {
+ try {
+ setDeletingAccount(true);
+
+ // Call API to delete account
+ const response = await DeleteProfile();
+ console.log("Account deleted successfully:", response);
+
+ // Clear session storage
+ sessionStorage.clear();
+
+ // Close modal
+ setShowDeleteModal(false);
+
+ // Redirect to login/signup page
+ navigate("/");
+
+ } catch (error) {
+ console.error("Error deleting account:", error);
+ alert("Failed to delete account. Please try again.");
+ } finally {
+ setDeletingAccount(false);
+ }
+ };
+
+ return (
+
+ {showDeleteModal && (
+
+
+
+
+ Confirm Account Deletion
+
+ setShowDeleteModal(false)}
+ className="text-gray-400 hover:text-gray-500"
+ >
+
+
+
+
+
+ Are you sure you want to delete your account? This action cannot
+ be undone. All your data will be permanently removed.
+
+
+
+ setShowDeleteModal(false)}
+ className="mr-3 px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
+ disabled={deletingAccount}
+ >
+ Cancel
+
+
+ {deletingAccount ? "Deleting..." : "Delete Account"}
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ {userDetails.name.charAt(0)}
+
+
+
+
+
+ {userDetails.name}
+
+
{userDetails.organization}
+
+
+
+
+ {/* Account Information Section */}
+
+
+
+ Account Information
+
+
+
+
+ {/* Email Field */}
+
+
+
+ Email
+
+
+ Your primary email address
+
+
+
+
+
+ {/* Name Field */}
+
+
+
+ Full Name
+
+
Your display name
+
+
+
+
+ {/* Organization Field */}
+
+
+
+ Organization
+
+
+ Your company or team
+
+
+
+
+
+
+
+ {/* Password Section */}
+
+
+
+ Change Password
+
+
+
+
+
+
+
+ New Password
+
+
+
+
+
+
+
setShowPassword(!showPassword)}
+ >
+ {showPassword ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ Update
+
+
+
+ {/* Password Requirements */}
+
+
+ Password Requirements:
+
+
+
+
+ At least 8 characters
+
+
+
+ One uppercase letter
+
+
+
+ One lowercase letter
+
+
+
+ One number
+
+
+
+ One special character
+
+
+
+
+
+
+ {/* Danger Zone */}
+
+
+
Danger Zone
+
+
+
+
+
+ Delete Account
+
+
+ Once you delete your account, there is no going back. Please be
+ certain.
+
+
+
setShowDeleteModal(true)}
+ className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none cursor-pointer"
+ >
+
+ Delete Account
+
+
+
+
+
+ );
+};
+
+export default Profile;
diff --git a/frontend/src/components/homePages/roles.jsx b/frontend/src/components/homePages/roles.jsx
new file mode 100644
index 0000000..6a42f54
--- /dev/null
+++ b/frontend/src/components/homePages/roles.jsx
@@ -0,0 +1,1326 @@
+import React, { useState, useEffect, useRef } from "react";
+import { IoAddCircleSharp, IoTrashOutline } from "react-icons/io5";
+import {
+ FiMoreVertical,
+ FiEdit2,
+ FiXCircle,
+ FiCheckCircle,
+} from "react-icons/fi";
+import { IoMdClose } from "react-icons/io";
+import { ListRoles, GetRolePermissions, AddRoles, EnableRole, DisableRole, DeleteRole } from "../services/roles";
+
+const Roles = () => {
+ const [roles, setRoles] = useState([]);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [statusFilter, setStatusFilter] = useState("All");
+ const [roleTypeFilter, setRoleTypeFilter] = useState("All");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [currentPage, setCurrentPage] = useState(1);
+ const [showDropdown, setShowDropdown] = useState(null);
+ const [showAddModal, setShowAddModal] = useState(false);
+ const [showEditModal, setShowEditModal] = useState(false);
+ const [showPermissionsModal, setShowPermissionsModal] = useState(false);
+ const [selectedPermissions, setSelectedPermissions] = useState([]);
+ const [editingRole, setEditingRole] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [totalRoles, setTotalRoles] = useState(0);
+ const [loadingPermissions, setLoadingPermissions] = useState(false);
+ const [permissionsError, setPermissionsError] = useState(null);
+ const [selectedRole, setSelectedRole] = useState(null);
+ const [addingRole, setAddingRole] = useState(false);
+ const [addRoleError, setAddRoleError] = useState(null);
+ const [editablePermissions, setEditablePermissions] = useState(new Set());
+ const [editingRoleDetails, setEditingRoleDetails] = useState(false);
+ const [updatingRole, setUpdatingRole] = useState(false);
+ const [updateRoleError, setUpdateRoleError] = useState(null);
+ const [newRole, setNewRole] = useState({
+ name: "",
+ displayName: "",
+ description: "",
+ permissions: [{
+ route: "",
+ methods: [],
+ description: ""
+ }],
+ });
+ const dropdownRef = useRef(null);
+
+ const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
+
+ // Fetch roles from API
+ useEffect(() => {
+ fetchRoles();
+ }, [currentPage, rowsPerPage, statusFilter, roleTypeFilter]);
+
+ const fetchRoles = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ // Convert filters to API format (all lowercase)
+ // Status: "All" -> "", "Enabled" -> "active", "Disabled" -> "inactive"
+ let apiStatus = "";
+ if (statusFilter === "Enabled") {
+ apiStatus = "active";
+ } else if (statusFilter === "Disabled") {
+ apiStatus = "inactive";
+ } else {
+ apiStatus = ""; // "All" selected - empty parameter
+ }
+
+ // Role Type: "All" -> "", "Default" -> "system", "Custom" -> "custom"
+ let apiRoleType = "";
+ if (roleTypeFilter === "Default") {
+ apiRoleType = "system";
+ } else if (roleTypeFilter === "Custom") {
+ apiRoleType = "custom";
+ } else {
+ apiRoleType = ""; // "All" selected - empty parameter
+ }
+
+ const response = await ListRoles(apiStatus, currentPage, rowsPerPage, apiRoleType);
+
+ // Extract data from nested response structure
+ const roleData = response.data?.data || [];
+ const paginationData = response.data?.pagination || {};
+
+ // Transform API response to match the component's data structure
+ const transformedRoles = roleData.map((role) => ({
+ id: role.id,
+ name: role.string || role.name, // API uses 'string' field for role name
+ displayName: role.display_name || role.displayName,
+ description: role.description || "",
+ roleType: role.role_type || "default",
+ status: role.status ? "Enabled" : "Disabled",
+ permissions: role.permissions || [],
+ }));
+
+ setRoles(transformedRoles);
+ setTotalRoles(paginationData.total_items || transformedRoles.length);
+ } catch (err) {
+ console.error("Error fetching roles:", err);
+ setError("Failed to load roles. Please try again.");
+ setRoles([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const toggleDropdown = (roleId) => {
+ setShowDropdown(showDropdown === roleId ? null : roleId);
+ };
+
+ useEffect(() => {
+ function handleClickOutside(event) {
+ if (
+ showDropdown !== null &&
+ !event.target.closest(`[data-dropdown-id="${showDropdown}"]`)
+ ) {
+ setShowDropdown(null);
+ }
+ }
+ if (showDropdown !== null) {
+ document.addEventListener("mousedown", handleClickOutside);
+ }
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, [showDropdown]);
+
+ // Add a new permission field
+ const addPermissionField = (isEdit = false) => {
+ if (isEdit && editingRole) {
+ setEditingRole({
+ ...editingRole,
+ permissions: [...editingRole.permissions, { route: "", methods: [], description: "" }],
+ });
+ } else {
+ setNewRole({
+ ...newRole,
+ permissions: [...newRole.permissions, { route: "", methods: [], description: "" }]
+ });
+ }
+ };
+
+ // Remove a permission field
+ const removePermissionField = (index, isEdit = false) => {
+ if (isEdit && editingRole) {
+ const updatedPermissions = [...editingRole.permissions];
+ updatedPermissions.splice(index, 1);
+ setEditingRole({ ...editingRole, permissions: updatedPermissions });
+ } else {
+ const updatedPermissions = [...newRole.permissions];
+ updatedPermissions.splice(index, 1);
+ setNewRole({ ...newRole, permissions: updatedPermissions });
+ }
+ };
+
+ // Handle permission input changes
+ const handlePermissionChange = (index, field, value, isEdit = false) => {
+ if (isEdit && editingRole) {
+ const updatedPermissions = [...editingRole.permissions];
+ updatedPermissions[index] = {
+ ...updatedPermissions[index],
+ [field]: value
+ };
+ setEditingRole({ ...editingRole, permissions: updatedPermissions });
+ } else {
+ const updatedPermissions = [...newRole.permissions];
+ updatedPermissions[index] = {
+ ...updatedPermissions[index],
+ [field]: value
+ };
+ setNewRole({ ...newRole, permissions: updatedPermissions });
+ }
+ };
+
+ // Toggle HTTP method selection
+ const toggleMethod = (permIndex, method, isEdit = false) => {
+ if (isEdit && editingRole) {
+ const updatedPermissions = [...editingRole.permissions];
+ const currentMethods = updatedPermissions[permIndex].methods;
+
+ if (currentMethods.includes(method)) {
+ updatedPermissions[permIndex].methods = currentMethods.filter(m => m !== method);
+ } else {
+ updatedPermissions[permIndex].methods = [...currentMethods, method];
+ }
+
+ setEditingRole({ ...editingRole, permissions: updatedPermissions });
+ } else {
+ const updatedPermissions = [...newRole.permissions];
+ const currentMethods = updatedPermissions[permIndex].methods;
+
+ if (currentMethods.includes(method)) {
+ updatedPermissions[permIndex].methods = currentMethods.filter(m => m !== method);
+ } else {
+ updatedPermissions[permIndex].methods = [...currentMethods, method];
+ }
+
+ setNewRole({ ...newRole, permissions: updatedPermissions });
+ }
+ };
+
+ // Submit new role
+ const handleAddRole = async () => {
+ if (!newRole.name.trim() || !newRole.displayName.trim()) return;
+
+ const filteredPermissions = newRole.permissions.filter(
+ perm => perm.route.trim() && perm.methods.length > 0
+ );
+
+ if (filteredPermissions.length === 0) return;
+
+ try {
+ setAddingRole(true);
+ setAddRoleError(null);
+
+ // Call API to add role
+ await AddRoles(
+ newRole.name,
+ newRole.displayName,
+ newRole.description,
+ filteredPermissions
+ );
+
+ // Reset form
+ setNewRole({
+ name: "",
+ displayName: "",
+ description: "",
+ permissions: [{
+ route: "",
+ methods: [],
+ description: ""
+ }],
+ });
+
+ // Close modal
+ setShowAddModal(false);
+
+ // Refetch roles to update the list
+ await fetchRoles();
+
+ } catch (err) {
+ console.error("Error adding role:", err);
+ setAddRoleError("Failed to add role. Please try again.");
+ } finally {
+ setAddingRole(false);
+ }
+ };
+
+ const isSystemRole = (role) => {
+ // System roles have roleType "default"
+ return role.roleType === "default";
+ };
+
+ // Open edit modal - fetch permissions first
+ const openEditModal = async (role) => {
+ try {
+ setShowDropdown(null);
+ setEditingRole({ ...role });
+ setShowEditModal(true);
+ setEditablePermissions(new Set());
+ setEditingRoleDetails(false);
+ setUpdateRoleError(null);
+
+ // Fetch fresh permissions data
+ setLoadingPermissions(true);
+ const response = await GetRolePermissions(role.id);
+ const permissions = response.data?.permissions || [];
+
+ setEditingRole({
+ ...role,
+ permissions: permissions.length > 0 ? permissions : role.permissions
+ });
+ } catch (err) {
+ console.error("Error fetching permissions for edit:", err);
+ setUpdateRoleError("Failed to load permissions. Please try again.");
+ } finally {
+ setLoadingPermissions(false);
+ }
+ };
+
+ // Toggle permission editable state
+ const togglePermissionEditable = (index) => {
+ const newSet = new Set(editablePermissions);
+ if (newSet.has(index)) {
+ newSet.delete(index);
+ } else {
+ newSet.add(index);
+ }
+ setEditablePermissions(newSet);
+ };
+
+ // Submit edited role - smart update
+ const handleEditRole = async () => {
+ if (!editingRole.name.trim() || !editingRole.displayName.trim()) return;
+
+ const filteredPermissions = editingRole.permissions.filter(
+ perm => perm.route.trim() && perm.methods.length > 0
+ );
+
+ if (filteredPermissions.length === 0) return;
+
+ try {
+ setUpdatingRole(true);
+ setUpdateRoleError(null);
+
+ // Check what changed
+ const originalRole = roles.find(r => r.id === editingRole.id);
+ const roleDetailsChanged =
+ originalRole.name !== editingRole.name ||
+ originalRole.displayName !== editingRole.displayName ||
+ originalRole.description !== editingRole.description;
+
+ const permissionsChanged = editablePermissions.size > 0;
+
+ if (roleDetailsChanged || permissionsChanged) {
+ // TODO: Call UpdateRole API with only changed data
+ // For now, update optimistically
+ setRoles(
+ roles.map((role) =>
+ role.id === editingRole.id
+ ? { ...editingRole, permissions: filteredPermissions }
+ : role
+ )
+ );
+
+ console.log("Changes to update:", {
+ roleDetailsChanged,
+ permissionsChanged,
+ editedPermissionIndexes: Array.from(editablePermissions),
+ data: {
+ name: editingRole.name,
+ displayName: editingRole.displayName,
+ description: editingRole.description,
+ permissions: permissionsChanged ? filteredPermissions : null
+ }
+ });
+ }
+
+ setShowEditModal(false);
+ setEditingRole(null);
+ setEditablePermissions(new Set());
+ setEditingRoleDetails(false);
+ } catch (err) {
+ console.error("Error updating role:", err);
+ setUpdateRoleError("Failed to update role. Please try again.");
+ } finally {
+ setUpdatingRole(false);
+ }
+ };
+
+ // View permissions for a role
+ const viewRolePermissions = async (role) => {
+ try {
+ console.log('Role Details:', role);
+ setSelectedRole(role);
+ setShowPermissionsModal(true);
+ setLoadingPermissions(true);
+ setPermissionsError(null);
+
+ // Fetch permissions from API
+ const response = await GetRolePermissions(role.id);
+
+ // Extract permissions from response
+ const permissions = response.data?.permissions || [];
+
+ setSelectedPermissions(permissions);
+ } catch (err) {
+ console.error("Error fetching permissions:", err);
+ setPermissionsError("Failed to load permissions. Please try again.");
+ setSelectedPermissions([]);
+ } finally {
+ setLoadingPermissions(false);
+ }
+ };
+
+ const deleteRole = async (roleId) => {
+ try {
+ setShowDropdown(null);
+
+ // Optimistically remove from UI immediately
+ setRoles(roles.filter((role) => role.id !== roleId));
+
+ // Call API to delete role
+ await DeleteRole(roleId);
+
+ } catch (err) {
+ console.error("Error deleting role:", err);
+ // Refetch on error to restore the list
+ await fetchRoles();
+ alert("Failed to delete role. Please try again.");
+ }
+ };
+
+ const toggleRoleStatus = async (roleId) => {
+ try {
+ setShowDropdown(null);
+ const role = roles.find(r => r.id === roleId);
+ if (!role) return;
+ const updatedStatus = role.status === "Enabled" ? "Disabled" : "Enabled";
+ setRoles(
+ roles.map((r) =>
+ r.id === roleId
+ ? { ...r, status: updatedStatus }
+ : r
+ )
+ );
+ if (role.status === "Enabled") {
+ await DisableRole(roleId);
+ } else {
+ await EnableRole(roleId);
+ }
+
+ } catch (err) {
+ console.error("Error toggling role status:", err);
+ setRoles(
+ roles.map((r) =>
+ r.id === roleId
+ ? { ...r, status: role.status }
+ : r
+ )
+ );
+ alert("Failed to update role status. Please try again.");
+ }
+ };
+
+ // Client-side search filtering (searches within current page results)
+ const filteredRoles = roles.filter((role) => {
+ if (searchQuery === "") return true;
+ const matchesSearch = role.name
+ .toLowerCase()
+ .includes(searchQuery.toLowerCase()) ||
+ role.displayName.toLowerCase().includes(searchQuery.toLowerCase());
+ return matchesSearch;
+ });
+
+ const totalPages = Math.ceil(totalRoles / rowsPerPage);
+
+ return (
+
+ {/* Add Role Modal */}
+ {showAddModal && (
+
+
+
+
+ Add New Role
+
+ {
+ setShowAddModal(false);
+ setAddRoleError(null);
+ }}
+ className="text-gray-400 hover:text-gray-500 cursor-pointer"
+ disabled={addingRole}
+ >
+
+
+
+
+ {addRoleError && (
+
+ )}
+
+
+
+
+ Description
+
+
+
+
+
+ Permissions *
+
+
+ {newRole.permissions.map((permission, index) => (
+
+
+ Permission #{index + 1}
+ {newRole.permissions.length > 1 && (
+ removePermissionField(index)}
+ className="text-red-500 hover:text-red-700 text-sm flex items-center cursor-pointer"
+ >
+
+ Remove
+
+ )}
+
+
+
+
+ Route Path *
+ handlePermissionChange(index, 'route', e.target.value)}
+ placeholder="Enter route path (e.g., /api/users)"
+ />
+
+
+
+
HTTP Methods *
+
+ {httpMethods.map((method) => (
+ toggleMethod(index, method)}
+ className={`px-2 py-1 text-xs rounded-md border cursor-pointer ${
+ permission.methods.includes(method)
+ ? 'bg-stone-800 text-white border-stone-800'
+ : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-100'
+ }`}
+ >
+ {method}
+
+ ))}
+
+
+
+
+ Description
+ handlePermissionChange(index, 'description', e.target.value)}
+ placeholder="Describe what this permission allows"
+ />
+
+
+
+ ))}
+
+
addPermissionField(false)}
+ className="mt-2 text-sm text-stone-600 hover:text-stone-800 flex items-center cursor-pointer"
+ >
+ Add another permission
+
+
+
+
+
+
{
+ setShowAddModal(false);
+ setAddRoleError(null);
+ }}
+ disabled={addingRole}
+ className="mr-3 px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ >
+ Cancel
+
+
!p.route.trim() || p.methods.length === 0)
+ }
+ className="px-4 py-2 text-sm text-white bg-stone-800 border border-transparent rounded-md hover:bg-stone-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
+ >
+ {addingRole ? (
+ <>
+
+ Adding...
+ >
+ ) : (
+ 'Add Role'
+ )}
+
+
+
+
+ )}
+
+ {/* Edit Role Modal */}
+ {showEditModal && editingRole && (
+
+
+
+
Edit Role
+ {
+ setShowEditModal(false);
+ setEditingRole(null);
+ setEditablePermissions(new Set());
+ setEditingRoleDetails(false);
+ setUpdateRoleError(null);
+ }}
+ disabled={updatingRole}
+ className="text-gray-400 hover:text-gray-500 cursor-pointer disabled:opacity-50"
+ >
+
+
+
+
+ {updateRoleError && (
+
+ )}
+
+ {loadingPermissions ? (
+
+
+
Loading role details...
+
+ ) : (
+ <>
+ {/* Role Details Section with Toggle */}
+
+
+
Role Details
+
+ setEditingRoleDetails(e.target.checked)}
+ className="mr-2 h-4 w-4 text-stone-600 focus:ring-stone-500 border-gray-300 rounded"
+ />
+ Enable editing
+
+
+
+
+
+
+
+ Description
+
+
+
+
+ {/* Permissions Section */}
+
+
+
+ Permissions *
+
+
+ Check the box to edit a permission
+
+
+
+ {editingRole.permissions && editingRole.permissions.length > 0 ? (
+ editingRole.permissions.map((permission, index) => {
+ const isEditable = editablePermissions.has(index);
+ return (
+
+
+
+ Permission #{index + 1}
+
+
+
+ togglePermissionEditable(index)}
+ disabled={updatingRole}
+ className="mr-2 h-4 w-4 text-stone-600 focus:ring-stone-500 border-gray-300 rounded"
+ />
+
+ {isEditable ? 'Editing' : 'Edit'}
+
+
+ {editingRole.permissions.length > 1 && isEditable && (
+ removePermissionField(index, true)}
+ disabled={updatingRole}
+ className="text-red-500 hover:text-red-700 text-sm flex items-center cursor-pointer disabled:opacity-50"
+ >
+
+ Remove
+
+ )}
+
+
+
+
+
+ Route Path *
+ handlePermissionChange(index, 'route', e.target.value, true)}
+ placeholder="Enter route path"
+ disabled={!isEditable || updatingRole}
+ />
+
+
+
+
HTTP Methods *
+
+ {httpMethods.map((method) => (
+ toggleMethod(index, method, true)}
+ disabled={!isEditable || updatingRole}
+ className={`px-2 py-1 text-xs rounded-md border cursor-pointer transition-colors ${
+ permission.methods.includes(method)
+ ? 'bg-stone-800 text-white border-stone-800'
+ : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-100'
+ } ${
+ !isEditable || updatingRole ? 'opacity-50 cursor-not-allowed' : ''
+ }`}
+ >
+ {method}
+
+ ))}
+
+
+
+
+ Description
+ handlePermissionChange(index, 'description', e.target.value, true)}
+ placeholder="Describe what this permission allows"
+ disabled={!isEditable || updatingRole}
+ />
+
+
+
+ );
+ })
+ ) : (
+
+ No permissions found. Add a new permission below.
+
+ )}
+
+
addPermissionField(true)}
+ disabled={updatingRole}
+ className="mt-2 text-sm text-stone-600 hover:text-stone-800 flex items-center cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ >
+ Add another permission
+
+
+
+ >
+ )}
+
+
+
{
+ setShowEditModal(false);
+ setEditingRole(null);
+ setEditablePermissions(new Set());
+ setEditingRoleDetails(false);
+ setUpdateRoleError(null);
+ }}
+ disabled={updatingRole}
+ className="mr-3 px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ >
+ Cancel
+
+
!p.route.trim() || p.methods.length === 0)
+ }
+ className="px-4 py-2 text-sm text-white bg-stone-800 border border-transparent rounded-md hover:bg-stone-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
+ >
+ {updatingRole ? (
+ <>
+
+ Updating...
+ >
+ ) : (
+ 'Save Changes'
+ )}
+
+
+
+
+ )}
+
+ {/* View Permissions Modal */}
+ {showPermissionsModal && (
+
+
+
+
+
Role Permissions
+ {selectedRole && (
+
+ {selectedRole.displayName}
+ •
+ {selectedRole.name}
+
+ )}
+
+
{
+ setShowPermissionsModal(false);
+ setSelectedRole(null);
+ setSelectedPermissions([]);
+ setPermissionsError(null);
+ }}
+ className="text-gray-400 hover:text-gray-500 cursor-pointer"
+ >
+
+
+
+
+ {loadingPermissions ? (
+
+
+
Loading permissions...
+
+ ) : permissionsError ? (
+
+
{permissionsError}
+
selectedRole && viewRolePermissions(selectedRole)}
+ className="mt-2 text-sm text-blue-600 hover:text-blue-800 underline"
+ >
+ Retry
+
+
+ ) : selectedPermissions.length > 0 ? (
+
+ {selectedPermissions.map((permission, index) => (
+
+
+ Route:
+
+ {permission.route}
+
+
+
+
+
Methods:
+
+ {permission.methods.map((method) => (
+
+ {method}
+
+ ))}
+
+
+
+ {permission.description && (
+
+
Description:
+
{permission.description}
+
+ )}
+
+ ))}
+
+ ) : (
+
+ No permissions found for this role
+
+ )}
+
+
+ setShowPermissionsModal(false)}
+ className="px-4 py-2 text-sm text-white bg-stone-800 border border-transparent rounded-md hover:bg-stone-700 cursor-pointer"
+ >
+ Close
+
+
+
+
+ )}
+
+ {/* Main Content */}
+
+
+
+
Roles
+ setShowAddModal(true)}
+ className="flex items-center gap-2 justify-center border align-middle select-none font-sans font-medium text-center duration-300 ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm hover:shadow-md bg-stone-800 hover:bg-stone-700 border-stone-900 text-stone-50 rounded-lg transition antialiased cursor-pointer"
+ >
+
+ Add Role
+
+
+
+ {/* Filters and Search */}
+
+
+
+
{
+ setRowsPerPage(Number(e.target.value));
+ setCurrentPage(1); // Reset to first page on page size change
+ }}
+ >
+ 5 per page
+ 10 per page
+ 20 per page
+
+
+
+
+
{
+ setStatusFilter(e.target.value);
+ setCurrentPage(1); // Reset to first page on filter change
+ }}
+ >
+ All Status
+ Enabled
+ Disabled
+
+
+
+
+
{
+ setRoleTypeFilter(e.target.value);
+ setCurrentPage(1); // Reset to first page on filter change
+ }}
+ >
+ All Types
+ System Roles
+ Custom Roles
+
+
+
+
+
+
+
+
+
+
+
setSearchQuery(e.target.value)}
+ />
+
+
+
+ {/* Roles Table */}
+
+
+
+
+
+
+ Name
+
+
+ Display Name
+
+
+ Role Type
+
+
+ Status
+
+
+ Permissions
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+
+
+
+ ) : error ? (
+
+
+
+
+
+ ) : filteredRoles.length > 0 ? (
+ filteredRoles.map((role) => (
+
+
+
+
+
+
+ {role.displayName}
+
+
+
+
+ {role.roleType === "default"
+ ? "System Role"
+ : "Custom Role"}
+
+
+
+
+
+ {role.status}
+
+
+
+ viewRolePermissions(role)}
+ className="inline-flex items-center justify-center cursor-pointer border align-middle select-none font-sans font-medium text-center transition-all ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm bg-transparent relative text-stone-700 hover:text-stone-700 border-stone-500 hover:bg-transparent duration-150 hover:border-stone-600 rounded-lg hover:opacity-60 hover:shadow-none"
+ >
+ View Permissions
+
+
+
+
+ {!isSystemRole(role) && (
+
toggleDropdown(role.id)}
+ className="text-gray-500 hover:text-gray-700 focus:outline-none"
+ >
+
+
+ )}
+ {showDropdown === role.id && (
+
+
+ openEditModal(role)}
+ className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
+ >
+
+ Edit
+
+ toggleRoleStatus(role.id)}
+ className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
+ >
+ {role.status === "Enabled" ? (
+
+ ) : (
+
+ )}
+ {role.status === "Enabled"
+ ? "Disable"
+ : "Enable"}
+
+ deleteRole(role.id)}
+ className="flex items-center px-4 py-2 text-sm text-red-600 hover:bg-gray-100 w-full text-left"
+ >
+
+ Delete
+
+
+
+ )}
+
+
+
+ ))
+ ) : (
+
+
+ No roles found matching your criteria
+
+
+ )}
+
+
+
+ {/* Pagination */}
+
+
+ Showing {Math.min((currentPage - 1) * rowsPerPage + 1, totalRoles)} to{" "}
+ {Math.min(currentPage * rowsPerPage, totalRoles)} of{" "}
+ {totalRoles} roles
+
+
+
+ setCurrentPage((prev) => Math.max(prev - 1, 1))
+ }
+ disabled={currentPage === 1}
+ className={`text-sm py-2 px-4 rounded-l cursor-pointer ${
+ currentPage === 1
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Prev
+
+
+ setCurrentPage((prev) => Math.min(prev + 1, totalPages))
+ }
+ disabled={currentPage === totalPages || totalPages === 0}
+ className={`text-sm py-2 px-4 rounded-r cursor-pointer ${
+ currentPage === totalPages || totalPages === 0
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Next
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Roles;
\ No newline at end of file
diff --git a/frontend/src/components/homePages/temp.jsx b/frontend/src/components/homePages/temp.jsx
new file mode 100644
index 0000000..717a446
--- /dev/null
+++ b/frontend/src/components/homePages/temp.jsx
@@ -0,0 +1,299 @@
+import React, { useState, useEffect, useRef } from "react";
+import { IoAddCircleSharp, IoTrashOutline, IoClose } from "react-icons/io5";
+import { FiMoreVertical, FiEdit2 } from "react-icons/fi";
+
+const Temp = () => {
+ const [roles, setRoles] = useState([
+ {
+ id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
+ name: "Admin",
+ roleType: "default",
+ status: "Enabled",
+ routes: ["/admin/logs", "/admin/views"],
+ },
+ {
+ id: "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
+ name: "Moderator",
+ roleType: "default",
+ status: "Enabled",
+ routes: ["/admin/logs", "/admin/views"],
+ },
+ {
+ id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
+ name: "User",
+ roleType: "default",
+ status: "Enabled",
+ routes: ["/admin/logs", "/admin/views"],
+ },
+ {
+ id: "550e8400-e29b-41d4-a716-446655440000",
+ name: "Guest",
+ roleType: "default",
+ status: "Enabled",
+ routes: ["/customer/logs", "/customer/views"],
+ },
+ {
+ id: "7d75aee6-0bd7-4c59-a6d5-c31c51984246",
+ name: "Manager",
+ roleType: "custom",
+ status: "Enabled",
+ routes: ["/customer/logs", "/customer/views"],
+ },
+ ]);
+
+ // State for modals and form
+ const [showAddModal, setShowAddModal] = useState(false);
+ const [showRoutesModal, setShowRoutesModal] = useState(false);
+ const [selectedRoutes, setSelectedRoutes] = useState([]);
+ const [newRole, setNewRole] = useState({
+ name: "",
+ routes: [""] // Start with one empty route
+ });
+
+ // Other existing state
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [statusFilter, setStatusFilter] = useState("All");
+ const [roleTypeFilter, setRoleTypeFilter] = useState("All");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [currentPage, setCurrentPage] = useState(1);
+ const [showDropdown, setShowDropdown] = useState(null);
+ const dropdownRef = useRef(null);
+
+ // Toggle dropdown with outside click handler
+ const toggleDropdown = (roleId) => {
+ setShowDropdown(showDropdown === roleId ? null : roleId);
+ };
+
+ useEffect(() => {
+ function handleClickOutside(event) {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ setShowDropdown(null);
+ }
+ }
+ if (showDropdown) {
+ document.addEventListener("mousedown", handleClickOutside);
+ }
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, [showDropdown]);
+
+ // Add a new route input field
+ const addRouteField = () => {
+ setNewRole({ ...newRole, routes: [...newRole.routes, ""] });
+ };
+
+ // Remove a route input field
+ const removeRouteField = (index) => {
+ const updatedRoutes = [...newRole.routes];
+ updatedRoutes.splice(index, 1);
+ setNewRole({ ...newRole, routes: updatedRoutes });
+ };
+
+ // Handle route input change
+ const handleRouteChange = (index, value) => {
+ const updatedRoutes = [...newRole.routes];
+ updatedRoutes[index] = value;
+ setNewRole({ ...newRole, routes: updatedRoutes });
+ };
+
+ // Submit new role
+ const handleAddRole = () => {
+ if (!newRole.name.trim()) return;
+
+ const filteredRoutes = newRole.routes.filter(route => route.trim());
+ if (filteredRoutes.length === 0) return;
+
+ const newRoleObj = {
+ id: crypto.randomUUID(),
+ name: newRole.name,
+ roleType: "custom",
+ status: "Enabled",
+ routes: filteredRoutes
+ };
+
+ setRoles([...roles, newRoleObj]);
+ setNewRole({ name: "", routes: [""] });
+ setShowAddModal(false);
+ };
+
+ // View routes for a role
+ const viewRoleRoutes = (routes) => {
+ setSelectedRoutes(routes);
+ setShowRoutesModal(true);
+ };
+
+ // Delete role
+ const deleteRole = (roleId) => {
+ setRoles(roles.filter(role => role.id !== roleId));
+ setShowDropdown(null);
+ };
+
+ // Filtering logic
+ const filteredRoles = roles.filter(role => {
+ const matchesStatus = statusFilter === "All" || role.status === statusFilter;
+ const matchesRoleType = roleTypeFilter === "All" || role.roleType === roleTypeFilter.toLowerCase();
+ const matchesSearch = role.name.toLowerCase().includes(searchQuery.toLowerCase());
+ return matchesStatus && matchesRoleType && matchesSearch;
+ });
+
+ const paginatedRoles = filteredRoles.slice(
+ (currentPage - 1) * rowsPerPage,
+ currentPage * rowsPerPage
+ );
+
+ const totalPages = Math.ceil(filteredRoles.length / rowsPerPage);
+
+ return (
+
+ {/* Add Role Modal */}
+ {showAddModal && (
+
+
+
+
Add New Role
+ setShowAddModal(false)}
+ className="text-gray-400 hover:text-gray-500"
+ >
+
+
+
+
+
+
+ Role Name
+
+ setNewRole({ ...newRole, name: e.target.value })}
+ placeholder="Enter role name"
+ />
+
+
+
+ Routes
+
+ {newRole.routes.map((route, index) => (
+
+ handleRouteChange(index, e.target.value)}
+ placeholder={`Route path ${index + 1}`}
+ />
+ {newRole.routes.length > 1 && (
+ removeRouteField(index)}
+ className="ml-2 text-red-500 hover:text-red-700"
+ >
+
+
+ )}
+
+ ))}
+
+ Add another route
+
+
+
+
+ setShowAddModal(false)}
+ className="mr-3 px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
+ >
+ Cancel
+
+ !r.trim())}
+ className="px-4 py-2 text-sm text-white bg-stone-800 border border-transparent rounded-md hover:bg-stone-700 disabled:opacity-50 disabled:cursor-not-allowed"
+ >
+ Add Role
+
+
+
+
+ )}
+
+ {/* View Routes Modal */}
+ {showRoutesModal && (
+
+
+
+
Role Routes
+ setShowRoutesModal(false)}
+ className="text-gray-400 hover:text-gray-500"
+ >
+
+
+
+
+
+ {selectedRoutes.map((route, index) => (
+
+
+ {route}
+
+
+ ))}
+
+
+
+ setShowRoutesModal(false)}
+ className="px-4 py-2 text-sm text-white bg-stone-800 border border-transparent rounded-md hover:bg-stone-700"
+ >
+ Close
+
+
+
+
+ )}
+
+ {/* Main Content */}
+
+
+
+
Roles
+ setShowAddModal(true)}
+ className="flex items-center gap-2 justify-center border align-middle select-none font-sans font-medium text-center duration-300 ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm hover:shadow-md bg-stone-800 hover:bg-stone-700 border-stone-900 text-stone-50 rounded-lg transition antialiased cursor-pointer"
+ >
+
+ Add Role
+
+
+
+ {/* Rest of your existing code (filters, table, pagination) */}
+ {/* Only change needed is to update the "View Routes" button to call viewRoleRoutes */}
+
+ viewRoleRoutes(role.routes)}
+ className="inline-flex items-center justify-center cursor-pointer border align-middle select-none font-sans font-medium text-center transition-all ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm bg-transparent relative text-stone-700 hover:text-stone-700 border-stone-500 hover:bg-transparent duration-150 hover:border-stone-600 rounded-lg hover:opacity-60 hover:shadow-none"
+ >
+ View Routes
+
+
+
+ {/* ... rest of your existing component code ... */}
+
+
+
+ );
+};
+
+export default Temp;
\ No newline at end of file
diff --git a/frontend/src/components/homePages/tokens.jsx b/frontend/src/components/homePages/tokens.jsx
new file mode 100644
index 0000000..3ceeb0c
--- /dev/null
+++ b/frontend/src/components/homePages/tokens.jsx
@@ -0,0 +1,582 @@
+import React, { useEffect, useState } from "react";
+import { IoAddCircleSharp, IoClose } from "react-icons/io5";
+import { FiCopy } from "react-icons/fi";
+import { MdOutlineBlock } from "react-icons/md";
+import { ListTokensPaginted, CreateTokens, RevokeToken, ListTokensWithStatus } from "../services/tokens";
+const Tokens = () => {
+ const [tokens, setTokens] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ // Initialize pagination from sessionStorage or use defaults
+ const [pagination, setPagination] = useState(() => {
+ const savedPagination = sessionStorage.getItem('tokensPagination');
+ if (savedPagination) {
+ try {
+ const parsed = JSON.parse(savedPagination);
+ return {
+ page: parsed.page || 1,
+ page_size: parsed.page_size || 5,
+ total_pages: 1,
+ total_items: 0,
+ has_next: false,
+ has_prev: false,
+ };
+ } catch (e) {
+ console.error('Error parsing saved pagination:', e);
+ }
+ }
+ return {
+ page: 1,
+ page_size: 5,
+ total_pages: 1,
+ total_items: 0,
+ has_next: false,
+ has_prev: false,
+ };
+ });
+
+ // Modal states
+ const [showAddModal, setShowAddModal] = useState(false);
+ const [showRevokeModal, setShowRevokeModal] = useState(false);
+ const [tokenToRevoke, setTokenToRevoke] = useState(null);
+ const [newToken, setNewToken] = useState({
+ name: "",
+ expiry: "",
+ });
+ const [addingToken, setAddingToken] = useState(false);
+ const [revokingToken, setRevokingToken] = useState(false);
+
+ // Other existing state
+ const [statusFilter, setStatusFilter] = useState("All");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [copiedTokenId, setCopiedTokenId] = useState(null);
+
+ // Save pagination to sessionStorage whenever it changes
+ useEffect(() => {
+ sessionStorage.setItem('tokensPagination', JSON.stringify({
+ page: pagination.page,
+ page_size: pagination.page_size,
+ }));
+ }, [pagination.page, pagination.page_size]);
+
+ // Function to reload page (will restore pagination from sessionStorage)
+ const pageReload = () => {
+ window.location.reload();
+ };
+
+ // Helper function to format date
+ const formatDate = (dateString) => {
+ return new Date(dateString).toLocaleDateString("en-US", {
+ month: "short",
+ day: "2-digit",
+ year: "numeric",
+ });
+ };
+
+ // Transform API response to match UI structure
+ const transformApiTokens = (apiTokens) => {
+ return apiTokens.map((token) => ({
+ id: token.token_id,
+ name: token.name,
+ created: formatDate(token.created_at),
+ expiry: formatDate(token.expiry_at),
+ status: token.status ? "Active" : "Revoked",
+ }));
+ };
+
+ useEffect(() => {
+ const fetchTokens = async () => {
+ try {
+ setLoading(true);
+
+ let response;
+ // Use different API based on status filter
+ if (statusFilter === "Active" || statusFilter === "Revoked") {
+ // Convert "Active" to "active", "Revoked" to "revoked" for API
+ const statusValue = statusFilter.toLowerCase();
+ response = await ListTokensWithStatus(statusValue, pagination.page, pagination.page_size);
+ } else {
+ // For "All" or any other value, use regular paginated list
+ response = await ListTokensPaginted(pagination.page, pagination.page_size);
+ }
+
+ console.log("API Response:", response);
+
+ // Extract tokens from nested response structure
+ const apiTokens = response.data?.data || response.data?.data || [];
+ const paginationData = response?.data?.pagination || {};
+
+ const transformedTokens = transformApiTokens(apiTokens);
+
+ setTokens(transformedTokens);
+ setPagination(prev => ({
+ ...prev,
+ total_pages: paginationData.total_pages ?? 1,
+ total_items: paginationData.total_items ?? 0,
+ has_next: paginationData.has_next ?? false,
+ has_prev: paginationData.has_prev ?? false,
+ }));
+ } catch (error) {
+ console.error('Error fetching tokens:', error);
+ // Keep empty array on error or set fallback data
+ setTokens([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTokens();
+ }, [pagination.page, pagination.page_size, statusFilter]);
+
+ // Copy token ID
+ const copyTokenId = (tokenId) => {
+ navigator.clipboard.writeText(tokenId);
+ setCopiedTokenId(tokenId);
+ setTimeout(() => setCopiedTokenId(null), 2000);
+ };
+
+ // Prepare to revoke token
+ const prepareRevokeToken = (tokenId) => {
+ setTokenToRevoke(tokenId);
+ setShowRevokeModal(true);
+ };
+
+ // Confirm revoke token
+ const confirmRevokeToken = async () => {
+ if (!tokenToRevoke) return;
+
+ try {
+ setRevokingToken(true);
+
+ // Call API to revoke token
+ const response = await RevokeToken(tokenToRevoke);
+ console.log("Token revoked successfully:", response);
+
+ // Reload page to refresh data (pagination will be restored from sessionStorage)
+ pageReload();
+
+ } catch (error) {
+ console.error("Error revoking token:", error);
+ setRevokingToken(false);
+ }
+ };
+
+ // Add new token
+ const addToken = async () => {
+ if (!newToken.name.trim() || !newToken.expiry) return;
+
+ try {
+ setAddingToken(true);
+
+ // Convert date to YYYY-MM-DD format for API
+ const expiryDate = new Date(newToken.expiry).toISOString().split('T')[0];
+
+ // Call API to create token
+ const response = await CreateTokens(newToken.name, expiryDate);
+ console.log("Token created successfully:", response);
+
+ // Reload page to refresh data (pagination will be restored from sessionStorage)
+ pageReload();
+
+ } catch (error) {
+ console.error("Error creating token:", error);
+ setAddingToken(false);
+ }
+ };
+
+ // Filter tokens (client-side search only, status is handled by API)
+ const filteredTokens = tokens.filter((token) => {
+ const matchesSearch =
+ token.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ token.name.toLowerCase().includes(searchQuery.toLowerCase());
+ return matchesSearch;
+ });
+
+ // Use filtered tokens for display (server already handles pagination and status filtering)
+ const displayTokens = filteredTokens;
+
+ return (
+
+ {/* Add Token Modal */}
+ {showAddModal && (
+
+
+
+
+ Create New Token
+
+ setShowAddModal(false)}
+ className="text-gray-400 hover:text-gray-500"
+ >
+
+
+
+
+
+ setShowAddModal(false)}
+ className="mr-3 px-4 cursor-pointer py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
+ >
+ Cancel
+
+
+ {addingToken ? "Creating..." : "Create Token"}
+
+
+
+
+ )}
+
+ {/* Revoke Confirmation Modal */}
+ {showRevokeModal && (
+
+
+
+
+ Revoke Token
+
+ setShowRevokeModal(false)}
+ className="text-gray-400 cursor-pointer hover:text-gray-500"
+ >
+
+
+
+
+
+ Are you sure you want to revoke this token? This action cannot
+ be undone.
+
+
+
+ setShowRevokeModal(false)}
+ className="mr-3 px-4 py-2 text-sm text-gray-700 cursor-pointer bg-white border border-gray-300 rounded-md hover:bg-gray-50"
+ >
+ Cancel
+
+
+ {revokingToken ? "Revoking..." : "Revoke Token"}
+
+
+
+
+ )}
+
+ {/* Main Content */}
+
+
+
+
Tokens
+ setShowAddModal(true)}
+ className="flex items-center gap-2 justify-center border align-middle select-none font-sans font-medium text-center duration-300 ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm hover:shadow-md bg-stone-800 hover:bg-stone-700 border-stone-900 text-stone-50 rounded-lg transition antialiased cursor-pointer"
+ >
+
+ Add Token
+
+
+
+ {/* Filters and Search */}
+
+
+
+
{
+ const newPageSize = Number(e.target.value);
+ setPagination(prev => ({
+ ...prev,
+ page: 1, // Reset to page 1 when page size changes
+ page_size: newPageSize,
+ }));
+ }}
+ >
+ 5 per page
+ 10 per page
+ 20 per page
+
+
+
+
+
{
+ setStatusFilter(e.target.value);
+ // Reset to page 1 when status filter changes
+ setPagination(prev => ({
+ ...prev,
+ page: 1,
+ }));
+ }}
+ >
+ All Status
+ Active
+ Revoked
+
+
+
+
+
+
+
+
+
+
+
setSearchQuery(e.target.value)}
+ />
+
+
+
+ {/* Tokens Table */}
+
+
+
+
+
+
+ Token ID
+
+
+ Name
+
+
+ Created at
+
+
+ Expiry at
+
+
+ Status
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+ Loading tokens...
+
+
+ ) : displayTokens.length > 0 ? (
+ displayTokens.map((token) => (
+
+
+
+
+ {token.id.substring(0, 8)}...
+
+
copyTokenId(token.id)}
+ className="ml-2 text-gray-400 hover:text-gray-600 opacity-0 group-hover:opacity-100 transition-opacity"
+ title="Copy Token ID"
+ >
+
+
+ {copiedTokenId === token.id && (
+
+ Copied!
+
+ )}
+
+
+
+
+ {token.name}
+
+
+
+
+ {token.created}
+
+
+
+
+ {token.expiry}
+
+
+
+
+
+ {token.status}
+
+
+
+ {token.status === "Active" ? (
+ prepareRevokeToken(token.id)}
+ className="flex items-center text-sm text-red-600 hover:text-red-800 transition-colors cursor-pointer"
+ >
+
+ Revoke
+
+ ) : (
+
+ Revoked
+
+ )}
+
+
+ ))
+ ) : (
+
+
+ No tokens found matching your criteria
+
+
+ )}
+
+
+
+ {/* Pagination */}
+
+
+ {loading ? (
+ "Loading..."
+ ) : (
+ <>
+ Showing page {pagination.page} of {pagination.total_pages} ({pagination.total_items} total tokens)
+ >
+ )}
+
+
+
+ setPagination(prev => ({
+ ...prev,
+ page: prev.page - 1,
+ }))
+ }
+ disabled={!pagination.has_prev || loading}
+ className={`text-sm py-2 px-4 rounded-l cursor-pointer ${
+ !pagination.has_prev || loading
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Prev
+
+
+ setPagination(prev => ({
+ ...prev,
+ page: prev.page + 1,
+ }))
+ }
+ disabled={!pagination.has_next || loading}
+ className={`text-sm py-2 px-4 rounded-r cursor-pointer ${
+ !pagination.has_next || loading
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Next
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Tokens;
diff --git a/frontend/src/components/homePages/users.jsx b/frontend/src/components/homePages/users.jsx
new file mode 100644
index 0000000..7e01b76
--- /dev/null
+++ b/frontend/src/components/homePages/users.jsx
@@ -0,0 +1,387 @@
+import React, { useState, useEffect } from "react";
+import {
+ RiUserAddFill,
+ RiEditLine,
+ RiDeleteBinLine,
+ RiMore2Fill,
+} from "react-icons/ri";
+import { FiUserX, FiUserCheck } from "react-icons/fi";
+import { ListUsers } from "../services/user";
+
+const Users = () => {
+ const [users, setUsers] = useState([]);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [statusFilter, setStatusFilter] = useState("All");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [currentPage, setCurrentPage] = useState(1);
+ const [showDropdown, setShowDropdown] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [totalUsers, setTotalUsers] = useState(0);
+
+ // Fetch users from API
+ useEffect(() => {
+ fetchUsers();
+ }, [currentPage, rowsPerPage, statusFilter]);
+
+ const fetchUsers = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ // Convert statusFilter to lowercase for API (Active -> active, Inactive -> inactive)
+ const apiStatus = statusFilter === "All" ? "" : statusFilter.toLowerCase();
+
+ const response = await ListUsers(apiStatus, currentPage, rowsPerPage);
+
+ // Extract data from nested response structure
+ const userData = response.data?.data || [];
+ const paginationData = response.data?.pagination || {};
+
+ // Transform API response to match the component's data structure
+ const transformedUsers = userData.map((user, index) => ({
+ id: user.id || user.user_id || `user-${index}`,
+ name: user.name || user.username || "Unknown User",
+ email: user.email,
+ role: user.roles && user.roles.length > 0 ? user.roles.join(", ") : (user.role || "User"),
+ created: user.created_at
+ ? new Date(user.created_at).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ })
+ : new Date().toLocaleDateString(),
+ status: user.status ? "Active" : "Inactive",
+ }));
+
+ setUsers(transformedUsers);
+ setTotalUsers(paginationData.total_items || transformedUsers.length);
+ } catch (err) {
+ console.error("Error fetching users:", err);
+ setError("Failed to load users. Please try again.");
+ setUsers([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const toggleDropdown = (userId) => {
+ setShowDropdown(showDropdown === userId ? null : userId);
+ };
+
+ const deleteUser = (userId) => {
+ // TODO: Add API call to delete user
+ setUsers(users.filter((user) => user.id !== userId));
+ setShowDropdown(null);
+ // Optionally refetch after delete
+ // fetchUsers();
+ };
+
+ const toggleUserStatus = (userId) => {
+ // TODO: Add API call to update user status
+ setUsers(
+ users.map((user) =>
+ user.id === userId
+ ? {
+ ...user,
+ status: user.status === "Active" ? "Inactive" : "Active",
+ }
+ : user
+ )
+ );
+ setShowDropdown(null);
+ // Optionally refetch after status change
+ // fetchUsers();
+ };
+
+ // Client-side search filtering (searches within current page results)
+ const filteredUsers = users.filter((user) => {
+ if (searchQuery === "") return true;
+ const matchesSearch =
+ user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ user.email.toLowerCase().includes(searchQuery.toLowerCase());
+ return matchesSearch;
+ });
+
+ const totalPages = Math.ceil(totalUsers / rowsPerPage);
+
+ return (
+
+
+
+
+
+ User Management
+
+
+
+ Add User
+
+
+
+ {/* Filters and Search */}
+
+
+
+
{
+ setRowsPerPage(Number(e.target.value));
+ setCurrentPage(1); // Reset to first page on page size change
+ }}
+ >
+ 5 per page
+ 10 per page
+ 20 per page
+
+
+
+
+
{
+ setStatusFilter(e.target.value);
+ setCurrentPage(1); // Reset to first page on filter change
+ }}
+ >
+ All Status
+ Active
+ Inactive
+
+
+
+
+
+
+
+
+
+
+
setSearchQuery(e.target.value)}
+ />
+
+
+
+ {/* Users Table */}
+
+
+
+
+
+
+ User
+
+
+ Role
+
+
+ Created at
+
+
+ Status
+
+
+ Actions
+
+
+
+
+ {loading ? (
+
+
+
+
+
+ ) : error ? (
+
+
+
+
+
+ ) : filteredUsers.length > 0 ? (
+ filteredUsers.map((user) => (
+
+
+
+
+
+ {user.name.charAt(0)}
+
+
+
+
+ {user.name}
+
+
+ {user.email}
+
+
+
+
+
+
+ {user.role}
+
+
+
+
+ {user.created}
+
+
+
+
+
+ {user.status}
+
+
+
+
+
toggleDropdown(user.id)}
+ className="text-gray-500 hover:text-gray-700 focus:outline-none"
+ >
+
+
+ {showDropdown === user.id && (
+
+
+ toggleUserStatus(user.id)}
+ className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
+ >
+ {user.status === "Active" ? (
+
+ ) : (
+
+ )}
+ {user.status === "Active"
+ ? "Deactivate"
+ : "Activate"}
+
+ deleteUser(user.id)}
+ className="flex items-center px-4 py-2 text-sm text-red-600 hover:bg-gray-100 w-full text-left"
+ >
+
+ Delete
+
+
+
+ )}
+
+
+
+ ))
+ ) : (
+
+
+ No users found matching your criteria
+
+
+ )}
+
+
+
+ {/* Pagination */}
+
+
+ Showing {Math.min((currentPage - 1) * rowsPerPage + 1, totalUsers)} to{" "}
+ {Math.min(currentPage * rowsPerPage, totalUsers)} of{" "}
+ {totalUsers} users
+
+
+
+ setCurrentPage((prev) => Math.max(prev - 1, 1))
+ }
+ disabled={currentPage === 1}
+ className={`text-sm py-2 px-4 rounded-l cursor-pointer ${
+ currentPage === 1
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Prev
+
+
+ setCurrentPage((prev) => Math.min(prev + 1, totalPages))
+ }
+ disabled={currentPage === totalPages || totalPages === 0}
+ className={`text-sm py-2 px-4 rounded-r cursor-pointer ${
+ currentPage === totalPages || totalPages === 0
+ ? "bg-gray-200 text-gray-500 cursor-not-allowed"
+ : "bg-gray-300 hover:bg-gray-400 text-gray-800"
+ }`}
+ >
+ Next
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Users;
diff --git a/frontend/src/components/landingPage.jsx b/frontend/src/components/landingPage.jsx
new file mode 100644
index 0000000..80325ec
--- /dev/null
+++ b/frontend/src/components/landingPage.jsx
@@ -0,0 +1,97 @@
+import rolesUI from "../assets/roles.png";
+import usersUI from "../assets/users.png";
+import dashboardUI from "../assets/dashboard.png"
+import { useNavigate } from "react-router-dom";
+const LandingPage = () => {
+ let navigate = useNavigate();
+ return (
+
+
+
+
+ No more auth headaches. Just ship.
+
+
+ A plug-and-play authentication and authorization SDK with tenant
+ support, JWT, RBAC, and more.
+
+
+
{
+ sessionStorage.setItem("button", "register")
+ navigate("/auth")
+ }}
+ className="inline-flex items-center justify-center border align-middle select-none font-sans font-medium text-center duration-300 ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm hover:shadow-md relative bg-gradient-to-b from-stone-700 to-stone-800 border-stone-900 text-stone-50 rounded-lg hover:bg-gradient-to-b hover:from-stone-800 hover:to-stone-800 hover:border-stone-900 after:absolute after:inset-0 after:rounded-[inherit] after:box-shadow after:shadow-[inset_0_1px_0px_rgba(255,255,255,0.25),inset_0_-2px_0px_rgba(0,0,0,0.35)] after:pointer-events-none transition antialiased cursor-pointer"
+ >
+ Get Started
+
+
+
+
+
+
+
+
+ 🚀 Launch Secure Login Faster Than Ever
+
+
+ Authentication in Minutes
+
+
+ No more writing login flows from scratch. Use our SDKs to
+ integrate secure authentication in under 10 minutes.
+
+
+
+
+
+
+
+
+
+ 👥 Manage Users Like a Pro, No Backend Required
+
+
+ Effortless User Management
+
+
+ View, update, and delete users with a sleek dashboard. Built-in
+ support for password resets, profile edits, and user search.
+
+
+
+
+
+
+
+
+
+ 🔐 Lock Down Access with Precision
+
+
+ Role-Based Access Control (RBAC)
+
+
+ Define roles and permissions for every API endpoint. Your data
+ stays secure—only the right people see the right things.
+
+
+
+
+
+ );
+};
+
+export default LandingPage;
diff --git a/frontend/src/components/navbar.jsx b/frontend/src/components/navbar.jsx
new file mode 100644
index 0000000..6024015
--- /dev/null
+++ b/frontend/src/components/navbar.jsx
@@ -0,0 +1,114 @@
+import React, { useState } from "react";
+import { useNavigate } from "react-router-dom";
+const Navbar = () => {
+ const [isOpen, setIsOpen] = useState(false);
+ let navigate = useNavigate();
+ return (
+
+
+
+
{navigate("/")}}
+ className="font-sans cursor-pointer antialiased text-base md:text-lg text-stone-800 block py-1 font-semibold"
+ >
+ TrustKit
+
+
+
+
+ (navigate("/about"))}
+ className="font-sans cursor-pointer antialiased text-base text-stone-800 p-1"
+ >
+ About Us
+
+
+
+ {navigate("/contact")}}
+ className="font-sans antialiased text-base text-stone-800 p-1 cursor-pointer"
+ >
+ Contact Us
+
+
+
+
+ {/* Sign In Button */}
+
{
+ sessionStorage.setItem("button", "login");
+ navigate("/auth");
+ }}
+ className="inline-flex items-center justify-center border align-middle select-none font-sans font-medium text-center transition-all ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed focus:shadow-none text-sm py-2 px-4 shadow-sm bg-transparent relative text-stone-700 hover:text-stone-700 border-stone-500 hover:bg-transparent duration-150 hover:border-stone-600 rounded-lg hover:opacity-60 hover:shadow-none lg:ml-auto lg:inline-block cursor-pointer"
+ >
+ Login
+
+ {/* Components Dropdown for Mobile */}
+
setIsOpen(!isOpen)}
+ className="inline-grid place-items-center border font-sans font-medium text-center transition-all duration-300 ease-in disabled:opacity-50 disabled:shadow-none disabled:pointer-events-none text-sm min-w-[38px] min-h-[38px] rounded-md shadow-sm hover:shadow-md bg-stone-800 hover:bg-stone-700 border-stone-900 text-stone-50 ml-auto lg:hidden"
+ >
+
+
+
+
+
+
+
+ {/* Mobile Menu (collapsed by default) */}
+
+
+
+ );
+};
+
+export default Navbar;
diff --git a/frontend/src/components/otp.jsx b/frontend/src/components/otp.jsx
new file mode 100644
index 0000000..7df8bd7
--- /dev/null
+++ b/frontend/src/components/otp.jsx
@@ -0,0 +1,93 @@
+import { useNavigate } from "react-router-dom";
+import React from "react";
+const OTP = () => {
+ let navigate = useNavigate();
+ const [showNewPassword, setShowNewPassword] = React.useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = React.useState(false);
+ const [newPassword, setNewPassword] = React.useState("");
+ const [confirmPassword, setConfirmPassword] = React.useState("");
+
+ const passwordsMatch = newPassword === confirmPassword && newPassword !== "";
+
+ return (
+ <>
+
+
+
+ Set New Password
+
+
+
+
+
+ >
+ );
+};
+
+export default OTP;
\ No newline at end of file
diff --git a/frontend/src/components/reset.jsx b/frontend/src/components/reset.jsx
new file mode 100644
index 0000000..5ab6dd0
--- /dev/null
+++ b/frontend/src/components/reset.jsx
@@ -0,0 +1,57 @@
+import { useNavigate } from "react-router-dom";
+
+const Reset = () => {
+ let navigate = useNavigate();
+ return (
+ <>
+
+
+
+ Reset Password
+
+
+ You will receive an e-mail please follow the directions given
+
+
+
+
+
+ >
+ );
+};
+
+export default Reset;
diff --git a/frontend/src/components/services/auth.js b/frontend/src/components/services/auth.js
new file mode 100644
index 0000000..72ae40b
--- /dev/null
+++ b/frontend/src/components/services/auth.js
@@ -0,0 +1,31 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const CreateTenant = async (email, password, name, organization) => {
+ try {
+ const response = await axios.post(API_URL + "/tenant", {
+ name: name,
+ email: email,
+ password: password,
+ campany: organization,
+ });
+ return response.data;
+ } catch (error) {
+ console.log("eror while creating the tenant for the user: ", error);
+ throw error;
+ }
+};
+
+export const LoginTenant = async (email, password) => {
+ try {
+ const response = await axios.post(API_URL + "/tenant/login", {
+ email: email,
+ password: password,
+ });
+ return response.data;
+ } catch (error) {
+ console.log("eror while logging in the tenant for the user: ", error);
+ throw error;
+ }
+};
diff --git a/frontend/src/components/services/dashboard.js b/frontend/src/components/services/dashboard.js
new file mode 100644
index 0000000..f8d10f0
--- /dev/null
+++ b/frontend/src/components/services/dashboard.js
@@ -0,0 +1,44 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const GetDashBoardDetails = async () => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+ const response = await axios.get(API_URL + "/tenant/dashboard", {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+ return response.data;
+ } catch (error) {
+ console.log("eror while creating the tenant for the user: ", error);
+ throw error;
+ }
+};
+
+export const GetActiveRoles = async (page, page_size) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/tokens/status?page=${page}&&page_size=${page_size}&&status=active`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/services/messages.js b/frontend/src/components/services/messages.js
new file mode 100644
index 0000000..adf3857
--- /dev/null
+++ b/frontend/src/components/services/messages.js
@@ -0,0 +1,83 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const ListMessages = async (page, page_size, status) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ // Build URL with optional status parameter
+ let url = `${API_URL}/tenant/messages?page=${page}&page_size=${page_size}`;
+ if (status) {
+ url += `&status=${status}`;
+ }
+
+ const response = await axios.get(url, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching messages:", error);
+ throw error;
+ }
+};
+
+export const ApproveMessage = async (id) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+ console.log("the messageid : ", id);
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.put(
+ `${API_URL}/tenant/messages/approve?id=${id}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while approving message:", error);
+ throw error;
+ }
+};
+
+export const RejectMessage = async (id) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.put(
+ `${API_URL}/tenant/messages/reject?id=${id}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while rejecting message:", error);
+ throw error;
+ }
+};
diff --git a/frontend/src/components/services/profile.js b/frontend/src/components/services/profile.js
new file mode 100644
index 0000000..e9d3542
--- /dev/null
+++ b/frontend/src/components/services/profile.js
@@ -0,0 +1,78 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const GetProfile = async () => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/me`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+}
+
+export const DeleteProfile = async () => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.delete(
+ `${API_URL}/tenant`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+}
+
+export const SetPassword = async () => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.delete(
+ `${API_URL}/tenant`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/services/roles.js b/frontend/src/components/services/roles.js
new file mode 100644
index 0000000..77d8a10
--- /dev/null
+++ b/frontend/src/components/services/roles.js
@@ -0,0 +1,161 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const ListRoles = async (status, page, page_size, roleType) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/roles?page=${page}&page_size=${page_size}&roletype=${roleType}&status=${status}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+};
+
+export const GetRolePermissions = async (roleId) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/roles/persmissions?roleId=${roleId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+};
+
+export const AddRoles = async (name, displayName, description, permissions) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.post(
+ `${API_URL}/tenant/roles/`,
+ {
+ name: name,
+ display_name: displayName,
+ description: description,
+ permissions: permissions,
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+};
+
+export const EnableRole = async(roleId) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.put(
+ `${API_URL}/tenant/roles/enable?roleId=${roleId}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while enabling the role:", error);
+ throw error;
+ }
+}
+
+export const DisableRole = async(roleId) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.put(
+ `${API_URL}/tenant/roles/disable?roleId=${roleId}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while disabling the role:", error);
+ throw error;
+ }
+}
+
+export const DeleteRole = async(roleId) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.delete(
+ `${API_URL}/tenant/roles?roleId=${roleId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while deleting the role:", error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/services/tokens.js b/frontend/src/components/services/tokens.js
new file mode 100644
index 0000000..d615a47
--- /dev/null
+++ b/frontend/src/components/services/tokens.js
@@ -0,0 +1,108 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const CreateTokens = async (name, expiry) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+ console.log(expiry);
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.post(
+ `${API_URL}/tenant/tokens`,
+ {
+ name: name,
+ expiry_at: expiry,
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+};
+
+export const RevokeToken = async (token) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.put(
+ `${API_URL}/tenant/tokens/${token}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while revoking tokens:", error);
+ throw error;
+ }
+};
+
+export const ListTokensPaginted = async (page, page_size) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/tokens?page=${page}&&page_size=${page_size}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+};
+
+export const ListTokensWithStatus = async (status, page, page_size) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/tokens/status?page=${page}&&page_size=${page_size}&&status=${status}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/services/user.js b/frontend/src/components/services/user.js
new file mode 100644
index 0000000..4bb12d4
--- /dev/null
+++ b/frontend/src/components/services/user.js
@@ -0,0 +1,28 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8081";
+
+export const ListUsers = async (status, page, page_size) => {
+ try {
+ const authToken = sessionStorage.getItem("authToken");
+
+ if (!authToken) {
+ throw new Error("No auth token found");
+ }
+
+ const response = await axios.get(
+ `${API_URL}/tenant/users?page=${page}&page_size=${page_size}&status=${status}`,
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ return response.data;
+ } catch (error) {
+ console.error("Error while fetching tokens:", error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/signup.jsx b/frontend/src/components/signup.jsx
new file mode 100644
index 0000000..fdef4fc
--- /dev/null
+++ b/frontend/src/components/signup.jsx
@@ -0,0 +1,280 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { CreateTenant, LoginTenant } from "./services/auth";
+
+const SignUp = () => {
+ let navigate = useNavigate();
+ const [isShow, setIsShow] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [formInputs, setFormInputs] = useState({
+ email: "",
+ password: "",
+ name: "",
+ organization: ""
+ });
+ let route = sessionStorage.getItem("button");
+ const formData = {
+ heading: "Login",
+ subheading: "Enter your email and password to login",
+ buttonDetails: "LogIn",
+ callheading: "Not registered ?",
+ buttoncall: "Create Account",
+ };
+ useEffect(() => {
+ if (route === "register") {
+ setIsShow(true);
+ }
+ }, [route]);
+ if (route === "register") {
+ formData.buttonDetails = "SignIn";
+ formData.heading = "SignIn";
+ formData.subheading = "Plug in, power up—secure access made simple.";
+ formData.callheading = "Already a user ?";
+ formData.buttoncall = "Login";
+ }
+ function pageReload() {
+ location.reload();
+ }
+
+ const handleInputChange = (e) => {
+ setFormInputs({
+ ...formInputs,
+ [e.target.name]: e.target.value
+ });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+
+ try {
+ if (route === "register") {
+ // Create new tenant
+ const response = await CreateTenant(
+ formInputs.email,
+ formInputs.password,
+ formInputs.name,
+ formInputs.organization
+ );
+ console.log("Tenant created successfully:", response);
+ // Navigate to login or dashboard
+ sessionStorage.setItem("button", "login");
+ pageReload();
+ } else {
+ // Login existing tenant
+ const response = await LoginTenant(
+ formInputs.email,
+ formInputs.password
+ );
+ console.log("Login successful:", response.data.token);
+ // Store auth token if needed
+ if (response.data.token) {
+ sessionStorage.setItem("authToken", response.data.token);
+ }
+ // Navigate to dashboard
+ navigate("/home");
+ }
+ } catch (error) {
+ console.error("Authentication error:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
TrustKit
+
+ Your auth, your rules—managed from one place.
+
+
+
+
+
+
+ {formData.heading}
+
+
+ {formData.subheading}
+
+
+
+
+
+ {formData.callheading}
+ {
+ if (route === "login") {
+ sessionStorage.setItem("button","register");
+ } else {
+ sessionStorage.setItem("button","login");
+ }
+ pageReload();
+ }}
+ className="font-sans antialiased text-stone-800 font-semibold text-sm cursor-pointer"
+ >
+ {formData.buttoncall}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SignUp;
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..a461c50
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1 @@
+@import "tailwindcss";
\ No newline at end of file
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
new file mode 100644
index 0000000..2a95b5c
--- /dev/null
+++ b/frontend/src/main.jsx
@@ -0,0 +1,11 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import App from "./App.jsx";
+import { ThemeProvider } from "@material-tailwind/react";
+
+createRoot(document.getElementById("root")).render(
+
+
+
+);
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
new file mode 100644
index 0000000..66b1412
--- /dev/null
+++ b/frontend/tailwind.config.js
@@ -0,0 +1,12 @@
+ const withMT = require("@material-tailwind/react/utils/withMT");
+
+ module.exports = withMT({
+ content: [
+ "./index.html",
+ "./src/**/*.{vue,js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [require('@tailwindcss/typography')],
+ });
\ No newline at end of file
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000..9853e22
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,9 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ assetsInclude: ['**/*.md'],
+});