Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions platforms/marketplace/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
104 changes: 104 additions & 0 deletions platforms/marketplace/README.md
Original file line number Diff line number Diff line change
@@ -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 ([email protected] / admin123)

Comment on lines +32 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove hardcoded admin credentials from docs

Publishing [email protected] / admin123 invites credential reuse and accidental deployment with defaults. Replace with placeholders and document a secure setup/seed process.

- - Maintained functional admin authentication system ([email protected] / admin123)
+ - Maintained functional admin authentication system (use environment-seeded admin; see “Setup”)

Would you like a short “Setup” section PR to add secure seeding steps?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Maintained functional admin authentication system (admin@marketplace.com / admin123)
- Maintained functional admin authentication system (use environment-seeded admin; see “Setup”)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

32-32: Bare URL used

(MD034, no-bare-urls)

🤖 Prompt for AI Agents
In platforms/marketplace/README.md around lines 32 to 33, remove the hardcoded
admin credentials and replace them with placeholders (e.g. ADMIN_EMAIL and
ADMIN_PASSWORD) and a short “Setup”/“Seeding” instruction: explain how to create
an admin securely (use env vars or a CLI/seeder script, generate a strong
password, run a one-time migration/seeder command, and rotate/change the
password on first login), and add a note to never commit real credentials to
source control. Ensure the README shows placeholder values, points to the
seeder/migration command or script, and documents where to store credentials
(e.g. CI secrets or vault) for deployments.

# 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
14 changes: 14 additions & 0 deletions platforms/marketplace/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove maximum-scale=1 for accessibility (disables zoom).

This blocks pinch-zoom and can violate WCAG expectations.

-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
🤖 Prompt for AI Agents
In platforms/marketplace/client/index.html around line 5, the meta viewport tag
includes maximum-scale=1 which disables pinch-zoom and harms accessibility;
remove maximum-scale=1 (and any user-scalable=no) from the content attribute so
the viewport allows zooming, leaving something like width=device-width,
initial-scale=1.0 (and optionally add a comment explaining why zoom must remain
enabled).

<title>W3DS Marketplace - Discover the MetaState</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions platforms/marketplace/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Switch>
<Route path="/" component={HomePage} />
<Route path="/app/:id" component={AppDetailPage} />
<ProtectedRoute path="/admin" component={AdminDashboard} />
<Route path="/admin/auth" component={AuthPage} />
<Route component={NotFound} />
</Switch>
);
}

function App() {
return (
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<AuthProvider>
<Toaster />
<Router />
</AuthProvider>
</TooltipProvider>
</QueryClientProvider>
);
}

export default App;
67 changes: 67 additions & 0 deletions platforms/marketplace/client/src/components/ObjectUploader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from "react";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add useEffect for proper Uppy lifecycle management.

You create an Uppy instance but never close it on unmount, leaking listeners/state across mounts.

-import { useState } from "react";
+import { useState, useEffect } from "react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useState } from "react";
import { useState, useEffect } from "react";
🤖 Prompt for AI Agents
In platforms/marketplace/client/src/components/ObjectUploader.tsx around line 1,
the component creates an Uppy instance but never tears it down, leaking
listeners and state across mounts; wrap Uppy creation inside a useEffect (or
create it once with useRef and initialize in useEffect) and in the effect's
cleanup call uppy.close() (and null out any state/ref if used), ensure the
effect has an appropriate dependency array so an Uppy instance is only created
once per component lifecycle, and update any setState usage to happen inside the
effect after creating Uppy.

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<string, unknown>, Record<string, unknown>>
) => 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);
})
);
Comment on lines +34 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Close Uppy on unmount and surface upload errors.

Ensure cleanup and basic error propagation; prevents memory leaks and silent failures.

   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);
       })
+      .on("upload-error", (_file, error) => {
+        console.error("Upload failed:", error);
+      })
   );
+
+  useEffect(() => {
+    return () => {
+      uppy.close();
+    };
+  }, [uppy]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
})
);
const [uppy] = useState(() =>
new Uppy({
restrictions: {
maxNumberOfFiles,
maxFileSize,
allowedFileTypes: ['image/*'],
},
autoProceed: false,
})
.use(AwsS3, {
getUploadParameters: onGetUploadParameters,
})
.on("complete", (result) => {
onComplete?.(result);
setShowModal(false);
})
.on("upload-error", (_file, error) => {
console.error("Upload failed:", error);
})
);
useEffect(() => {
return () => {
uppy.close();
};
}, [uppy]);
🤖 Prompt for AI Agents
In platforms/marketplace/client/src/components/ObjectUploader.tsx around lines
34 to 51, the Uppy instance is created but not cleaned up and upload errors
aren't surfaced; add a useEffect that registers an "error" event handler on uppy
to call an onError callback (or at least console.error) and returns a cleanup
function that removes the handler and closes/destroys the uppy instance
(uppy.close() or uppy.destroy()) on unmount to prevent memory leaks and surface
upload failures to the parent.


return (
<div>
<Button onClick={() => setShowModal(true)} className={buttonClassName} type="button">
{children}
</Button>

<DashboardModal
uppy={uppy}
open={showModal}
onRequestClose={() => setShowModal(false)}
proudlyDisplayPoweredByUppy={false}
/>
</div>
);
}
56 changes: 56 additions & 0 deletions platforms/marketplace/client/src/components/ui/accordion.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"

const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName

const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))

AccordionContent.displayName = AccordionPrimitive.Content.displayName

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
Loading
Loading