-
Notifications
You must be signed in to change notification settings - Fork 4
Fix/marketplace #366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix/marketplace #366
Conversation
Caution Review failedThe pull request is closed. WalkthroughRemoves 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
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)
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" }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 likeFunction
,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.jsonplatforms/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:
- Generate a Next.js 15 authentication implementation using best practices (NextAuth.js v5/Auth.js, or Next.js server actions)?
- 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:
- Generate a dashboard implementation with common admin features (user management, analytics, etc.)?
- 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-debounceThen 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
ortext
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 versionOption 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:
- Simplified
alt
text to just the app name (the context "logo" is implied)- Added
role="img"
andaria-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 enginesAvoid 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 byws
. Sincews
was removed, this is probably dead weight.- "optionalDependencies": { - "bufferutil": "^4.0.8" - } + "optionalDependencies": {}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ 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; explicitdotenv.config()
and custom__dirname
logic are only needed for standalone Node scripts or nonstandard.env
locations. I found no imports or direct executions ofplatforms/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 missingrel="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 goodPin matches eslint-config-next 15.4.2 below. No concerns.
64-64
: nanoid addition looks fineNo issues.
platforms/marketplace/next.config.js
Outdated
webpack: (config) => { | ||
config.resolve.alias = { | ||
...config.resolve.alias, | ||
'@shared': './shared', | ||
}; | ||
return config; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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:
- Duplication: The same alias is defined in both places, violating DRY principles.
- Relative path hazard: Using
'./shared'
instead of an absolute path can cause resolution issues depending on the webpack context directory. - 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.
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.
platforms/marketplace/package.json
Outdated
"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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
platforms/marketplace/package.json
Outdated
"@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" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
export default function AppDetailPage() { | ||
const params = useParams(); | ||
const id = params.id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
return ( | ||
<html lang="en"> | ||
<body> | ||
<Providers> | ||
<Header /> | ||
<div className="min-h-screen bg-background"> | ||
{children} | ||
</div> | ||
</Providers> | ||
</body> | ||
</html> | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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.
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", | ||
}, | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Data staleness: Changes require code deploys rather than database updates
- Type safety: The local
App
type diverges from the database schema - 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:
- Open a tracking issue for implementing the API routes and data fetching?
- Generate a migration plan to align the frontend with the database schema?
- 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.
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(); | ||
}, | ||
}, | ||
}, | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify the default queryFn assumptions and consider adding error handling configuration.
The default queryFn
implementation makes several assumptions that could lead to runtime issues:
-
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. -
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. -
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.
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, | |
}, | |
}, | |
})); |
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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
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:
- Remove
searchParams
from deps to break the circular dependency - Use
window.location.search
instead ofsearchParams
to read current state - Add 300ms debounce to reduce router updates while typing
- Use
router.replace
instead ofrouter.push
to avoid polluting history - 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.
platforms/marketplace/tsconfig.json
Outdated
"@assets/*": [ | ||
"./client/src/assets/*" | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
"@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.
e8dcaa7
to
1e738e4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. UseasChild
.- <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
, andonComplete
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:
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; }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 usesProtectedRoute
, making the admin dashboard accessible without authentication. This is consistent with the authentication stub that always returnsnull
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
⛔ 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()
toDate.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 usinguseAuth()
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/srcecho -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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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) andapp-detail.tsx
(around line 73), the{app.logoUrl ? <img …/> : null}
branches render nothing whenlogoUrl
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.
<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> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
<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> |
<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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
<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.
<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> | ||
))} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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'>; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.
strict: false, | ||
allow: [".."], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- The server only serves intended files
- No sensitive files from parent directories are exposed
- 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.
Description of change
Issue Number
Type of change
How the change has been tested
Change checklist
Summary by CodeRabbit
New Features
Changes
Removed/Disabled
Chores