aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2025-08-03 23:35:06 -0700
committerMohamedBassem <me@mbassem.com>2025-08-03 23:59:45 -0700
commitc68e5099797d5b49ed6441ce04d7c77105327f73 (patch)
tree296fe5f473f46d802fcf94fa203ca37672112c30 /apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx
parent03aa17200ed80c2978bf496991c6afbb5a04258b (diff)
downloadkarakeep-c68e5099797d5b49ed6441ce04d7c77105327f73.tar.zst
feat(web): Add special cards for specific websites. Fixes #1344
Diffstat (limited to 'apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx')
-rw-r--r--apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx137
1 files changed, 137 insertions, 0 deletions
diff --git a/apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx b/apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx
new file mode 100644
index 00000000..aaf33565
--- /dev/null
+++ b/apps/web/components/dashboard/preview/content-renderers/AmazonRenderer.tsx
@@ -0,0 +1,137 @@
+import { ShoppingCart } from "lucide-react";
+
+import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
+
+import { ContentRenderer } from "./types";
+
+function extractAmazonProductInfo(
+ url: string,
+): { asin: string; domain: string } | null {
+ const patterns = [
+ // Standard product URLs
+ /amazon\.([a-z.]+)\/.*\/dp\/([A-Z0-9]{10})/,
+ /amazon\.([a-z.]+)\/dp\/([A-Z0-9]{10})/,
+ // Shortened URLs
+ /amazon\.([a-z.]+)\/gp\/product\/([A-Z0-9]{10})/,
+ // Mobile URLs
+ /amazon\.([a-z.]+)\/.*\/product\/([A-Z0-9]{10})/,
+ // International variations
+ /amazon\.([a-z.]+)\/.*\/([A-Z0-9]{10})/,
+ ];
+
+ for (const pattern of patterns) {
+ const match = url.match(pattern);
+ if (match) {
+ return {
+ domain: match[1],
+ asin: match[2],
+ };
+ }
+ }
+ return null;
+}
+
+function canRenderAmazon(bookmark: ZBookmark): boolean {
+ if (bookmark.content.type !== BookmarkTypes.LINK) {
+ return false;
+ }
+
+ const url = bookmark.content.url;
+ return extractAmazonProductInfo(url) !== null;
+}
+
+function AmazonRendererComponent({ bookmark }: { bookmark: ZBookmark }) {
+ if (bookmark.content.type !== BookmarkTypes.LINK) {
+ return null;
+ }
+
+ const productInfo = extractAmazonProductInfo(bookmark.content.url);
+ if (!productInfo) {
+ return null;
+ }
+
+ const { title, description, imageUrl } = bookmark.content;
+
+ return (
+ <div className="relative h-full w-full overflow-auto">
+ <div className="mx-auto flex max-w-2xl flex-col items-center p-6">
+ {/* Product Image */}
+ {imageUrl && (
+ <div className="mb-6 w-full max-w-md">
+ <img
+ src={imageUrl}
+ alt={title || "Amazon Product"}
+ className="h-auto max-h-96 w-full rounded-lg object-contain shadow-lg"
+ />
+ </div>
+ )}
+
+ {/* Product Info Card */}
+ <div className="w-full rounded-lg border bg-card p-6 shadow-sm">
+ {/* Title */}
+ {title && (
+ <h2 className="mb-4 line-clamp-3 text-xl font-semibold">{title}</h2>
+ )}
+
+ {/* Description */}
+ {description && (
+ <p className="mb-6 line-clamp-4 text-muted-foreground">
+ {description}
+ </p>
+ )}
+
+ {/* Product Details */}
+ <div className="mb-6 space-y-3">
+ <div className="flex items-center gap-2 text-sm">
+ <span className="font-medium">ASIN:</span>
+ <span className="font-mono text-muted-foreground">
+ {productInfo.asin}
+ </span>
+ </div>
+ <div className="flex items-center gap-2 text-sm">
+ <span className="font-medium">Domain:</span>
+ <span className="text-muted-foreground">
+ amazon.{productInfo.domain}
+ </span>
+ </div>
+ </div>
+
+ {/* Action Buttons */}
+ <div className="flex gap-3">
+ <a
+ href={bookmark.content.url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="flex flex-1 items-center justify-center gap-2 rounded-md bg-[#FF9900] px-4 py-2 text-center font-medium text-white transition-colors hover:bg-[#FF9900]/90"
+ >
+ <ShoppingCart size={16} />
+ View on Amazon
+ </a>
+ <a
+ href={`https://www.amazon.${productInfo.domain}/dp/${productInfo.asin}`}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="rounded-md border border-border px-4 py-2 text-sm transition-colors hover:bg-accent"
+ >
+ Direct Link
+ </a>
+ </div>
+
+ {/* Amazon Disclaimer */}
+ <p className="mt-4 text-center text-xs text-muted-foreground">
+ Product information from Amazon. Prices and availability may vary.
+ </p>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+export const amazonRenderer: ContentRenderer = {
+ id: "amazon",
+ name: "Amazon Product",
+ icon: ShoppingCart,
+ canRender: canRenderAmazon,
+ component: AmazonRendererComponent,
+ priority: 10,
+};