You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
6.1 KiB
JavaScript
139 lines
6.1 KiB
JavaScript
import React, {useState} from 'react'
|
|
import Head from 'next/head'
|
|
import Router from 'next/router'
|
|
import {FormController, IntegerInput, Button} from '~/components/form'
|
|
import isNum from 'validator/lib/isNumeric'
|
|
import {DateTime} from 'luxon'
|
|
import {Icon} from '@rmwc/icon'
|
|
|
|
import Modal from '~/components/modal'
|
|
|
|
import useCart, {useSetCart} from '~/hooks/useCart'
|
|
import styles from './style.module.css'
|
|
|
|
Item.getInitialProps = async function({ctx: {axios, query: {slug}}}){
|
|
const {data: item} = await axios.get(`/api/items/by-slug/${slug}`)
|
|
|
|
if(!item) {
|
|
const err = new Error("Not found")
|
|
err.status = 404
|
|
throw err;
|
|
}
|
|
|
|
if(item.preorder_availability_date) {
|
|
item.preorder_availability_date = DateTime.fromISO(item.preorder_availability_date).toLocaleString(DateTime.DATE_FULL)
|
|
}
|
|
|
|
return {item}
|
|
}
|
|
|
|
export default function Item({item}){
|
|
const cart = useCart()
|
|
const setCart = useSetCart()
|
|
// Pick first one with featured flag or 0
|
|
const featuredIndex = item.images.reduce((p, im, i) => ((p !== undefined) ? p : (im.featured ? i : undefined)), undefined) || 0
|
|
const [selectedIndex, setSelected] = useState(featuredIndex);
|
|
const [imageModalVisible, setImageModalVisible] = useState(false);
|
|
|
|
const numInCart = cart?.items?.find(i => i.item.uuid === item.uuid)?.count || 0
|
|
|
|
const numberInStock = item.number_in_stock
|
|
const numberPreorderSlots = (item.preorder_maximum || 0) - (-Math.min(0, item.number_in_stock))
|
|
|
|
const availabilityText =
|
|
numberInStock > 0
|
|
? `${numberInStock} in stock`
|
|
: item.preorder_availability_date && numberPreorderSlots > 0
|
|
? `${numberPreorderSlots} available for pre-order`
|
|
: 'Currently out of stock'
|
|
|
|
const totalAvailable = (numberInStock > 0 ? numberInStock : numberPreorderSlots) - numInCart
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>{item.name} | Society of Socks</title>
|
|
</Head>
|
|
<div className={styles.pageContainer}>
|
|
<div className={'dark ' + styles.itemDetails}>
|
|
{item.images && item.images.length > 0 && (
|
|
<div className={styles.imageContainer}>
|
|
<div className={styles.card}>
|
|
<picture>
|
|
<source srcSet={`/api/images/${item.images[selectedIndex].uuid}/thumb.webp`} type="image/webp"/>
|
|
<img src={`/api/images/${item.images[selectedIndex].uuid}/thumb.png`} onClick={() => setImageModalVisible(true)} />
|
|
</picture>
|
|
</div>
|
|
{item.images && item.images.length > 1 &&
|
|
<div className={styles.imageSelector}>
|
|
{item.images.map((image, index) => (
|
|
<picture>
|
|
<source srcset={`/api/images/${image.uuid}/thumb.webp`} type="image/webp"/>
|
|
<img key={image.uuid}
|
|
onClick={()=>setSelected(index)}
|
|
className={index === selectedIndex ? styles.selectedImage : undefined}
|
|
src={`/api/images/${image.uuid}/thumb.png`}
|
|
/>
|
|
</picture>
|
|
))}
|
|
</div>
|
|
}
|
|
<Modal title={`${item.name} (Image ${selectedIndex + 1})`} visible={imageModalVisible} onDeactivate={() => setImageModalVisible(false)}>
|
|
<div className={styles.imageCarousel}>
|
|
<button onClick={() => setSelected(index => (index - 1 < 0) ? item.images.length - 1 : index - 1)}>
|
|
<Icon icon="chevron_left" />
|
|
</button>
|
|
<div className={styles.fullImage}>
|
|
<picture>
|
|
<source srcSet={`/api/images/${item.images[selectedIndex].uuid}/large.webp`} type="image/webp"/>
|
|
<img src={`/api/images/${item.images[selectedIndex].uuid}/large.png`} onClick={() => setImageModalVisible(true)} />
|
|
</picture>
|
|
</div>
|
|
<button onClick={() => setSelected(index => (index + 1) % item.images.length)}>
|
|
<Icon icon="chevron_right" />
|
|
</button>
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
)}
|
|
<div className={styles.controls}>
|
|
<h2>{item.name}</h2>
|
|
{ item.description.split('\n').map(p=>p.trim()).filter(p=>p !== '').map((p,i)=><p key={i}>{p}</p>) }
|
|
|
|
<FormController className={styles.form} url={`/api/cart/add/${item.uuid}`} afterSubmit={(cart)=>{setCart(cart); Router.push('/store/cart')}}>
|
|
<IntegerInput label="Add to cart" name="count" initialValue="1" minimum="1" maximum={totalAvailable.toFixed(0)} validate={value=>isNum(value, {no_symbols: true}) && parseInt(value) <= totalAvailable} />
|
|
{
|
|
totalAvailable > 0
|
|
? <Button type="submit">Add to cart</Button>
|
|
: <Button enabled={false} type="submit">Out of stock</Button>
|
|
}
|
|
|
|
</FormController>
|
|
|
|
<div className={styles.notes}>
|
|
<span>Price: ${(parseInt(item.price_cents) / 100).toFixed(2)} each</span>
|
|
<span>
|
|
{availabilityText}
|
|
</span>
|
|
<span>{numInCart} in <button style={{color: "white"}} className="buttonLink" onClick={() => Router.push(`/store/cart`)}>cart</button></span>
|
|
</div>
|
|
|
|
{numberInStock < 1 && item.preorder_availability_date && numberPreorderSlots > 0 && (
|
|
<p className="warning">
|
|
<strong>Note: </strong>
|
|
This is a pre-order item. If you place an order with this item in your cart, it will not ship
|
|
until at least {item.preorder_availability_date}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className={styles.moreDetails}>
|
|
<h2>Additional Information</h2>
|
|
<p>All socks are only available in adult medium sizes (approximately US Men's size 10 - 13 depending on stretching) at the moment - we're working on getting more sizes available but not quite ready for that yet.</p>
|
|
<p>All purchases are shipped within a few days of your purchase, (USPS first class).</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|