diff --git a/platforms/marketplace/.gitignore b/platforms/marketplace/.gitignore new file mode 100644 index 00000000..f9ba7f8b --- /dev/null +++ b/platforms/marketplace/.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/marketplace/README.md b/platforms/marketplace/README.md new file mode 100644 index 00000000..8d8148cc --- /dev/null +++ b/platforms/marketplace/README.md @@ -0,0 +1,104 @@ +# Overview + +This is a full-stack web application built as an app marketplace or directory. The platform allows users to browse and review applications, while providing administrative capabilities for managing app listings. The application features a public-facing frontend for discovering apps and submitting reviews, alongside an admin dashboard for content management. + +# User Preferences + +Preferred communication style: Simple, everyday language. + +# Recent Changes (Latest First) + +## Admin Dashboard Improvements (August 11, 2025) +- Simplified admin login page to basic card - removed registration form, information section, and security notices +- Fixed desktop layout for cleaner, more functional admin dashboard +- Updated "View Public Site" button to open in new tab using target="_blank" +- Renamed "Marketplace Apps" to "Post-Platforms" throughout admin interface +- Added pagination system with 10 items per page for better data management +- Implemented pagination controls with Previous/Next buttons and numbered pages +- Added pagination info showing current range of displayed items +- Downloaded W3DS logo locally and recolored from gray to correct MetaState brand purple (#D9B3FF) +- Removed search functionality from navigation header for cleaner design +- Reduced hero section and categories padding for more compact layout +- Updated main description to "MetaState Post-Platforms for sovereign control of your data" + +## Admin Interface Rebranding (August 11, 2025) +- Admin login page completely rebranded with MetaState Foundation styling +- Login and register buttons updated with lime green styling (HSL 85,100%,85%) +- Admin dashboard header modernized with bold typography and branded colors +- Stats cards redesigned with custom rounded containers using MetaState purple and green +- Replaced shadcn Card components with custom styled div containers +- Added hover effects and scaling micro-interactions to buttons +- Fixed JSX syntax errors during rebranding process +- Maintained functional admin authentication system (admin@marketplace.com / admin123) + +# System Architecture + +## Frontend Architecture +- **Framework**: React with TypeScript using Vite as the build tool +- **Routing**: Wouter for client-side navigation +- **UI Components**: Radix UI primitives with shadcn/ui component library +- **Styling**: Tailwind CSS with custom design tokens and CSS variables +- **State Management**: TanStack Query (React Query) for server state management +- **Authentication**: Context-based auth provider with session management + +## Backend Architecture +- **Runtime**: Node.js with Express.js framework +- **Language**: TypeScript with ES modules +- **Authentication**: Passport.js with local strategy using session-based auth +- **Password Security**: Built-in crypto module with scrypt for password hashing +- **Session Storage**: In-memory store for development (MemoryStore) +- **API Design**: RESTful endpoints with JSON responses + +## Database Architecture +- **Database**: PostgreSQL via Neon serverless +- **ORM**: Drizzle ORM with connection pooling +- **Schema Management**: Drizzle migrations with schema definitions in TypeScript +- **Tables**: Users, apps, and reviews with proper foreign key relationships +- **Features**: UUID primary keys, automatic timestamps, cascade deletions + +## File Storage +- **Object Storage**: Google Cloud Storage integration +- **File Uploads**: Uppy file uploader with drag-and-drop interface +- **Access Control**: Custom ACL system for object permissions +- **Storage Client**: Google Cloud Storage SDK with external account credentials via Replit sidecar + +## Development Environment +- **Hot Reload**: Vite development server with HMR +- **Error Handling**: Runtime error overlay for development +- **Logging**: Custom request/response logging middleware +- **Build Process**: Vite for frontend bundling, esbuild for server bundling + +# External Dependencies + +## Core Technologies +- **React**: Frontend framework with hooks and context API +- **Express.js**: Web application framework for Node.js +- **PostgreSQL**: Primary database via Neon serverless platform +- **Drizzle ORM**: Type-safe database operations and migrations + +## Authentication & Security +- **Passport.js**: Authentication middleware with local strategy +- **Express Session**: Session management with configurable stores +- **Node.js Crypto**: Built-in cryptographic functions for password hashing + +## File Management +- **Google Cloud Storage**: Object storage service for file uploads +- **Uppy**: Modern file uploader with multiple plugins +- **AWS S3 Plugin**: Uppy plugin for S3-compatible storage uploads + +## UI & Styling +- **Radix UI**: Headless UI primitives for accessibility +- **Tailwind CSS**: Utility-first CSS framework +- **Lucide React**: SVG icon library +- **shadcn/ui**: Pre-built component library + +## Development Tools +- **Vite**: Frontend build tool and development server +- **TypeScript**: Static type checking for JavaScript +- **TanStack Query**: Data fetching and caching library +- **Wouter**: Minimalist routing library for React + +## Hosting & Deployment +- **Replit**: Development and hosting platform +- **Neon Database**: Serverless PostgreSQL hosting +- **Google Cloud Platform**: Object storage and authentication services \ No newline at end of file diff --git a/platforms/marketplace/client/index.html b/platforms/marketplace/client/index.html new file mode 100644 index 00000000..3a9924c6 --- /dev/null +++ b/platforms/marketplace/client/index.html @@ -0,0 +1,14 @@ + + + + + + W3DS Marketplace - Discover the MetaState + + +
+ + + + + \ No newline at end of file diff --git a/platforms/marketplace/client/src/App.tsx b/platforms/marketplace/client/src/App.tsx new file mode 100644 index 00000000..9b098bbd --- /dev/null +++ b/platforms/marketplace/client/src/App.tsx @@ -0,0 +1,39 @@ +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 { AuthProvider } from "@/hooks/use-auth"; +import { ProtectedRoute } from "@/lib/protected-route"; +import HomePage from "@/pages/home-page"; +import AppDetailPage from "@/pages/app-detail"; +import AdminDashboard from "@/pages/admin-dashboard"; +import AuthPage from "@/pages/auth-page"; +import NotFound from "@/pages/not-found"; + +function Router() { + return ( + + + + + + + + ); +} + +function App() { + return ( + + + + + + + + + ); +} + +export default App; diff --git a/platforms/marketplace/client/src/components/ObjectUploader.tsx b/platforms/marketplace/client/src/components/ObjectUploader.tsx new file mode 100644 index 00000000..c9617095 --- /dev/null +++ b/platforms/marketplace/client/src/components/ObjectUploader.tsx @@ -0,0 +1,67 @@ +import { useState } from "react"; +import type { ReactNode } from "react"; +import Uppy from "@uppy/core"; +import { DashboardModal } from "@uppy/react"; +import "@uppy/core/dist/style.min.css"; +import "@uppy/dashboard/dist/style.min.css"; +import AwsS3 from "@uppy/aws-s3"; +import type { UploadResult } from "@uppy/core"; +import { Button } from "@/components/ui/button"; + +interface ObjectUploaderProps { + maxNumberOfFiles?: number; + maxFileSize?: number; + onGetUploadParameters: () => Promise<{ + method: "PUT"; + url: string; + }>; + onComplete?: ( + result: UploadResult, Record> + ) => void; + buttonClassName?: string; + children: ReactNode; +} + +export function ObjectUploader({ + maxNumberOfFiles = 1, + maxFileSize = 10485760, // 10MB default + onGetUploadParameters, + onComplete, + buttonClassName, + children, +}: ObjectUploaderProps) { + const [showModal, setShowModal] = useState(false); + const [uppy] = useState(() => + new Uppy({ + restrictions: { + maxNumberOfFiles, + maxFileSize, + allowedFileTypes: ['image/*'], + }, + autoProceed: false, + }) + .use(AwsS3, { + shouldUseMultipart: false, + getUploadParameters: onGetUploadParameters, + }) + .on("complete", (result) => { + onComplete?.(result); + setShowModal(false); + }) + ); + + return ( +
+ + + setShowModal(false)} + proudlyDisplayPoweredByUppy={false} + /> +
+ ); +} diff --git a/platforms/marketplace/client/src/components/ui/accordion.tsx b/platforms/marketplace/client/src/components/ui/accordion.tsx new file mode 100644 index 00000000..e6a723d0 --- /dev/null +++ b/platforms/marketplace/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/marketplace/client/src/components/ui/alert-dialog.tsx b/platforms/marketplace/client/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..8722561c --- /dev/null +++ b/platforms/marketplace/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/marketplace/client/src/components/ui/alert.tsx b/platforms/marketplace/client/src/components/ui/alert.tsx new file mode 100644 index 00000000..41fa7e05 --- /dev/null +++ b/platforms/marketplace/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/marketplace/client/src/components/ui/aspect-ratio.tsx b/platforms/marketplace/client/src/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..c4abbf37 --- /dev/null +++ b/platforms/marketplace/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/marketplace/client/src/components/ui/avatar.tsx b/platforms/marketplace/client/src/components/ui/avatar.tsx new file mode 100644 index 00000000..51e507ba --- /dev/null +++ b/platforms/marketplace/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/marketplace/client/src/components/ui/badge.tsx b/platforms/marketplace/client/src/components/ui/badge.tsx new file mode 100644 index 00000000..f000e3ef --- /dev/null +++ b/platforms/marketplace/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/marketplace/client/src/components/ui/breadcrumb.tsx b/platforms/marketplace/client/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..60e6c96f --- /dev/null +++ b/platforms/marketplace/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) =>