mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Start migration
This commit is contained in:
136
site/components/common/About/AboutSlider.tsx
Normal file
136
site/components/common/About/AboutSlider.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
useBreakpointValue,
|
||||
Stack,
|
||||
Heading,
|
||||
Text,
|
||||
Container,
|
||||
} from '@chakra-ui/react';
|
||||
// Here we have used react-icons package for the icons
|
||||
import { BiLeftArrowAlt, BiRightArrowAlt } from 'react-icons/bi';
|
||||
// And react-slick as our Carousel Lib
|
||||
import Slider from 'react-slick';
|
||||
|
||||
// Settings for the slider
|
||||
const settings = {
|
||||
dots: true,
|
||||
arrows: false,
|
||||
fade: true,
|
||||
infinite: true,
|
||||
autoplay: true,
|
||||
speed: 500,
|
||||
autoplaySpeed: 5000,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
};
|
||||
|
||||
export default function AboutSlider() {
|
||||
// As we have used custom buttons, we need a reference variable to
|
||||
// change the state
|
||||
const [slider, setSlider] = React.useState<Slider | null>(null);
|
||||
|
||||
// These are the breakpoints which changes the position of the
|
||||
// buttons as the screen size changes
|
||||
const top = useBreakpointValue({ base: '90%', md: '50%' });
|
||||
const side = useBreakpointValue({ base: '30%', md: '40px' });
|
||||
|
||||
// This list contains all the data for carousels
|
||||
// This can be static or loaded from a server
|
||||
const cards = [
|
||||
{
|
||||
title: 'Storia del Mercato',
|
||||
text:
|
||||
`Ciò che è sempre stato alla base del progresso della civiltà umana è lo scambio (di beni, di idee, di conoscenze). In età neolitica avveniva sotto forma di baratto. Nell'antichità e per tutto il Medioevo i principali canali di scambio erano il mercato, in genere allestito nelle piazze delle città e la fiera, imponente evento commerciale. Il mercato giornaliero, settimanale o mensile, vivacizzava la città e provvedeva alle esigenze della popolazione; era il luogo dello scambio tra la produzione dell'artigianato urbano e quella agricola. La fiera era un evento periodico, suddiviso in più circuiti predeterminati, dove tanti operatori economici convergevano per scambiarsi in natura, in moneta o tramite titoli di credito, beni in quantità consistenti; non mancavano le agevolazioni fiscali per i mercanti stranieri che vi convenivano. Ma la fiera era caratterizzata anche per il clima di confusione e festività; accadeva di tutto: circolazione di persone, di merci e cultura, spettacoli, giochi d'azzardo, reliquie di santi in bella mostra. Mentre l'attività produttiva (e a volte anche commerciale) del mercante, così come dell'artigiano si svolgeva nella bottega. Fino ai primi decenni del XVII secolo le fiere erano il principale canale di scambio commerciale e connotavano i territori entro le quali raggiungevano la loro fortuna. Con l'avvento dell'industrializzazione si afferma in Europa e nel mondo il sistema capitalistico di produzione: le botteghe artigiane diventano grandi fabbriche, le fiere tradizionali sono meno affollate e meno rinomate, divenendo sempre più sbocco di derrate agricole, e lasciando spazio a un nuovo tipo di fiera: la fiera campionaria, il cui massimo esempio è rappresentato dall’esposizione nazionale e universale. Nella città nascono gli spazi rispondenti alle nuove produzioni industriali e ai nuovi consumi: i passages, le gallerie, i grandi magazzini, gli antenati dell'odierno centro commerciale, tempio dell'attuale società consumistica del XXI secolo. Oggi sia i centri commerciali che le fiere specializzate (miranti ai singoli comparti dell'industria) sono gli attori fondamentali nel sistema di distribuzione delle novità introdotte dal grande mercato, propongono nuove idee e nuovi modelli di comportamento e consumo e, facilitando anche un'omologazione degli stessi, rispondono alle nuove dinamiche della globalizzazione. Se essi proliferano e riempiono il tessuto urbano, centrale o periferico, nei borghi e nei paesi, si trovano ancora le manifestazioni tipiche della sagra, del mercatino, della piccola fiera, della mostra a tema. Questi piccoli eventi preservano una cultura e una tradizione locali, talvolta ancora espressione della vita artigianale, attenuando il fenomeno della globalizzazione. In particolare il mercatino (agenzia d'affari) può essere strutturato come manifestazione periodica o avente una propria sede fissa. Rimane il luogo in cui possono rinvenirsi oggetti particolari, testimonianza del passato (più o meno recente) e non delle novità industriali.`,
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1516796181074-bf453fbfa3e6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1yZWxhdGVkfDV8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=900&q=60',
|
||||
},
|
||||
{
|
||||
title: 'Iniziativa',
|
||||
text:
|
||||
`Se è vero che oggi sia i grandi centri commerciali che questi piccoli eventi sono fenomeni culturali (i primi riguardanti la cultura attuale, globale, di massa, i secondi quella passata), sono luoghi d'intrattenimento e svago, hanno entrambi carattere di coinvolgimento emotivo e grande varietà e assortimento merceologico, è anche vero che gli ultimi sono organizzati esponendo le merci tuttalpiù per generi, settori. I titolari, attenti soprattutto al valore economico del bene usato, possono limitarsi a fornire ai visitatori informazioni basilari sugli stessi, non soddisfacendo mai completamente un ipotetico arricchimento culturale dei propri clienti. Ecco l'ipotesi di una nuova formula di agenzia d'affari che aspiri a essere un'esposizione a tema (come la fiera e la mostra a tema) e allo stesso tempo avere grande varietà e assortimento di culture e generi, il tutto ordinato in un disegno che susciti emozioni e fornisca al cliente la massima trasmissione culturale che può scaturire dall'osservazione di prodotti usati; ridia vita alle merci, ricordandosi delle culture e tradizioni locali (oggi più che mai in pericolo) e sia attenta alle urgenti istanze di ecologia, riuso e sostenibilità. Safara è uno spazio innovativo che espone prodotti datati come un normale mercatino, ma li reinserisce nella loro "cornice" originaria, affianco ad oggetti e simboli dello stesso periodo storico. È costituita da un percorso ramificato in più "fermate", ognuna delle quali rappresenti un decennio del 900. Perfino ogni singolo oggetto racconterà in breve la sua storia. Se ciò che è sempre stato alla base del progresso della civiltà umana è lo scambio, è ancora attraverso esso che Safara fornisce un elemento di diversificazione rispetto agli altri attori dell’usato. Riutilizzando la visione ancestrale del commercio basata sul baratto, e adattandola in chiave moderna, assieme ad altri servizi unici, Safara è davvero un’agenzia unica nel suo genere. Pensiamo inoltre che l’idea possa costituire un supporto utile come completamento di altra manifestazione locale.`,
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1438183972690-6d4658e3290e?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2274&q=80',
|
||||
},
|
||||
{
|
||||
title: 'Chi Siamo',
|
||||
text:
|
||||
`Safara è un'agenzia d'affari, più nota con il termine di mercatino dell'usato, che a differenza dei concorrenti, affianca all'offerta commerciale un'offerta culturale, tentando parallelamente di valorizzare la sua regione di provenienza e di fornirle un servizio utile, soprattutto in un periodo storico delicatissimo per il pianeta intero.`,
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1507237998874-b4d52d1dd655?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1yZWxhdGVkfDR8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=900&q=60',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
position={'relative'}
|
||||
height={'600px'}
|
||||
width={'full'}
|
||||
overflow={'hidden'}>
|
||||
{/* CSS files for react-slick */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
charSet="UTF-8"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css"
|
||||
/>
|
||||
{/* Left Icon */}
|
||||
<IconButton
|
||||
aria-label="left-arrow"
|
||||
variant="ghost"
|
||||
position="absolute"
|
||||
left={side}
|
||||
top={top}
|
||||
transform={'translate(0%, -50%)'}
|
||||
zIndex={2}
|
||||
onClick={() => slider?.slickPrev()}>
|
||||
<BiLeftArrowAlt size="40px" />
|
||||
</IconButton>
|
||||
{/* Right Icon */}
|
||||
<IconButton
|
||||
aria-label="right-arrow"
|
||||
variant="ghost"
|
||||
position="absolute"
|
||||
right={side}
|
||||
top={top}
|
||||
transform={'translate(0%, -50%)'}
|
||||
zIndex={2}
|
||||
onClick={() => slider?.slickNext()}>
|
||||
<BiRightArrowAlt size="40px" />
|
||||
</IconButton>
|
||||
{/* Slider */}
|
||||
<Slider {...settings} ref={(slider: any) => setSlider(slider)}>
|
||||
{cards.map((card, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
height={'6xl'}
|
||||
position="relative"
|
||||
backgroundPosition="center"
|
||||
backgroundRepeat="no-repeat"
|
||||
backgroundSize="cover">
|
||||
{/* This is the block you need to change, to customize the caption */}
|
||||
<Container size="container.2xl" height="600px" position="relative">
|
||||
<Stack
|
||||
spacing={6}
|
||||
w={'full'}
|
||||
maxW={'lg'}>
|
||||
<Heading fontSize={{ base: '3xl', md: '4xl', lg: '5xl' }}>
|
||||
{card.title}
|
||||
</Heading>
|
||||
<Text fontSize={{ base: 'md', lg: 'lg' }} color="GrayText">
|
||||
{card.text}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Box>
|
||||
))}
|
||||
</Slider>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -21,6 +21,18 @@ const links = [
|
||||
name: 'Home',
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
name: 'About',
|
||||
url: '/about'
|
||||
},
|
||||
{
|
||||
name: 'News',
|
||||
url: '/news'
|
||||
},
|
||||
{
|
||||
name: 'Contact',
|
||||
url: '/contact'
|
||||
}
|
||||
]
|
||||
|
||||
const Footer: FC<Props> = ({ className, pages }) => {
|
||||
|
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalBody,
|
||||
Box,
|
||||
} from "@chakra-ui/react"
|
||||
|
||||
import decadesManifest from '../../../../static_data/decadesManifest.json';
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ImageMapper from "react-img-mapper"
|
||||
|
||||
export default function MarkerCardModal(props: {
|
||||
isOpen: boolean,
|
||||
onModalClose: () => void,
|
||||
decade: string,
|
||||
}) {
|
||||
|
||||
const {locale} = useRouter();
|
||||
const containerRef = useRef<any>();
|
||||
|
||||
const decadeColor = decadesManifest[props.decade as keyof typeof decadesManifest].color;
|
||||
|
||||
const [width, setWidth] = useState(200);
|
||||
const [isMapLoaded, setIsMapLoaded] = useState<boolean>(false)
|
||||
|
||||
const [mapDefinition, setMapDefinition] = useState<any>()
|
||||
|
||||
const getContainerSize = () => {
|
||||
if(containerRef.current != undefined) {
|
||||
setWidth(containerRef.current.clientWidth);
|
||||
}
|
||||
};
|
||||
|
||||
const getMapDefinition = async () => {
|
||||
const tempMapDefinition = await import("../../../../static_data/regions/abruzzo/" + props.decade + "/plan/manifest.json")
|
||||
tempMapDefinition.areas.forEach((area: any) => {
|
||||
area.href = "/" + locale + area.href
|
||||
})
|
||||
setMapDefinition(tempMapDefinition)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getContainerSize()
|
||||
}, [isMapLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
getMapDefinition()
|
||||
return window.addEventListener("resize", getContainerSize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal onClose={props.onModalClose} isOpen={props.isOpen} isCentered>
|
||||
<ModalOverlay />
|
||||
<ModalContent rounded={"lg"}>
|
||||
<ModalBody rounded={"lg"} style={{background: 'linear-gradient(120deg, ' + decadeColor + ' 0%, #000000 130%)'}} p={5}>
|
||||
<Box
|
||||
w={'full'}
|
||||
ref={containerRef}
|
||||
>
|
||||
<ImageMapper onLoad={() => setIsMapLoaded(true)} natural stayHighlighted responsive={true} parentWidth={width} map={mapDefinition} src={"/regions/abruzzo/" + props.decade + "/plan/plan.jpeg"}></ImageMapper>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@@ -17,6 +17,8 @@ import { MenuSidebarView } from '@components/common/UserNav'
|
||||
import type { Page } from '@commerce/types/page'
|
||||
import type { Category } from '@commerce/types/site'
|
||||
import type { Link as LinkProps } from '../UserNav/MenuSidebarView'
|
||||
import navBarLinks from '../../../static_data/navBarLinks.json';
|
||||
import Script from 'next/script'
|
||||
|
||||
const Loading = () => (
|
||||
<div className="w-80 h-80 flex items-center text-center justify-center p-3">
|
||||
@@ -108,11 +110,8 @@ const Layout: React.FC<Props> = ({
|
||||
pageProps: { categories = [], ...pageProps },
|
||||
}) => {
|
||||
const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
|
||||
const { locale = 'en-US' } = useRouter()
|
||||
const navBarlinks = categories.slice(0, 2).map((c) => ({
|
||||
label: c.name,
|
||||
href: `/search/${c.slug}`,
|
||||
}))
|
||||
const { locale = 'it' } = useRouter()
|
||||
const navBarlinks = navBarLinks.links;
|
||||
|
||||
return (
|
||||
<CommerceProvider locale={locale}>
|
||||
@@ -133,6 +132,22 @@ const Layout: React.FC<Props> = ({
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
{/** Sendinblue Chat Script to implement Widget */}
|
||||
<Script id="show-banner" strategy="lazyOnload">
|
||||
{`
|
||||
<!-- Sendinblue Conversations {literal} -->
|
||||
(function(d, w, c) {
|
||||
w.SibConversationsID = '632dab316c8a1e0b083a91c2';
|
||||
w[c] = w[c] || function() {
|
||||
(w[c].q = w[c].q || []).push(arguments);
|
||||
};
|
||||
var s = d.createElement('script');
|
||||
s.async = true;
|
||||
s.src = 'https://conversations-widget.sendinblue.com/sib-conversations.js';
|
||||
if (d.head) d.head.appendChild(s);
|
||||
})(document, window, 'SibConversations');
|
||||
`}
|
||||
</Script>
|
||||
</div>
|
||||
</CommerceProvider>
|
||||
)
|
||||
|
57
site/components/common/Navbar/NavBarFiltersDrawer.tsx
Normal file
57
site/components/common/Navbar/NavBarFiltersDrawer.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Drawer, DrawerOverlay, DrawerContent, DrawerHeader, DrawerBody, Link, Box, Stack, Heading, Divider } from "@chakra-ui/react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import filtersData from '../../../static_data/navBarMenuData.json';
|
||||
import NavBarFiltersItem from './NavBarFiltersItem';
|
||||
|
||||
export default function NavBarFiltersDrawer(props: {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
}) {
|
||||
|
||||
const [placement, setPlacement] = React.useState('left' as const)
|
||||
const [regions, setRegions] = React.useState<NavItem[]>([]);
|
||||
const [categories, setCategories] = React.useState<NavItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setRegions(filtersData.regions);
|
||||
setCategories(filtersData.categories);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer placement={placement} onClose={props.onClose} isOpen={props.isOpen}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerBody>
|
||||
<Stack mt={5} direction={"column"} spacing={"20"}>
|
||||
<Box>
|
||||
<Heading size={"lg"} mb={5}>Regions</Heading>
|
||||
{
|
||||
regions.map(region => (
|
||||
<NavBarFiltersItem key={region.label} {...region} />
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading mb={5} size={"lg"}>Categories</Heading>
|
||||
{
|
||||
categories.map(category => (
|
||||
<NavBarFiltersItem key={category.label} {...category} />
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
</Stack>
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
label: string;
|
||||
subLabel?: string;
|
||||
children?: Array<NavItem>;
|
||||
href?: string;
|
||||
enabled: boolean;
|
||||
};
|
83
site/components/common/Navbar/NavBarFiltersItem.tsx
Normal file
83
site/components/common/Navbar/NavBarFiltersItem.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Text,
|
||||
IconButton,
|
||||
Button,
|
||||
Stack,
|
||||
Collapse,
|
||||
Icon,
|
||||
Link,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
useColorModeValue,
|
||||
useBreakpointValue,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
HamburgerIcon,
|
||||
CloseIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@chakra-ui/icons';
|
||||
|
||||
const NavBarFiltersItem = ({ label, children, href, enabled }: NavItem) => {
|
||||
const { isOpen, onToggle } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Stack spacing={4} onClick={children && onToggle}>
|
||||
<Flex
|
||||
py={2}
|
||||
as={Link}
|
||||
href={enabled ? href : '#'}
|
||||
justify={'space-between'}
|
||||
align={'center'}
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
<Text
|
||||
fontWeight={600}
|
||||
color={useColorModeValue('gray.600', 'gray.200')}>
|
||||
{label}
|
||||
</Text>
|
||||
{children && (
|
||||
<Icon
|
||||
as={ChevronDownIcon}
|
||||
transition={'all .25s ease-in-out'}
|
||||
transform={isOpen ? 'rotate(180deg)' : ''}
|
||||
w={6}
|
||||
h={6}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Collapse in={isOpen} animateOpacity style={{ marginTop: '0!important' }}>
|
||||
<Stack
|
||||
mt={2}
|
||||
pl={4}
|
||||
borderLeft={1}
|
||||
borderStyle={'solid'}
|
||||
borderColor={useColorModeValue('gray.200', 'gray.700')}
|
||||
align={'start'}>
|
||||
{children &&
|
||||
children.map((child) => (
|
||||
<Link key={child.label} py={2} href={enabled ? child.href : '#'}>
|
||||
{child.label}
|
||||
</Link>
|
||||
))}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
interface NavItem {
|
||||
label: string;
|
||||
subLabel?: string;
|
||||
children?: Array<NavItem>;
|
||||
href?: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export default NavBarFiltersItem;
|
@@ -4,6 +4,8 @@ import s from './Navbar.module.css'
|
||||
import NavbarRoot from './NavbarRoot'
|
||||
import { Logo, Container } from '@components/ui'
|
||||
import { Searchbar, UserNav } from '@components/common'
|
||||
import { useDisclosure } from '@chakra-ui/react'
|
||||
import NavBarFiltersDrawer from './NavBarFiltersDrawer'
|
||||
|
||||
interface Link {
|
||||
href: string
|
||||
@@ -14,43 +16,54 @@ interface NavbarProps {
|
||||
links?: Link[]
|
||||
}
|
||||
|
||||
const Navbar: FC<NavbarProps> = ({ links }) => (
|
||||
<NavbarRoot>
|
||||
<Container clean className="mx-auto max-w-8xl px-6">
|
||||
<div className={s.nav}>
|
||||
<div className="flex items-center flex-1">
|
||||
<Link href="/">
|
||||
<a className={s.logo} aria-label="Logo">
|
||||
<Logo />
|
||||
</a>
|
||||
</Link>
|
||||
<nav className={s.navMenu}>
|
||||
<Link href="/search">
|
||||
<a className={s.link}>All</a>
|
||||
</Link>
|
||||
{links?.map((l) => (
|
||||
<Link href={l.href} key={l.href}>
|
||||
<a className={s.link}>{l.label}</a>
|
||||
const Navbar: FC<NavbarProps> = ({ links }) => {
|
||||
|
||||
const { isOpen: isOpenDrawer, onOpen: onOpenDrawer, onClose: onCloseDrawer } = useDisclosure()
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavBarFiltersDrawer onClose={onCloseDrawer} isOpen={isOpenDrawer} ></NavBarFiltersDrawer>
|
||||
<NavbarRoot>
|
||||
<Container clean className="mx-auto max-w-8xl px-6">
|
||||
<div className={s.nav}>
|
||||
<div className="flex items-center flex-1">
|
||||
<Link href="/">
|
||||
<a className={s.logo} aria-label="Logo">
|
||||
<Logo />
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
{process.env.COMMERCE_SEARCH_ENABLED && (
|
||||
<div className="justify-center flex-1 hidden lg:flex">
|
||||
<Searchbar />
|
||||
<nav className={s.navMenu}>
|
||||
<Link href="/search">
|
||||
<a className={s.link}>All</a>
|
||||
</Link>
|
||||
{links?.map((l) => (
|
||||
<Link href={l.href} key={l.href}>
|
||||
{
|
||||
l.label === 'Regions' ? (<a onClick={onOpenDrawer} className={s.link}>{l.label}</a>)
|
||||
: <a className={s.link}>{l.label}</a>
|
||||
}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
{process.env.COMMERCE_SEARCH_ENABLED && (
|
||||
<div className="justify-center flex-1 hidden lg:flex">
|
||||
<Searchbar />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-end flex-1 space-x-8">
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-end flex-1 space-x-8">
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
{process.env.COMMERCE_SEARCH_ENABLED && (
|
||||
<div className="flex pb-4 lg:px-6 lg:hidden">
|
||||
<Searchbar id="mobile-search" />
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</NavbarRoot>
|
||||
)
|
||||
{process.env.COMMERCE_SEARCH_ENABLED && (
|
||||
<div className="flex pb-4 lg:px-6 lg:hidden">
|
||||
<Searchbar id="mobile-search" />
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</NavbarRoot>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
|
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalBody,
|
||||
} from "@chakra-ui/react"
|
||||
import { Product } from "@commerce/types"
|
||||
import ProductCardRoom from "../../../product/ProductCardRoom/ProductCardRoom"
|
||||
|
||||
import decadesManifest from '../../../../static_data/decadesManifest.json';
|
||||
import ResourceCardModal from "@components/common/Room/ResourceCardModal/ResourceCardModal";
|
||||
import { MarkerData, MarkerResourcePayload } from "../RoomTypes/RoomTypes";
|
||||
|
||||
export default function MarkerCardModal(props: {
|
||||
isOpen: boolean,
|
||||
onModalClose: () => void,
|
||||
marker: MarkerData,
|
||||
decade: string,
|
||||
onAudioPlayerPlay?: (player: HTMLAudioElement) => void,
|
||||
onAudioPlayerPause?: () => void
|
||||
}) {
|
||||
|
||||
const decadeColor = decadesManifest[props.decade as keyof typeof decadesManifest].color;
|
||||
|
||||
const getCardToRender = (markerType: string) => {
|
||||
switch(markerType) {
|
||||
case "product":
|
||||
return <ProductCardRoom decade={props.decade} product={props.marker.markerPayload as Product.Product} />
|
||||
case "image":
|
||||
case "video":
|
||||
return <ResourceCardModal decade={props.decade} resourcePayload={props.marker.markerPayload as MarkerResourcePayload} />
|
||||
case "audio":
|
||||
return <ResourceCardModal decade={props.decade} resourcePayload={props.marker.markerPayload as MarkerResourcePayload}
|
||||
onAudioPlay={props.onAudioPlayerPlay!} onAudioPause={props.onAudioPlayerPause!} onModalClose={props.onModalClose} />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal onClose={props.onModalClose} isOpen={props.isOpen} isCentered>
|
||||
<ModalOverlay />
|
||||
<ModalContent rounded={"lg"}>
|
||||
<ModalBody rounded={"lg"} style={{background: 'linear-gradient(120deg, ' + decadeColor + ' 0%, #000000 130%)'}} p={5}>
|
||||
{
|
||||
getCardToRender(props.marker.markerType)
|
||||
}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@@ -0,0 +1,44 @@
|
||||
import { Box, Stack, Button, Text } from "@chakra-ui/react";
|
||||
import { createRef } from "react";
|
||||
import H5AudioPlayer from "react-h5-audio-player";
|
||||
|
||||
export default function AudioCardContent(props: {
|
||||
style: any
|
||||
resourcePath: string,
|
||||
resourceCaption: string,
|
||||
onPlay: (player: HTMLAudioElement) => void,
|
||||
onPause: () => void,
|
||||
onClose: () => void
|
||||
}) {
|
||||
|
||||
const player = createRef<H5AudioPlayer>()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
className={props.style.imageContainer}
|
||||
w={'full'}
|
||||
>
|
||||
<H5AudioPlayer
|
||||
src={props.resourcePath}
|
||||
ref={player}
|
||||
onCanPlay={e => props.onPlay(player.current?.audio.current!)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
p={5}
|
||||
className={props.style.captionContainer}>
|
||||
|
||||
<Stack align={'center'}>
|
||||
<Text padding={0} color={'gray.500'} fontSize={'sm'} align={'center'}>
|
||||
{props.resourceCaption}
|
||||
</Text>
|
||||
<Button mt={5} onClick={(e) => {props.onClose();props.onPause();}} colorScheme='teal' variant='solid'>
|
||||
BACKGROUND AUDIO
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
};
|
@@ -0,0 +1,40 @@
|
||||
import { Box, Stack, Image, Text } from "@chakra-ui/react";
|
||||
import screenfull from "screenfull";
|
||||
|
||||
export default function ImageCardContent(props: {
|
||||
style: any
|
||||
resourcePath: string
|
||||
resourceCaption: string
|
||||
}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
className={props.style.imageContainer}
|
||||
w={'full'}
|
||||
height={'220px'}
|
||||
>
|
||||
|
||||
<Image cursor={'pointer'} onClick={() => openFullScreen('resource-image')} id='resource-image' alt='Resource Image Not Found' src={props.resourcePath} />
|
||||
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
p={5}
|
||||
className={props.style.captionContainer}>
|
||||
|
||||
<Stack mt={6} align={'center'}>
|
||||
<Text padding={0} color={'gray.500'} fontSize={'sm'} align={'center'}>
|
||||
{props.resourceCaption}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
const openFullScreen = (imageId: string) => {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.request(document.getElementById(imageId)!);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
import { Box, Stack, Image, Text } from "@chakra-ui/react";
|
||||
|
||||
export default function VideoCardContent(props: {
|
||||
style: any
|
||||
resourcePath: string
|
||||
resourceCaption: string
|
||||
}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
className={props.style.imageContainer}
|
||||
w={'full'}
|
||||
height={'220px'}
|
||||
>
|
||||
|
||||
<video controls >
|
||||
<source src={props.resourcePath} type="video/mp4"/>
|
||||
</video>
|
||||
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
p={5}
|
||||
className={props.style.captionContainer}>
|
||||
|
||||
<Stack mt={6} align={'center'}>
|
||||
<Text padding={0} color={'gray.500'} fontSize={'sm'} align={'center'}>
|
||||
{props.resourceCaption}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
};
|
@@ -0,0 +1,64 @@
|
||||
import { Box, Flex, propNames, Stack, Text } from "@chakra-ui/react"
|
||||
import { MarkerResourcePayload } from "../RoomTypes/RoomTypes"
|
||||
import { Image } from "@chakra-ui/react"
|
||||
|
||||
import 'react-h5-audio-player/lib/styles.css';
|
||||
|
||||
import style from './ResourceCardStyle.module.css';
|
||||
import ImageCardContent from "../ResourceCardContent/ImageCardContent";
|
||||
import AudioCardContent from "../ResourceCardContent/AudioCardContent";
|
||||
import VideoCardContent from "../ResourceCardContent/VideoCardContent";
|
||||
|
||||
export default function ResourceCardModal(props: {
|
||||
decade: string,
|
||||
resourcePayload: MarkerResourcePayload,
|
||||
onModalClose?: () => void,
|
||||
onAudioPlay?: (player: HTMLAudioElement) => void,
|
||||
onAudioPause?: () => void
|
||||
}) {
|
||||
|
||||
const RES_PATH = '/regions/abruzzo/' + props.decade + '/resources/' + props.resourcePayload.resourceSource;
|
||||
|
||||
const getResourceContent = () => {
|
||||
switch(props.resourcePayload.resourceType) {
|
||||
case 'image':
|
||||
return (
|
||||
<ImageCardContent resourceCaption={props.resourcePayload.resourceCaption} resourcePath={RES_PATH} style={style} />
|
||||
)
|
||||
case 'audio':
|
||||
return (
|
||||
<AudioCardContent resourceCaption={props.resourcePayload.resourceCaption} style={style} resourcePath={RES_PATH} onPlay={props.onAudioPlay!} onPause={props.onAudioPause!} onClose={props.onModalClose!} />
|
||||
)
|
||||
case 'video':
|
||||
return (
|
||||
<VideoCardContent resourceCaption={props.resourcePayload.resourceCaption} style={style} resourcePath={RES_PATH} />
|
||||
)
|
||||
default:
|
||||
return (<></>)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="full" alignItems="center" justifyContent="center" direction={'row'}>
|
||||
<Box
|
||||
maxW={'445px'}
|
||||
w={'full'}
|
||||
boxShadow={'2xl'}
|
||||
rounded={'md'}
|
||||
overflow={'hidden'}
|
||||
className={style.cardBody}>
|
||||
|
||||
<Image
|
||||
className={style.decadeIcon}
|
||||
src={'/assets/polygons/' + props.decade + '.svg'}
|
||||
alt={`Picture of Decade`}
|
||||
/>
|
||||
|
||||
{
|
||||
getResourceContent()
|
||||
}
|
||||
|
||||
</Box>
|
||||
</Flex>
|
||||
)
|
||||
};
|
@@ -0,0 +1,17 @@
|
||||
|
||||
.decadeIcon {
|
||||
margin: 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
background-color: rgba(255, 255, 255, 0.70);
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
22
site/components/common/Room/RoomTypes/RoomTypes.tsx
Normal file
22
site/components/common/Room/RoomTypes/RoomTypes.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Product } from "@commerce/types"
|
||||
|
||||
export type MarkerResourcePayload = {
|
||||
resourceType: string,
|
||||
resourceSource: string,
|
||||
resourceName: string,
|
||||
resourceCaption: string
|
||||
}
|
||||
|
||||
export type MarkerData = {
|
||||
markerType: string,
|
||||
markerPayload: Product.Product | MarkerResourcePayload
|
||||
}
|
||||
|
||||
export type MarkerJson = {
|
||||
markerType: string,
|
||||
markerSource: string,
|
||||
resourceName?: string,
|
||||
resourceCaption?: string,
|
||||
longitude: number,
|
||||
latitude: number
|
||||
}
|
100
site/components/product/ProductCardRoom/ProductCardRoom.tsx
Normal file
100
site/components/product/ProductCardRoom/ProductCardRoom.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
Image,
|
||||
Heading,
|
||||
Divider,
|
||||
Text,
|
||||
Stack,
|
||||
Link,
|
||||
} from '@chakra-ui/react';
|
||||
import NextLink from "next/link"
|
||||
import { Product } from '@commerce/types';
|
||||
|
||||
import style from './ProductCardRoomStyle.module.css';
|
||||
|
||||
export default function ProductCardRoom(props: {
|
||||
product: Product.Product,
|
||||
decade: string
|
||||
}) {
|
||||
|
||||
let historicDescription = props.product.metafields
|
||||
.filter(meta => meta.key == 'descrizione_storica')
|
||||
.map(meta => meta.value);
|
||||
let technicalDescription = props.product.metafields
|
||||
.filter(meta => meta.key == 'descrizione_tecnica')
|
||||
.map(meta => meta.value);
|
||||
let nationOrigin = props.product.metafields
|
||||
.filter(meta => meta.key == 'nazionalit_')
|
||||
.map(meta => meta.value);
|
||||
|
||||
return (
|
||||
<Flex w="full" alignItems="center" justifyContent="center" direction={'row'}>
|
||||
<Box
|
||||
maxW={'445px'}
|
||||
w={'full'}
|
||||
boxShadow={'2xl'}
|
||||
rounded={'md'}
|
||||
overflow={'hidden'}
|
||||
className={style.cardBody}>
|
||||
|
||||
<Image
|
||||
className={style.flagIcon}
|
||||
src={'http://purecatamphetamine.github.io/country-flag-icons/3x2/' + nationOrigin + '.svg'}
|
||||
alt={`Picture of Flag`}
|
||||
rounded={'lg'}
|
||||
/>
|
||||
|
||||
<Image
|
||||
className={style.decadeIcon}
|
||||
src={'/assets/polygons/colorized/' + props.decade + '.svg'}
|
||||
alt={`Picture of Decade`}
|
||||
/>
|
||||
|
||||
<Box
|
||||
className={style.imageContainer}
|
||||
w={'full'}
|
||||
height={'220px'}
|
||||
>
|
||||
<NextLink href={'/product/' + props.product.slug} passHref>
|
||||
<Link style={{textDecoration: 'none', height: 'inherit'}}>
|
||||
<Image
|
||||
src={
|
||||
props.product.images[0].url
|
||||
}
|
||||
objectFit={'cover'}
|
||||
margin={'auto'}
|
||||
height={'inherit'}
|
||||
/>
|
||||
</Link>
|
||||
</NextLink>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
p={5}
|
||||
className={style.captionContainer}>
|
||||
|
||||
<Stack align={'center'}>
|
||||
<Heading fontSize={'2xl'} textAlign={'center'} fontFamily={'body'} fontWeight={500}>
|
||||
{props.product.name}
|
||||
</Heading>
|
||||
</Stack>
|
||||
|
||||
<Stack mt={6} align={'center'}>
|
||||
<Divider borderColor={'blackAlpha.600'} />
|
||||
{historicDescription.pop()?.split('\n').map((line, index) => (
|
||||
<Text key={index} padding={0} color={'gray.500'} fontSize={'sm'} align={'center'}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
<Divider borderColor={'blackAlpha.600'} />
|
||||
<Text color={'gray.500'} fontSize={'sm'} align={'center'}>
|
||||
{technicalDescription}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
.flagIcon {
|
||||
margin: 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.decadeIcon {
|
||||
margin: 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
background-color: rgba(255, 255, 255, 0.70);
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
72
site/components/product/ProductModel/ProductModel.tsx
Normal file
72
site/components/product/ProductModel/ProductModel.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import Script from 'next/script';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function ProductModel(props: {
|
||||
modelPath: string
|
||||
}) {
|
||||
|
||||
const [windowSize, setWindowSize] = useState({
|
||||
width: 600,
|
||||
height: 600,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// only execute all the code below in client side
|
||||
// Handler to call on window resize
|
||||
function handleResize() {
|
||||
// Set window width/height to state
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}
|
||||
|
||||
// Add event listener
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// Call handler right away so state gets updated with initial window size
|
||||
handleResize();
|
||||
|
||||
// Remove event listener on cleanup
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []); // Empty array ensures that effect is only run on mount
|
||||
|
||||
const modelViewerTag = `
|
||||
|
||||
<style>
|
||||
model-viewer {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
#productModelViewer {
|
||||
height: ${windowSize.width < 600 ? windowSize.width : 600}px;
|
||||
width: ${windowSize.width < 600 ? windowSize.width : 600}px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<model-viewer src="${props.modelPath}" ar ar-modes="webxr scene-viewer quick-look" seamless-poster shadow-intensity="1" camera-controls enable-pan shadow-intensity="0" enviroment-image="neutral"></model-viewer>
|
||||
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script type='module' src='https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js'></Script>
|
||||
<div id="productModelViewer" dangerouslySetInnerHTML={{__html: modelViewerTag}} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user