diff --git a/platforms/eReputation/.gitignore b/platforms/eReputation/.gitignore
new file mode 100644
index 00000000..f9ba7f8b
--- /dev/null
+++ b/platforms/eReputation/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+.DS_Store
+server/public
+vite.config.ts.*
+*.tar.gz
\ No newline at end of file
diff --git a/platforms/eReputation/README.md b/platforms/eReputation/README.md
new file mode 100644
index 00000000..e8a5b634
--- /dev/null
+++ b/platforms/eReputation/README.md
@@ -0,0 +1,172 @@
+# eReputation - Professional Reputation Management Platform
+
+## Overview
+
+eReputation is a full-stack web application for professional reputation analysis and management. The platform allows users to calculate reputation scores for themselves and others (users, groups, platforms) using AI-powered analysis. It features a modern React frontend with shadcn/ui components and an Express.js backend with PostgreSQL database integration.
+
+## Recent Changes (August 11, 2025)
+
+### Production-Ready Email/Password Authentication System Completed
+- Successfully replaced Replit auth with comprehensive email/password authentication
+- Implemented bcrypt password hashing for secure credential storage
+- Created session-based authentication with PostgreSQL session store
+- Built QR code-style authentication interface with eReputation branding
+- Updated all API routes and middleware to use new requireAuth system
+- Created TypeORM User entity with email, password, firstName, lastName fields
+- Cleaned authentication page design with W3DS branding and MetaState messaging
+
+### Database Migration to TypeORM Completed
+- Successfully migrated from Drizzle ORM to TypeORM with PostgreSQL
+- Created comprehensive TypeORM entities preserving all existing schema relationships
+- Implemented automatic database schema creation and initialization
+- Maintained backward compatibility through schema re-exports
+- Database migration infrastructure ready for seamless data preservation
+
+### AI Functionality Removed - Simple Digital Signatures Added
+- Removed all AI/OpenAI functionality from reference creation and reputation calculation
+- Implemented simple digital signature system for references using basic cryptographic hashing
+- References now include digital signature and timestamp for authenticity verification
+- Reputation calculations use simplified random scoring instead of AI analysis
+- System ready for future replacement with more sophisticated signature libraries
+
+### Unified Reference View Modal System Completed
+- Created shared ReferenceViewModal component for consistent modal behavior across all pages
+- Fixed dashboard reference view to display actual reference content from database instead of mock data
+- Both dashboard and references pages now use identical modal component for viewing references
+- Updated activity handler to access full reference data from activity.data property
+- Eliminated duplicate modal code and ensured consistent user experience
+- Mobile-first responsive design maintained across all reference viewing interfaces
+- Maintained separate modal systems: ReferenceViewModal for references, Activity Details modal for eReputation calculations
+
+### Landing Page Redesign Completed
+- Implemented clean gradient background (fig/30 to fig/10, bottom to top)
+- Removed blur effects behind logo for cleaner appearance
+- Updated modal background to fig-10 for consistency
+- Applied branded fig background icons with swiss-cheese gold text throughout
+- Updated feature text to "Calculate", "Reference", "Share" with proper spacing
+- Optimized icon spacing for desktop viewing (gap-8, justify-center)
+
+## User Preferences
+
+Preferred communication style: Simple, everyday language.
+
+## System Architecture
+
+### Frontend Architecture
+- **Framework**: React 18 with TypeScript
+- **Build Tool**: Vite for fast development and optimized builds
+- **UI Library**: shadcn/ui components built on Radix UI primitives
+- **Styling**: Tailwind CSS with custom design tokens and CSS variables
+- **State Management**: TanStack Query (React Query) for server state
+- **Routing**: Wouter for lightweight client-side routing
+- **Form Handling**: React Hook Form with Zod validation
+
+### Backend Architecture
+- **Runtime**: Node.js with Express.js framework
+- **Language**: TypeScript with ES modules
+- **Database**: PostgreSQL with Neon serverless driver
+- **ORM**: TypeORM for entity-based database operations
+- **Authentication**: Email/password authentication with bcrypt hashing
+- **Session Management**: Express sessions with PostgreSQL storage
+- **File Uploads**: Multer for handling file attachments
+
+## Key Components
+
+### Authentication System
+- **Provider**: Email/password authentication with bcrypt
+- **Session Storage**: PostgreSQL-backed sessions with connect-pg-simple
+- **Security**: HTTP-only secure cookies with session-based authentication
+- **User Management**: Registration and login with secure password hashing
+- **Interface**: QR code-style login page with eReputation and W3DS branding
+
+### Reputation Analysis Engine
+- **AI Integration**: OpenAI GPT-4o for reputation analysis
+- **Analysis Types**: Self-assessment, user evaluation, group/platform analysis
+- **Variables**: Configurable analysis parameters (comment history, references, qualifications, etc.)
+- **Scoring**: 1-10 reputation score with confidence metrics
+
+### Database Schema
+- **Users**: Profile information and authentication data
+- **Reputation Calculations**: Analysis results with scores and confidence
+- **References**: Professional endorsements and testimonials
+- **File Uploads**: Document attachments for evidence
+- **Sessions**: Authentication session storage
+
+### File Management
+- **Upload Handling**: Multer-based file processing
+- **File Types**: Images (JPEG, PNG, GIF) and documents (PDF, DOC, DOCX)
+- **Size Limits**: 10MB maximum file size
+- **Storage**: Local filesystem storage with configurable paths
+
+## Data Flow
+
+1. **Authentication Flow**:
+ - User initiates login via Replit Auth
+ - OIDC provider validates credentials
+ - Session created and stored in PostgreSQL
+ - User profile created/updated in database
+
+2. **Reputation Calculation Flow**:
+ - User selects analysis type and variables
+ - Backend creates calculation record with "processing" status
+ - OpenAI API analyzes based on selected parameters
+ - Results stored with score, confidence, and detailed analysis
+ - Frontend updates to show completed analysis
+
+3. **Reference Management Flow**:
+ - Users can create references for others
+ - File uploads supported for evidence
+ - References linked to target users/groups/platforms
+ - Analysis engine can incorporate reference data
+
+## External Dependencies
+
+### Core Infrastructure
+- **Database**: Neon PostgreSQL for serverless database hosting
+- **Authentication**: Replit Auth service for OIDC authentication
+- **AI Processing**: OpenAI API for reputation analysis
+
+### Development Tools
+- **Package Manager**: npm with lockfile version 3
+- **TypeScript**: Type checking and compilation
+- **ESBuild**: Production bundling for server code
+- **Drizzle Kit**: Database migrations and schema management
+
+### UI/UX Libraries
+- **Radix UI**: Accessible component primitives
+- **Tailwind CSS**: Utility-first styling framework
+- **Lucide React**: Icon library
+- **React Hook Form**: Form state management
+- **Zod**: Runtime type validation
+
+## Deployment Strategy
+
+### Development Environment
+- **Dev Server**: Vite dev server with HMR for frontend
+- **Backend**: tsx for TypeScript execution with hot reload
+- **Database**: Drizzle push for schema synchronization
+- **Environment**: NODE_ENV=development with debug logging
+
+### Production Build
+- **Frontend**: Vite build to dist/public directory
+- **Backend**: ESBuild bundle to dist/index.js
+- **Static Serving**: Express serves built frontend assets
+- **Process**: Single Node.js process serving both frontend and API
+
+### Environment Configuration
+- **Database**: DATABASE_URL for PostgreSQL connection
+- **Auth**: REPL_ID, SESSION_SECRET, ISSUER_URL for authentication
+- **AI**: OPENAI_API_KEY for reputation analysis
+- **Domains**: REPLIT_DOMAINS for CORS and auth configuration
+
+### File Structure
+```
+├── client/ # React frontend application
+├── server/ # Express.js backend API
+├── shared/ # Shared TypeScript types and schemas
+├── dist/ # Production build output
+├── uploads/ # File upload storage
+└── migrations/ # Database migration files
+```
+
+The application follows a monorepo structure with clear separation between frontend, backend, and shared code, making it easy to maintain and scale both components independently while sharing common types and utilities.
\ No newline at end of file
diff --git a/platforms/eReputation/client/index.html b/platforms/eReputation/client/index.html
new file mode 100644
index 00000000..4b4d09e3
--- /dev/null
+++ b/platforms/eReputation/client/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platforms/eReputation/client/src/App.tsx b/platforms/eReputation/client/src/App.tsx
new file mode 100644
index 00000000..2f8a1fcb
--- /dev/null
+++ b/platforms/eReputation/client/src/App.tsx
@@ -0,0 +1,40 @@
+import { Switch, Route } from "wouter";
+import { queryClient } from "./lib/queryClient";
+import { QueryClientProvider } from "@tanstack/react-query";
+import { Toaster } from "@/components/ui/toaster";
+import { TooltipProvider } from "@/components/ui/tooltip";
+import { useAuth } from "@/hooks/useAuth";
+import AuthPage from "@/pages/auth-page";
+import Dashboard from "@/pages/dashboard";
+import References from "@/pages/references";
+import NotFound from "@/pages/not-found";
+
+function Router() {
+ const { isAuthenticated, isLoading } = useAuth();
+
+ // Show auth page if loading or not authenticated
+ if (isLoading || !isAuthenticated) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+
+function App() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx b/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx
new file mode 100644
index 00000000..2e2eacf2
--- /dev/null
+++ b/platforms/eReputation/client/src/components/modals/other-calculation-modal.tsx
@@ -0,0 +1,528 @@
+import { useState, useEffect } from "react";
+import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
+import { useDebouncedCallback } from 'use-debounce';
+import { apiRequest } from "@/lib/queryClient";
+import { useToast } from "@/hooks/use-toast";
+import { isUnauthorizedError } from "@/lib/authUtils";
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Progress } from "@/components/ui/progress";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { Label } from "@/components/ui/label";
+import { Input } from "@/components/ui/input";
+import ViewReputationModal from "./view-reputation-modal";
+
+
+interface OtherCalculationModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const TARGET_TYPES = [
+ {
+ value: "user",
+ label: "User",
+ icon: (
+
+
+
+ )
+ },
+ {
+ value: "group",
+ label: "Group",
+ icon: (
+
+
+
+ )
+ },
+ {
+ value: "platform",
+ label: "Platform",
+ icon: (
+
+
+
+ )
+ }
+];
+
+const ANALYSIS_STEPS = [
+ { label: "Analyzing post interactions", platform: "Pictique" },
+ { label: "Evaluating social engagement", platform: "Blabsy" },
+ { label: "Processing positive feedback", platform: "Comment Likes" },
+ { label: "Processing negative feedback", platform: "Comment Dislikes" },
+ { label: "Measuring social interactions", platform: "Social Interactions" },
+ { label: "Finalizing eReputation score", platform: "Calculating" }
+];
+
+const ALL_VARIABLES = ["comment-history", "references", "qualifications", "profile-completeness", "engagement", "activity-frequency"];
+
+const OTHER_VARIABLES = [
+ {
+ id: "comment-history",
+ label: "Comment History",
+ description: "Analyze past interactions and feedback"
+ },
+ {
+ id: "references",
+ label: "References",
+ description: "Professional endorsements received"
+ },
+ {
+ id: "qualifications",
+ label: "Qualifications",
+ description: "Educational and professional credentials"
+ },
+ {
+ id: "profile-completeness",
+ label: "Profile Completeness",
+ description: "How complete your profile information is"
+ },
+ {
+ id: "engagement",
+ label: "Likes/Dislikes",
+ description: "Community engagement metrics"
+ },
+ {
+ id: "activity-frequency",
+ label: "Activity Frequency",
+ description: "Consistency of platform participation"
+ }
+];
+
+export default function OtherCalculationModal({ open, onOpenChange }: OtherCalculationModalProps) {
+ const [targetType, setTargetType] = useState("");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [selectedTarget, setSelectedTarget] = useState(null);
+ const [isCalculating, setIsCalculating] = useState(false);
+ const [currentStep, setCurrentStep] = useState(0);
+ const [progress, setProgress] = useState(0);
+ const [showViewModal, setShowViewModal] = useState(false);
+ const [reputationResult, setReputationResult] = useState(null);
+ const { toast } = useToast();
+ const queryClient = useQueryClient();
+
+ // Progress simulation effect
+ useEffect(() => {
+ if (isCalculating && currentStep < ANALYSIS_STEPS.length) {
+ const timer = setTimeout(() => {
+ setCurrentStep(prev => prev + 1);
+ setProgress(prev => Math.min(100, prev + (100 / ANALYSIS_STEPS.length)));
+ }, 800 + Math.random() * 400); // Random delay between 800-1200ms for realism
+
+ return () => clearTimeout(timer);
+ }
+ }, [isCalculating, currentStep]);
+
+ const debouncedSearch = useDebouncedCallback((query: string) => {
+ if (query.length >= 2) {
+ refetch();
+ }
+ }, 300);
+
+ const { data: searchResults = [], refetch } = useQuery({
+ queryKey: ['/api/search', targetType, searchQuery],
+ queryFn: () => {
+ if (!targetType || searchQuery.length < 2) return [];
+ const endpoint = `/api/search/${targetType}s?q=${encodeURIComponent(searchQuery)}`;
+ return fetch(endpoint, { credentials: "include" }).then(res => res.json());
+ },
+ enabled: false,
+ });
+
+ const calculateMutation = useMutation({
+ mutationFn: async () => {
+ const response = await apiRequest("POST", "/api/reputation/calculate", {
+ targetType: targetType,
+ targetId: selectedTarget?.id || '',
+ targetName: selectedTarget?.name || selectedTarget?.title || 'Unknown',
+ variables: ALL_VARIABLES
+ });
+ return response.json();
+ },
+ onError: (error) => {
+ setIsCalculating(false);
+ setCurrentStep(0);
+ setProgress(0);
+
+ if (isUnauthorizedError(error)) {
+ toast({
+ title: "Unauthorized",
+ description: "You are logged out. Logging in again...",
+ variant: "destructive",
+ });
+ setTimeout(() => {
+ window.location.href = "/api/login";
+ }, 500);
+ return;
+ }
+ toast({
+ title: "Calculation Failed",
+ description: error instanceof Error ? error.message : "Failed to calculate reputation",
+ variant: "destructive",
+ });
+ },
+ });
+
+ // When progress completes, wait a bit then show results
+ useEffect(() => {
+ if (currentStep >= ANALYSIS_STEPS.length && isCalculating && calculateMutation.data) {
+ // Small delay to show 100% completion
+ setTimeout(() => {
+ setReputationResult(calculateMutation.data);
+ setIsCalculating(false);
+ setShowViewModal(true);
+
+ // Update dashboard queries
+ queryClient.invalidateQueries({ queryKey: ["/api/dashboard/stats"] });
+ queryClient.invalidateQueries({ queryKey: ["/api/dashboard/activities"] });
+ }, 500);
+ }
+ }, [currentStep, isCalculating, calculateMutation.data, queryClient]);
+
+ const resetForm = () => {
+ setTargetType("");
+ setSearchQuery("");
+ setSelectedTarget(null);
+ setIsCalculating(false);
+ setCurrentStep(0);
+ setProgress(0);
+ setReputationResult(null);
+ };
+
+ const handleSearchChange = (value: string) => {
+ setSearchQuery(value);
+ // Trigger search if query is long enough
+ if (value.length >= 2) {
+ debouncedSearch(value);
+ }
+ // Don't automatically set selected target, let user pick from results
+ if (!value.trim()) {
+ setSelectedTarget(null);
+ }
+ };
+
+ const handleSelectTarget = (target: any) => {
+ setSelectedTarget(target);
+ setSearchQuery(target.name);
+ };
+
+ const handleStartCalculation = () => {
+ if (!targetType) {
+ toast({
+ title: "Invalid Selection",
+ description: "Please select a target type",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ if (!selectedTarget) {
+ toast({
+ title: "Invalid Selection",
+ description: "Please select a target to evaluate",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ setIsCalculating(true);
+ setCurrentStep(0);
+ setProgress(0);
+ calculateMutation.mutate();
+ };
+
+ const handleCloseModal = () => {
+ // Reset all states when closing
+ resetForm();
+ onOpenChange(false);
+ };
+
+ return (
+
+
+
+
+
+
+ Evaluate Others' eReputation
+ Calculate eReputation for users, groups, or platforms throughout the W3DS
+
+
+
+
+
+
+ {/* Progress Bar or Ready State */}
+ {isCalculating ? (
+ // Calculating state - show progress
+
+
+
Calculating {selectedTarget?.name || 'Target'}'s eReputation
+
+ {currentStep < ANALYSIS_STEPS.length
+ ? ANALYSIS_STEPS[currentStep].label
+ : "Calculation complete!"
+ }
+
+
+
+ {/* Progress Bar */}
+
+
+
+ Progress
+ {Math.round(progress)}%
+
+
+
+ {/* Current Platform */}
+ {currentStep < ANALYSIS_STEPS.length && (
+
+
+
+
+
+ {ANALYSIS_STEPS[currentStep].platform}
+
+
+ {ANALYSIS_STEPS[currentStep].label}
+
+
+
+
+ )}
+
+ {/* Steps completed */}
+
+ {ANALYSIS_STEPS.slice(0, currentStep).map((step, index) => (
+
+ ))}
+
+
+ ) : selectedTarget ? (
+ // Ready to calculate state
+
+
+
+
+
Ready to Calculate
+
+ We'll analyze {selectedTarget.name}'s eReputation across multiple post-platforms including likes, dislikes, and engagement metrics.
+
+
+
+
+
+
+
+
+
Analysis includes all eReputation factors automatically
+
+
+
+ ) : (
+ // Default placeholder state when no target selected
+
+
+
+
+
Select Target to Evaluate
+
+ Choose a target type below and search for a user, group, or platform to calculate their eReputation across multiple post-platforms.
+
+
+
+
+
+
+
+
+
Analysis includes all eReputation factors automatically
+
+
+
+ )}
+
+ {/* Target Selection */}
+
+
Select Target Type
+
+
+ {TARGET_TYPES.map((type) => (
+
+
+
+ {type.icon}
+
+ {type.label}
+
+ ))}
+
+
+
+
+ {/* Search Target */}
+
+
+ Search Target
+
+
+
handleSearchChange(e.target.value)}
+ className="pl-10 border-2 border-fig/20 focus:border-fig/40 focus:ring-fig/20 rounded-2xl"
+ disabled={!targetType}
+ />
+
+
+
+
+ {/* Search Results Dropdown - Absolute positioned overlay */}
+ {searchQuery.length >= 2 && searchResults.length > 0 && !selectedTarget && (
+
+ {searchResults.map((result: any, index: number) => (
+
handleSelectTarget(result)}
+ className="w-full text-left px-4 py-3 hover:bg-fig-10 transition-colors border-b border-fig/10 last:border-b-0 first:rounded-t-2xl last:rounded-b-2xl"
+ >
+
+
+
+
{result.name}
+
{result.type}
+
+
+
+ ))}
+
+ )}
+
+ {/* Manual Entry Option */}
+ {searchQuery.length >= 2 && !selectedTarget && (
+
+
handleSelectTarget({ id: searchQuery.trim(), name: searchQuery.trim() })}
+ className="w-full text-left px-4 py-3 hover:bg-fig-10 transition-colors rounded-2xl"
+ >
+
+
+
+
Add "{searchQuery}"
+
Use custom name
+
+
+
+
+ )}
+
+
+ {/* Selected Target Display */}
+ {selectedTarget && (
+
+
+
+
+
+
+
{selectedTarget.name}
+
+
{
+ setSelectedTarget(null);
+ setSearchQuery("");
+ }}
+ className="text-fig/50 hover:text-fig"
+ >
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ Cancel
+
+
+ {isCalculating ? (
+ <>
+
+ Calculating...
+ >
+ ) : (
+ "Calculate eReputation"
+ )}
+
+
+
+
+
+ {/* View Reputation Modal */}
+ {
+ setShowViewModal(open);
+ if (!open) {
+ handleCloseModal();
+ }
+ }}
+ reputationData={reputationResult}
+ />
+
+ );
+}
diff --git a/platforms/eReputation/client/src/components/modals/reference-modal.tsx b/platforms/eReputation/client/src/components/modals/reference-modal.tsx
new file mode 100644
index 00000000..d24bdcf5
--- /dev/null
+++ b/platforms/eReputation/client/src/components/modals/reference-modal.tsx
@@ -0,0 +1,446 @@
+import { useState } from "react";
+import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
+import { useToast } from "@/hooks/use-toast";
+import { isUnauthorizedError } from "@/lib/authUtils";
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { Label } from "@/components/ui/label";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import FileUpload from "@/components/ui/file-upload";
+import { useDebouncedCallback } from "use-debounce";
+
+interface ReferenceModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const TARGET_TYPES = [
+ {
+ value: "user",
+ label: "User",
+ icon: (
+
+
+
+ )
+ },
+ {
+ value: "group",
+ label: "Group",
+ icon: (
+
+
+
+ )
+ },
+ {
+ value: "platform",
+ label: "Platform",
+ icon: (
+
+
+
+ )
+ }
+];
+
+const REFERENCE_TYPES = [
+ { value: "professional", label: "Professional Work" },
+ { value: "academic", label: "Academic Achievement" },
+ { value: "character", label: "Character Reference" },
+ { value: "skill", label: "Skill Endorsement" },
+ { value: "leadership", label: "Leadership Qualities" }
+];
+
+export default function ReferenceModal({ open, onOpenChange }: ReferenceModalProps) {
+ const [targetType, setTargetType] = useState("");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [selectedTarget, setSelectedTarget] = useState(null);
+ const [referenceText, setReferenceText] = useState("");
+ const [referenceType, setReferenceType] = useState("");
+ const [files, setFiles] = useState([]);
+ const { toast } = useToast();
+ const queryClient = useQueryClient();
+
+ const debouncedSearch = useDebouncedCallback((query: string) => {
+ if (query.length >= 2) {
+ refetch();
+ }
+ }, 300);
+
+ const { data: searchResults = [], refetch } = useQuery({
+ queryKey: ['/api/search', targetType, searchQuery],
+ queryFn: () => {
+ if (!targetType || searchQuery.length < 2) return [];
+ const endpoint = `/api/search/${targetType}s?q=${encodeURIComponent(searchQuery)}`;
+ return fetch(endpoint, { credentials: "include" }).then(res => res.json());
+ },
+ enabled: false,
+ });
+
+ const submitMutation = useMutation({
+ mutationFn: async (data: any) => {
+ const formData = new FormData();
+
+ // Add text fields
+ Object.keys(data).forEach(key => {
+ if (key !== 'files') {
+ formData.append(key, data[key]);
+ }
+ });
+
+ // Add files
+ files.forEach(file => {
+ formData.append('files', file);
+ });
+
+ const response = await fetch('/api/references', {
+ method: 'POST',
+ body: formData,
+ credentials: 'include',
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`${response.status}: ${error}`);
+ }
+
+ return response.json();
+ },
+ onSuccess: () => {
+ toast({
+ title: "Reference Submitted",
+ description: "Your professional reference has been successfully submitted.",
+ });
+ queryClient.invalidateQueries({ queryKey: ["/api/dashboard/stats"] });
+ queryClient.invalidateQueries({ queryKey: ["/api/dashboard/activities"] });
+ onOpenChange(false);
+ resetForm();
+ },
+ onError: (error) => {
+ if (isUnauthorizedError(error)) {
+ toast({
+ title: "Unauthorized",
+ description: "You are logged out. Logging in again...",
+ variant: "destructive",
+ });
+ setTimeout(() => {
+ window.location.href = "/api/login";
+ }, 500);
+ return;
+ }
+ toast({
+ title: "Submission Failed",
+ description: error instanceof Error ? error.message : "Failed to submit reference",
+ variant: "destructive",
+ });
+ },
+ });
+
+ const resetForm = () => {
+ setTargetType("");
+ setSearchQuery("");
+ setSelectedTarget(null);
+ setReferenceText("");
+ setReferenceType("");
+ setFiles([]);
+ };
+
+ const handleSearchChange = (value: string) => {
+ setSearchQuery(value);
+ // Trigger search if query is long enough
+ if (value.length >= 2) {
+ debouncedSearch(value);
+ }
+ // Don't automatically set selected target, let user pick from results
+ if (!value.trim()) {
+ setSelectedTarget(null);
+ }
+ };
+
+ const handleSelectTarget = (target: any) => {
+ setSelectedTarget(target);
+ setSearchQuery(target.name);
+ };
+
+ const handleSubmit = () => {
+ if (!targetType) {
+ toast({
+ title: "Invalid Selection",
+ description: "Please select a target type",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ if (!selectedTarget) {
+ toast({
+ title: "Invalid Selection",
+ description: "Please select who you want to reference",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ if (!referenceText.trim()) {
+ toast({
+ title: "Missing Information",
+ description: "Please write a reference text",
+ variant: "destructive",
+ });
+ return;
+ }
+
+
+
+ if (referenceText.length > 500) {
+ toast({
+ title: "Text Too Long",
+ description: "Please keep your reference under 500 characters",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ submitMutation.mutate({
+ targetType,
+ targetId: selectedTarget.id,
+ targetName: selectedTarget.name,
+ content: referenceText,
+ referenceType: 'general'
+ });
+ };
+
+ return (
+
+
+
+
+
+
+ Send an eReference
+ Provide professional eReferences throughout the W3DS
+
+
+
+
+
+
+ {/* Target Selection */}
+
+
Select eReference Target
+
+
+ {TARGET_TYPES.map((type) => (
+
+
+
+ {type.icon}
+
+ {type.label}
+
+ ))}
+
+
+
+
+ {/* Search Target */}
+
+
+ Find eReference Target
+
+
+
handleSearchChange(e.target.value)}
+ className="pl-10 border-2 border-fig/20 focus:border-fig/40 focus:ring-fig/20 rounded-2xl"
+ disabled={!targetType}
+ />
+
+
+
+
+ {/* Search Results Dropdown - Absolute positioned overlay */}
+ {searchQuery.length >= 2 && searchResults.length > 0 && !selectedTarget && (
+
+ {searchResults.map((result: any, index: number) => (
+
handleSelectTarget(result)}
+ className="w-full text-left px-4 py-3 hover:bg-fig-10 transition-colors border-b border-fig/10 last:border-b-0 first:rounded-t-2xl last:rounded-b-2xl"
+ >
+
+
+
+
{result.name}
+
{result.type}
+
+
+
+ ))}
+
+ )}
+
+ {/* Manual Entry Option */}
+ {searchQuery.length >= 2 && !selectedTarget && (
+
+
handleSelectTarget({ id: searchQuery.trim(), name: searchQuery.trim() })}
+ className="w-full text-left px-4 py-3 hover:bg-fig-10 transition-colors rounded-2xl"
+ >
+
+
+
+
Add "{searchQuery}"
+
Use custom name
+
+
+
+
+ )}
+
+
+ {/* Selected Target Display */}
+ {selectedTarget && (
+
+
+
+
+
+
+
{selectedTarget.name}
+
+
{
+ setSelectedTarget(null);
+ setSearchQuery("");
+ }}
+ className="text-fig/50 hover:text-fig"
+ >
+
+
+
+
+
+
+ )}
+
+
+ {/* Reference Text */}
+
+
+ {/* File Upload */}
+
+
+ Supporting Documents (Optional)
+
+
+
+
+
+
+
+ Click to upload or drag files here
+
+
PDF, DOC, JPG up to 10MB
+
+
setFiles(Array.from(e.target.files || []))}
+ className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
+ />
+
+ {files.length > 0 && (
+
+ {files.map((file, index) => (
+
+ {file.name}
+ setFiles(files.filter((_, i) => i !== index))}
+ className="text-fig/50 hover:text-fig ml-2"
+ >
+ ×
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
onOpenChange(false)}
+ disabled={submitMutation.isPending}
+ className="order-2 sm:order-1 flex-1 border-2 border-fig/30 text-fig/70 hover:bg-fig-10 hover:border-fig/40 font-bold h-11 sm:h-12 opacity-80"
+ >
+ Cancel
+
+
+ {submitMutation.isPending ? (
+ <>
+
+ Submitting...
+ >
+ ) : (
+ <>
+
+
+
+ Sign & Submit eReference
+ >
+ )}
+
+
+
+
+
+ );
+}
diff --git a/platforms/eReputation/client/src/components/modals/reference-view-modal.tsx b/platforms/eReputation/client/src/components/modals/reference-view-modal.tsx
new file mode 100644
index 00000000..31689a01
--- /dev/null
+++ b/platforms/eReputation/client/src/components/modals/reference-view-modal.tsx
@@ -0,0 +1,91 @@
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+
+interface ReferenceViewModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ reference: any;
+}
+
+export default function ReferenceViewModal({ open, onOpenChange, reference }: ReferenceViewModalProps) {
+ return (
+
+
+
+
+
+ eReference {reference?.type === 'Sent' ? 'for' : 'from'} {reference?.forFrom}
+
+
+ Professional reference details and status
+
+
+
+
+ {/* Reference Info */}
+
+
+
Type
+
{reference?.type}
+
+
+
Date
+
{reference?.date}
+
+
+
+ {/* Status */}
+
+
Status
+
+ {reference?.status?.toLowerCase()}
+
+
+
+ {/* Reference Content */}
+
+
Reference Details
+
+ {reference?.content || 'Reference content not available'}
+
+
+
+ {/* Digital Signature Info */}
+ {reference?.id && String(reference.id).startsWith('ref_') && (
+
+
Digital Signature
+
+ This reference has been digitally signed for authenticity verification.
+
+
+ )}
+
+
+
+ onOpenChange(false)}
+ className="border-2 border-fig/30 text-fig/70 hover:bg-fig-10 hover:border-fig/40 font-bold h-11 px-8"
+ >
+ Close
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/platforms/eReputation/client/src/components/modals/self-calculation-modal.tsx b/platforms/eReputation/client/src/components/modals/self-calculation-modal.tsx
new file mode 100644
index 00000000..90bd4e47
--- /dev/null
+++ b/platforms/eReputation/client/src/components/modals/self-calculation-modal.tsx
@@ -0,0 +1,254 @@
+import { useState, useEffect } from "react";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { apiRequest } from "@/lib/queryClient";
+import { useToast } from "@/hooks/use-toast";
+import { isUnauthorizedError } from "@/lib/authUtils";
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Progress } from "@/components/ui/progress";
+import ViewReputationModal from "./view-reputation-modal";
+
+interface SelfCalculationModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+const ANALYSIS_STEPS = [
+ { label: "Analyzing post interactions", platform: "Pictique" },
+ { label: "Evaluating social engagement", platform: "Blabsy" },
+ { label: "Processing positive feedback", platform: "Comment Likes" },
+ { label: "Processing negative feedback", platform: "Comment Dislikes" },
+ { label: "Measuring social interactions", platform: "Social Interactions" },
+ { label: "Finalizing eReputation score", platform: "Calculating" }
+];
+
+const ALL_VARIABLES = ["comment-history", "references", "qualifications", "profile-completeness", "engagement", "activity-frequency"];
+
+export default function SelfCalculationModal({ open, onOpenChange }: SelfCalculationModalProps) {
+ const [isCalculating, setIsCalculating] = useState(false);
+ const [currentStep, setCurrentStep] = useState(0);
+ const [progress, setProgress] = useState(0);
+ const [showViewModal, setShowViewModal] = useState(false);
+ const [reputationResult, setReputationResult] = useState(null);
+ const { toast } = useToast();
+ const queryClient = useQueryClient();
+
+ // Progress simulation effect
+ useEffect(() => {
+ if (isCalculating && currentStep < ANALYSIS_STEPS.length) {
+ const timer = setTimeout(() => {
+ setCurrentStep(prev => prev + 1);
+ setProgress(prev => Math.min(100, prev + (100 / ANALYSIS_STEPS.length)));
+ }, 800 + Math.random() * 400); // Random delay between 800-1200ms for realism
+
+ return () => clearTimeout(timer);
+ }
+ }, [isCalculating, currentStep]);
+
+ const calculateMutation = useMutation({
+ mutationFn: async () => {
+ const response = await apiRequest("POST", "/api/reputation/calculate", {
+ targetType: "self",
+ targetId: null,
+ targetName: null,
+ variables: ALL_VARIABLES
+ });
+ return response.json();
+ },
+ onError: (error) => {
+ setIsCalculating(false);
+ setCurrentStep(0);
+ setProgress(0);
+
+ if (isUnauthorizedError(error)) {
+ toast({
+ title: "Unauthorized",
+ description: "You are logged out. Logging in again...",
+ variant: "destructive",
+ });
+ setTimeout(() => {
+ window.location.href = "/api/login";
+ }, 500);
+ return;
+ }
+ toast({
+ title: "Calculation Failed",
+ description: error instanceof Error ? error.message : "Failed to calculate reputation",
+ variant: "destructive",
+ });
+ },
+ });
+
+ const handleStartCalculation = () => {
+ setIsCalculating(true);
+ setCurrentStep(0);
+ setProgress(0);
+ calculateMutation.mutate();
+ };
+
+ const handleCloseModal = () => {
+ // Reset all states when closing
+ setIsCalculating(false);
+ setCurrentStep(0);
+ setProgress(0);
+ setReputationResult(null);
+ onOpenChange(false);
+ };
+
+ // When progress completes, wait a bit then show results
+ useEffect(() => {
+ if (currentStep >= ANALYSIS_STEPS.length && isCalculating && calculateMutation.data) {
+ // Small delay to show 100% completion
+ setTimeout(() => {
+ setReputationResult(calculateMutation.data);
+ setIsCalculating(false);
+ setShowViewModal(true);
+
+ // Update dashboard queries
+ queryClient.invalidateQueries({ queryKey: ["/api/dashboard/stats"] });
+ queryClient.invalidateQueries({ queryKey: ["/api/dashboard/activities"] });
+ }, 500);
+ }
+ }, [currentStep, isCalculating, calculateMutation.data, queryClient]);
+
+ return (
+
+
+
+
+
+
+ Calculate My eReputation
+ Calculate your current eReputation throughout the W3DS
+
+
+
+
+
+ {!isCalculating ? (
+ // Initial state - ready to start calculation
+
+
+
+
+
Ready to Calculate
+
+ We'll analyze your eReputation across multiple post-platforms including likes, dislikes, and engagement metrics.
+
+
+
+
+
+
+
+
+
Analysis includes all eReputation factors automatically
+
+
+
+ ) : (
+ // Calculating state - show progress
+
+
+
Calculating Your eReputation
+
+ {currentStep < ANALYSIS_STEPS.length
+ ? ANALYSIS_STEPS[currentStep].label
+ : "Calculation complete!"
+ }
+
+
+
+ {/* Progress Bar */}
+
+
+
+ Progress
+ {Math.round(progress)}%
+
+
+
+ {/* Current Platform */}
+ {currentStep < ANALYSIS_STEPS.length && (
+
+
+
+
+
+ {ANALYSIS_STEPS[currentStep].platform}
+
+
+ {ANALYSIS_STEPS[currentStep].label}
+
+
+
+
+ )}
+
+ {/* Steps completed */}
+
+ {ANALYSIS_STEPS.slice(0, currentStep).map((step, index) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+
+ Cancel
+
+
+ {isCalculating ? (
+ <>
+
+ Calculating...
+ >
+ ) : (
+ "Calculate eReputation"
+ )}
+
+
+
+
+
+ {/* View Reputation Modal */}
+ {
+ setShowViewModal(open);
+ if (!open) {
+ handleCloseModal();
+ }
+ }}
+ reputationData={reputationResult}
+ />
+
+ );
+}
diff --git a/platforms/eReputation/client/src/components/modals/view-reputation-modal.tsx b/platforms/eReputation/client/src/components/modals/view-reputation-modal.tsx
new file mode 100644
index 00000000..ef2622ae
--- /dev/null
+++ b/platforms/eReputation/client/src/components/modals/view-reputation-modal.tsx
@@ -0,0 +1,111 @@
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+
+interface ViewReputationModalProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ reputationData: {
+ score: string;
+ confidence: string;
+ analysis: string;
+ targetName?: string;
+ } | null;
+}
+
+export default function ViewReputationModal({
+ open,
+ onOpenChange,
+ reputationData
+}: ViewReputationModalProps) {
+ if (!reputationData) return null;
+
+ const score = parseFloat(reputationData.score);
+ const confidence = parseFloat(reputationData.confidence);
+
+ // Score color based on value
+ const getScoreColor = (score: number) => {
+ if (score >= 8) return "text-green-600";
+ if (score >= 6) return "text-yellow-600";
+ return "text-red-600";
+ };
+
+ // Score gradient for circle
+ const getScoreGradient = (score: number) => {
+ if (score >= 8) return "from-green-500 to-green-600";
+ if (score >= 6) return "from-yellow-500 to-yellow-600";
+ return "from-red-500 to-red-600";
+ };
+
+ return (
+
+
+
+
+
+
+
+ eReputation Results
+
+
+
+
+
+
+ {/* Score Display */}
+
+
+ {/* Background circle */}
+
+
+ {/* Score circle with gradient */}
+
+
+
+ {score.toFixed(1)}
+
+
+ out of 10
+
+
+
+
+
+
+
+ {reputationData.targetName ? `${reputationData.targetName}'s eReputation` : "Your eReputation"}
+
+
+ Confidence: {Math.round(confidence * 100)}%
+
+
+
+
+ {/* Analysis */}
+
+
Analysis
+
+ {reputationData.analysis}
+
+
+
+
+
+
+ onOpenChange(false)}
+ className="bg-fig hover:bg-fig/90 text-white font-bold h-11 sm:h-12 px-8 shadow-lg hover:shadow-xl transition-all duration-300"
+ >
+ Close
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/platforms/eReputation/client/src/components/ui/accordion.tsx b/platforms/eReputation/client/src/components/ui/accordion.tsx
new file mode 100644
index 00000000..e6a723d0
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/accordion.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/platforms/eReputation/client/src/components/ui/alert-dialog.tsx b/platforms/eReputation/client/src/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..8722561c
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,139 @@
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/platforms/eReputation/client/src/components/ui/alert.tsx b/platforms/eReputation/client/src/components/ui/alert.tsx
new file mode 100644
index 00000000..41fa7e05
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/platforms/eReputation/client/src/components/ui/aspect-ratio.tsx b/platforms/eReputation/client/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 00000000..c4abbf37
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/platforms/eReputation/client/src/components/ui/avatar.tsx b/platforms/eReputation/client/src/components/ui/avatar.tsx
new file mode 100644
index 00000000..51e507ba
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/platforms/eReputation/client/src/components/ui/badge.tsx b/platforms/eReputation/client/src/components/ui/badge.tsx
new file mode 100644
index 00000000..f000e3ef
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+ outline: "text-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/platforms/eReputation/client/src/components/ui/breadcrumb.tsx b/platforms/eReputation/client/src/components/ui/breadcrumb.tsx
new file mode 100644
index 00000000..60e6c96f
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode
+ }
+>(({ ...props }, ref) => )
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More
+
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/platforms/eReputation/client/src/components/ui/button.tsx b/platforms/eReputation/client/src/components/ui/button.tsx
new file mode 100644
index 00000000..36496a28
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/platforms/eReputation/client/src/components/ui/calendar.tsx b/platforms/eReputation/client/src/components/ui/calendar.tsx
new file mode 100644
index 00000000..2174f710
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/calendar.tsx
@@ -0,0 +1,68 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+export type CalendarProps = React.ComponentProps
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ )
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
diff --git a/platforms/eReputation/client/src/components/ui/card.tsx b/platforms/eReputation/client/src/components/ui/card.tsx
new file mode 100644
index 00000000..f62edea5
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/platforms/eReputation/client/src/components/ui/carousel.tsx b/platforms/eReputation/client/src/components/ui/carousel.tsx
new file mode 100644
index 00000000..9c2b9bf3
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/carousel.tsx
@@ -0,0 +1,260 @@
+import * as React from "react"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
+>(
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return
+ }
+
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return
+ }
+
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+ }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+
+ Previous slide
+
+ )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+
+ Next slide
+
+ )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/platforms/eReputation/client/src/components/ui/chart.tsx b/platforms/eReputation/client/src/components/ui/chart.tsx
new file mode 100644
index 00000000..39fba6d6
--- /dev/null
+++ b/platforms/eReputation/client/src/components/ui/chart.tsx
@@ -0,0 +1,365 @@
+"use client"
+
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a ")
+ }
+
+ return context
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+