-
Notifications
You must be signed in to change notification settings - Fork 4
feat: marketplace #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: marketplace #345
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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) | ||
|
||
# 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 |
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" /> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove 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
Suggested change
🤖 Prompt for AI Agents
|
||||||
<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> |
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; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Button onClick={() => setShowModal(true)} className={buttonClassName} type="button"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<DashboardModal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uppy={uppy} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
open={showModal} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onRequestClose={() => setShowModal(false)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proudlyDisplayPoweredByUppy={false} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
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 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.Would you like a short “Setup” section PR to add secure seeding steps?
📝 Committable suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
32-32: Bare URL used
(MD034, no-bare-urls)
🤖 Prompt for AI Agents