Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions apps/extensions/app/category/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
ArrowRight,
Shield,
Lock,
Eye,
ShoppingCart,
Coins,
Moon,
Expand All @@ -21,13 +20,13 @@ import {
Video,
Clipboard,
Gauge,
Globe,
Puzzle
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import extensionsData from '@serp-tools/app-core/data/extensions.json';

// Icon mapping for extensions
const iconMap: { [key: string]: any } = {
const iconMap: { [key: string]: LucideIcon } = {
'ublock-origin': Shield,
'lastpass': Lock,
'grammarly': CheckSquare,
Expand All @@ -45,16 +44,33 @@ const iconMap: { [key: string]: any } = {
'momentum': Gauge,
};

interface ExtensionData {
id: string;
slug?: string;
name: string;
description: string;
category?: string;
chromeStoreUrl?: string;
firefoxAddonUrl?: string;
url?: string;
tags?: string[];
isNew?: boolean;
isPopular?: boolean;
rating?: number;
users?: string;
isActive?: boolean;
}

// Process extensions from JSON data
const processedExtensions = extensionsData
.filter((extension: any) => extension.isActive)
.map((extension: any) => ({
const processedExtensions = (extensionsData as ExtensionData[])
.filter((extension) => extension.isActive)
.map((extension) => ({
id: extension.id,
name: extension.name,
description: extension.description,
category: extension.category || 'other',
icon: iconMap[extension.id] || Puzzle,
href: extension.chromeStoreUrl || extension.firefoxAddonUrl || extension.url,
href: extension.chromeStoreUrl || extension.firefoxAddonUrl || extension.url || '#',
tags: extension.tags || [],
isNew: extension.isNew || false,
isPopular: extension.isPopular || false,
Expand All @@ -66,8 +82,13 @@ const processedExtensions = extensionsData
export default function HomePage() {
const [selectedCategory, setSelectedCategory] = useState("all");
const [searchQuery, setSearchQuery] = useState("");
const [extensions, setExtensions] = useState(processedExtensions);
const [categories, setCategories] = useState<any[]>([]);
const [extensions] = useState(processedExtensions);
interface Category {
id: string;
name: string;
count: number;
}
const [categories, setCategories] = useState<Category[]>([]);

useEffect(() => {
// Create categories from extensions
Expand Down
122 changes: 72 additions & 50 deletions apps/extensions/app/extensions/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import { Button } from "@serp-tools/ui/components/button";
import { ExternalLink, Shield, Lock, CheckSquare, ShoppingCart, Coins, Moon, Bookmark, Palette, Code, Video, Clipboard, Gauge, Puzzle, DollarSign } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import extensionsData from '@serp-tools/app-core/data/extensions.json';
import { notFound } from 'next/navigation';

interface ExtensionData {
id: string;
slug: string;
name: string;
description: string;
category?: string;
chromeStoreUrl?: string;
firefoxAddonUrl?: string;
url?: string;
tags?: string[];
isNew?: boolean;
isPopular?: boolean;
rating?: number;
users?: string;
isActive?: boolean;
}

// Generate static params for all extensions
export async function generateStaticParams() {
return extensionsData
.filter((extension: any) => extension.isActive)
.map((extension: any) => ({
return (extensionsData as ExtensionData[])
.filter((extension) => extension.isActive)
.map((extension) => ({
id: extension.id,
}));
}

// Icon mapping for extensions using slug
const iconMap: { [key: string]: any } = {
const iconMap: { [key: string]: LucideIcon } = {
'ublock-origin': Shield,
'lastpass': Lock,
'grammarly': CheckSquare,
Expand All @@ -32,14 +50,15 @@ const iconMap: { [key: string]: any } = {
};

interface PageProps {
params: {
params: Promise<{
id: string;
};
}>;
}

export default function SingleExtensionPage({ params }: PageProps) {
export default async function SingleExtensionPage({ params }: PageProps) {
const { id } = await params;
// Find the extension by ID from the JSON data
const extensionData = extensionsData.find((ext: any) => ext.id === params.id);
const extensionData = (extensionsData as ExtensionData[]).find((ext) => ext.id === id);

// If extension not found, return 404
if (!extensionData) {
Expand All @@ -65,14 +84,14 @@ export default function SingleExtensionPage({ params }: PageProps) {
const Icon = extension.icon;

// Get related extensions from same category
const alternatives = extensionsData
.filter((ext: any) =>
const alternatives = (extensionsData as ExtensionData[])
.filter((ext) =>
ext.category === extension.category &&
ext.id !== extension.id &&
ext.isActive
)
.slice(0, 10)
.map((ext: any) => ({
.map((ext) => ({
id: ext.id,
name: ext.name,
description: ext.description,
Expand Down Expand Up @@ -108,68 +127,71 @@ export default function SingleExtensionPage({ params }: PageProps) {
</div>

{/* Content Section */}
<div className="prose prose-sm prose-neutral dark:prose-invert max-w-none">
<p>{extension.description}</p>
<div className="prose prose-neutral dark:prose-invert max-w-none">
<p className="lead">{extension.description}</p>

{extension.tags && extension.tags.length > 0 && (
<>
<p><strong>Features:</strong></p>
<h3>Features</h3>
<ul>
{extension.tags.map((tag: string, index: number) => (
<li key={index}>
<p>{tag.charAt(0).toUpperCase() + tag.slice(1).replace(/-/g, ' ')}</p>
{tag.charAt(0).toUpperCase() + tag.slice(1).replace(/-/g, ' ')}
</li>
))}
</ul>
</>
)}

{/* Additional info */}
<div className="not-prose mt-6 space-y-2">
<h3>Details</h3>
<ul>
{extension.rating && (
<p className="text-sm text-muted-foreground">
<li>
<strong>Rating:</strong> {extension.rating} / 5 stars
</p>
</li>
)}
{extension.users && (
<p className="text-sm text-muted-foreground">
<li>
<strong>Users:</strong> {extension.users}
</p>
</li>
)}
{extension.category && (
<p className="text-sm text-muted-foreground">
<li>
<strong>Category:</strong> {extension.category.charAt(0).toUpperCase() + extension.category.slice(1)}
</p>
</li>
)}
</div>
</ul>

{/* Store Links */}
<div className="not-prose mt-6 space-y-2">
{extension.chromeStoreUrl && (
<p className="text-sm">
<a
href={extension.chromeStoreUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Available on Chrome Web Store →
</a>
</p>
)}
{extension.firefoxAddonUrl && (
<p className="text-sm">
<a
href={extension.firefoxAddonUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
Available on Firefox Add-ons →
</a>
</p>
)}
</div>
{(extension.chromeStoreUrl || extension.firefoxAddonUrl) && (
<>
<h3>Download</h3>
<ul>
{extension.chromeStoreUrl && (
<li>
<a
href={extension.chromeStoreUrl}
target="_blank"
rel="noopener noreferrer"
>
Available on Chrome Web Store →
</a>
</li>
)}
{extension.firefoxAddonUrl && (
<li>
<a
href={extension.firefoxAddonUrl}
target="_blank"
rel="noopener noreferrer"
>
Available on Firefox Add-ons →
</a>
</li>
)}
</ul>
</>
)}
</div>
</article>

Expand Down
41 changes: 31 additions & 10 deletions apps/extensions/app/extensions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
ArrowRight,
Shield,
Lock,
Eye,
ShoppingCart,
Coins,
Moon,
Expand All @@ -21,13 +20,13 @@ import {
Video,
Clipboard,
Gauge,
Globe,
Puzzle
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import extensionsData from '@serp-tools/app-core/data/extensions.json';

// Icon mapping for extensions using slug
const iconMap: { [key: string]: any } = {
const iconMap: { [key: string]: LucideIcon } = {
'ublock-origin': Shield,
'lastpass': Lock,
'grammarly': CheckSquare,
Expand All @@ -45,17 +44,34 @@ const iconMap: { [key: string]: any } = {
'momentum': Gauge,
};

interface ExtensionData {
id: string;
slug: string;
name: string;
description: string;
category?: string;
chromeStoreUrl?: string;
firefoxAddonUrl?: string;
url?: string;
tags?: string[];
isNew?: boolean;
isPopular?: boolean;
rating?: number;
users?: string;
isActive?: boolean;
}

// Process extensions from JSON data - use the Chrome extension ID directly
const processedExtensions = extensionsData
.filter((extension: any) => extension.isActive)
.map((extension: any) => ({
const processedExtensions = (extensionsData as ExtensionData[])
.filter((extension) => extension.isActive)
.map((extension) => ({
id: extension.id, // Use the Chrome extension ID directly
slug: extension.slug,
name: extension.name,
description: extension.description,
category: extension.category || 'other',
icon: iconMap[extension.slug] || Puzzle,
href: extension.chromeStoreUrl || extension.firefoxAddonUrl || extension.url,
href: extension.chromeStoreUrl || extension.firefoxAddonUrl || extension.url || '#',
tags: extension.tags || [],
isNew: extension.isNew || false,
isPopular: extension.isPopular || false,
Expand All @@ -67,8 +83,13 @@ const processedExtensions = extensionsData
export default function HomePage() {
const [selectedCategory, setSelectedCategory] = useState("all");
const [searchQuery, setSearchQuery] = useState("");
const [extensions, setExtensions] = useState(processedExtensions);
const [categories, setCategories] = useState<any[]>([]);
const [extensions] = useState(processedExtensions);
interface Category {
id: string;
name: string;
count: number;
}
const [categories, setCategories] = useState<Category[]>([]);

useEffect(() => {
// Create categories from extensions
Expand Down Expand Up @@ -127,7 +148,7 @@ export default function HomePage() {
</section>

{/* Main Content */}
<section className="container py-12">
<section className="container max-w-6xl py-12">
{/* Search and Filter Bar */}
<ExtensionsSearchBar
searchQuery={searchQuery}
Expand Down
Loading