Skip to content

Conversation

@devlopersabbir
Copy link
Owner

@devlopersabbir devlopersabbir commented Aug 24, 2025

Summary by CodeRabbit

  • New Features

    • Introduced Admin area: dashboard, manage extensions, and upload flow with multi-step forms.
    • Added Seller area: dashboard, extensions list with filters/actions, profile settings, and storefront pages.
    • Public seller store pages with search and category filters.
    • New reusable UI components: Sidebar system, Table, Breadcrumbs, Sheet (drawer), Progress, Switch, Avatar, Tooltip, Skeleton, Textarea.
    • Loading states for various routes.
  • Documentation

    • Expanded README with Nx workspace guidance, CI, releasing, and tooling links.
  • Chores

    • Added CI workflow, Prettier config/ignore, .npmrc, VS Code settings, updated .gitignore, client tsconfig, and .env examples.

@vercel
Copy link

vercel bot commented Aug 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
browserplugins Error Error Aug 24, 2025 4:13pm

@coderabbitai
Copy link

coderabbitai bot commented Aug 24, 2025

Walkthrough

Introduces a CI workflow (GitHub Actions) and multiple repo housekeeping files. Adds a Next.js client app (apps/client) with admin, seller, and public pages, a sidebar system, numerous UI primitives, and a Zustand-based store/provider. Expands README with Nx guidance. Adds environment examples for client/server. Many existing client files received formatting-only updates.

Changes

Cohort / File(s) Summary
CI Pipeline
/.github/workflows/ci.yml
New CI workflow running pnpm + Nx for lint, test, build; includes optional Nx Cloud notes and a final fix-ci step.
Repo ignores & tooling
/.gitignore, /.prettierignore, /.prettierrc, /.npmrc, /.vscode/settings.json
Updated ignore patterns (globalized Node/Next artifacts, Nx caches), added Prettier config/ignore, npm peer settings, and VS Code NX Console setting.
Docs
/README.md
Rewritten to center on Nx workspace usage, CI setup, task running, release/versioning, and links.
Server env template
/apps/server/.env.example
Adds PORT and DATABASE_URL placeholders.
Client app setup
/apps/client/package.json, /apps/client/tsconfig.json, /apps/client/.env.example, /apps/client/.gitignore, /apps/client/eslint.config.mjs, /apps/client/README.md
Introduces client Next.js app config, scripts, deps (Next 15/React 19), TS config with path alias, ESLint flat config, env example, and app-specific ignore.
Admin layout & navigation
/apps/client/src/app/(admin)/layout.tsx, .../(admin)/_components/admin-sidebar.tsx, .../(admin)/_constants/menu.tsx, .../(admin)/_constants/index.ts, .../(admin)/_constants/dammy*.ts*
Adds admin shell with Sidebar, nav items, stats/recent data, and a constants barrel.
Admin pages & pieces
.../(admin)/admin/page.tsx, .../(admin)/admin/extensions/page.tsx, .../(admin)/admin/upload/page.tsx, .../(admin)/_components/shared/page-header.tsx, .../(admin)/_components/upload-form/*
Adds Admin Dashboard, Manage Extensions, and Upload page with form sections (basic info, browser compatibility, tags, file uploads). Note: basic-information.tsx contains an early stray brace and incomplete handlers.
Admin store/provider
.../(admin)/_stores/extension-store.ts, .../(admin)/_providers/template-store-provider.tsx
Adds Zustand store (extension form/files/state) and a React context provider + hook for consuming the store.
Seller layout & pages
/apps/client/src/app/(seller)/layout.tsx, .../(seller)/seller/page.tsx, .../(seller)/seller/extensions/page.tsx, .../(seller)/seller/profile/page.tsx, .../(seller)/seller/store/page.tsx, .../(seller)/seller/extensions/loading.tsx
Adds Seller dashboard shell and pages (overview, extensions table with bulk actions, profile editor, storefront), plus route-level loading components.
Public store pages
/apps/client/src/app/(public)/(store)/store/[username]/page.tsx, .../(public)/(store)/store/[username]/loading.tsx, .../(public)/loading.tsx, /apps/client/src/app/[username]/store/page.tsx, /apps/client/src/app/[username]/store/loading.tsx
Adds public seller store pages with profile, filters, extension grid, and loading placeholders.
Checkout/Extension routes (formatting)
.../(public)/_components/checkout/checkout-page.tsx, .../(public)/checkout/[id]/page.tsx, .../(public)/extension/[id]/page.tsx
Formatting-only changes; behavior unchanged.
Public components (formatting or minor tweak)
.../(public)/_components/**/*, .../components/shared/*, .../app/components/toggle-wish-list-button.tsx
Mostly formatting-only. One change: toggle-wish-list now uses != for removal filtering.
UI primitives (new)
/apps/client/src/components/ui/{avatar,breadcrumb,progress,sheet,sidebar,skeleton,switch,table,textarea,tooltip}.tsx
Adds a suite of UI components (Radix-based and custom): Avatar, Breadcrumb, Progress, Sheet, a full Sidebar system with Provider/Trigger/Menu, Skeleton, Switch, Table, Textarea, Tooltip.
Hooks (new)
/apps/client/src/hooks/use-mobile.ts
New useIsMobile hook using matchMedia and a 768px breakpoint.
Constants & types
/apps/client/src/constants/*, /apps/client/src/@types/**/*
Mostly formatting. categories.data.tsx now uses as const; types reflowed without API change.
Auth/Dashboard layouts (formatting)
/apps/client/src/app/(auth)/**/*, .../(dashboard)/**/*, .../(public)/layout.tsx, /apps/client/src/providers/base-providers.tsx
Formatting-only adjustments; exports and behavior unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Seller
  participant UI as Seller Upload Page
  participant Store as Zustand Store
  participant Toast as Toast/Notifier

  Seller->>UI: Open UploadExtension
  UI->>UI: Step navigation (1..4)
  UI->>Store: setFormData()/setFiles() via handlers
  Seller->>UI: Click Submit (Step 4)
  UI->>UI: isSubmitting = true
  UI-->>Toast: Show submitting...
  UI->>UI: Simulate upload (timeout)
  UI-->>Toast: Success message
  UI->>Store: Reset formData/files
  UI->>UI: isSubmitting = false
Loading
sequenceDiagram
  autonumber
  actor Admin
  participant Layout as AdminLayout
  participant Sidebar as Sidebar (Provider)
  participant Page as Manage/Upload Pages

  Admin->>Layout: Navigate /admin/*
  Layout->>Sidebar: Initialize context
  Sidebar-->>Admin: Sidebar UI (Trigger/Rail/Menu)
  Admin->>Page: Interact with page content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

Hop hop! I stitch the panes just right,
Sidebars slide, and tables light—
Sellers sing, admins compile,
CI hums along in style.
With Nx maps and stars above,
I ship this warren full of love.
(∩`-´)⊃━☆🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch monorepo

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nx-cloud
Copy link

nx-cloud bot commented Aug 24, 2025

🤖 Nx Cloud AI has a fix for the failures below

fix lint errors and syntax issues across client and server 🔧

🔴 Not verified, ready to apply • View Fix ↗


View your CI Pipeline Execution ↗ for commit 695fe3e

Command Status Duration Result
nx run-many -t lint test build ❌ Failed 19s View ↗

☁️ Nx Cloud last updated this comment at 2025-08-24 16:46:37 UTC

Copy link

@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: 71

Caution

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

⚠️ Outside diff range comments (1)
.gitignore (1)

49-54: Ignore environment files at the repo root to avoid leaking secrets

Root .gitignore no longer ignores .env*. Given you added .env.example under apps/server and apps/client, add root-level env ignores to prevent accidental commits.

Append:

+# Environment files (root)
+.env
+.env.*
+.env.local
+.env.*.local

Comment on lines +44 to +47
- run: pnpm exec nx run-many -t lint test build
# Nx Cloud recommends fixes for failures to help you get CI green faster. Learn more: https://nx.dev/ci/features/self-healing-ci
- run: pnpm exec nx fix-ci
if: always()
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Guard against missing Nx targets (especially “test”)

If no project defines a test target, nx run-many -t lint test build can fail the job. Either split into conditional steps or verify the targets exist first.

Option A (verify locally and keep as-is if safe). Please run:

Option B (conditionally run per target). Example:

-      - run: pnpm exec nx run-many -t lint test build
+      - name: Detect Nx targets
+        id: detect
+        run: |
+          echo "has_lint=$(pnpm -s nx show projects --withTarget lint | wc -l)" >> $GITHUB_OUTPUT
+          echo "has_test=$(pnpm -s nx show projects --withTarget test | wc -l)" >> $GITHUB_OUTPUT
+          echo "has_build=$(pnpm -s nx show projects --withTarget build | wc -l)" >> $GITHUB_OUTPUT
+
+      - name: Lint
+        if: ${{ steps.detect.outputs.has_lint != '0' }}
+        run: pnpm exec nx run-many -t lint
+
+      - name: Test
+        if: ${{ steps.detect.outputs.has_test != '0' }}
+        run: pnpm exec nx run-many -t test
+
+      - name: Build
+        if: ${{ steps.detect.outputs.has_build != '0' }}
+        run: pnpm exec nx run-many -t build

🏁 Script executed:

#!/bin/bash
# Show projects that define each target
pnpm -s nx show projects --withTarget lint
pnpm -s nx show projects --withTarget test
pnpm -s nx show projects --withTarget build

Length of output: 135


🏁 Script executed:

#!/bin/bash
# Locate NX workspace/project config files and check for target definitions
find . -type f \( -name 'workspace.json' -o -name 'angular.json' -o -name 'project.json' \) | while read f; do
  echo "Checking $f:"
  echo "  lint targets:"
  grep -n '"lint":' "$f" || echo "    none"
  echo "  test targets:"
  grep -n '"test":' "$f" || echo "    none"
  echo "  build targets:"
  grep -n '"build":' "$f" || echo "    none"
done

Length of output: 16273


🏁 Script executed:

#!/bin/bash
# Check for root workspace configuration files
echo "Root configuration files:"
ls -1 workspace.json angular.json nx.json package.json 2>/dev/null

# Display projects section if present
for cfg in workspace.json angular.json; do
  if [ -f "$cfg" ]; then
    echo "---- $cfg projects section ----"
    grep -n '"projects"' "$cfg" || echo "  no projects entry"
  fi
done

# Show content of nx.json projects key
if [ -f nx.json ]; then
  echo "---- nx.json projects key ----"
  grep -n '"projects"' nx.json || echo "  no projects entry"
fi

# Find all project.json files outside node_modules and report targets
echo "---- project.json files ----"
find . -type f -name project.json ! -path '*/node_modules/*' | while read f; do
  echo "== $f =="
  echo "  lint target:"
  grep -n '"lint":' "$f" || echo "    none"
  echo "  test target:"
  grep -n '"test":' "$f" || echo "    none"
  echo "  build target:"
  grep -n '"build":' "$f" || echo "    none"
done

Length of output: 587


CI step will always fail without any Nx targets
It looks like your workspace has no project definitions (no workspace.json/angular.json and no project.json files), and even nx show projects --withTarget … returns nothing. As a result, the single pnpm exec nx run-many -t lint test build will error out every time. You must guard or split these commands so the job can succeed when targets are absent.

Please update your .github/workflows/ci.yml accordingly. For example, you can detect which targets exist and run them conditionally:

-      - run: pnpm exec nx run-many -t lint test build
+      - name: Detect Nx targets
+        id: detect
+        run: |
+          echo "has_lint=$(pnpm -s nx show projects --withTarget lint | wc -l)" >> $GITHUB_OUTPUT
+          echo "has_test=$(pnpm -s nx show projects --withTarget test | wc -l)" >> $GITHUB_OUTPUT
+          echo "has_build=$(pnpm -s nx show projects --withTarget build | wc -l)" >> $GITHUB_OUTPUT
+
+      - name: Lint
+        if: ${{ steps.detect.outputs.has_lint != '0' }}
+        run: pnpm exec nx run-many -t lint
+
+      - name: Test
+        if: ${{ steps.detect.outputs.has_test != '0' }}
+        run: pnpm exec nx run-many -t test
+
+      - name: Build
+        if: ${{ steps.detect.outputs.has_build != '0' }}
+        run: pnpm exec nx run-many -t build

• Ensure that each target step is skipped if no projects define it.
• Keep the pnpm exec nx fix-ci step (if: always()) unchanged so self-healing still runs.
• Alternatively, define at least one project with test, lint, or build targets if CI should always run them.

📝 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
- run: pnpm exec nx run-many -t lint test build
# Nx Cloud recommends fixes for failures to help you get CI green faster. Learn more: https://nx.dev/ci/features/self-healing-ci
- run: pnpm exec nx fix-ci
if: always()
# Detect which Nx targets actually exist
- name: Detect Nx targets
id: detect
run: |
echo "has_lint=$(pnpm -s nx show projects --withTarget lint | wc -l)" >> $GITHUB_OUTPUT
echo "has_test=$(pnpm -s nx show projects --withTarget test | wc -l)" >> $GITHUB_OUTPUT
echo "has_build=$(pnpm -s nx show projects --withTarget build | wc -l)" >> $GITHUB_OUTPUT
# Run lint only if there are projects with a lint target
- name: Lint
if: ${{ steps.detect.outputs.has_lint != '0' }}
run: pnpm exec nx run-many -t lint
# Run tests only if there are projects with a test target
- name: Test
if: ${{ steps.detect.outputs.has_test != '0' }}
run: pnpm exec nx run-many -t test
# Run build only if there are projects with a build target
- name: Build
if: ${{ steps.detect.outputs.has_build != '0' }}
run: pnpm exec nx run-many -t build
# Nx Cloud recommends fixes for failures to help you get CI green faster.
# Learn more: https://nx.dev/ci/features/self-healing-ci
- run: pnpm exec nx fix-ci
if: always()
🤖 Prompt for AI Agents
.github/workflows/ci.yml lines 44-47: the workflow blindly runs "pnpm exec nx
run-many -t lint test build" which fails when no projects define those targets;
update the CI to detect existing targets and only run the corresponding nx
commands (or split into separate steps per target) so each step is skipped when
no projects define that target, while leaving the "pnpm exec nx fix-ci" step
with "if: always()" unchanged; implement this by querying nx (e.g., "nx show
projects --withTarget <target>") or parsing workspace config, and run nx
run-many or run-commands conditionally per target only when projects are
returned.

@@ -0,0 +1 @@
SERVER_URL="" No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

For client-side access in Next.js, prefix with NEXT_PUBLIC_

Environment variables must start with NEXT_PUBLIC_ to be exposed to the browser. If this is consumed in the client app, rename accordingly and add a newline.

-SERVER_URL=""
+NEXT_PUBLIC_SERVER_URL=
📝 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
SERVER_URL=""
# apps/client/.env.example
NEXT_PUBLIC_SERVER_URL=
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 1-1: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🤖 Prompt for AI Agents
In apps/client/.env.example around lines 1 to 1, the SERVER_URL variable will
not be exposed to the browser; rename it to NEXT_PUBLIC_SERVER_URL and ensure
the file ends with a newline. Update the key to NEXT_PUBLIC_SERVER_URL="" so
Next.js can expose it to client-side code and add a trailing newline character
to the file.

Comment on lines +33 to +35
# env files (can opt-in for committing if needed)
.env

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Broaden env ignores to prevent accidental secret commits.

Only ignoring “.env” risks leaking values from .env.local, .env.production, etc. Keep .env.example tracked.

Apply this diff:

-# env files (can opt-in for committing if needed)
-.env
+# env files (opt-in via .env.example only)
+.env
+.env.local
+.env.*.local
+.env.development
+.env.production
+.env.test
+!.env.example
📝 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
# env files (can opt-in for committing if needed)
.env
# env files (opt-in via .env.example only)
.env
.env.local
.env.*.local
.env.development
.env.production
.env.test
!.env.example
🤖 Prompt for AI Agents
In apps/client/.gitignore around lines 33 to 35, the file only ignores ".env"
which still allows committing other env variants (.env.local, .env.production,
etc.); update the ignore pattern to ignore all env files by replacing the ".env"
entry with ".env*" and add an explicit exception for the example file by adding
a negate rule for ".env.example" (e.g., add "!.env.example") so the example
stays tracked while all actual env variants are ignored.

Comment on lines +1 to +2
import { sortsBy } from "@/constants";
export type SortBy = (typeof sortsBy)[number];
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

SortBy currently resolves to the object type, not the union of .value strings.

(typeof sortsBy)[number] is the option object type. If callers expect "popular" | "rating" | ..., expose that union via ["value"].

Apply:

 import { sortsBy } from "@/constants";
-export type SortBy = (typeof sortsBy)[number];
+export type SortOption = (typeof sortsBy)[number];
+export type SortBy = (typeof sortsBy)[number]["value"];

Then verify usages:


🏁 Script executed:

#!/bin/bash
# Look for places that treat SortBy as a string vs as an object
rg -nP -C2 '\bSortBy\b' apps/client | sed -n '1,200p'

Length of output: 2900


Ensure SortBy reflects the union of .value strings and update all usages

We need to distinguish the option object type from its string values. In apps/client/src/@types/extension/filter.ts, introduce a new SortOption for the object and redefine SortBy as the union of its "value" fields. Then update every consumer to use SortBy directly (instead of SortBy["value"]).

• In src/@types/extension/filter.ts:

-import { sortsBy } from "@/constants";
-export type SortBy = (typeof sortsBy)[number];
+import { sortsBy } from "@/constants";
+export type SortOption = (typeof sortsBy)[number];
+export type SortBy = SortOption["value"];

• In apps/client/src/app/(public)/_components/filters/base-filters.tsx:

  • Change
    const [sortBy, setSortBy] = useState<SortBy["value"]>("popular");
    to
    const [sortBy, setSortBy] = useState<SortBy>("popular");

• In apps/client/src/app/(public)/_components/filters/sort-by-filter.tsx:

  • Update the Props to:
    type Props = {
      sortBy: SortBy;
      setSortBy: Dispatch<SetStateAction<SortBy>>;
    }
  • Change the onValueChange handler signature:
    - onValueChange={(v: SortBy["value"]) => setSortBy(v)}
    + onValueChange={(v: SortBy) => setSortBy(v)}

These refactors ensure SortBy is always the correct string union, eliminating the mismatch between object and string types.

🤖 Prompt for AI Agents
In apps/client/src/@types/extension/filter.ts around lines 1-2, the current
SortBy type aliases the entire option objects; create a new SortOption type
representing the option object (label/value/other props) and redefine SortBy as
the union of SortOption["value"] (i.e., the string literal union of all .value
entries). Then update consumers: in
apps/client/src/app/(public)/_components/filters/base-filters.tsx change the
useState generic from SortBy["value"] to SortBy and initialize with "popular";
in apps/client/src/app/(public)/_components/filters/sort-by-filter.tsx update
Props to use sortBy: SortBy and setSortBy: Dispatch<SetStateAction<SortBy>> and
adjust the onValueChange handler signature to accept a SortBy (string union)
instead of SortOption["value"]. Ensure imports reference the new
SortOption/SortBy types where needed.

Comment on lines +1 to +12
export const categories = [
"Productivity",
"Security",
"Developer Tools",
"Social Media",
"Shopping",
"Entertainment",
"Utilities",
"Communication",
];

export const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera"];
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Duplication and drift risk: unify with existing canonical sources.

These arrays duplicate values defined elsewhere (categories data and schema browsers), with different casing/entries (e.g., “Opera” here vs. schema’s set without Opera, “Microsoft Edge” naming). This will drift and cause inconsistent validation/UI.

Replace with derived exports from single sources:

-export const categories = [
-  "Productivity",
-  "Security",
-  "Developer Tools",
-  "Social Media",
-  "Shopping",
-  "Entertainment",
-  "Utilities",
-  "Communication",
-];
-
-export const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera"];
+import { categories as categoriesData } from "@/constants/categories.data"
+import { browsers as schemaBrowsers } from "@browserplugins/schema"
+
+const toTitleCase = (s: string) =>
+  s.replace(/\w\S*/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
+
+// Derive human-readable labels from the canonical categories list, skipping the "all" pseudo-option.
+export const categories = categoriesData
+  .filter((c) => c.value !== "all")
+  .map((c) => c.label) as const
+
+// Canonical browser list comes from the schema; map to display labels.
+export const browsers = (schemaBrowsers as readonly string[]).map(toTitleCase) as const
📝 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 const categories = [
"Productivity",
"Security",
"Developer Tools",
"Social Media",
"Shopping",
"Entertainment",
"Utilities",
"Communication",
];
export const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera"];
import { categories as categoriesData } from "@/constants/categories.data"
import { browsers as schemaBrowsers } from "@browserplugins/schema"
const toTitleCase = (s: string) =>
s.replace(/\w\S*/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
// Derive human-readable labels from the canonical categories list, skipping the "all" pseudo-option.
export const categories = categoriesData
.filter((c) => c.value !== "all")
.map((c) => c.label) as const
// Canonical browser list comes from the schema; map to display labels.
export const browsers = (schemaBrowsers as readonly string[]).map(toTitleCase) as const
🤖 Prompt for AI Agents
In apps/client/src/app/(admin)/_constants/dammy.ts lines 1-12, the arrays
duplicate canonical category and browser lists and risk drift; replace these
hardcoded arrays by importing the single source-of-truth modules (the canonical
categories data and the schema/browser enum) and re-exporting derived arrays
(map/format values to the UI’s expected casing if necessary), ensuring you
normalize names (e.g., map "Microsoft Edge" ↔ "Edge"/"Opera" differences) rather
than keeping separate literals so validation and UI remain consistent.

] as const;

export type AllExtension = (typeof allExtensions)[number];
export const allExtensions = [
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

License keys included in client bundle — treat as sensitive; mask or remove.

If these are real or realistic keys, they should not ship to the client. Even as sample data, clearly mark as demo and mask values to avoid accidental leakage.

Apply this diff:

+// DEMO DATA: No real license keys. Do not include sensitive data in client bundles.
 export const allExtensions = [
   {
     id: 1,
     name: "ProductivityPro Max",
     browser: "chrome",
     purchaseDate: "2024-12-15",
-    licenseKey: "PROD-ABC123DEF456",
+    licenseKey: "DEMO-XXXX-XXXX",
     downloadUrl: "/download/1",
     status: "active",
     price: 39.99,
     description:
       "Ultimate productivity suite with AI-powered task management and focus tools",
   },
   {
     id: 2,
     name: "SecureVault",
     browser: "firefox",
     purchaseDate: "2024-12-10",
-    licenseKey: "SECURE-XYZ789GHI012",
+    licenseKey: "DEMO-XXXX-XXXX",
     downloadUrl: "/download/2",
     status: "active",
     price: 29.99,
     description:
       "Military-grade password manager with biometric authentication",
   },
   {
     id: 3,
     name: "DevTools Master",
     browser: "chrome",
     purchaseDate: "2024-12-12",
-    licenseKey: "DEV-ABC123DEF456",
+    licenseKey: "DEMO-XXXX-XXXX",
     downloadUrl: "/download/3",
     status: "active",
     price: 24.99,
     description: "Advanced developer tools with code snippets and API testing",
   },
 ] as const;

To further reduce risk, consider moving any “purchases” data behind a server route and fetching per-user licenses at request time.

Also applies to: 7-7, 19-19, 31-31

🤖 Prompt for AI Agents
In apps/client/src/constants/all-extensions.ts around lines 1 (and also lines 7,
19, 31), the file currently contains real or realistic license keys that are
being bundled into the client; remove or mask those values and do not hardcode
secrets in frontend code. Replace any license strings with clearly-marked demo
placeholders (e.g. "DEMO-xxxx...") or null, and move real license/purchase data
to a secure server endpoint (or load them from an authenticated API at runtime)
so keys are not included in the build; if needed, reference licenses via IDs and
fetch the actual values from the backend per-user, or read non-secret build-time
flags from environment variables that are not checked into source. Ensure
comments/documentation indicate demo-only values and update tests/fixtures
accordingly.

Comment on lines +1 to +39
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify no other sensitive tokens/keys are present in the repo.

Run a quick scan to catch similar patterns and ensure nothing else ships to the client.


🏁 Script executed:

#!/bin/bash
# Search for license-like keys in the client app.
rg -nP --glob 'apps/client/**' -C2 '\b(licenseKey|apiKey|secret|token)\b\s*:'

# Also scan for common secret patterns (heuristic).
rg -nP --glob 'apps/**' -C2 '(AKIA[0-9A-Z]{16}|AIzaSy[0-9A-Za-z\-_]{35}|sk-[A-Za-z0-9]{20,}|-----BEGIN (RSA|EC) PRIVATE KEY-----)'

Length of output: 1358


Remove Hardcoded License Keys and Confirm No Other Secrets

A ripgrep scan of apps/client confirms no API keys, tokens, or private keys aside from the licenseKey fields in all-extensions.ts. Those hardcoded values should be removed or replaced with secure placeholders.

• apps/client/src/constants/all-extensions.ts
– Lines 7, 19, 31: instances of
ts licenseKey: "PROD-ABC123DEF456" licenseKey: "SECURE-XYZ789GHI012" licenseKey: "DEV-ABC123DEF456"
– Action: remove these literal keys and fetch them from a secure source (e.g., environment variables or a secrets API).

No other sensitive tokens or secrets were detected in the client code.

🤖 Prompt for AI Agents
In apps/client/src/constants/all-extensions.ts around lines 1 to 39, remove the
hardcoded licenseKey string literals on lines 7, 19, and 31 and replace them
with secure placeholders that are resolved at runtime from a safe source (e.g.,
process.env, a secrets API, or a server endpoint) rather than embedding secrets
in source; update the shape so licenseKey is optional or set to a non-secret
placeholder in this file, and modify any consuming code to fetch the real
license value from the secure store when needed (or inject it from secure server
props), ensuring types still align with AllExtension.

Comment on lines +28 to +33
{
value: "safary",
label: "Safary",
icon: <Compass className="w-4 h-4" />,
color: "#F59E0B",
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Correct Safari value/label spelling to ensure consistent filtering and display

The current "safary" will break any logic that expects "safari".

   {
-    value: "safary",
-    label: "Safary",
+    value: "safari",
+    label: "Safari",
     icon: <Compass className="w-4 h-4" />,
     color: "#F59E0B",
   },
📝 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
{
value: "safary",
label: "Safary",
icon: <Compass className="w-4 h-4" />,
color: "#F59E0B",
},
{
value: "safari",
label: "Safari",
icon: <Compass className="w-4 h-4" />,
color: "#F59E0B",
},
🤖 Prompt for AI Agents
In apps/client/src/constants/browsers.data.tsx around lines 28 to 33, the
browser entry is misspelled as "safary"/"Safary" which will break
filtering/display logic expecting "safari"; update the value to "safari" and the
label to "Safari" (keep the icon and color unchanged) so all comparisons and UI
display use the correct spelling.

Comment on lines +9 to +15
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use mql.matches for initial state and onChange; guard for SSR.

Currently you compute against window.innerWidth while subscribing to a matchMedia query. Use the MediaQueryList state directly and guard window for SSR.

-  React.useEffect(() => {
-    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
-    const onChange = () => {
-      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
-    }
-    mql.addEventListener("change", onChange)
-    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
-    return () => mql.removeEventListener("change", onChange)
-  }, [])
+  React.useEffect(() => {
+    if (typeof window === "undefined") return
+    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+    const onChange = () => setIsMobile(mql.matches)
+    // initialize
+    setIsMobile(mql.matches)
+    // subscribe
+    mql.addEventListener("change", onChange)
+    return () => mql.removeEventListener("change", onChange)
+  }, [])
📝 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 mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
React.useEffect(() => {
if (typeof window === "undefined") return
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => setIsMobile(mql.matches)
// initialize
setIsMobile(mql.matches)
// subscribe
mql.addEventListener("change", onChange)
return () => mql.removeEventListener("change", onChange)
}, [])
🤖 Prompt for AI Agents
In apps/client/src/hooks/use-mobile.ts around lines 9 to 15, the hook currently
uses window.innerWidth for initial state and inside the change handler which is
inconsistent with the matchMedia subscription and fails in SSR; change to guard
for SSR (check typeof window !== "undefined"), create the MediaQueryList only
when window exists, use mql.matches for the initial setIsMobile value and inside
the onChange handler, and keep the addEventListener/removeEventListener calls
wrapped in the same window guard so the hook does nothing during server-side
rendering.

## BrowserPlugins
# Browserplugins

<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix MD045: put alt text on the image, not the anchor

The current alt is on <a> instead of <img>, breaking accessibility tooling.

Apply this diff:

-<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
+<a href="https://nx.dev" target="_blank" rel="noreferrer"><img alt="Nx logo" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45" /></a>
📝 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
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
<a href="https://nx.dev" target="_blank" rel="noreferrer"><img alt="Nx logo" src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45" /></a>
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

3-3: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
In README.md around line 3, the alt attribute is incorrectly placed on the <a>
tag instead of the <img>, which breaks accessibility checks; remove the alt from
the anchor and add a proper alt attribute to the <img> element (e.g., alt="Nx
logo"), ensuring the anchor keeps href/target/rel and the image retains
src/width.

Copy link

@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.

Review continued from previous batch...

Comment on lines +24 to +28
const { formData, setFormData } = useExtensionStore((state) => state);


};
return (
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Introduce a small state update helper and keep the function body valid

Define handleInputChange inside the component and remove the stray brace. This also centralizes type coercion (e.g., numbers).

 export default function BasicInformation() {
   const { formData, setFormData } = useExtensionStore((state) => state);
-
-  
-  };
+  const handleInputChange = <K extends keyof typeof formData>(
+    field: K,
+    value: typeof formData[K]
+  ) => {
+    setFormData({ ...formData, [field]: value });
+  };
📝 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 { formData, setFormData } = useExtensionStore((state) => state);
};
return (
export default function BasicInformation() {
const { formData, setFormData } = useExtensionStore((state) => state);
const handleInputChange = <K extends keyof typeof formData>(
field: K,
value: typeof formData[K]
) => {
setFormData({ ...formData, [field]: value });
};
return (
/* …rest of JSX… */
);
}
🤖 Prompt for AI Agents
In apps/client/src/app/(admin)/_components/upload-form/basic-information.tsx
around lines 24-28, there's a stray closing brace and no input handler; define a
handleInputChange function inside the component (before return) that accepts
(name: keyof typeof formData, value: string | number | boolean), coerces types
appropriately (e.g., parseInt for numeric fields), and calls setFormData(prev =>
({ ...prev, [name]: coercedValue })), then remove the stray brace so the
component body remains valid and wire inputs to this new handler.

Comment on lines +26 to +28

};
return (
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Syntax error: stray brace ends the function before the return

There’s an orphan }; before the JSX, which matches the static analysis error and breaks parsing.

Apply:

-  
-  };
-  return (
+  // Add helpers here (see next comment)
+  return (
📝 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 (
// Add helpers here (see next comment)
return (
🤖 Prompt for AI Agents
In apps/client/src/app/(admin)/_components/upload-form/basic-information.tsx
around lines 26 to 28, there is an orphan closing brace and semicolon ("};")
that prematurely ends the function before the JSX return, causing a syntax
error; remove that stray "};" (or move it so the function's closing
brace/semicolon occurs after the JSX return) ensuring the function body opens
and closes around the return statement and all braces/parens are properly
balanced.

Comment on lines +41 to +46
id={'name'}
value={''}
onChange={(e)=> setFormData}
placeholder="Enter extension name"
required
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Name input is an uncontrolled no-op; wire it to the store

value={''} freezes the input, and onChange={(e)=> setFormData} doesn’t update anything. Bind to formData.name and call the helper.

-            <Input
-              id={'name'}
-              value={''}
-              onChange={(e)=> setFormData}
-              placeholder="Enter extension name"
-              required
-            />
+            <Input
+              id="name"
+              value={(formData as any)?.name ?? ""}
+              onChange={(e) => handleInputChange("name" as any, e.target.value)}
+              placeholder="Enter extension name"
+              required
+            />

If you can share the ExtensionStore/ExtensionSchema fields, we can remove the as any casts and make this fully typed.

🤖 Prompt for AI Agents
In apps/client/src/app/(admin)/_components/upload-form/basic-information.tsx
around lines 41 to 46, the Name input is currently a frozen uncontrolled no-op
(value is set to empty string and onChange doesn't update state); change value
to read from the store (e.g. formData.name) and wire onChange to call the form
update helper with the new value (e.g. call the existing
setFormData/updateFormField helper passing the event target value and field key)
so the input reflects and updates the store; remove the empty string and ensure
the onChange uses the helper function rather than passing setFormData directly
(and replace any temporary as any casts later with the proper
ExtensionStore/ExtensionSchema types).

Comment on lines +49 to +56
<Label htmlFor="version">Version</Label>
<Input
id="version"
// value={formData.version}
// onChange={(e) => handleInputChange("version", e.target.value)}
placeholder="1.0.0"
required
/>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wire Version input to store

It’s currently commented out. Bind to formData.version.

             <Input
               id="version"
-              // value={formData.version}
-              // onChange={(e) => handleInputChange("version", e.target.value)}
+              value={(formData as any)?.version ?? ""}
+              onChange={(e) => handleInputChange("version" as any, e.target.value)}
               placeholder="1.0.0"
               required
             />
📝 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
<Label htmlFor="version">Version</Label>
<Input
id="version"
// value={formData.version}
// onChange={(e) => handleInputChange("version", e.target.value)}
placeholder="1.0.0"
required
/>
<Label htmlFor="version">Version</Label>
<Input
id="version"
value={(formData as any)?.version ?? ""}
onChange={(e) => handleInputChange("version" as any, e.target.value)}
placeholder="1.0.0"
required
/>
🤖 Prompt for AI Agents
In apps/client/src/app/(admin)/_components/upload-form/basic-information.tsx
around lines 49 to 56, the Version input is commented out and not bound to
state; re-enable wiring so the input's value is formData.version and its
onChange calls handleInputChange("version", e.target.value). Restore the two
commented lines (value and onChange), keep the placeholder and required props,
and ensure formData and handleInputChange are in scope (imported or passed) so
the component updates the form state when the user types.

Comment on lines +60 to +71
<div className="space-y-2">
<Label htmlFor="shortDescription">Short Description</Label>
<Input
id="shortDescription"
// value={formData.shortDescription}
// onChange={(e) =>
// handleInputChange("shortDescription", e.target.value)
// }
placeholder="Brief description (max 100 characters)"
maxLength={100}
required
/>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wire Short Description input to store

Currently inert due to commented bindings.

           <Input
             id="shortDescription"
-            //   value={formData.shortDescription}
-            //   onChange={(e) =>
-            //     handleInputChange("shortDescription", e.target.value)
-            //   }
+            value={(formData as any)?.shortDescription ?? ""}
+            onChange={(e) =>
+              handleInputChange("shortDescription" as any, e.target.value)
+            }
             placeholder="Brief description (max 100 characters)"
             maxLength={100}
             required
           />
📝 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="space-y-2">
<Label htmlFor="shortDescription">Short Description</Label>
<Input
id="shortDescription"
// value={formData.shortDescription}
// onChange={(e) =>
// handleInputChange("shortDescription", e.target.value)
// }
placeholder="Brief description (max 100 characters)"
maxLength={100}
required
/>
<div className="space-y-2">
<Label htmlFor="shortDescription">Short Description</Label>
<Input
id="shortDescription"
value={(formData as any)?.shortDescription ?? ""}
onChange={(e) =>
handleInputChange("shortDescription" as any, e.target.value)
}
placeholder="Brief description (max 100 characters)"
maxLength={100}
required
/>
</div>
🤖 Prompt for AI Agents
In apps/client/src/app/(admin)/_components/upload-form/basic-information.tsx
around lines 60 to 71, the Short Description Input is inert because the value
and onChange bindings are commented out; restore wiring by making the Input a
controlled component: set value to formData.shortDescription and set onChange to
call handleInputChange("shortDescription", e.target.value) (or the equivalent
handler in this component), keeping maxLength and required, and ensure formData
and handleInputChange are properly typed/imported so the component re-renders
and the short description is stored.

Comment on lines +121 to +127
<Link href="/login">
<Button variant="ghost">Login</Button>
</Link>
<Link href="/register">
<Button>Get Started</Button>
</Link>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix invalid nested interactive controls in header.

renders an anchor; wrapping a Button inside creates …, which is invalid and hurts a11y. Invert: Button asChild → Link.
-              <Link href="/login">
-                <Button variant="ghost">Login</Button>
-              </Link>
-              <Link href="/register">
-                <Button>Get Started</Button>
-              </Link>
+              <Button asChild variant="ghost">
+                <Link href="/login">Login</Link>
+              </Button>
+              <Button asChild>
+                <Link href="/register">Get Started</Link>
+              </Button>
📝 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="/login">
<Button variant="ghost">Login</Button>
</Link>
<Link href="/register">
<Button>Get Started</Button>
</Link>
</div>
<Button asChild variant="ghost">
<Link href="/login">Login</Link>
</Button>
<Button asChild>
<Link href="/register">Get Started</Link>
</Button>
</div>
🤖 Prompt for AI Agents
In apps/client/src/app/[username]/store/page.tsx around lines 121-127, the
current markup nests a <Button> inside a Next.js <Link> producing invalid
<a><button>…</button></a>; change to render the Button as the parent by using
the Button component's asChild prop and move the Link inside it (e.g., <Button
asChild><Link href="/...">...</Link></Button>) for both Login and Get Started so
the rendered element is a single anchor without a nested button, preserving
styles and accessibility.

Comment on lines +346 to +347
<Download className="mr-1 h-3 w-3" />
{extension.downloads.toLocaleString()}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace deprecated onKeyPress with onKeyDown.

onKeyPress is deprecated; use onKeyDown and check e.key === "Enter".

-                  onKeyPress={(e) => e.key === "Enter" && addTag()}
+                  onKeyDown={(e) => e.key === "Enter" && addTag()}
-                  onKeyPress={(e) => e.key === "Enter" && addFeature()}
+                  onKeyDown={(e) => e.key === "Enter" && addFeature()}

Also applies to: 377-378

🤖 Prompt for AI Agents
In apps/client/src/app/[username]/store/page.tsx around lines 346-347 (and
similarly at 377-378), the component uses the deprecated onKeyPress handler;
replace it with onKeyDown and change the handler to check for e.key === "Enter"
before triggering the action. Update the JSX props to use onKeyDown, ensure the
event is typed (e: React.KeyboardEvent) if needed, and keep the same behavior by
invoking the same callback only when e.key === "Enter".

Comment on lines +355 to +369
<Link href={`/extension/${extension.id}`} className="flex-1">
<Button
variant="outline"
size="sm"
className="w-full bg-transparent"
>
<Eye className="mr-1 h-3 w-3" />
View Details
</Button>
</Link>
<Link href={`/checkout/${extension.id}`} className="flex-1">
<Button size="sm" className="w-full">
Buy Now
</Button>
</Link>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix invalid nested interactive controls in cards.

Same issue inside the extension cards. Prefer Button asChild with Link.

-                  <Link href={`/extension/${extension.id}`} className="flex-1">
-                    <Button
-                      variant="outline"
-                      size="sm"
-                      className="w-full bg-transparent"
-                    >
-                      <Eye className="mr-1 h-3 w-3" />
-                      View Details
-                    </Button>
-                  </Link>
-                  <Link href={`/checkout/${extension.id}`} className="flex-1">
-                    <Button size="sm" className="w-full">
-                      Buy Now
-                    </Button>
-                  </Link>
+                  <Button asChild variant="outline" size="sm" className="flex-1 w-full bg-transparent">
+                    <Link href={`/extension/${extension.id}`}>
+                      <Eye className="mr-1 h-3 w-3" />
+                      View Details
+                    </Link>
+                  </Button>
+                  <Button asChild size="sm" className="flex-1 w-full">
+                    <Link href={`/checkout/${extension.id}`}>Buy Now</Link>
+                  </Button>
📝 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={`/extension/${extension.id}`} className="flex-1">
<Button
variant="outline"
size="sm"
className="w-full bg-transparent"
>
<Eye className="mr-1 h-3 w-3" />
View Details
</Button>
</Link>
<Link href={`/checkout/${extension.id}`} className="flex-1">
<Button size="sm" className="w-full">
Buy Now
</Button>
</Link>
<Button asChild variant="outline" size="sm" className="flex-1 w-full bg-transparent">
<Link href={`/extension/${extension.id}`}>
<Eye className="mr-1 h-3 w-3" />
View Details
</Link>
</Button>
<Button asChild size="sm" className="flex-1 w-full">
<Link href={`/checkout/${extension.id}`}>
Buy Now
</Link>
</Button>
🤖 Prompt for AI Agents
In apps/client/src/app/[username]/store/page.tsx around lines 355 to 369, the
current code nests Button inside Link which creates invalid nested interactive
controls; replace each Link-wrapping-Button with a single Button using the
asChild prop and place the Link as the Button's child (e.g. <Button asChild
...><Link href={`/extension/${extension.id}`}>...</Link></Button> and similarly
for the checkout link) so the rendered DOM has only one interactive element per
card while preserving Link navigation and Button styling.

Comment on lines +56 to +60
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove conflicting ARIA roles on current page item.

BreadcrumbPage sets role="link" and aria-disabled="true" but also aria-current="page". The current page item should not present as a link; remove role/aria-disabled to avoid confusing AT.

-      role="link"
-      aria-disabled="true"
       aria-current="page"
📝 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
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props}
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props}
🤖 Prompt for AI Agents
In apps/client/src/components/ui/breadcrumb.tsx around lines 56-60, the
BreadcrumbPage component sets role="link" and aria-disabled="true" while also
using aria-current="page", which incorrectly exposes the current page as a link
to assistive tech; remove role="link" and aria-disabled="true" from the element
that represents the current page (leave aria-current="page"), or conditionally
only add role/aria-disabled when the item is not the current page so the current
item is presented as a non-interactive element.

Comment on lines +27 to +31
type: "video",
url: `<iframe width="1182" height="665" src="https://www.youtube.com/embed/Y0vW-xrDH30" title="🔥 Build OWN YouTube Video Downloader Chrome Extension With #react &amp; #CRXJS | [2023 - ST SABBIR]" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>`, // Example YouTube embed URL
thumbnail: "/placeholder-thumb.jpg",
},
],
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid embedding raw iframe HTML in data; prefer structured media to reduce XSS risk.

Storing full <iframe> HTML encourages dangerouslySetInnerHTML downstream. Store a structured descriptor and render safely in a component.

Refactor example:

-        type: "video",
-        url: `<iframe width="1182" height="665" src="https://www.youtube.com/embed/Y0vW-xrDH30" ... allowfullscreen></iframe>`,
-        thumbnail: "/placeholder-thumb.jpg",
+        type: "youtube",
+        videoId: "Y0vW-xrDH30",
+        thumbnail: "/placeholder-thumb.jpg",

Then render via a dedicated <Media> component that whitelists providers and sets sandboxed attributes.

📝 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
type: "video",
url: `<iframe width="1182" height="665" src="https://www.youtube.com/embed/Y0vW-xrDH30" title="🔥 Build OWN YouTube Video Downloader Chrome Extension With #react &amp; #CRXJS | [2023 - ST SABBIR]" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>`, // Example YouTube embed URL
thumbnail: "/placeholder-thumb.jpg",
},
],
{
type: "youtube",
videoId: "Y0vW-xrDH30",
thumbnail: "/placeholder-thumb.jpg",
},

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