-
Notifications
You must be signed in to change notification settings - Fork 0
Monorepo #1
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
base: sabbir
Are you sure you want to change the base?
Monorepo #1
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughIntroduces 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
| 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
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: 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 secretsRoot
.gitignoreno longer ignores.env*. Given you added.env.exampleunder apps/server and apps/client, add root-level env ignores to prevent accidental commits.Append:
+# Environment files (root) +.env +.env.* +.env.local +.env.*.local
| - 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() |
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.
💡 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 buildLength 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"
doneLength 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"
doneLength 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.
| - 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 | |||
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
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.
| 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.
| # env files (can opt-in for committing if needed) | ||
| .env | ||
|
|
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.
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.
| # 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.
| import { sortsBy } from "@/constants"; | ||
| export type SortBy = (typeof sortsBy)[number]; |
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.
💡 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
to
const [sortBy, setSortBy] = useState<SortBy["value"]>("popular");
const [sortBy, setSortBy] = useState<SortBy>("popular");
• In apps/client/src/app/(public)/_components/filters/sort-by-filter.tsx:
- Update the
Propsto:type Props = { sortBy: SortBy; setSortBy: Dispatch<SetStateAction<SortBy>>; }
- Change the
onValueChangehandler 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.
| export const categories = [ | ||
| "Productivity", | ||
| "Security", | ||
| "Developer Tools", | ||
| "Social Media", | ||
| "Shopping", | ||
| "Entertainment", | ||
| "Utilities", | ||
| "Communication", | ||
| ]; | ||
|
|
||
| export const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera"]; |
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
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.
| 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 = [ |
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.
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.
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.
💡 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.
| { | ||
| value: "safary", | ||
| label: "Safary", | ||
| icon: <Compass className="w-4 h-4" />, | ||
| color: "#F59E0B", | ||
| }, |
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
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.
| { | |
| 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.
| 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) |
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
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.
| 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> |
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
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.
| <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.
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.
Review continued from previous batch...
| const { formData, setFormData } = useExtensionStore((state) => state); | ||
|
|
||
|
|
||
| }; | ||
| return ( |
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
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.
| 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.
|
|
||
| }; | ||
| return ( |
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.
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.
| }; | |
| 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.
| id={'name'} | ||
| value={''} | ||
| onChange={(e)=> setFormData} | ||
| placeholder="Enter extension name" | ||
| required | ||
| /> |
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.
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).
| <Label htmlFor="version">Version</Label> | ||
| <Input | ||
| id="version" | ||
| // value={formData.version} | ||
| // onChange={(e) => handleInputChange("version", e.target.value)} | ||
| placeholder="1.0.0" | ||
| required | ||
| /> |
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
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.
| <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.
| <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 | ||
| /> |
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
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.
| <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.
| <Link href="/login"> | ||
| <Button variant="ghost">Login</Button> | ||
| </Link> | ||
| <Link href="/register"> | ||
| <Button>Get Started</Button> | ||
| </Link> | ||
| </div> |
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
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.
| <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.
| <Download className="mr-1 h-3 w-3" /> | ||
| {extension.downloads.toLocaleString()} |
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
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".
| <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> |
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
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.
| <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.
| role="link" | ||
| aria-disabled="true" | ||
| aria-current="page" | ||
| className={cn("text-foreground font-normal", className)} | ||
| {...props} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove 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.
| 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.
| 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 & #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", | ||
| }, | ||
| ], |
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
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.
| 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 & #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", | |
| }, |
Summary by CodeRabbit
New Features
Documentation
Chores