ronylu commited on
Commit
851da48
·
verified ·
1 Parent(s): 10c4f5a

Upload 4 files

Browse files
src/pages/Home.jsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { Link } from "react-router-dom";
3
+ import { products } from "../data/products.js";
4
+
5
+ export default function Home() {
6
+ const featured = products[0];
7
+
8
+ return (
9
+ <div className="space-y-12">
10
+ <section className="overflow-hidden rounded-3xl border border-neutral-200 bg-gradient-to-b from-neutral-50 to-white">
11
+ <div className="grid items-center gap-10 p-8 md:grid-cols-2 md:p-12">
12
+ <div className="space-y-4">
13
+ <p className="text-xs font-semibold uppercase tracking-wider text-neutral-500">New</p>
14
+ <h1 className="text-4xl font-semibold tracking-tight md:text-5xl">
15
+ {featured.name}
16
+ </h1>
17
+ <p className="text-lg text-neutral-600">{featured.tagline}</p>
18
+ <div className="flex flex-wrap gap-3 pt-2">
19
+ <Link
20
+ to={`/product/${featured.slug}`}
21
+ className="rounded-full bg-neutral-900 px-5 py-2.5 text-sm font-semibold text-white hover:bg-neutral-800"
22
+ >
23
+ Learn more
24
+ </Link>
25
+ <Link
26
+ to="/store"
27
+ className="rounded-full border border-neutral-300 bg-white px-5 py-2.5 text-sm font-semibold text-neutral-900 hover:bg-neutral-50"
28
+ >
29
+ Shop all
30
+ </Link>
31
+ </div>
32
+ <p className="text-xs text-neutral-500 pt-2">
33
+ Tip: Replace placeholder images/specs with your own licensed assets & data.
34
+ </p>
35
+ </div>
36
+
37
+ <div className="relative">
38
+ <img
39
+ src={featured.heroImage}
40
+ alt={featured.name}
41
+ className="w-full rounded-2xl border border-neutral-200 object-cover"
42
+ loading="lazy"
43
+ />
44
+ </div>
45
+ </div>
46
+ </section>
47
+
48
+ <section className="grid gap-4 md:grid-cols-3">
49
+ {products.map((p) => (
50
+ <Link
51
+ key={p.slug}
52
+ to={`/product/${p.slug}`}
53
+ className="group rounded-3xl border border-neutral-200 bg-white p-5 hover:shadow-sm"
54
+ >
55
+ <div className="overflow-hidden rounded-2xl border border-neutral-200">
56
+ <img
57
+ src={p.heroImage}
58
+ alt={p.name}
59
+ className="h-44 w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
60
+ loading="lazy"
61
+ />
62
+ </div>
63
+ <div className="pt-4">
64
+ <p className="text-xs font-semibold uppercase tracking-wider text-neutral-500">{p.category}</p>
65
+ <h3 className="pt-1 text-lg font-semibold tracking-tight">{p.name}</h3>
66
+ <p className="pt-1 text-sm text-neutral-600">{p.tagline}</p>
67
+ <p className="pt-3 text-sm font-semibold text-neutral-900">From ${p.priceFrom}</p>
68
+ </div>
69
+ </Link>
70
+ ))}
71
+ </section>
72
+ </div>
73
+ );
74
+ }
src/pages/NotFound.jsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { Link } from "react-router-dom";
3
+
4
+ export default function NotFound() {
5
+ return (
6
+ <div className="rounded-3xl border border-neutral-200 bg-white p-8">
7
+ <h2 className="text-2xl font-semibold">Page not found</h2>
8
+ <p className="pt-2 text-neutral-600">The page you requested doesn’t exist.</p>
9
+ <Link to="/" className="mt-6 inline-block rounded-full bg-neutral-900 px-5 py-2.5 text-sm font-semibold text-white">
10
+ Go home
11
+ </Link>
12
+ </div>
13
+ );
14
+ }
src/pages/Product.jsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { Link, useParams } from "react-router-dom";
3
+ import { getProduct } from "../data/products.js";
4
+
5
+ function SpecRow({ label, value }) {
6
+ return (
7
+ <div className="flex items-center justify-between gap-6 border-b border-neutral-200 py-3 text-sm">
8
+ <span className="text-neutral-600">{label}</span>
9
+ <span className="font-medium text-neutral-900">{value}</span>
10
+ </div>
11
+ );
12
+ }
13
+
14
+ export default function Product() {
15
+ const { slug } = useParams();
16
+ const p = getProduct(slug);
17
+
18
+ if (!p) {
19
+ return (
20
+ <div className="rounded-3xl border border-neutral-200 bg-white p-8">
21
+ <h2 className="text-2xl font-semibold">Product not found</h2>
22
+ <p className="pt-2 text-neutral-600">The product you’re looking for doesn’t exist.</p>
23
+ <Link to="/store" className="mt-6 inline-block rounded-full bg-neutral-900 px-5 py-2.5 text-sm font-semibold text-white">
24
+ Back to Store
25
+ </Link>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ return (
31
+ <div className="space-y-10">
32
+ <div className="flex items-center justify-between">
33
+ <Link to="/store" className="text-sm font-semibold text-neutral-700 hover:text-neutral-900">
34
+ ← Back to Store
35
+ </Link>
36
+ <p className="text-sm text-neutral-500">From ${p.priceFrom}</p>
37
+ </div>
38
+
39
+ <section className="grid gap-8 md:grid-cols-2 md:items-start">
40
+ <div className="space-y-4">
41
+ <p className="text-xs font-semibold uppercase tracking-wider text-neutral-500">{p.category}</p>
42
+ <h1 className="text-4xl font-semibold tracking-tight">{p.name}</h1>
43
+ <p className="text-lg text-neutral-600">{p.tagline}</p>
44
+
45
+ <div className="flex flex-wrap gap-3 pt-2">
46
+ <button className="rounded-full bg-neutral-900 px-5 py-2.5 text-sm font-semibold text-white hover:bg-neutral-800">
47
+ Buy (demo)
48
+ </button>
49
+ <a
50
+ className="rounded-full border border-neutral-300 bg-white px-5 py-2.5 text-sm font-semibold text-neutral-900 hover:bg-neutral-50"
51
+ href="https://www.apple.com/"
52
+ target="_blank"
53
+ rel="noreferrer"
54
+ >
55
+ Compare on official site
56
+ </a>
57
+ </div>
58
+
59
+ <div className="pt-6">
60
+ <h3 className="text-base font-semibold">Highlights</h3>
61
+ <ul className="mt-3 list-disc space-y-2 pl-5 text-sm text-neutral-700">
62
+ {p.highlights.map((h, idx) => (
63
+ <li key={idx}>{h}</li>
64
+ ))}
65
+ </ul>
66
+ </div>
67
+ </div>
68
+
69
+ <div className="space-y-4">
70
+ <div className="overflow-hidden rounded-3xl border border-neutral-200">
71
+ <img src={p.heroImage} alt={p.name} className="w-full object-cover" loading="lazy" />
72
+ </div>
73
+
74
+ <div className="grid grid-cols-2 gap-3">
75
+ {p.gallery.map((src, idx) => (
76
+ <div key={idx} className="overflow-hidden rounded-2xl border border-neutral-200">
77
+ <img src={src} alt={`${p.name} ${idx + 1}`} className="h-32 w-full object-cover" loading="lazy" />
78
+ </div>
79
+ ))}
80
+ </div>
81
+ </div>
82
+ </section>
83
+
84
+ <section className="grid gap-6 md:grid-cols-2">
85
+ <div className="rounded-3xl border border-neutral-200 bg-white p-6">
86
+ <h3 className="text-base font-semibold">Specifications</h3>
87
+ <div className="mt-3">
88
+ {Object.entries(p.specs).map(([k, v]) => (
89
+ <SpecRow key={k} label={k.toUpperCase()} value={v} />
90
+ ))}
91
+ </div>
92
+ <p className="pt-4 text-xs text-neutral-500">
93
+ Replace these sample specs with your own data or data you’re licensed to publish.
94
+ </p>
95
+ </div>
96
+
97
+ <div className="rounded-3xl border border-neutral-200 bg-white p-6">
98
+ <h3 className="text-base font-semibold">Video</h3>
99
+ <p className="pt-2 text-sm text-neutral-600">
100
+ Embedded video (YouTube). Use official videos or content you have rights to.
101
+ </p>
102
+ <div className="mt-4 aspect-video overflow-hidden rounded-2xl border border-neutral-200">
103
+ <iframe
104
+ className="h-full w-full"
105
+ src={`https://www.youtube.com/embed/${p.youtubeId}`}
106
+ title="Product video"
107
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
108
+ allowFullScreen
109
+ />
110
+ </div>
111
+ </div>
112
+ </section>
113
+
114
+ <section className="rounded-3xl border border-neutral-200 bg-neutral-50 p-6 text-sm text-neutral-700">
115
+ <b>Note:</b> This is an original demo UI inspired by modern ecommerce layouts.
116
+ Do not copy Apple’s site design or use Apple’s copyrighted assets without permission.
117
+ </section>
118
+ </div>
119
+ );
120
+ }
src/pages/Store.jsx ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useMemo, useState } from "react";
2
+ import { Link } from "react-router-dom";
3
+ import { products } from "../data/products.js";
4
+
5
+ export default function Store() {
6
+ const [q, setQ] = useState("");
7
+
8
+ const filtered = useMemo(() => {
9
+ const term = q.trim().toLowerCase();
10
+ if (!term) return products;
11
+ return products.filter((p) =>
12
+ [p.name, p.category, p.tagline].some((x) => x.toLowerCase().includes(term))
13
+ );
14
+ }, [q]);
15
+
16
+ return (
17
+ <div className="space-y-6">
18
+ <div className="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
19
+ <div>
20
+ <h2 className="text-3xl font-semibold tracking-tight">Store</h2>
21
+ <p className="text-neutral-600">Browse products (demo data).</p>
22
+ </div>
23
+ <div className="w-full md:w-80">
24
+ <label className="text-xs font-semibold uppercase tracking-wider text-neutral-500">Search</label>
25
+ <input
26
+ value={q}
27
+ onChange={(e) => setQ(e.target.value)}
28
+ placeholder="Search products..."
29
+ className="mt-2 w-full rounded-xl border border-neutral-300 bg-white px-4 py-2.5 text-sm outline-none focus:border-neutral-900"
30
+ />
31
+ </div>
32
+ </div>
33
+
34
+ <div className="grid gap-4 md:grid-cols-3">
35
+ {filtered.map((p) => (
36
+ <Link
37
+ key={p.slug}
38
+ to={`/product/${p.slug}`}
39
+ className="group rounded-3xl border border-neutral-200 bg-white p-5 hover:shadow-sm"
40
+ >
41
+ <div className="overflow-hidden rounded-2xl border border-neutral-200">
42
+ <img
43
+ src={p.heroImage}
44
+ alt={p.name}
45
+ className="h-44 w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
46
+ loading="lazy"
47
+ />
48
+ </div>
49
+ <div className="pt-4">
50
+ <p className="text-xs font-semibold uppercase tracking-wider text-neutral-500">{p.category}</p>
51
+ <h3 className="pt-1 text-lg font-semibold tracking-tight">{p.name}</h3>
52
+ <p className="pt-1 text-sm text-neutral-600">{p.tagline}</p>
53
+ <p className="pt-3 text-sm font-semibold text-neutral-900">From ${p.priceFrom}</p>
54
+ </div>
55
+ </Link>
56
+ ))}
57
+ </div>
58
+
59
+ {filtered.length === 0 ? (
60
+ <div className="rounded-2xl border border-neutral-200 bg-neutral-50 p-6 text-neutral-700">
61
+ No results. Try a different search term.
62
+ </div>
63
+ ) : null}
64
+ </div>
65
+ );
66
+ }