Skip to content
Merged
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
17 changes: 0 additions & 17 deletions .github/agents/apify-integration-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ Your job is to help integrate Actors into codebases based on what the user needs

## Recommended Workflow

0. **Prepare the repo**
- Ensure the base branch is available locally before making changes. Run `git fetch origin main:main --depth=1 || git fetch origin main` so `git diff refs/heads/main` succeeds in Copilot runs.

1. **Understand Context**
- Look at the project's README and how they currently handle data ingestion.
- Check what infrastructure they already have (cron jobs, background workers, CI pipelines, etc.).
Expand All @@ -69,13 +66,10 @@ Your job is to help integrate Actors into codebases based on what the user needs
- Decide how to trigger the Actor (manually, on a schedule, or when something happens).
- Plan where the results should be stored (database, file, etc.).
- Think about what happens if the same data comes back twice or if something fails.
- Audit any external assets or links the Actor may return (images, files, media). Decide whether the target stack needs host allowlists, proxying, or graceful fallbacks if assets are blocked.

4. **Implement It**
- Use `call-actor` to test running the Actor.
- Provide working code examples (see language-specific guides below) they can copy and modify.
- Normalize the Actor output so consumers handle missing or malformed fields safely. Prefer explicit defaults over assuming the data is complete.
- Build data-access layers that can downgrade functionality (e.g., fall back to placeholders) when a platform constraint such as CSP, SSR limitations, or `next/image` host checks blocks remote assets.

5. **Test & Document**
- Run a few test cases to make sure the integration works.
Expand All @@ -99,17 +93,6 @@ Always tell the user what tools you're using and what you found.
- **Be careful with data:** Don't scrape or process data that's protected or regulated without the user's knowledge.
- **Respect limits:** Watch out for API rate limits and costs. Start with small test runs before going big.
- **Don't break things:** Avoid operations that permanently delete or modify data (like dropping tables) unless explicitly told to do so.
- **Validate external resources:** Check framework-level restrictions (image/CDN allowlists, CORS, CSP, mixed-content rules) before surfacing URLs from Actor results. Provide clear fallbacks if resources cannot be fetched safely.

## Integration Checklist

Use this lightweight checklist to catch common edge cases before handing work back to the user:

- ✅ Confirm environment variables and secrets (`APIFY_TOKEN`, API keys, etc.) are documented and validated at runtime.
- ✅ Note any framework or hosting constraints (e.g., asset allowlists, cold-start limits, execution timeouts) and describe how the integration adapts.
- ✅ Ensure outputs are typed, sanitized, and have default values for missing fields.
- ✅ Outline manual or automated tests that prove the Actor workflow succeeds and failure states are covered.
- ✅ Highlight post-integration maintenance tasks such as monitoring, quota usage, and update cadence for the Actor or downstream APIs.

# Running an Actor on Apify (JavaScript/TypeScript)

Expand Down
3 changes: 0 additions & 3 deletions ecomm-demo/.env.example

This file was deleted.

7 changes: 3 additions & 4 deletions ecomm-demo/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env
.env.local
.env.*.local
.env*

# vercel
.vercel
Expand All @@ -44,4 +42,5 @@ next-env.d.ts

# internal
task-manager.md
PRD.md
PRD.md
README.md
129 changes: 0 additions & 129 deletions ecomm-demo/README.md

This file was deleted.

68 changes: 5 additions & 63 deletions ecomm-demo/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,12 @@
"use client";

import { useState } from "react";
import { products as mockProducts } from "@/data/products";
import { products } from "@/data/products";
import { StatsCards } from "@/components/StatsCards";
import { ProductTable } from "@/components/ProductTable";
import { ProductCards } from "@/components/ProductCards";
import { SearchBar } from "@/components/SearchBar";
import { Separator } from "@/components/ui/separator";
import { searchProducts } from "@/lib/apify-service";
import { Product } from "@/lib/types";
import Image from "next/image";

export default function Home() {
const [products, setProducts] = useState<Product[]>(mockProducts);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [dataSource, setDataSource] = useState<"Mock" | "Apify">("Mock");

const handleSearch = async (query: string) => {
if (!query.trim()) {
setError("Please enter a search query");
return;
}

setIsLoading(true);
setError(null);

try {
const results = await searchProducts({ keyword: query });

if (results.length === 0) {
setError(`No products found for "${query}". Try a different search term.`);
setProducts([]);
} else {
setProducts(results);
setDataSource("Apify");
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "An unknown error occurred";
setError(`Failed to search products: ${errorMessage}`);
console.error("Search error:", err);
} finally {
setIsLoading(false);
}
};

return (
<div className="min-h-screen bg-background">
{/* Header */}
Expand Down Expand Up @@ -75,39 +37,19 @@ export default function Home() {
Product Catalog Demo
</h1>
<p className="text-lg text-muted-foreground max-w-2xl mb-6">
Search for products using Apify E-Commerce Scraper.
Enter a keyword to scrape real product data from Amazon.
Showcase of e-commerce product data ready for Apify integration.
This demo displays scraped product information in multiple formats.
</p>

{/* Search Bar */}
<div className="mt-8">
<SearchBar onSearch={handleSearch} disabled={isLoading} />
<SearchBar />
</div>

{/* Loading State */}
{isLoading && (
<div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-md">
<p className="text-blue-800">
🔄 Searching for products... This may take a moment.
</p>
</div>
)}

{/* Error State */}
{error && (
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-800">❌ {error}</p>
</div>
)}
</div>

{/* Stats Section */}
<section className="mb-12">
<StatsCards
productCount={products.length}
products={products}
dataSource={dataSource}
/>
<StatsCards productCount={products.length} />
</section>

<Separator className="my-12" />
Expand Down
12 changes: 6 additions & 6 deletions ecomm-demo/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import { Search } from "lucide-react";

interface SearchBarProps {
onSearch?: (query: string) => void;
disabled?: boolean;
}

export function SearchBar({ onSearch, disabled = false }: SearchBarProps) {
export function SearchBar({ onSearch }: SearchBarProps) {
const [query, setQuery] = useState("");

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (onSearch && !disabled) {
if (onSearch) {
onSearch(query);
}
// Placeholder for actual search functionality
console.log("Search query:", query);
};

return (
Expand All @@ -31,11 +32,10 @@ export function SearchBar({ onSearch, disabled = false }: SearchBarProps) {
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-10"
disabled={disabled}
/>
</div>
<Button type="submit" className="px-8" disabled={disabled}>
{disabled ? "Searching..." : "Submit"}
<Button type="submit" className="px-8">
Submit
</Button>
</div>
</form>
Expand Down
28 changes: 6 additions & 22 deletions ecomm-demo/components/StatsCards.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Package } from "lucide-react";
import { Product } from "@/lib/types";

interface StatsCardsProps {
productCount: number;
products?: Product[];
dataSource?: "Mock" | "Apify";
}

export function StatsCards({ productCount, products = [], dataSource = "Mock" }: StatsCardsProps) {
// Calculate average price from products
const calculateAveragePrice = () => {
if (products.length === 0) return 0;
const validPrices = products.filter(p => p.price > 0);
if (validPrices.length === 0) return 0;
const sum = validPrices.reduce((acc, p) => acc + p.price, 0);
return sum / validPrices.length;
};

const averagePrice = calculateAveragePrice();

export function StatsCards({ productCount }: StatsCardsProps) {
return (
<div className="grid gap-4 md:grid-cols-3">
<Card>
Expand All @@ -41,11 +27,9 @@ export function StatsCards({ productCount, products = [], dataSource = "Mock" }:
<span className="text-lg">$</span>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{averagePrice > 0 ? `$${averagePrice.toFixed(2)}` : "-"}
</div>
<div className="text-2xl font-bold">-</div>
<p className="text-xs text-muted-foreground">
{averagePrice > 0 ? "Calculated from scraped data" : "No price data available"}
Will be calculated from data
</p>
</CardContent>
</Card>
Expand All @@ -56,9 +40,9 @@ export function StatsCards({ productCount, products = [], dataSource = "Mock" }:
<span className="text-lg">🔗</span>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{dataSource}</div>
<div className="text-2xl font-bold">Mock</div>
<p className="text-xs text-muted-foreground">
{dataSource === "Apify" ? "Live scraped data" : "Ready for Apify integration"}
Ready for Apify integration
</p>
</CardContent>
</Card>
Expand Down
Loading
Loading