feat: initial public release (MAESTRO)

This commit is contained in:
oss-sync
2026-06-03 05:08:00 +00:00
commit f5c7666f6b
823 changed files with 184150 additions and 0 deletions
@@ -0,0 +1,92 @@
import type { AmazonData } from './types';
function StarRating({ rating, reviewCount }: { rating: number; reviewCount?: number }) {
const full = Math.floor(rating);
const half = rating - full >= 0.5;
const stars: string[] = [];
for (let i = 0; i < full; i++) stars.push('\u2605');
if (half) stars.push('\u2606');
return (
<span className="text-amber-400 text-sm">
{stars.join('')} {rating.toFixed(1)}
{reviewCount != null && <span className="text-slate-400 text-xs ml-1">({reviewCount.toLocaleString()})</span>}
</span>
);
}
export function AmazonProductsDetail({ data }: { data: AmazonData }) {
const { query, products } = data;
return (
<div className="p-6">
<h2 className="text-lg font-bold text-slate-800 mb-4">
&#128722; Amazon : {query}
</h2>
<div className="space-y-6">
{products.map((p, i) => (
<div key={p.asin} className="bg-white border border-slate-200 rounded-xl p-4">
<div className="flex gap-4 flex-col sm:flex-row">
{/* Product image */}
<div className="w-full sm:w-40 h-40 bg-slate-50 rounded-lg flex items-center justify-center flex-shrink-0 overflow-hidden">
{p.imageUrl ? (
<img src={p.imageUrl} alt={p.title} className="max-h-full max-w-full object-contain" />
) : (
<span className="text-4xl">&#128190;</span>
)}
</div>
{/* Product info */}
<div className="flex-1 min-w-0">
<div className="text-slate-400 mb-1" style={{ fontSize: 13 }}>#{i + 1}</div>
<h3 className="text-sm font-semibold text-slate-800 leading-snug mb-2">{p.title}</h3>
{p.price && (
<div className="text-xl font-bold text-red-600 mb-1">{p.price}</div>
)}
{p.rating != null && (
<div className="mb-2">
<StarRating rating={p.rating} reviewCount={p.reviewCount} />
</div>
)}
<div className="text-xs text-slate-400 mb-3">ASIN: {p.asin}</div>
<div className="flex gap-2 flex-wrap">
<a
href={p.productUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 px-3 py-1.5 bg-amber-400 hover:bg-amber-500 text-slate-900 text-xs font-semibold rounded-lg no-underline transition-colors"
>
Amazon
</a>
<a
href={p.keepaDetailUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 px-3 py-1.5 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg no-underline transition-colors"
>
Keepa
</a>
</div>
</div>
</div>
{/* Keepa price graph */}
<div className="mt-4 bg-slate-50 rounded-lg p-3">
<div className="text-xs text-slate-500 mb-2">&#128200; (Keepa)</div>
<img
src={p.keepaGraphUrl}
alt={`${p.title} 価格推移`}
className="w-full rounded"
loading="lazy"
/>
</div>
</div>
))}
</div>
</div>
);
}