Skip to content

Conversation

sosweetham
Copy link
Member

@sosweetham sosweetham commented Oct 14, 2025

Description of change

Issue Number

Type of change

  • Breaking (any change that would cause existing functionality to not work as expected)
  • New (a change which implements a new feature)
  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)
  • Docs (changes to the documentation)
  • Chore (refactoring, build scripts or anything else that isn't user-facing)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Marketplace demo mode with built-in app catalog and richer app detail pages.
  • Changes

    • Home and detail pages use local/mock data for faster, simplified browsing.
    • Admin route and header simplified; admin area no longer requires runtime auth.
  • Removed/Disabled

    • Authentication, reviews/ratings, file upload flows, and server-side storage/DB features disabled or removed.
  • Chores

    • Simplified server logging, static asset serving, and config cleanups.

@sosweetham sosweetham requested a review from coodos as a code owner October 14, 2025 03:05
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Removes backend DB, auth, storage, ACL, and many server routes; replaces client runtime auth and data fetching with static stubs and local JSON/types; drops ProtectedRoute in favor of Route for /admin; prunes dependencies and build config; server now serves static assets and a minimal /api/health endpoint.

Changes

Cohort / File(s) Summary
Routing & Auth (client)
platforms/marketplace/client/src/App.tsx, platforms/marketplace/client/src/lib/protected-route.tsx, platforms/marketplace/client/src/hooks/use-auth.tsx, platforms/marketplace/client/src/pages/auth-page.tsx
Removed ProtectedRoute; replaced /admin route with plain Route; removed dynamic auth flow and replaced it with a static AuthContext (user:null); auth page replaced with “Authentication Disabled” UI.
Admin UI
platforms/marketplace/client/src/pages/admin-dashboard.tsx
Removed logout flow and dynamic user actions; header shows static “Admin”; changed import source to local @/types.
Marketplace UI → Static Data
platforms/marketplace/client/src/pages/home-page.tsx, platforms/marketplace/client/src/pages/app-detail.tsx, platforms/marketplace/client/src/data/apps.json, platforms/marketplace/client/src/types.ts, platforms/marketplace/client/src/components/ObjectUploader.tsx
Replaced API queries/mutations with local apps.json and new types.ts; simplified app list/detail UIs; reviews and uploads reduced to static placeholders/stubs; ObjectUploader reduced to a stub button.
Server: routes, auth, storage, DB, ACL removed
platforms/marketplace/server/index.ts, platforms/marketplace/server/routes.ts, platforms/marketplace/server/auth.ts, platforms/marketplace/server/db.ts, platforms/marketplace/server/storage.ts, platforms/marketplace/server/objectStorage.ts, platforms/marketplace/server/objectAcl.ts
Deleted auth/session, DB initialization, storage and ACL modules, and complex route handlers; server now serves /assets, logs requests, and exposes a minimal /api/health endpoint.
Build & Config pruning
platforms/marketplace/package.json, platforms/marketplace/tsconfig.json, platforms/marketplace/vite.config.ts, platforms/marketplace/drizzle.config.ts, platforms/marketplace/shared/schema.ts, platforms/marketplace/server/vite.ts
Removed many server-side dependencies and drizzle-related config/scripts; narrowed tsconfig includes/paths; updated Vite aliases and publicDir; replaced nanoid cache-bust with Date.now; deleted shared DB schema and drizzle config.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Router as Client Router
  participant Admin as AdminDashboard

  Note over Router: Before
  User->>Router: Navigate /admin
  Router->>Router: ProtectedRoute → useAuth
  alt Loading
    Router-->>User: Show loader
  else Not admin
    Router-->>User: Redirect /admin/auth
  else Admin
    Router->>Admin: Render
  end

  Note over Router: After
  User->>Router: Navigate /admin
  Router->>Admin: Render (no auth gate)
Loading
sequenceDiagram
  autonumber
  actor Client
  participant Server
  participant Health as /api/health

  Note over Server: Before
  Client->>Server: APIs (auth, apps, reviews, storage)
  Server-->>Client: Data / mutations

  Note over Server: After
  Client->>Health: GET /api/health
  Health-->>Client: { status: "ok" }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Fix/marketplace #366 — Appears to contain the same set of marketplace simplifications and routing/auth changes.
  • feat: marketplace #345 — Introduced the marketplace auth/DB/storage features that this PR strips back.

Suggested labels

bug

Suggested reviewers

  • sosweetham

Poem

A rabbit hopped through code so wide,
I chewed the sessions and servers aside.
Now static apps rest in tidy rows,
A health-check hums where the old API goes.
I twitch my nose — light build, light load, off we go! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description only contains the unused template headings and does not include any filled sections such as the issue number, selected change type, testing details, or a completed change checklist, which prevents reviewers from understanding the context, scope, and validation of the changes. Please complete the description by providing the related issue number, selecting the appropriate change type, describing how you tested the changes, and checking off the items in the change checklist to ensure all required information is available for review.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Fix/marketplace” is a generic placeholder that does not convey the substantive changes made in this pull request and fails to highlight the primary modifications such as the removal of authentication and backend storage modules and the introduction of static data stubs for the marketplace, making it unclear to reviewers what the main purpose of this PR is. Please update the title to explicitly summarize the core change—for example, “Remove backend auth and storage, stub marketplace data with static JSON”—so that reviewers can immediately grasp the intent and scope.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e738e4 and 808069c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • platforms/marketplace/package.json (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (13)
platforms/marketplace/src/app/app/[id]/page.tsx (1)

1-15: Consider making this a server component.

This component has no client-side interactivity (no state, effects, or event handlers). Since it only displays the route parameter, it could be a server component for better performance and smaller bundle size.

If converted to a server component, the implementation would be:

-'use client';
-
-import { useParams } from 'next/navigation';
-
-export default function AppDetailPage() {
-  const params = useParams();
-  const id = params.id;
+type Props = {
+  params: Promise<{ id: string }>;
+};
+
+export default async function AppDetailPage({ params }: Props) {
+  const { id } = await params;
 
   return (
     <div className="min-h-screen bg-white p-8">
       <h1 className="text-4xl font-bold">App Detail - {id}</h1>
       <p className="text-gray-600 mt-4">This page will show details for app {id}</p>
     </div>
   );
 }

Based on learnings (Next.js 15 requires awaiting params in server components).

platforms/marketplace/.eslintrc.json (1)

4-5: Consider re-enabling these TypeScript rules after migration.

Disabling @typescript-eslint/no-misused-promises and @typescript-eslint/ban-types weakens type safety:

  • no-misused-promises prevents common async bugs (e.g., passing async functions where sync callbacks are expected, conditions using Promises)
  • ban-types prevents problematic types like Function, Object, {}

While these may be temporarily disabled during migration, they should be re-enabled and code fixed incrementally to maintain type safety.

Consider creating a TODO or tracking issue to re-enable these rules and address violations systematically. Would you like me to help identify the violations that would need fixing?

platforms/marketplace/.env.example (1)

1-7: LGTM! Consider adding more environment variable examples.

The database and GCS configuration examples are clear and well-documented. The commented-out GCS variables appropriately indicate optional configuration.

For completeness, consider documenting additional common Next.js environment variables:

 # Database
 DATABASE_URL=your_database_url_here
 
+# Next.js
+# NODE_ENV=development
+# NEXT_PUBLIC_API_URL=http://localhost:3000
+
 # Google Cloud Storage (if needed)
 # GCS_BUCKET_NAME=your_bucket_name
 # GOOGLE_APPLICATION_CREDENTIALS=path/to/credentials.json
platforms/marketplace/src/app/admin/auth/page.tsx (1)

1-12: Placeholder implementation noted.

The static UI scaffold is correctly structured as a client component. The placeholder text clearly indicates authentication needs implementation.

This page requires authentication implementation. Would you like me to:

  1. Generate a Next.js 15 authentication implementation using best practices (NextAuth.js v5/Auth.js, or Next.js server actions)?
  2. Open a new issue to track the authentication implementation task?
platforms/marketplace/src/app/admin/page.tsx (1)

1-10: Placeholder implementation noted.

The static UI scaffold is correctly structured as a client component with appropriate Next.js App Router conventions.

This dashboard page requires implementation of admin features. Would you like me to:

  1. Generate a dashboard implementation with common admin features (user management, analytics, etc.)?
  2. Open a new issue to track the dashboard implementation task?
platforms/marketplace/src/components/Header.tsx (1)

13-21: Alternative: Use a library for debouncing.

If you prefer a more robust solution, consider using a debounce hook from a library like use-debounce:

npm install use-debounce

Then simplify the implementation:

+'use client';
+
+import { Search } from "lucide-react";
+import { Input } from "@/components/ui/input";
+import { useRouter, useSearchParams } from "next/navigation";
-import { useEffect, useState } from "react";
+import { useState } from "react";
+import { useDebounce } from 'use-debounce';
+
+export function Header() {
+  const router = useRouter();
+  const searchParams = useSearchParams();
+  const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
+  const [debouncedQuery] = useDebounce(searchQuery, 300);
+
-  useEffect(() => {
+  useEffect(() => {
-    const params = new URLSearchParams(searchParams);
+    const params = new URLSearchParams(window.location.search);
-    if (searchQuery) {
-      params.set('q', searchQuery);
+    if (debouncedQuery) {
+      params.set('q', debouncedQuery);
     } else {
       params.delete('q');
     }
-    router.push(`/?${params.toString()}`, { scroll: false });
-  }, [searchQuery, router, searchParams]);
+    router.replace(`/?${params.toString()}`, { scroll: false });
+  }, [debouncedQuery, router]);
platforms/marketplace/README.md (1)

28-49: Add language identifier to fenced code block.

The project structure code block is missing a language identifier, which triggers a markdown linting warning and reduces syntax highlighting support in various markdown renderers.

Apply this diff:

 ### Project Structure
 
-```
+```plaintext
 src/
 ├── app/

Alternatively, use tree or text as the language identifier.

Based on static analysis hints.

platforms/marketplace/lucide-react.d.ts (1)

1-21: Manual icon declarations require maintenance as icon usage grows.

This type declaration file solves React 19 compatibility issues with lucide-react, but only declares 6 icons. As the application grows and uses more icons, each new icon must be manually added here, creating a maintenance burden.

Consider these approaches to reduce maintenance:

Option 1: Monitor lucide-react for official React 19 support

Track the lucide-react package for React 19 peer dependency updates. Once official support is released, remove this declaration file entirely.

Check for updates:

#!/bin/bash
# Check lucide-react package info and React peer dependencies
npm view lucide-react peerDependencies
npm view lucide-react version

Option 2: Use a wildcard export (with caveats)

If many icons are needed, consider a catch-all export:

declare module 'lucide-react' {
  import { ForwardRefExoticComponent, SVGProps, RefAttributes } from 'react';

  export interface LucideProps extends Omit<SVGProps<SVGSVGElement>, 'ref'> {
    size?: string | number;
    absoluteStrokeWidth?: boolean;
  }

  export type LucideIcon = ForwardRefExoticComponent<
    Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
  >;

  // Wildcard for all icons (loses autocomplete but reduces maintenance)
  export const Search: LucideIcon;
  export const Store: LucideIcon;
  // ... keep specific icons for autocomplete where needed
  
  // Or use module augmentation with index signature if TypeScript supports it
}

However, this loses TypeScript autocomplete benefits. The current approach is reasonable for a small, stable icon set.

Based on learnings.

platforms/marketplace/src/app/page.tsx (2)

125-135: Improve accessibility for logo images.

The alt text for app logos uses a generic pattern that may not be sufficiently descriptive for screen reader users. Additionally, when logos are missing, the placeholder <Store /> icon lacks proper labeling.

Apply this diff:

                    {app.logoUrl ? (
                      <img 
                        src={app.logoUrl} 
-                       alt={`${app.name} logo`}
+                       alt={app.name}
                        className="w-16 h-16 rounded-lg object-cover"
                      />
                    ) : (
-                     <div className="w-16 h-16 rounded-lg flex items-center justify-center bg-primary/10">
+                     <div 
+                       className="w-16 h-16 rounded-lg flex items-center justify-center bg-primary/10"
+                       role="img"
+                       aria-label={`${app.name} placeholder icon`}
+                     >
                        <Store className="w-8 h-8 text-primary" />
                      </div>
                    )}

Changes:

  1. Simplified alt text to just the app name (the context "logo" is implied)
  2. Added role="img" and aria-label to the placeholder div for screen reader support

73-87: Consider extracting filter logic into a custom hook.

The filtering logic combines search and category filters inline, which could be extracted for better testability and reusability.

Consider creating a custom hook:

// hooks/use-filtered-apps.ts
export function useFilteredApps(apps: App[], searchQuery: string, category: string) {
  return useMemo(() => {
    return apps.filter(app => {
      const matchesSearch = app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
                           app.description.toLowerCase().includes(searchQuery.toLowerCase());
      const matchesCategory = category === "All Apps" || 
                             app.category.toLowerCase() === category.toLowerCase();
      return matchesSearch && matchesCategory;
    });
  }, [apps, searchQuery, category]);
}

Then use it in the component:

-  const filteredApps = apps.filter(app => {
-    const matchesSearch = app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
-                         app.description.toLowerCase().includes(searchQuery.toLowerCase());
-    const matchesCategory = selectedCategory === "All Apps" || 
-                           app.category.toLowerCase() === selectedCategory.toLowerCase();
-    return matchesSearch && matchesCategory;
-  });
+  const filteredApps = useFilteredApps(apps, searchQuery, selectedCategory);

This improves testability and memoizes the expensive filter operation.

platforms/marketplace/package.json (3)

7-10: Scripts: add type-check; Turbopack flag optional

  • Add a dedicated TypeScript check to CI.
  • The --turbopack flag is optional in Next 15 (already default in dev). Keep if you want explicitness.
   "scripts": {
-    "dev": "next dev --turbopack",
+    "dev": "next dev",
     "build": "next build",
     "start": "next start",
-    "lint": "next lint",
+    "lint": "next lint",
+    "typecheck": "tsc --noEmit",
     "db:push": "drizzle-kit push"
   },

1-12: Mark package private and declare engines

Avoid accidental publish and document the Node version Next 15 requires.

 {
   "name": "marketplace",
   "version": "1.0.0",
+  "private": true,
   "type": "module",
   "license": "MIT",
+  "engines": {
+    "node": ">=18.17"
+  },
+  "packageManager": "[email protected]", // or [email protected]/[email protected] per repo standard
   "scripts": {

94-96: Remove unused optional dependency (likely stale)

bufferutil is typically used by ws. Since ws was removed, this is probably dead weight.

-  "optionalDependencies": {
-    "bufferutil": "^4.0.8"
-  }
+  "optionalDependencies": {}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3e3b19 and e8dcaa7.

⛔ Files ignored due to path filters (2)
  • platforms/marketplace/package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (40)
  • platforms/marketplace/.env.example (1 hunks)
  • platforms/marketplace/.eslintrc.json (1 hunks)
  • platforms/marketplace/README.md (1 hunks)
  • platforms/marketplace/client/index.html (0 hunks)
  • platforms/marketplace/client/src/App.tsx (0 hunks)
  • platforms/marketplace/client/src/hooks/use-auth.tsx (0 hunks)
  • platforms/marketplace/client/src/index.css (0 hunks)
  • platforms/marketplace/client/src/lib/protected-route.tsx (0 hunks)
  • platforms/marketplace/client/src/lib/queryClient.ts (0 hunks)
  • platforms/marketplace/client/src/main.tsx (0 hunks)
  • platforms/marketplace/client/src/pages/admin-dashboard.tsx (0 hunks)
  • platforms/marketplace/client/src/pages/app-detail.tsx (0 hunks)
  • platforms/marketplace/client/src/pages/auth-page.tsx (0 hunks)
  • platforms/marketplace/client/src/pages/home-page.tsx (0 hunks)
  • platforms/marketplace/client/src/pages/not-found.tsx (0 hunks)
  • platforms/marketplace/lucide-react.d.ts (1 hunks)
  • platforms/marketplace/next-env.d.ts (1 hunks)
  • platforms/marketplace/next.config.js (1 hunks)
  • platforms/marketplace/package.json (3 hunks)
  • platforms/marketplace/postcss.config.js (0 hunks)
  • platforms/marketplace/postcss.config.mjs (1 hunks)
  • platforms/marketplace/server/auth.ts (0 hunks)
  • platforms/marketplace/server/db.ts (1 hunks)
  • platforms/marketplace/server/index.ts (0 hunks)
  • platforms/marketplace/server/objectAcl.ts (0 hunks)
  • platforms/marketplace/server/objectStorage.ts (0 hunks)
  • platforms/marketplace/server/routes.ts (0 hunks)
  • platforms/marketplace/server/storage.ts (0 hunks)
  • platforms/marketplace/server/vite.ts (0 hunks)
  • platforms/marketplace/src/app/admin/auth/page.tsx (1 hunks)
  • platforms/marketplace/src/app/admin/page.tsx (1 hunks)
  • platforms/marketplace/src/app/app/[id]/page.tsx (1 hunks)
  • platforms/marketplace/src/app/globals.css (1 hunks)
  • platforms/marketplace/src/app/layout.tsx (1 hunks)
  • platforms/marketplace/src/app/page.tsx (1 hunks)
  • platforms/marketplace/src/app/providers.tsx (1 hunks)
  • platforms/marketplace/src/components/Header.tsx (1 hunks)
  • platforms/marketplace/tailwind.config.ts (1 hunks)
  • platforms/marketplace/tsconfig.json (1 hunks)
  • platforms/marketplace/vite.config.ts (0 hunks)
💤 Files with no reviewable changes (21)
  • platforms/marketplace/postcss.config.js
  • platforms/marketplace/client/src/lib/protected-route.tsx
  • platforms/marketplace/client/src/pages/app-detail.tsx
  • platforms/marketplace/client/src/lib/queryClient.ts
  • platforms/marketplace/vite.config.ts
  • platforms/marketplace/client/src/pages/home-page.tsx
  • platforms/marketplace/client/src/pages/auth-page.tsx
  • platforms/marketplace/server/storage.ts
  • platforms/marketplace/server/vite.ts
  • platforms/marketplace/server/objectAcl.ts
  • platforms/marketplace/client/index.html
  • platforms/marketplace/client/src/main.tsx
  • platforms/marketplace/client/src/hooks/use-auth.tsx
  • platforms/marketplace/server/routes.ts
  • platforms/marketplace/client/src/pages/not-found.tsx
  • platforms/marketplace/server/auth.ts
  • platforms/marketplace/client/src/index.css
  • platforms/marketplace/server/index.ts
  • platforms/marketplace/client/src/App.tsx
  • platforms/marketplace/server/objectStorage.ts
  • platforms/marketplace/client/src/pages/admin-dashboard.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
platforms/marketplace/src/app/providers.tsx (2)
platforms/dreamSync/client/src/lib/queryClient.ts (1)
  • queryClient (44-57)
platforms/marketplace/src/components/ui/toaster.tsx (1)
  • Toaster (11-33)
platforms/marketplace/src/app/layout.tsx (2)
platforms/marketplace/src/app/providers.tsx (1)
  • Providers (8-31)
platforms/marketplace/src/components/Header.tsx (1)
  • Header (8-47)
platforms/marketplace/src/components/Header.tsx (1)
platforms/marketplace/lucide-react.d.ts (1)
  • Search (15-15)
platforms/marketplace/src/app/page.tsx (1)
platforms/marketplace/shared/schema.ts (1)
  • apps (16-30)
platforms/marketplace/server/db.ts (1)
infrastructure/eid-wallet/vitest.workspace.js (1)
  • dirname (8-11)
🪛 markdownlint-cli2 (0.18.1)
platforms/marketplace/README.md

28-28: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint
🔇 Additional comments (10)
platforms/marketplace/tailwind.config.ts (1)

5-8: LGTM! Content paths align with Next.js 15 App Router structure.

The updated content scanning paths correctly target the App Router directories (./src/app/**/*) and components (./src/components/**/*), including MDX file support. This configuration is appropriate for the Next.js migration and follows best practices.

platforms/marketplace/.eslintrc.json (1)

2-2: LGTM!

Extending next/core-web-vitals is the recommended baseline ESLint configuration for Next.js projects.

platforms/marketplace/next-env.d.ts (1)

1-5: LGTM!

This is the standard auto-generated Next.js TypeScript definitions file. The triple-slash references correctly include Next.js types and image types for the project.

platforms/marketplace/postcss.config.mjs (1)

1-6: LGTM!

This is the correct PostCSS configuration for Tailwind CSS v4, which uses a single @tailwindcss/postcss plugin instead of the separate plugins used in v3. The ESM format is appropriate for modern tooling.

platforms/marketplace/src/app/globals.css (2)

1-1: LGTM!

Using @import "tailwindcss" is the correct syntax for Tailwind CSS v4, which replaces the @tailwind directives used in v3.


3-56: LGTM! Well-structured theme system.

The CSS custom properties provide a comprehensive design token system with proper light/dark theme support:

  • Consistent HSL color format enables easy theme manipulation
  • Complete coverage of semantic color tokens (background, foreground, primary, secondary, etc.)
  • Chart colors defined for both themes
  • Standard radius variable for consistent rounded corners

The implementation follows modern theming best practices for Next.js applications.

platforms/marketplace/server/db.ts (1)

6-14: Review necessity of explicit dotenv.config in db.ts
Next.js 15 auto-loads .env at the project root; explicit dotenv.config() and custom __dirname logic are only needed for standalone Node scripts or nonstandard .env locations. I found no imports or direct executions of platforms/marketplace/server/db.ts outside Next.js—please confirm before removing.

platforms/marketplace/src/app/page.tsx (1)

146-157: Add security headers for external links.

External links to app URLs should include rel="noopener noreferrer" to prevent security issues and information leakage. While the store links (lines 161-185) correctly include these attributes, the main app URL link is missing rel="noreferrer".

Apply this diff:

                  {app.url && (
                    <a 
                      href={app.url} 
                      target="_blank" 
-                     rel="noopener noreferrer"
+                     rel="noopener noreferrer"
                      className="w-full"
                    >

Wait, reviewing the code again - the rel="noopener noreferrer" is already present on line 150. Let me cancel this comment.

platforms/marketplace/package.json (2)

14-14: Next 15.4.2 pin looks good

Pin matches eslint-config-next 15.4.2 below. No concerns.


64-64: nanoid addition looks fine

No issues.

Comment on lines 3 to 9
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@shared': './shared',
};
return config;
},
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 | 🟠 Major

Remove redundant webpack alias configuration.

The webpack configuration adds a @shared alias that duplicates the path mapping already defined in tsconfig.json (lines 27-29). Next.js automatically respects TypeScript path mappings, making this webpack customization unnecessary and potentially problematic:

  1. Duplication: The same alias is defined in both places, violating DRY principles.
  2. Relative path hazard: Using './shared' instead of an absolute path can cause resolution issues depending on the webpack context directory.
  3. Maintenance burden: Changes to the alias require updates in two locations.

Apply this diff to remove the redundant configuration:

 /** @type {import('next').NextConfig} */
-const nextConfig = {
-  webpack: (config) => {
-    config.resolve.alias = {
-      ...config.resolve.alias,
-      '@shared': './shared',
-    };
-    return config;
-  },
-};
+const nextConfig = {};
 
 export default nextConfig;

The TypeScript path mapping in tsconfig.json will continue to work correctly for both TypeScript compilation and Next.js bundling.

📝 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
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@shared': './shared',
};
return config;
},
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
🤖 Prompt for AI Agents
In platforms/marketplace/next.config.js around lines 3 to 9, the webpack config
adds a redundant '@shared' alias using a relative './shared' path which
duplicates the tsconfig.json path mapping and can cause resolution issues;
remove the entire addition of the '@shared' alias from config.resolve.alias (or
delete the custom webpack alias block entirely) so Next.js will rely on the
TypeScript path mapping in tsconfig.json; if you must keep a webpack alias
instead of removing it, replace the relative path with an absolute path resolved
from __dirname to avoid context-dependent resolution.

Comment on lines 66 to 69
"react": "19.1.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-dom": "19.1.0",
"react-hook-form": "^7.55.0",
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 | 🔴 Critical

🧩 Analysis chain

React 19: verify peerDeps across UI libs to avoid install/runtime issues

Several libs may still declare React 18 peer ranges (Radix UI, framer-motion, react-icons, next-themes, @uppy/react, react-day-picker, recharts, embla, react-resizable-panels). Check for mismatches now to avoid surprises.


🏁 Script executed:

#!/bin/bash
# Inspect peerDependencies of likely React-bound packages at the exact versions in package.json
set -euo pipefail

# Read deps from this package.json
PKG_JSON="platforms/marketplace/package.json"

# Heuristics: packages that are likely to have react peerDeps
jq -r '
  (.dependencies // {} + .devDependencies // {})
  | to_entries
  | map(select(
      .key
      | test("^@radix-ui/react-")
        or . == "framer-motion"
        or . == "lucide-react"
        or . == "next-themes"
        or . == "@uppy/react"
        or . == "react-day-picker"
        or . == "react-hook-form"
        or . == "react-icons"
        or . == "recharts"
        or . == "embla-carousel-react"
        or . == "react-resizable-panels"
        or test("react$", "i") # catch generic packages ending with react
    ))
  | .[]
  | @tsv
' "$PKG_JSON" | while IFS=$'\t' read -r name version; do
  # Normalize range by removing leading ^ or ~ for fetching a concrete meta; then ask npm registry for the exact version string if range.
  ver_trimmed="${version//#/}"
  ver_trimmed="${ver_trimmed//^/}"
  ver_trimmed="${ver_trimmed//~/}"
  # Fetch metadata for the resolved version (best-effort; ranges may 404)
  url="https://registry.npmjs.org/${name/\\//%2F}/${ver_trimmed}"
  echo "== $name@$version"
  if out="$(curl -fsS "$url")"; then
    echo "$out" | jq -r '
      .peerDependencies // {}
      | to_entries
      | map("\(.key) => \(.value)")
      | .[]? // "no peerDependencies"
    '
  else
    echo "Could not fetch $url (range or private?). Skipping."
  fi
  echo
done

echo "Expectation: All react/react-dom peer ranges should include ^19."

Length of output: 113


🏁 Script executed:

#!/bin/bash
set -euo pipefail

PKG_JSON="platforms/marketplace/package.json"

# Extract relevant React-bound packages
jq -r '
  (.dependencies // {} + .devDependencies // {})
  | to_entries
  | map(select(
      .key
      | test("^@radix-ui/react-")
        or . == "framer-motion"
        or . == "lucide-react"
        or . == "next-themes"
        or . == "@uppy/react"
        or . == "react-day-picker"
        or . == "react-hook-form"
        or . == "react-icons"
        or . == "recharts"
        or . == "embla-carousel-react"
        or . == "react-resizable-panels"
        or test("react$", "i")
    ))
  | .[]
  | "\(.key)\t\(.value)"
' "$PKG_JSON" | while IFS=$'\t' read -r name version; do
  # Strip range prefixes for metadata fetch
  ver="${version#^}"
  ver="${ver#~}"
  url="https://registry.npmjs.org/${name//\//%2F}/${ver}"
  echo "== $name@$version"
  if meta=$(curl -fsS "$url"); then
    echo "$meta" | jq -r '
      .peerDependencies // {}
      | to_entries
      | map("\(.key) => \(.value)")
      | .[]? // "no peerDependencies"
    '
  else
    echo "Could not fetch $url"
  fi
  echo
done

echo "Expectation: All react/react-dom peer ranges should include ^19."

Length of output: 7655


React-19 peerDependencies need updating

  • framer-motion@^11.13.1: react ⇒ ^18.0.0
  • react-day-picker@^8.10.1: react ⇒ ^16.8.0 || ^17.0.0 || ^18.0.0
  • lucide-react@^0.453.0: react ⇒ ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc (no final ^19.0.0)
    Ensure these peer ranges include ^19.0.0 to avoid install/runtime failures.
🤖 Prompt for AI Agents
In platforms/marketplace/package.json around lines 66 to 69, the project lists
react 19.1.0 but some transitive packages have peer dependency ranges that don't
include ^19.0.0; update package.json to ensure install compatibility by either
bumping those direct dependency versions to releases that declare support for
^19.0.0 (e.g., upgrade framer-motion to a version that supports react ^19,
ensure react-day-picker and lucide-react versions include ^19.0.0) or add a
package manager override/resolution (e.g., "overrides" or "resolutions") that
forces those packages' peer ranges to accept ^19.0.0; make sure to run install
and test to confirm no peer dep warnings remain.

Comment on lines 81 to 93
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.1.3",
"@types/connect-pg-simple": "^7.0.3",
"@types/express": "4.17.21",
"@types/express-session": "^1.18.0",
"@types/node": "20.16.11",
"@types/passport": "^1.0.16",
"@types/passport-local": "^1.0.38",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/ws": "^8.5.13",
"@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"drizzle-kit": "^0.30.4",
"esbuild": "^0.25.0",
"eslint": "^9",
"eslint-config-next": "15.4.2",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.17",
"tsx": "^4.19.1",
"typescript": "5.6.3",
"vite": "^5.4.19"
"tailwindcss": "^4",
"typescript": "^5"
},
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 | 🔴 Critical

🧩 Analysis chain

Tailwind v4 + ESLint 9: compat checks

  • Ensure @tailwindcss/typography version is compatible with Tailwind v4; older 0.5.x was for v3.
  • Confirm eslint-config-next 15.4.2 officially supports ESLint 9 in your setup.

🌐 Web query:

Is @tailwindcss/typography compatible with Tailwind CSS v4? What plugin version is required?

💡 Result:

Yes — the official @tailwindcss/typography plugin supports Tailwind CSS v4. Install the package (npm install -D @tailwindcss/typography) and register it in your CSS with the v4 CSS-first syntax:

@import "tailwindcss";
@plugin "@tailwindcss/typography";

(Use the current @tailwindcss/typography release from npm — the plugin’s README shows the v4 installation/usage above.) [1][2]

Sources:
[1] tailwindlabs/tailwindcss-typography README (installation + @plugin example).
[2] Tailkits guide (shows v4 CSS-first install using @plugin).


🌐 Web query:

Does eslint-config-next 15.4.2 support ESLint 9 without additional plugins?

💡 Result:

Yes — Next.js 15 (and its eslint-config-next v15.4.2) supports ESLint 9 out of the box. Next 15 adds ESLint 9 support and the Next docs say eslint-config-next includes the needed plugins/config; in some environments (pnpm, monorepos, or when mixing existing configs) you may still need to install or configure plugins/compat layers manually. [1][2][3]

Sources:

  • Next.js 15 release notes (ESLint 9 support). [1]
  • Next.js ESLint docs (eslint-config-next details). [2]
  • @next/eslint-plugin-next npm page (package info for v15.4.2). [3]

Update @tailwindcss/typography for Tailwind CSS v4 and confirm ESLint support

  • Upgrade the @tailwindcss/typography dependency in platforms/marketplace/package.json from ^0.5.15 to the latest release (>=0.6.0) for v4 compatibility.
  • No action needed for ESLint 9—[email protected] supports it out of the box.
🤖 Prompt for AI Agents
In platforms/marketplace/package.json around lines 81 to 93, the
@tailwindcss/typography dependency is pinned to ^0.5.15 which is incompatible
with Tailwind CSS v4; update the version specifier to a v4-compatible release
(>=0.6.0, e.g. ^0.6.0 or latest) in the dependencies block and then run your
package manager to update the lockfile (npm/yarn/pnpm install) so the lockfile
and node_modules reflect the new version; no changes are required for ESLint as
[email protected] already supports ESLint 9.

Comment on lines 5 to 7
export default function AppDetailPage() {
const params = useParams();
const id = params.id;
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 | 🟡 Minor

Add TypeScript types for route parameters.

The params and id variables lack explicit types. In Next.js 15 App Router with TypeScript, defining the shape of route parameters improves type safety and catches errors at build time.

Apply this diff to add proper typing:

+type Props = {
+  params: Promise<{ id: string }>;
+};
+
-export default function AppDetailPage() {
-  const params = useParams();
-  const id = params.id;
+export default function AppDetailPage() {
+  const params = useParams<{ id: string }>();
+  const id = params.id as string;

Note: In Next.js 15, params in page components are async and should be awaited if passed as props, but useParams() returns them synchronously in client components.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In platforms/marketplace/src/app/app/[id]/page.tsx around lines 5-7, the params
and id from useParams lack TypeScript types; mark the file as a client component
(add "use client" at the top) if you intend to use the hook, then call useParams
with an explicit generic for the route shape (e.g. useParams<{ id: string }>())
and type the id variable (e.g. const id: string | undefined = params?.id) so the
route parameter shape is enforced and avoids unsafe any/implicit any usage.

Comment on lines 16 to 27
return (
<html lang="en">
<body>
<Providers>
<Header />
<div className="min-h-screen bg-background">
{children}
</div>
</Providers>
</body>
</html>
);
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 | 🟠 Major

Move min-h-screen to the body element for proper layout.

The min-h-screen class is currently applied to a div inside the body, which can cause layout issues. In Next.js App Router with Tailwind, the full-height container should be applied directly to the body element to ensure proper viewport height behavior.

Apply this diff:

  return (
    <html lang="en">
-     <body>
+     <body className="min-h-screen bg-background">
        <Providers>
          <Header />
-         <div className="min-h-screen bg-background">
+         <div>
            {children}
          </div>
        </Providers>
      </body>
    </html>
  );
📝 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
return (
<html lang="en">
<body>
<Providers>
<Header />
<div className="min-h-screen bg-background">
{children}
</div>
</Providers>
</body>
</html>
);
return (
<html lang="en">
<body className="min-h-screen bg-background">
<Providers>
<Header />
<div>
{children}
</div>
</Providers>
</body>
</html>
);
🤖 Prompt for AI Agents
In platforms/marketplace/src/app/layout.tsx around lines 16 to 27, the
min-h-screen class (and bg-background) is applied to the inner div instead of
the body; move the className="min-h-screen bg-background" onto the <body>
element and remove those classes from the inner div (keep the div for children
or remove it if unnecessary) so the body itself becomes the full-height
container and layout behaves correctly.

Comment on lines 10 to 71
type App = {
id: string;
name: string;
description: string;
category: string;
logoUrl: string | null;
url?: string;
appStoreUrl?: string;
playStoreUrl?: string;
};

const APPS: App[] = [
{
id: "eid-wallet",
name: "eID for W3DS",
description: "Secure digital identity wallet for W3DS. Maintain sovereign control over your digital identity.",
category: "Identity",
logoUrl: null,
appStoreUrl: "https://apps.apple.com/in/app/eid-for-w3ds/id6747748667",
playStoreUrl: "https://play.google.com/store/apps/details?id=foundation.metastate.eid_wallet",
},
{
id: "blabsy",
name: "Blabsy",
description: "Micro blogging first style application for sharing thoughts across the W3DS ecosystem.",
category: "Social",
logoUrl: null,
url: "https://blabsy.w3ds.metastate.foundation",
},
{
id: "pictique",
name: "Pictique",
description: "Photo sharing first style application for sharing moments across the W3DS ecosystem.",
category: "Social",
logoUrl: null,
url: "https://pictique.w3ds.metastate.foundation",
},
{
id: "evoting",
name: "eVoting",
description: "Secure, transparent, and verifiable electronic voting platform with cryptographic guarantees.",
category: "Governance",
logoUrl: null,
url: "https://evoting.w3ds.metastate.foundation",
},
{
id: "group-charter",
name: "Charter Manager",
description: "Define rules, manage memberships, and ensure transparent governance for your communities.",
category: "Governance",
logoUrl: null,
url: "https://charter.w3ds.metastate.foundation",
},
{
id: "dreamsync",
name: "DreamSync",
description: "Individual discovery platform, find people of interest across the W3DS ecosystem.",
category: "Wellness",
logoUrl: null,
url: "https://dreamsync.w3ds.metastate.foundation",
},
];
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 | 🟠 Major

Hardcoded data doesn't match database schema and should be migrated to API calls.

The App type and APPS array are hardcoded in the component, but a database schema exists at platforms/marketplace/shared/schema.ts (lines 15-29) with a different structure:

Schema fields not in local type:

  • fullDescription, screenshots, status, averageRating, totalReviews, createdAt, updatedAt

Type field differences:

  • Local: url, appStoreUrl, playStoreUrl (all optional)
  • Schema: link (required), no store URLs

Hardcoded data issues:

  1. Data staleness: Changes require code deploys rather than database updates
  2. Type safety: The local App type diverges from the database schema
  3. Scalability: Won't scale beyond a handful of apps

While the README acknowledges that "API routes are not yet implemented," this creates technical debt that should be tracked.

Do you want me to:

  1. Open a tracking issue for implementing the API routes and data fetching?
  2. Generate a migration plan to align the frontend with the database schema?
  3. Provide example Next.js 15 API route implementations for /api/apps?
🤖 Prompt for AI Agents
In platforms/marketplace/src/app/page.tsx lines 10-71, the file defines a local
App type and hardcoded APPS array that diverge from
platforms/marketplace/shared/schema.ts (missing fields like fullDescription,
screenshots, status, averageRating, totalReviews, createdAt, updatedAt and
different link vs url/appStoreUrl/playStoreUrl). Replace the hardcoded data by
removing APPS and instead import or map to the canonical schema type used in
shared/schema.ts (or create a shared TypeScript type), implement data fetching
from an API route (e.g., /api/apps) in this server component (or call an
existing backend service), update the local type to include the required schema
fields (ensure link is required and include the extra metadata), and
create/track a follow-up task to implement the API route and migrate existing
entries so frontend no longer relies on hardcoded values.

Comment on lines 9 to 21
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey }) => {
const res = await fetch(queryKey[0] as string);
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.json();
},
},
},
}));
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 | 🟠 Major

Verify the default queryFn assumptions and consider adding error handling configuration.

The default queryFn implementation makes several assumptions that could lead to runtime issues:

  1. Type safety: queryKey[0] as string assumes the first element is always a valid URL string, but React Query doesn't enforce this at the type level. If a query is misconfigured with a non-string key, this will fail at runtime.

  2. Missing retry and staleTime policies: Unlike the dreamSync example (which sets retry: false, staleTime: Infinity, refetchInterval: false, refetchOnWindowFocus: false), this configuration will use React Query's default retry logic (3 retries with exponential backoff) and aggressive refetching, which may cause unnecessary network requests.

  3. Generic error messages: The error HTTP error! status: ${res.status} doesn't provide actionable context. Consider including the URL and response body when available.

Consider applying this diff to add safer defaults and better error handling:

  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        queryFn: async ({ queryKey }) => {
-         const res = await fetch(queryKey[0] as string);
+         const url = queryKey[0];
+         if (typeof url !== 'string') {
+           throw new Error('Query key must be a string URL');
+         }
+         const res = await fetch(url);
          if (!res.ok) {
-           throw new Error(`HTTP error! status: ${res.status}`);
+           throw new Error(`HTTP ${res.status} fetching ${url}`);
          }
          return res.json();
        },
+       retry: false,
+       refetchOnWindowFocus: false,
+       staleTime: Infinity,
      },
    },
  }));
📝 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 [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey }) => {
const res = await fetch(queryKey[0] as string);
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.json();
},
},
},
}));
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
queryFn: async ({ queryKey }) => {
const url = queryKey[0];
if (typeof url !== 'string') {
throw new Error('Query key must be a string URL');
}
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status} fetching ${url}`);
}
return res.json();
},
retry: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
},
},
}));

Comment on lines 13 to 21
useEffect(() => {
const params = new URLSearchParams(searchParams);
if (searchQuery) {
params.set('q', searchQuery);
} else {
params.delete('q');
}
router.push(`/?${params.toString()}`, { scroll: false });
}, [searchQuery, router, searchParams]);
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 | 🟠 Major

Potential infinite loop and missing debounce for search input.

The useEffect has searchParams in its dependency array while also calling router.push, which updates searchParams. This creates a circular dependency that could cause an infinite render loop:

  1. searchQuery changes → useEffect runs → router.push updates URL → searchParams changes → useEffect runs again.

While Next.js may internally prevent duplicate pushes, this pattern is fragile. Additionally, every keystroke triggers a router push, creating unnecessary history entries.

Apply this diff to fix the circular dependency and add debouncing:

+'use client';
+
+import { Search } from "lucide-react";
+import { Input } from "@/components/ui/input";
+import { useRouter, useSearchParams } from "next/navigation";
+import { useEffect, useState, useRef } from "react";
+
 export function Header() {
   const router = useRouter();
   const searchParams = useSearchParams();
   const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
+  const debounceTimer = useRef<NodeJS.Timeout>();

   useEffect(() => {
+    if (debounceTimer.current) {
+      clearTimeout(debounceTimer.current);
+    }
+
+    debounceTimer.current = setTimeout(() => {
-    const params = new URLSearchParams(searchParams);
+      const params = new URLSearchParams(window.location.search);
-    if (searchQuery) {
-      params.set('q', searchQuery);
-    } else {
-      params.delete('q');
-    }
-    router.push(`/?${params.toString()}`, { scroll: false });
-  }, [searchQuery, router, searchParams]);
+      if (searchQuery) {
+        params.set('q', searchQuery);
+      } else {
+        params.delete('q');
+      }
+      router.replace(`/?${params.toString()}`, { scroll: false });
+    }, 300);
+
+    return () => {
+      if (debounceTimer.current) {
+        clearTimeout(debounceTimer.current);
+      }
+    };
+  }, [searchQuery, router]);

Key changes:

  1. Remove searchParams from deps to break the circular dependency
  2. Use window.location.search instead of searchParams to read current state
  3. Add 300ms debounce to reduce router updates while typing
  4. Use router.replace instead of router.push to avoid polluting history
  5. Cleanup timeout on unmount or when effect re-runs
🤖 Prompt for AI Agents
In platforms/marketplace/src/components/Header.tsx around lines 13 to 21, the
effect currently depends on searchParams and calls router.push which can cause a
circular update loop and triggers router updates on every keystroke; remove
searchParams from the dependency array, read the current query string via
window.location.search to build URLSearchParams, replace router.push with
router.replace, and wrap the router.replace call in a 300ms debounced timer
(clear the timeout on cleanup and before setting a new one) so router updates
are suppressed while typing and do not pollute history.

Comment on lines 30 to 32
"@assets/*": [
"./client/src/assets/*"
]
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 | 🟠 Major

🧩 Analysis chain

Remove or update the stale @assets/* path alias.

The @assets/* path mapping points to ./client/src/assets/*, but the client directory is explicitly excluded on line 55. This is a remnant from the pre-migration Express + Vite structure and will cause resolution failures if the alias is used.

Run this script to check if the alias is referenced anywhere in the codebase:

If no usages are found, apply this diff to remove the stale alias:

       "@shared/*": [
         "./shared/*"
       ],
-      "@assets/*": [
-        "./client/src/assets/*"
-      ]
     },

If usages exist, update the alias to point to the correct location in the new structure (likely ./src/assets/* or ./public/assets/*).


🏁 Script executed:

#!/bin/bash
# Search for usage of @assets/ import paths
rg -n --type=ts --type=tsx '@assets/' src/

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for usage of @assets/ import paths across the repository
rg -n '@assets/' --glob '!.git/**' --glob '!node_modules/**'

Length of output: 59


Remove the stale @assets/* path alias from platforms/marketplace/tsconfig.json
No import references to @assets/ exist in the codebase; you can safely delete the following lines under compilerOptions.paths:

-      "@assets/*": [
-        "./client/src/assets/*"
-      ]
📝 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
"@assets/*": [
"./client/src/assets/*"
]
🤖 Prompt for AI Agents
In platforms/marketplace/tsconfig.json around lines 30 to 32, remove the stale
"@assets/*": ["./client/src/assets/*"] entry from compilerOptions.paths; ensure
JSON remains valid after deletion (remove any now-unnecessary trailing comma or
merge with surrounding entries) and then re-run TypeScript build/IDE to confirm
no path resolutions reference it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
platforms/marketplace/package.json (1)

71-82: Tailwind v3 core + @tailwindcss/vite v4 plugin mismatch (will break builds).

You’re using tailwindcss "^3.4.17" with @tailwindcss/vite "^4.1.3". The Vite plugin is for Tailwind v4 only. Fix by either:

  • Staying on v3: remove @tailwindcss/vite and keep your PostCSS-based setup.
  • Or moving to v4: bump tailwindcss to ^4.x and update plugins (e.g., @tailwindcss/typography to a v4-compatible release), then align config per v4 docs (CSS-first; PostCSS deps optional).

Apply this minimal v3-safe diff:

   "devDependencies": {
@@
-    "@tailwindcss/vite": "^4.1.3",
@@
-    "tailwindcss": "^3.4.17",
+    "tailwindcss": "^3.4.17",
platforms/marketplace/client/src/pages/app-detail.tsx (2)

49-54: Same nesting issue: Link wraps Button (anchor > button).

Use asChild to avoid nested interactive elements.

-          <Link href="/">
-            <Button className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-transform duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
-              Back to marketplace
-            </Button>
-          </Link>
+          <Button
+            asChild
+            className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-transform duration-200"
+            style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
+          >
+            <Link href="/">Back to marketplace</Link>
+          </Button>

Based on learnings


63-69: Same nesting issue for the back button.

<Link> renders an anchor; don't place a <Button> inside. Use asChild.

-        <Link href="/">
-          <Button className="mb-8 text-black font-bold px-6 py-3 rounded-full hover:scale-105 transition-all duration-200 border-2 border-gray-200 bg-white hover:border-black">
-            <ArrowLeft className="w-4 h-4 mr-2" />
-            Back to marketplace
-          </Button>
-        </Link>
+        <Button
+          asChild
+          className="mb-8 text-black font-bold px-6 py-3 rounded-full hover:scale-105 transition-all duration-200 border-2 border-gray-200 bg-white hover:border-black"
+        >
+          <Link href="/">
+            <ArrowLeft className="w-4 h-4 mr-2" />
+            Back to marketplace
+          </Link>
+        </Button>

Based on learnings

🧹 Nitpick comments (6)
platforms/marketplace/server/routes.ts (1)

5-7: Add no-store caching and a HEAD handler for health checks.

Prevents stale responses and supports proxies/LBs that use HEAD.

-  app.get("/api/health", (req, res) => {
-    res.json({ status: "ok", message: "Marketplace server is running" });
-  });
+  app.get("/api/health", (_req, res) => {
+    res.set("Cache-Control", "no-store");
+    res.status(200).json({ status: "ok", message: "Marketplace server is running" });
+  });
+  app.head("/api/health", (_req, res) => {
+    res.set("Cache-Control", "no-store");
+    res.status(200).end();
+  });
platforms/marketplace/package.json (2)

64-64: Consider bumping wouter to ^3.4.0.

Minor improvements and fixes in 3.4.x; no breaking changes within v3.

Based on learnings


10-10: Nice addition: type-check script.

"check": "tsc" is good. Consider using "tsc --noEmit" if you don’t rely on tsconfig emit.

platforms/marketplace/client/src/components/ObjectUploader.tsx (2)

4-14: Consider removing or documenting unused props.

The maxNumberOfFiles, maxFileSize, onGetUploadParameters, and onComplete props are defined in the interface but completely ignored by the stub implementation. This creates confusion about the component's actual API.

Consider one of these approaches:

  1. Remove unused props if this is a permanent stub:

    interface ObjectUploaderProps {
    -  maxNumberOfFiles?: number;
    -  maxFileSize?: number;
    -  onGetUploadParameters?: () => Promise<{
    -    method: "PUT";
    -    url: string;
    -  }>;
    -  onComplete?: (result: any) => void;
      buttonClassName?: string;
      children: ReactNode;
    }
  2. Add a comment explaining these props are reserved for future backend implementation:

    // TODO: Props below are reserved for when backend upload is implemented
    maxNumberOfFiles?: number;
    // ...

21-27: Alert message could be more informative.

The alert message "Upload functionality requires backend database" could better guide users by suggesting alternatives or next steps.

Consider: "Upload functionality requires backend implementation. This is a demo mode with static data."

platforms/marketplace/client/src/App.tsx (1)

18-18: Admin route is now publicly accessible.

The /admin route no longer uses ProtectedRoute, making the admin dashboard accessible without authentication. This is consistent with the authentication stub that always returns null for the user.

Note: Ensure this is the intended behavior for your deployment. If this marketplace will be deployed publicly, you may want to add a different protection mechanism (e.g., environment-based access control, IP allowlist, or re-enable authentication in the future).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8dcaa7 and 1e738e4.

⛔ Files ignored due to path filters (7)
  • platforms/marketplace/assets/blabsy.svg is excluded by !**/*.svg
  • platforms/marketplace/assets/charter.png is excluded by !**/*.png
  • platforms/marketplace/assets/eid-w3ds.png is excluded by !**/*.png
  • platforms/marketplace/assets/evoting.png is excluded by !**/*.png
  • platforms/marketplace/assets/pictique.svg is excluded by !**/*.svg
  • platforms/marketplace/assets/w3dslogo.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • platforms/marketplace/client/src/App.tsx (1 hunks)
  • platforms/marketplace/client/src/components/ObjectUploader.tsx (1 hunks)
  • platforms/marketplace/client/src/data/apps.json (1 hunks)
  • platforms/marketplace/client/src/hooks/use-auth.tsx (1 hunks)
  • platforms/marketplace/client/src/lib/protected-route.tsx (0 hunks)
  • platforms/marketplace/client/src/pages/admin-dashboard.tsx (3 hunks)
  • platforms/marketplace/client/src/pages/app-detail.tsx (4 hunks)
  • platforms/marketplace/client/src/pages/auth-page.tsx (1 hunks)
  • platforms/marketplace/client/src/pages/home-page.tsx (2 hunks)
  • platforms/marketplace/client/src/types.ts (1 hunks)
  • platforms/marketplace/drizzle.config.ts (0 hunks)
  • platforms/marketplace/package.json (1 hunks)
  • platforms/marketplace/server/auth.ts (0 hunks)
  • platforms/marketplace/server/db.ts (0 hunks)
  • platforms/marketplace/server/index.ts (2 hunks)
  • platforms/marketplace/server/objectAcl.ts (0 hunks)
  • platforms/marketplace/server/objectStorage.ts (0 hunks)
  • platforms/marketplace/server/routes.ts (1 hunks)
  • platforms/marketplace/server/storage.ts (0 hunks)
  • platforms/marketplace/server/vite.ts (1 hunks)
  • platforms/marketplace/shared/schema.ts (0 hunks)
  • platforms/marketplace/tsconfig.json (2 hunks)
  • platforms/marketplace/vite.config.ts (2 hunks)
💤 Files with no reviewable changes (8)
  • platforms/marketplace/client/src/lib/protected-route.tsx
  • platforms/marketplace/server/db.ts
  • platforms/marketplace/server/storage.ts
  • platforms/marketplace/server/objectStorage.ts
  • platforms/marketplace/server/auth.ts
  • platforms/marketplace/drizzle.config.ts
  • platforms/marketplace/server/objectAcl.ts
  • platforms/marketplace/shared/schema.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • platforms/marketplace/server/index.ts
🧰 Additional context used
🧬 Code graph analysis (2)
platforms/marketplace/client/src/App.tsx (1)
platforms/marketplace/client/src/pages/admin-dashboard.tsx (1)
  • AdminDashboard (21-683)
platforms/marketplace/client/src/pages/admin-dashboard.tsx (1)
platforms/marketplace/client/src/hooks/use-auth.tsx (1)
  • useAuth (26-32)
🔇 Additional comments (11)
platforms/marketplace/server/routes.ts (1)

5-7: Confirm external health checks updated to /api/health
No in-repo Dockerfile or K8s probe definitions found—ensure your Docker/K8s ingress HEALTHCHECK and liveness/readinessProbe directives point to /api/health.

platforms/marketplace/server/vite.ts (1)

58-58: Cache-busting simplified appropriately for dev server.

The change from nanoid() to Date.now() reduces dependencies while maintaining sufficient cache-busting for a development server with HMR. In the unlikely event that multiple requests occur within the same millisecond, they would receive the same cache key, but this is acceptable for local development use.

platforms/marketplace/tsconfig.json (1)

2-2: LGTM! Shared path removed consistently.

The removal of shared/**/* from the include array and @shared/* from the path mappings aligns with the broader PR objective to eliminate the shared schema layer. This is consistent with the migration to local type definitions.

Also applies to: 19-19

platforms/marketplace/vite.config.ts (1)

26-26: Good addition of publicDir configuration.

Adding explicit publicDir configuration improves clarity about where static assets are served from.

platforms/marketplace/client/src/hooks/use-auth.tsx (2)

3-8: LGTM! Auth simplified consistently with PR objectives.

The authentication context is now a static stub that always returns null for the user, which aligns with the PR's goal of removing backend authentication. The comment on line 3 clearly documents this change.


12-24: Provider implementation is appropriate for static context.

The AuthProvider correctly provides static stub values, maintaining the hook API while removing authentication logic. This allows components to continue using useAuth() without code changes.

platforms/marketplace/client/src/pages/admin-dashboard.tsx (4)

4-4: LGTM! Type import migrated to local definitions.

The change from @shared/schema to @/types is consistent with the removal of the shared schema layer across the codebase.


22-22: Auth usage simplified appropriately.

Removing the logoutMutation destructuring aligns with the simplified auth context that no longer provides logout functionality.


277-277: UI simplified to static admin label.

The header now displays a static "Admin" label instead of user email and logout button, which is consistent with the stub authentication implementation.


40-108: Implement or stub /api/admin/apps endpoints or remove mutation calls in demo mode.

  • No Next.js API route handlers found under pages/api/admin; the CRUD mutations will 404 at runtime.
  • Either restore or stub these routes, or guard mutations behind a “demo/static” flag with graceful user messaging.
  • Ensure UI doesn’t break when these calls fail.
platforms/marketplace/client/src/data/apps.json (1)

1-51: ```shell
#!/bin/bash
echo "References to apps.json in codebase:"
rg -n 'apps.json' -C3 platforms/marketplace/client/src

echo -e "\nImports of apps.json in TS/JS files:"
rg -n "import .*apps.json" -C3 platforms/marketplace/client/src


</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

"name": "DreamSync",
"description": "Individual discovery platform, find people of interest across the W3DS ecosystem.",
"category": "Wellness",
"logoUrl": null,
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 | 🟠 Major

🧩 Analysis chain

Verify null logoUrl is handled gracefully.

The DreamSync app has logoUrl: null. Ensure all components that render app logos handle null values correctly (e.g., showing a fallback icon or placeholder).

Run this script to check logo rendering logic:


🏁 Script executed:

#!/bin/bash
# Search for logo rendering code
echo "Checking logo rendering logic:"
rg -n 'logoUrl|app\.logo' --type=tsx --type=ts platforms/marketplace/client/src/ -C 3

Length of output: 58


🏁 Script executed:

#!/bin/bash
echo "Re-checking logo rendering logic by matching file extensions directly:"
rg -n 'logoUrl|app\.logo' -g '*.tsx' -g '*.ts' platforms/marketplace/client/src/ -C 3

Length of output: 7185


Add fallback UI for null logoUrl

  • In home-page.tsx (around line 119) and app-detail.tsx (around line 73), the {app.logoUrl ? <img …/> : null} branches render nothing when logoUrl is null. Provide a default icon or placeholder in the “else” branch.
🤖 Prompt for AI Agents
In platforms/marketplace/client/src/data/apps.json around line 48 and in the
components home-page.tsx (≈line 119) and app-detail.tsx (≈line 73), the UI
renders nothing when app.logoUrl is null; update those components to render a
consistent fallback (e.g., a default SVG/icon component or styled placeholder
div with accessible alt/title) in the else branch instead of null, ensure the
placeholder respects existing sizing/CSS and includes an aria-label or alt text
for accessibility; keep the conditional as {app.logoUrl ? <img .../> :
<PlaceholderIcon />} and import or create a small reusable PlaceholderIcon
component so both files use the same fallback.

Comment on lines 95 to +118
<div className="flex space-x-4">
<a href={app.link} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
Visit Website
</Button>
</a>
<Button className="text-black font-bold px-6 py-4 rounded-full border-2 border-gray-200 bg-white hover:border-black hover:scale-105 transition-all duration-200">
<Share className="w-5 h-5 mr-2" />
Share
</Button>
{(app as any).appStoreUrl && (app as any).playStoreUrl ? (
<>
<a href={(app as any).appStoreUrl} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
App Store
</Button>
</a>
<a href={(app as any).playStoreUrl} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
Play Store
</Button>
</a>
</>
) : (
<a href={(app as any).url} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
Visit Website
</Button>
</a>
)}
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 | 🟠 Major

Link rendering logic hides single available store links.

Current condition only shows stores when both appStoreUrl and playStoreUrl exist; otherwise it falls back to website. This drops a single available store link and may render a broken website link if url is missing.

Render whichever links are present (one or many), and only show “Visit Website” if url exists. Also avoid any by aligning types (see types.ts comment).

-          <div className="flex space-x-4">
-            {(app as any).appStoreUrl && (app as any).playStoreUrl ? (
-              <>
-                <a href={(app as any).appStoreUrl} target="_blank" rel="noopener noreferrer" className="flex-1">
-                  <Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
-                    <ExternalLink className="w-5 h-5 mr-3" />
-                    App Store
-                  </Button>
-                </a>
-                <a href={(app as any).playStoreUrl} target="_blank" rel="noopener noreferrer" className="flex-1">
-                  <Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
-                    <ExternalLink className="w-5 h-5 mr-3" />
-                    Play Store
-                  </Button>
-                </a>
-              </>
-            ) : (
-              <a href={(app as any).url} target="_blank" rel="noopener noreferrer" className="flex-1">
-                <Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
-                  <ExternalLink className="w-5 h-5 mr-3" />
-                  Visit Website
-                </Button>
-              </a>
-            )}
-          </div>
+          <div className="flex space-x-4">
+            {/* App Store link (optional) */}
+            {(app as any).appStoreUrl && (
+              <Button asChild className="flex-1 w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
+                <a href={(app as any).appStoreUrl} target="_blank" rel="noopener noreferrer">
+                  <ExternalLink className="w-5 h-5 mr-3" />
+                  App Store
+                </a>
+              </Button>
+            )}
+            {/* Play Store link (optional) */}
+            {(app as any).playStoreUrl && (
+              <Button asChild className="flex-1 w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
+                <a href={(app as any).playStoreUrl} target="_blank" rel="noopener noreferrer">
+                  <ExternalLink className="w-5 h-5 mr-3" />
+                  Play Store
+                </a>
+              </Button>
+            )}
+            {/* Website link (optional) */}
+            {(app as any).url && (
+              <Button asChild className="flex-1 w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
+                <a href={(app as any).url} target="_blank" rel="noopener noreferrer">
+                  <ExternalLink className="w-5 h-5 mr-3" />
+                  Visit Website
+                </a>
+              </Button>
+            )}
+          </div>

Based on learnings

📝 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
<div className="flex space-x-4">
<a href={app.link} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
Visit Website
</Button>
</a>
<Button className="text-black font-bold px-6 py-4 rounded-full border-2 border-gray-200 bg-white hover:border-black hover:scale-105 transition-all duration-200">
<Share className="w-5 h-5 mr-2" />
Share
</Button>
{(app as any).appStoreUrl && (app as any).playStoreUrl ? (
<>
<a href={(app as any).appStoreUrl} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
App Store
</Button>
</a>
<a href={(app as any).playStoreUrl} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
Play Store
</Button>
</a>
</>
) : (
<a href={(app as any).url} target="_blank" rel="noopener noreferrer" className="flex-1">
<Button className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<ExternalLink className="w-5 h-5 mr-3" />
Visit Website
</Button>
</a>
)}
<div className="flex space-x-4">
{/* App Store link (optional) */}
{(app as any).appStoreUrl && (
<Button asChild className="flex-1 w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<a href={(app as any).appStoreUrl} target="_blank" rel="noopener noreferrer">
<ExternalLink className="w-5 h-5 mr-3" />
App Store
</a>
</Button>
)}
{/* Play Store link (optional) */}
{(app as any).playStoreUrl && (
<Button asChild className="flex-1 w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
<a href={(app as any).playStoreUrl} target="_blank" rel="noopener noreferrer">
<ExternalLink className="w-5 h-5 mr-3" />
Play Store
</a>
</Button>
)}
{/* Website link (optional) */}
{(app as any).url && (
<Button asChild className="flex-1 w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
<a href={(app as any).url} target="_blank" rel="noopener noreferrer">
<ExternalLink className="w-5 h-5 mr-3" />
Visit Website
</a>
</Button>
)}
</div>

Comment on lines +18 to +25
<Link href="/">
<Button
className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
>
Go to Marketplace
</Button>
</div>

<Button
type="submit"
className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
disabled={loginMutation.isPending}
>
{loginMutation.isPending ? "Signing in..." : "Sign In"}
</Button>
</form>
</Link>
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 | 🟠 Major

Avoid nesting a button inside an anchor (invalid HTML/a11y).

<Link> renders an anchor. Wrapping <Button> (which renders a button) inside it creates nested interactive elements. Use the Button asChild pattern so the anchor becomes the root.

Apply this diff:

-            <Link href="/">
-              <Button 
-                className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
-                style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
-              >
-                Go to Marketplace
-              </Button>
-            </Link>
+            <Button
+              asChild
+              className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
+              style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
+            >
+              <Link href="/">Go to Marketplace</Link>
+            </Button>

Based on learnings

📝 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
<Link href="/">
<Button
className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
>
Go to Marketplace
</Button>
</div>
<Button
type="submit"
className="w-full text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
disabled={loginMutation.isPending}
>
{loginMutation.isPending ? "Signing in..." : "Sign In"}
</Button>
</form>
</Link>
<Button
asChild
className="text-black font-bold px-8 py-4 rounded-full hover:scale-105 transition-all duration-200"
style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
>
<Link href="/">Go to Marketplace</Link>
</Button>
🤖 Prompt for AI Agents
In platforms/marketplace/client/src/pages/auth-page.tsx around lines 18-25, the
code nests a <Button> inside a <Link> which creates nested interactive elements;
change to the Button asChild pattern so the anchor is the root element: make the
Button the outer component with the prop asChild and move the <Link href="/">Go
to Marketplace</Link> inside it (keep the Button className and style on the
Button), or alternatively put the <Link> as the child element of <Button
asChild>; this removes the nested button/anchor, preserves the href on the
anchor, and keeps the same styling and behavior for accessibility.

Comment on lines +116 to 185
<Link key={app.id} href={`/app/${app.id}`}>
<div className="bg-white rounded-3xl p-8 border border-gray-100 hover:border-gray-300 transition-all duration-200 group cursor-pointer">
<div className="flex items-start space-x-6 mb-6">
{app.logoUrl ? (
<img
src={app.logoUrl}
alt={`${app.name} logo`}
className="w-20 h-20 rounded-2xl object-cover flex-shrink-0"
/>
) : (
<div className="w-20 h-20 rounded-2xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
<Store className="w-10 h-10 text-black" />
</div>
)}
<div className="flex-1">
<h3 className="text-2xl font-bold text-black mb-2 group-hover:text-gray-700">
{app.name}
</h3>
<div className="flex items-center space-x-2 mb-3">
<Badge className="bg-gray-100 text-gray-700 font-semibold uppercase tracking-wide">
{app.category}
</Badge>
</div>
</div>
</div>
<p className="text-gray-700 mb-6 line-clamp-2 font-medium">
{app.description}
</p>
<div className="flex items-center justify-end gap-3">
{(app as any).appStoreUrl && (app as any).playStoreUrl ? (
<>
<a
href={(app as any).appStoreUrl}
target="_blank"
rel="noopener noreferrer"
className="flex-1"
onClick={(e) => e.stopPropagation()}
>
<Button className="w-full text-black font-bold px-4 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
App Store
</Button>
</a>
<a
href={(app as any).playStoreUrl}
target="_blank"
rel="noopener noreferrer"
className="flex-1"
onClick={(e) => e.stopPropagation()}
>
<Button className="w-full text-black font-bold px-4 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
Play Store
</Button>
</a>
</>
) : (
<a
href={(app as any).url || '#'}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<Button className="text-black font-bold px-6 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
Open App <ArrowRight className="w-4 h-4 ml-2" />
</Button>
</a>
)}
</div>
</div>
<p className="text-gray-700 mb-6 line-clamp-2 font-medium">
{app.description}
</p>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-500">
{app.totalReviews} reviews
</span>
<Link href={`/app/${app.id}`}>
<Button className="text-black font-bold px-6 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
View Details <ArrowRight className="w-4 h-4 ml-2" />
</Button>
</Link>
</div>
</div>
</Link>
))}
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 | 🟠 Major

Nested anchors (Link wrapping card + inner ) and nested button-in-anchor.

  • <Link> renders an anchor; inner <a> tags (store/website) create anchor-in-anchor, which is invalid HTML and hurts accessibility.
  • Buttons inside anchors are also nested interactive elements.

Use a clickable card with useLocation() for navigation, and render action links as Button asChild anchors.

Apply this diff to the card block:

-                <Link key={app.id} href={`/app/${app.id}`}>
-                  <div className="bg-white rounded-3xl p-8 border border-gray-100 hover:border-gray-300 transition-all duration-200 group cursor-pointer">
+                  <div
+                    key={app.id}
+                    role="link"
+                    tabIndex={0}
+                    onClick={() => setLocation(`/app/${app.id}`)}
+                    onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && setLocation(`/app/${app.id}`)}
+                    className="bg-white rounded-3xl p-8 border border-gray-100 hover:border-gray-300 transition-all duration-200 group cursor-pointer outline-none focus:ring-2 focus:ring-black"
+                  >
                     <div className="flex items-start space-x-6 mb-6">
                       {app.logoUrl ? (
                         <img 
                           src={app.logoUrl} 
                           alt={`${app.name} logo`} 
                           className="w-20 h-20 rounded-2xl object-cover flex-shrink-0"
                         />
                       ) : (
                         <div className="w-20 h-20 rounded-2xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
                           <Store className="w-10 h-10 text-black" />
                         </div>
                       )}
                       <div className="flex-1">
                         <h3 className="text-2xl font-bold text-black mb-2 group-hover:text-gray-700">
                           {app.name}
                         </h3>
                         <div className="flex items-center space-x-2 mb-3">
                           <Badge className="bg-gray-100 text-gray-700 font-semibold uppercase tracking-wide">
                             {app.category}
                           </Badge>
                         </div>
                       </div>
                     </div>
                     <p className="text-gray-700 mb-6 line-clamp-2 font-medium">
                       {app.description}
                     </p>
                     <div className="flex items-center justify-end gap-3">
-                      {(app as any).appStoreUrl && (app as any).playStoreUrl ? (
-                        <>
-                          <a 
-                            href={(app as any).appStoreUrl} 
-                            target="_blank" 
-                            rel="noopener noreferrer"
-                            className="flex-1"
-                            onClick={(e) => e.stopPropagation()}
-                          >
-                            <Button className="w-full text-black font-bold px-4 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
-                              App Store
-                            </Button>
-                          </a>
-                          <a 
-                            href={(app as any).playStoreUrl} 
-                            target="_blank" 
-                            rel="noopener noreferrer"
-                            className="flex-1"
-                            onClick={(e) => e.stopPropagation()}
-                          >
-                            <Button className="w-full text-black font-bold px-4 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}>
-                              Play Store
-                            </Button>
-                          </a>
-                        </>
-                      ) : (
-                        <a 
-                          href={(app as any).url || '#'} 
-                          target="_blank" 
-                          rel="noopener noreferrer"
-                          onClick={(e) => e.stopPropagation()}
-                        >
-                          <Button className="text-black font-bold px-6 py-3 rounded-full hover:opacity-90" style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}>
-                            Open App <ArrowRight className="w-4 h-4 ml-2" />
-                          </Button>
-                        </a>
-                      )}
+                      {/* App Store (optional) */}
+                      {(app as any).appStoreUrl && (
+                        <Button
+                          asChild
+                          className="flex-1 w-full text-black font-bold px-4 py-3 rounded-full hover:opacity-90"
+                          style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
+                          onClick={(e) => e.stopPropagation()}
+                        >
+                          <a href={(app as any).appStoreUrl} target="_blank" rel="noopener noreferrer">App Store</a>
+                        </Button>
+                      )}
+                      {/* Play Store (optional) */}
+                      {(app as any).playStoreUrl && (
+                        <Button
+                          asChild
+                          className="flex-1 w-full text-black font-bold px-4 py-3 rounded-full hover:opacity-90"
+                          style={{ backgroundColor: 'hsl(270, 100%, 85%)' }}
+                          onClick={(e) => e.stopPropagation()}
+                        >
+                          <a href={(app as any).playStoreUrl} target="_blank" rel="noopener noreferrer">Play Store</a>
+                        </Button>
+                      )}
+                      {/* Website (optional) */}
+                      {(app as any).url && (
+                        <Button
+                          asChild
+                          className="text-black font-bold px-6 py-3 rounded-full hover:opacity-90"
+                          style={{ backgroundColor: 'hsl(85, 100%, 85%)' }}
+                          onClick={(e) => e.stopPropagation()}
+                        >
+                          <a href={(app as any).url} target="_blank" rel="noopener noreferrer">
+                            Open App <ArrowRight className="w-4 h-4 ml-2" />
+                          </a>
+                        </Button>
+                      )}
                     </div>
                   </div>
-                </Link>

Add useLocation and setter:

// at top-level imports
import { useLocation } from "wouter";

// inside HomePage component
const [, setLocation] = useLocation();

Based on learnings

🤖 Prompt for AI Agents
platforms/marketplace/client/src/pages/home-page.tsx lines 116-185: The card
currently nests anchors (outer Link -> inner <a>) and buttons inside anchors,
which is invalid and inaccessible; instead import and call useLocation() (const
[, setLocation] = useLocation()) in the component, remove the outer Link and
make the card a clickable div that navigates via setLocation(`/app/${app.id}`)
onClick, replace inner anchor-wrapped Buttons with Buttons rendered as anchors
using the Button asChild pattern for external links (or render plain <a> with
onClick stopping propagation) and ensure inner click handlers call
e.stopPropagation() so store/website clicks don’t trigger the card navigation;
also add the useLocation import at the top.

Comment on lines +2 to +41
export type App = {
id: string;
name: string;
description: string;
fullDescription: string | null;
category: string;
link: string;
logoUrl: string | null;
screenshots: string[] | null;
status: string;
averageRating: string | null;
totalReviews: number;
createdAt: Date;
updatedAt: Date;
};

export type InsertApp = Omit<App, 'id' | 'averageRating' | 'totalReviews' | 'createdAt' | 'updatedAt'>;

export type Review = {
id: string;
appId: string;
userName: string;
rating: number;
comment: string | null;
createdAt: Date;
};

export type InsertReview = Omit<Review, 'id' | 'createdAt'>;

export type User = {
id: string;
email: string;
username: string;
password: string;
isAdmin: boolean;
createdAt: Date;
};

export type InsertUser = Omit<User, 'id' | 'isAdmin' | 'createdAt'>;

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 | 🟠 Major

Align types with actual data to remove (any) casts and prevent runtime mismatches.

apps.json and pages use url, appStoreUrl, playStoreUrl, and JSON timestamps. Current App type has link, Date types, and averageRating as string. This forces (app as any) in multiple places and risks misuse.

Consider this update:

 export type App = {
   id: string;
   name: string;
   description: string;
-  fullDescription: string | null;
+  fullDescription?: string;         // optional in static JSON
   category: string;
-  link: string;
-  logoUrl: string | null;
-  screenshots: string[] | null;
-  status: string;
-  averageRating: string | null;
-  totalReviews: number;
-  createdAt: Date;
-  updatedAt: Date;
+  url?: string;                     // website
+  appStoreUrl?: string;             // iOS
+  playStoreUrl?: string;            // Android
+  logoUrl?: string;
+  screenshots?: string[];
+  status?: string;
+  averageRating?: number | null;
+  totalReviews?: number;
+  createdAt?: string;               // ISO strings in JSON
+  updatedAt?: string;
 };
 
-export type InsertApp = Omit<App, 'id' | 'averageRating' | 'totalReviews' | 'createdAt' | 'updatedAt'>;
+export type InsertApp = Omit<App, 'id' | 'averageRating' | 'totalReviews' | 'createdAt' | 'updatedAt'>;

Optionally, constrain category to a union of known categories and export it for reuse.

After updating, remove (app as any) in pages and rely on optional fields with proper guards.

📝 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
export type App = {
id: string;
name: string;
description: string;
fullDescription: string | null;
category: string;
link: string;
logoUrl: string | null;
screenshots: string[] | null;
status: string;
averageRating: string | null;
totalReviews: number;
createdAt: Date;
updatedAt: Date;
};
export type InsertApp = Omit<App, 'id' | 'averageRating' | 'totalReviews' | 'createdAt' | 'updatedAt'>;
export type Review = {
id: string;
appId: string;
userName: string;
rating: number;
comment: string | null;
createdAt: Date;
};
export type InsertReview = Omit<Review, 'id' | 'createdAt'>;
export type User = {
id: string;
email: string;
username: string;
password: string;
isAdmin: boolean;
createdAt: Date;
};
export type InsertUser = Omit<User, 'id' | 'isAdmin' | 'createdAt'>;
export type App = {
id: string;
name: string;
description: string;
fullDescription?: string; // optional in static JSON
category: string;
url?: string; // website
appStoreUrl?: string; // iOS
playStoreUrl?: string; // Android
logoUrl?: string;
screenshots?: string[];
status?: string;
averageRating?: number | null;
totalReviews?: number;
createdAt?: string; // ISO strings in JSON
updatedAt?: string;
};
export type InsertApp = Omit<App, 'id' | 'averageRating' | 'totalReviews' | 'createdAt' | 'updatedAt'>;

"@": path.resolve(import.meta.dirname, "client", "src"),
"@shared": path.resolve(import.meta.dirname, "shared"),
"@assets": path.resolve(import.meta.dirname, "attached_assets"),
"@assets": path.resolve(import.meta.dirname, "assets"),
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 | 🔴 Critical

🧩 Analysis chain

Verify the assets path change doesn't break existing references.

The @assets alias path changed from attached_assets to assets. Ensure all references to the old attached_assets directory have been updated or removed.

Run this script to check for any remaining references:


🏁 Script executed:

#!/bin/bash
# Search for references to the old attached_assets directory
rg -n 'attached_assets' --type=ts --type=tsx --glob '!node_modules/**' --glob '!dist/**'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for references to the old attached_assets directory across TS/TSX files
rg -n 'attached_assets' -g '*.ts' -g '*.tsx' --glob '!node_modules/**' --glob '!dist/**'

Length of output: 108


Update remaining @assets alias in dreamSync config
In platforms/dreamSync/vite.config.ts line 23 the @assets alias still points to "attached_assets"—change it to "assets" to match the marketplace update.

🤖 Prompt for AI Agents
In platforms/marketplace/vite.config.ts around line 12 the @assets alias
currently resolves to the old location; update the corresponding alias in
platforms/dreamSync/vite.config.ts (around line 23) so its @assets
path.resolve(import.meta.dirname, "attached_assets") becomes
path.resolve(import.meta.dirname, "assets") to match the marketplace change.

Comment on lines +22 to +23
strict: false,
allow: [".."],
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 | 🟡 Minor

Consider security implications of strict: false.

Setting strict: false and allowing parent directory access (allow: [".."]) reduces filesystem security restrictions. While this is necessary for the current setup, ensure that:

  1. The server only serves intended files
  2. No sensitive files from parent directories are exposed
  3. This configuration is appropriate for the deployment environment
🤖 Prompt for AI Agents
In platforms/marketplace/vite.config.ts around lines 22-23 the config sets
strict: false and allow: [".."] which relaxes filesystem access; restrict this
by only whitelisting the exact directories needed (use absolute paths via
path.resolve or path.join rather than ".."), set server.root to the intended
serve directory, guard the relaxed setting behind an env check so it is only
enabled in development, and add a small middleware or static-serving filter to
explicitly deny access to parent-directory sensitive paths (e.g., .env, /etc,
node_modules) and document the reason and audit surface before deploying.

@coodos coodos merged commit bdb073b into main Oct 14, 2025
0 of 3 checks passed
@coodos coodos deleted the fix/marketplace branch October 14, 2025 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants