├── .npmrc ├── public └── favicon.ico ├── .vscode ├── extensions.json └── launch.json ├── src ├── components │ ├── ui │ │ ├── Link.jsx │ │ ├── Check.jsx │ │ ├── SkeletonLoading.jsx │ │ ├── Card.jsx │ │ ├── Disclosure.jsx │ │ ├── Select.jsx │ │ ├── Modal.jsx │ │ ├── Badge.jsx │ │ ├── Input.jsx │ │ └── Button.jsx │ ├── icon │ │ ├── Spinner.jsx │ │ ├── Check.jsx │ │ ├── ChevronDown.jsx │ │ ├── ChevronRight.jsx │ │ ├── Selector.jsx │ │ ├── Search.jsx │ │ ├── ShoppingBag.jsx │ │ ├── User.jsx │ │ ├── Refresh.jsx │ │ ├── Trash.jsx │ │ └── Truck.jsx │ ├── login │ │ ├── index.jsx │ │ └── Login.jsx │ ├── register │ │ ├── index.jsx │ │ └── Register.jsx │ ├── checkout │ │ ├── CustomerDetail.jsx │ │ ├── Login.jsx │ │ ├── Payment.jsx │ │ ├── Register.jsx │ │ ├── index.jsx │ │ └── Shipping.jsx │ ├── ItemCard.jsx │ ├── Menu.jsx │ ├── store.js │ ├── profile │ │ ├── Address.jsx │ │ ├── Order.jsx │ │ ├── Account.jsx │ │ └── index.jsx │ ├── product │ │ ├── index.jsx │ │ └── ProductCard.jsx │ ├── OrderSummary.jsx │ ├── MiniCart.jsx │ ├── Footer.jsx │ ├── collection │ │ └── index.jsx │ ├── Nav.jsx │ ├── ElasticSearch.jsx │ ├── cart │ │ ├── index.jsx │ │ └── OrderLine.jsx │ └── home │ │ └── index.jsx ├── pages │ ├── checkout.astro │ ├── cart.astro │ ├── register.astro │ ├── login.astro │ ├── index.astro │ ├── profile.astro │ ├── product │ │ └── [slug].astro │ └── collection │ │ └── [slug].astro ├── utils │ ├── client │ │ └── gqlClient.js │ └── server │ │ └── gqlClient.js ├── layouts │ └── base.astro ├── api │ ├── schema.js │ ├── server.js │ └── client.js └── styles │ └── global.css ├── tailwind.config.cjs ├── .gitignore ├── astro.config.mjs ├── tsconfig.json ├── package.json ├── tailwind-themes.js └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | # Expose Astro dependencies for `pnpm` users 2 | shamefully-hoist=true 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turiguiliano88/astro-vendure-storefront/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /src/components/ui/Link.jsx: -------------------------------------------------------------------------------- 1 | export default function Link(props) { 2 | return {props.children}; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/checkout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from "../layouts/base.astro"; 3 | import App from '../components/checkout'; 4 | --- 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pages/cart.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from "../layouts/base.astro"; 3 | import App from '../components/cart' 4 | --- 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/icon/Spinner.jsx: -------------------------------------------------------------------------------- 1 | export default function SpinnerIcon(props) { 2 | return ( 3 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const themes = require("./tailwind-themes"); 2 | 3 | module.exports = { 4 | content: ["./src/**/*.{astro,html,js,jsx,svelte,ts,tsx,vue}"], 5 | theme: themes.defaultTheme, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/components/login/index.jsx: -------------------------------------------------------------------------------- 1 | import Nav from "../Nav"; 2 | import Login from "./Login"; 3 | export default function App() { 4 | return ( 5 |
6 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /src/components/register/index.jsx: -------------------------------------------------------------------------------- 1 | import Nav from "../Nav"; 2 | import Register from "./Register"; 3 | export default function App() { 4 | return ( 5 |
6 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | // import nodejs from "@astrojs/node"; 3 | import react from "@astrojs/react"; 4 | import tailwind from "@astrojs/tailwind"; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [react(), tailwind()], 9 | // adapter: nodejs(), 10 | server: { 11 | port: 3001, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/icon/Check.jsx: -------------------------------------------------------------------------------- 1 | export default function Check(props) { 2 | return ( 3 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/ui/Check.jsx: -------------------------------------------------------------------------------- 1 | export default function Check(props) { 2 | return ( 3 |
4 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/icon/ChevronDown.jsx: -------------------------------------------------------------------------------- 1 | export default function ChevronDown(props) { 2 | return ( 3 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/icon/ChevronRight.jsx: -------------------------------------------------------------------------------- 1 | export default function ChevronRight(props) { 2 | return ( 3 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/ui/SkeletonLoading.jsx: -------------------------------------------------------------------------------- 1 | export default function SkeletonLoading({ rows }) { 2 | return ( 3 |
4 | {[...Array(rows).keys()].map((index) => ( 5 |
9 | ))} 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/ui/Card.jsx: -------------------------------------------------------------------------------- 1 | export const Card = (props) => { 2 | return
{props.children}
; 3 | }; 4 | 5 | export const CardContent = (props) => { 6 | return
{props.children}
; 7 | }; 8 | 9 | export const CardTitle = (props) => { 10 | return ( 11 |
12 | {props.children} 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/Disclosure.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function Disclosure({ title, content }) { 4 | const [open, setOpen] = useState(false); 5 | 6 | return ( 7 |
8 |
{ 10 | setOpen(!open); 11 | }} 12 | > 13 | {title} 14 |
15 |
{content}
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/pages/register.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from "../layouts/base.astro"; 3 | import App from '../components/register' 4 | import { getActiveCustomer } from "../api/server"; 5 | 6 | const cookie = Astro.request.headers.get('cookie'); 7 | if (cookie) { 8 | const activeCustomer = (await getActiveCustomer(cookie)).activeCustomer; 9 | if (activeCustomer) return Astro.redirect('/profile'); 10 | } 11 | --- 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/pages/login.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from "../layouts/base.astro"; 3 | import App from '../components/login' 4 | import { getActiveCustomer } from "../api/server"; 5 | 6 | // const cookie = Astro.request.headers.get('cookie'); 7 | // if (cookie) { 8 | // const activeCustomer = (await getActiveCustomer(cookie)).activeCustomer; 9 | // if (activeCustomer) return Astro.redirect('/profile'); 10 | // } 11 | --- 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/icon/Selector.jsx: -------------------------------------------------------------------------------- 1 | export default function Selector(props) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/icon/Search.jsx: -------------------------------------------------------------------------------- 1 | export default function Search(props) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from '../layouts/base.astro'; 3 | import App from '../components/home' 4 | import { getProducts, getActiveCustomer, getActiveOrder, getCollectionsShort } from '../api/server'; 5 | 6 | const data = await getProducts(10); 7 | const products = data.products; 8 | 9 | const collections = (await getCollectionsShort()).collections; 10 | 11 | 12 | --- 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/utils/client/gqlClient.js: -------------------------------------------------------------------------------- 1 | const gqlShopURL = import.meta.env.PUBLIC_SHOPAPI; 2 | 3 | export const createQuery = async ({ query, variables }) => { 4 | let headers = { "Content-Type": "application/json" }; 5 | const response = await fetch(gqlShopURL, { 6 | method: "POST", 7 | headers, 8 | body: JSON.stringify({ 9 | query, 10 | variables, 11 | }), 12 | credentials: "include", 13 | }); 14 | const json = await response.json(); 15 | return json.data; 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/icon/ShoppingBag.jsx: -------------------------------------------------------------------------------- 1 | export default function ShoppingBag(props) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/layouts/base.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import '../styles/global.css'; 3 | import Footer from '../components/Footer' 4 | 5 | const { title } = Astro.props; 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | 13 | {title} 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/icon/User.jsx: -------------------------------------------------------------------------------- 1 | export default function User(props) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/checkout/CustomerDetail.jsx: -------------------------------------------------------------------------------- 1 | import Login from "./Login"; 2 | import Register from "./Register"; 3 | export default function CustomerDetail({ setOrder }) { 4 | return ( 5 |
6 |
7 | Step 0: Register or Login 8 |
9 |
10 | 11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable top-level await, and other modern ESM features. 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | // Enable node-style module resolution, for things like npm package imports. 7 | "moduleResolution": "node", 8 | // Enable JSON imports. 9 | "resolveJsonModule": true, 10 | // Enable stricter transpilation for better output. 11 | "isolatedModules": true, 12 | // Add type definitions for our Vite runtime. 13 | "types": ["vite/client"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/icon/Refresh.jsx: -------------------------------------------------------------------------------- 1 | export default function Refresh(props) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/server/gqlClient.js: -------------------------------------------------------------------------------- 1 | const gqlShopURL = import.meta.env.SERVER_SHOPAPI; 2 | 3 | export const createQuery = async ({ query, variables }, cookie) => { 4 | let headers = { "Content-Type": "application/json" }; 5 | if (cookie) headers.Cookie = cookie; 6 | const response = await fetch(gqlShopURL, { 7 | method: "POST", 8 | headers, 9 | body: JSON.stringify({ 10 | query, 11 | variables, 12 | }), 13 | credentials: "include", 14 | }); 15 | 16 | const json = await response.json(); 17 | return json.data; 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/icon/Trash.jsx: -------------------------------------------------------------------------------- 1 | export default function Trash(props) { 2 | return ( 3 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/profile.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from "../layouts/base.astro"; 3 | import App from '../components/profile'; 4 | // import { getActiveCustomer, getActiveOrder } from "../api/server"; 5 | 6 | 7 | // const cookie = Astro.request.headers.get('cookie'); 8 | // let totalQuantity, customer; 9 | // if (cookie) { 10 | // totalQuantity = (await getActiveOrder(cookie)).activeOrder?.totalQuantity; 11 | // const activeCustomer = (await getActiveCustomer(cookie)).activeCustomer; 12 | // if (activeCustomer) customer = activeCustomer; 13 | // } 14 | // if (!cookie || !customer) { 15 | // return Astro.redirect('/login'); 16 | // } 17 | --- 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/pages/product/[slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from '../../layouts/base.astro'; 3 | import { getProducts } from '../../api/server'; 4 | import App from '../../components/product'; 5 | 6 | export async function getStaticPaths() { 7 | const data = await getProducts(100); 8 | return data.products.items.map(item => { 9 | return { 10 | params: { 11 | slug: item.slug 12 | }, 13 | props: { 14 | product: item 15 | } 16 | } 17 | }) 18 | 19 | } 20 | 21 | const { product } = Astro.props; 22 | --- 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/ItemCard.jsx: -------------------------------------------------------------------------------- 1 | export default function ItemCard({ name, price, img_url, path }) { 2 | return ( 3 |
4 |
5 | 6 | 11 | 12 |
13 |
14 |
{name}
15 |
€{price}
16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/icon/Truck.jsx: -------------------------------------------------------------------------------- 1 | export default function Truck(props) { 2 | return ( 3 | 11 | 12 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ui/Select.jsx: -------------------------------------------------------------------------------- 1 | export default function Select(props) { 2 | return ( 3 |
4 | {props.label &&
{props.label}
} 5 |
6 | 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Menu.jsx: -------------------------------------------------------------------------------- 1 | import ChevronRightIcon from "./icon/ChevronRight"; 2 | 3 | export default function Menu(props) { 4 | return ( 5 |
6 | {props.tabs.map((item, index) => ( 7 | 8 |
17 | {item.value} 18 | 19 |
20 |
21 | ))} 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/store.js: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | import { getActiveCustomer, getActiveOrder } from "../api/client"; 3 | import produce from "immer"; 4 | 5 | export const useStore = create((set) => ({ 6 | customer: null, 7 | order: null, 8 | orderQuantity: null, 9 | loading: false, 10 | fetchAll: async () => { 11 | set({ loading: true }); 12 | const customer = (await getActiveCustomer()).activeCustomer; 13 | customer && set({ customer: customer }); 14 | const order = (await getActiveOrder()).activeOrder; 15 | order && set({ order: order, orderQuantity: order.totalQuantity }); 16 | set({ loading: false }); 17 | }, 18 | setCustomer: (data) => set({ customer: data }), 19 | setOrder: (data) => set({ order: data }), 20 | setOrderQuantity: (q) => set({ orderQuantity: q }), 21 | })); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/basics", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview" 10 | }, 11 | "devDependencies": { 12 | "@astrojs/node": "^0.1.1", 13 | "@astrojs/react": "^0.1.1", 14 | "@astrojs/tailwind": "^0.2.1", 15 | "@astrojs/turbolinks": "^0.1.2", 16 | "astro": "^1.0.0-beta.20", 17 | "react": "^18.1.0", 18 | "react-dom": "^18.1.0" 19 | }, 20 | "dependencies": { 21 | "@splidejs/react-splide": "^0.7.5", 22 | "astro-spa": "^1.3.9", 23 | "date-and-time": "^2.3.1", 24 | "immer": "^9.0.15", 25 | "react-router-dom": "^6.3.0", 26 | "recoil": "^0.7.3-alpha.2", 27 | "swr": "^1.3.0", 28 | "zustand": "^4.0.0-rc.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/ui/Modal.jsx: -------------------------------------------------------------------------------- 1 | export const Modal = ({ enabled, children }) => { 2 | return ( 3 |
8 |
9 |
10 | {children} 11 |
12 |
13 | ); 14 | }; 15 | 16 | export const ModalContent = (props) => { 17 | return
{props.children}
; 18 | }; 19 | 20 | export const ModalTitle = (props) => { 21 | return
{props.children}
; 22 | }; 23 | 24 | export const ModalAction = (props) => { 25 | return
{props.children}
; 26 | }; 27 | -------------------------------------------------------------------------------- /src/api/schema.js: -------------------------------------------------------------------------------- 1 | export const OrderSchema = ` 2 | id 3 | state 4 | createdAt 5 | code 6 | totalQuantity 7 | shipping 8 | subTotal 9 | total 10 | customer { 11 | id 12 | firstName 13 | lastName 14 | emailAddress 15 | } 16 | lines { 17 | id 18 | featuredAsset { 19 | preview 20 | } 21 | productVariant { 22 | name 23 | product { 24 | name 25 | slug 26 | } 27 | featuredAsset { 28 | preview 29 | } 30 | } 31 | quantity 32 | linePrice 33 | } 34 | `; 35 | 36 | export const CustomerSchema = ` 37 | lastName 38 | firstName 39 | emailAddress 40 | phoneNumber 41 | addresses { 42 | fullName 43 | streetLine1 44 | streetLine2 45 | city 46 | province 47 | phoneNumber 48 | defaultShippingAddress 49 | } 50 | user { 51 | id 52 | } 53 | orders { 54 | items { 55 | ${OrderSchema} 56 | } 57 | } 58 | `; 59 | -------------------------------------------------------------------------------- /src/components/profile/Address.jsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardTitle } from "../ui/Card"; 2 | import Button from "../ui/Button"; 3 | import Badge from "../ui/Badge"; 4 | 5 | export default function ProfileAddress({ addresses }) { 6 | return ( 7 | 8 | 9 |
Shipping address
10 |
11 | 12 | {addresses?.map((item, index) => ( 13 |
17 |
{item.fullName}
18 |
{item.streetLine1}
19 |
{`${item.city}, ${item.province}`}
20 |
{item.phoneNumber}
21 | {item.defaultShippingAddress && ( 22 |
23 | default 24 |
25 | )} 26 |
27 | ))} 28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/product/index.jsx: -------------------------------------------------------------------------------- 1 | import Nav from "../Nav"; 2 | import ProductCard from "./ProductCard"; 3 | import { useState, useEffect } from "react"; 4 | import { useStore } from "../store"; 5 | export default function App({ product, showSearchBox }) { 6 | const [showMiniCart, setShowMiniCart] = useState(false); 7 | const customer = useStore((state) => state.customer); 8 | const orderQuantity = useStore((state) => state.orderQuantity); 9 | const fetchAll = useStore((state) => state.fetchAll); 10 | 11 | useEffect(() => { 12 | fetchAll(); 13 | }, []); 14 | 15 | return ( 16 |
17 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/Badge.jsx: -------------------------------------------------------------------------------- 1 | export default function Badge(props) { 2 | let styleColor; 3 | switch (props.type) { 4 | case "neutral": 5 | styleColor = "bg-neutral-100 text-neutral-900 hover:ring-neutral-300"; 6 | break; 7 | case "secondary": 8 | styleColor = "bg-secondary text-white hover:ring-secondary/40"; 9 | break; 10 | case "transparent": 11 | styleColor = "bg-transparent hover:ring-transparent text-neutral-900"; 12 | break; 13 | default: 14 | // styleColor = "bg-primary text-gray-900 hover:ring-gray-900"; 15 | styleColor = "bg-primary text-white"; 16 | break; 17 | } 18 | 19 | let styleSize; 20 | switch (props.size) { 21 | case "medium": 22 | styleSize = "py-xs px-md"; 23 | break; 24 | case "large": 25 | styleSize = "py-sm px-lg"; 26 | break; 27 | default: 28 | styleSize = "py-xxs px-sm"; 29 | break; 30 | } 31 | return ( 32 | 35 | {props.children} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/ui/Input.jsx: -------------------------------------------------------------------------------- 1 | export default function Input(props) { 2 | let backgroundStyle = props.disabled ? "bg-neutral-200" : "bg-transparent"; 3 | backgroundStyle = props.backgroundStyle 4 | ? props.backgroundStyle 5 | : backgroundStyle; 6 | return ( 7 |
8 | {props.label &&
{props.label}
} 9 |
12 | 24 | {props.icon} 25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/OrderSummary.jsx: -------------------------------------------------------------------------------- 1 | import { Card, CardTitle, CardContent } from "./ui/Card"; 2 | import Button from "./ui/Button"; 3 | 4 | export default function OrderSummary({ order, showCheckoutButton }) { 5 | return ( 6 |
7 | 8 | Order Summary 9 | 10 |
11 | Subtotal 12 | €{order && (order?.subTotal / 100).toFixed(2)} 13 |
14 |
15 | Shipping 16 | €{order && (order?.shipping / 100).toFixed(2)} 17 |
18 |
19 |
20 | Total 21 | €{order && (order?.total / 100).toFixed(2)} 22 |
23 | {showCheckoutButton && ( 24 | 25 | 26 | 27 | )} 28 |
29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/collection/[slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Base from "../../layouts/base.astro"; 3 | import { getCollections } from "../../api/server"; 4 | import App from '../../components/collection' 5 | 6 | export async function getStaticPaths() { 7 | const data = await getCollections(); 8 | 9 | return data.collections.items.map(item => { 10 | let products = {}; 11 | item.productVariants.items.map(item => { 12 | products[item.product.id] = item.product 13 | }) 14 | return { 15 | params: { 16 | slug: item.slug 17 | }, 18 | props: { 19 | products: Object.values(products), 20 | name: item.name 21 | } 22 | } 23 | }) 24 | 25 | } 26 | 27 | // const cookie = Astro.request.headers.get('cookie'); 28 | // let totalQuantity, customerName; 29 | // if (cookie) { 30 | // totalQuantity = (await getActiveOrder(cookie)).activeOrder?.totalQuantity; 31 | // const activeCustomer = (await getActiveCustomer(cookie)).activeCustomer; 32 | // if (activeCustomer) customerName = activeCustomer?.firstName + ' ' + activeCustomer?.lastName; 33 | // } 34 | 35 | const { name } = Astro.props; 36 | const { products } = Astro.props 37 | --- 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | /* * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | } 5 | 6 | :root { 7 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, 8 | Apple Color Emoji, Segoe UI Emoji; 9 | font-size: 1rem; 10 | --user-font-scale: 1rem - 16px; 11 | font-size: clamp(0.875rem, 0.4626rem + 1.0309vw + var(--user-font-scale), 1.125rem); 12 | } 13 | 14 | body { 15 | padding: 4rem 2rem; 16 | width: 100%; 17 | min-height: 100vh; 18 | display: grid; 19 | justify-content: center; 20 | background: #f9fafb; 21 | color: #111827; 22 | } 23 | 24 | @media (prefers-color-scheme: dark) { 25 | body { 26 | background: #111827; 27 | color: #fff; 28 | } 29 | } */ 30 | 31 | input[type="checkbox"]:checked { 32 | background-image: url('data:image/svg+xml,'); 33 | border-color: transparent; 34 | background-color: currentColor; 35 | background-size: 100% 100%; 36 | background-position: center; 37 | background-repeat: no-repeat; 38 | } 39 | 40 | body { 41 | /* @apply bg-red-50; */ 42 | @apply text-neutral-800; 43 | } 44 | -------------------------------------------------------------------------------- /tailwind-themes.js: -------------------------------------------------------------------------------- 1 | const colors = require("tailwindcss/colors"); 2 | 3 | module.exports = { 4 | defaultTheme: { 5 | spacing: { 6 | 0: 0, 7 | 1: "4px", 8 | 2: "8px", 9 | 3: "16px", 10 | 4: "24px", 11 | 5: "32px", 12 | 6: "48px", 13 | sm: "16px", 14 | md: "24px", 15 | lg: "32px", 16 | xl: "48px", 17 | }, 18 | borderRadius: { 19 | sm: "10px", 20 | DEFAULT: "4px", 21 | md: "16px", 22 | lg: "32px", 23 | full: "9999px", 24 | }, 25 | fontSize: { 26 | xs: ["10px", "12px"], 27 | sm: ["14px", "21px"], 28 | base: ["16px", "24px"], 29 | lg: ["18px", "21.6px"], 30 | xl: ["20px", "24px"], 31 | "2xl": ["24px", "28.8px"], 32 | "3xl": ["28px", "33.6px"], 33 | "4xl": ["32px", "38.4px"], 34 | "5xl": ["40px", "48px"], 35 | }, 36 | extend: { 37 | colors: { 38 | primary: colors.red["600"], 39 | secondary: colors.green["400"], 40 | neutral: colors.neutral, 41 | }, 42 | spacing: { 43 | sm: "16px", 44 | xs: "8px", 45 | xxs: "4px", 46 | 40: "160px", 47 | }, 48 | flexShrink: { 49 | 2: 2, 50 | }, 51 | }, 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/MiniCart.jsx: -------------------------------------------------------------------------------- 1 | import Button from "./ui/Button"; 2 | import ShoppingBagIcon from "./icon/ShoppingBag"; 3 | export default function MiniCart({ 4 | showMiniCart, 5 | setShowMiniCart, 6 | totalQuantity, 7 | }) { 8 | return ( 9 |
10 | 18 |
25 |
26 |

27 | You have {totalQuantity} {totalQuantity > 1 ? "items" : "item"}. 28 |

29 | 30 | 31 | 32 |
33 |
34 |
setShowMiniCart(false)} 41 | >
42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 |
4 |
5 |
6 | Shipping 7 | UPS 8 | DHL 9 |
10 |
11 | Payment 12 | Visa/Mastercard 13 | Paypal 14 | Klarna 15 |
16 |
17 | Social 18 | Facebook 19 | Instagram 20 |
21 |
22 | Contact 23 | 24 | admin@minh.berlin 25 | 26 |
27 |
28 |
29 |
30 |

@Minh

31 |

32 | Made with ❤️ from Berlin 33 |

34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/ui/Button.jsx: -------------------------------------------------------------------------------- 1 | import SpinnerIcon from "../icon/Spinner"; 2 | export default function Button(props) { 3 | let styleColor; 4 | switch (props.type) { 5 | case "neutral": 6 | styleColor = "bg-neutral-200 text-neutral-900 hover:ring-neutral-600"; 7 | break; 8 | case "secondary": 9 | styleColor = "bg-secondary text-white hover:ring-secondary/40"; 10 | break; 11 | case "transparent": 12 | styleColor = 13 | "bg-transparent hover:before:content-['→_'] hover:ring-transparent text-neutral-900 font-semibold"; 14 | break; 15 | default: 16 | styleColor = "bg-primary text-white"; 17 | break; 18 | } 19 | 20 | let styleSize; 21 | switch (props.size) { 22 | case "small": 23 | styleSize = "py-xxs px-sm"; 24 | break; 25 | case "large": 26 | styleSize = "py-sm px-lg"; 27 | break; 28 | default: 29 | styleSize = "py-xs px-md"; 30 | break; 31 | } 32 | 33 | let loadingStyle = props.isLoading ? "" : ""; 34 | return ( 35 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/collection/index.jsx: -------------------------------------------------------------------------------- 1 | import Nav from "../Nav"; 2 | import { Card, CardTitle, CardContent } from "../ui/Card"; 3 | import ItemCard from "../ItemCard"; 4 | import { useStore } from "../store"; 5 | import { useEffect } from "react"; 6 | 7 | export default function App({ name, products }) { 8 | const customer = useStore((state) => state.customer); 9 | const order = useStore((state) => state.order); 10 | const fetchAll = useStore((state) => state.fetchAll); 11 | useEffect(() => { 12 | fetchAll(); 13 | }, []); 14 | return ( 15 | <> 16 |