diff --git a/app/[locale]/[[...slug]]/home-page.tsx b/app/[locale]/[[...slug]]/home-page.tsx
new file mode 100644
index 000000000..a1c871b59
--- /dev/null
+++ b/app/[locale]/[[...slug]]/home-page.tsx
@@ -0,0 +1,10 @@
+import DynamicContentManager from 'components/ui/dynamic-content-manager'
+
+// This is a Client Component. It receives data as props and
+// has access to state and effects just like Page components
+// in the `pages` directory.
+export default function HomePage({ data }: { data: object | any }) {
+ return (
+
+ )
+}
diff --git a/app/[locale]/[[...slug]]/page.tsx b/app/[locale]/[[...slug]]/page.tsx
index 0eb490984..064210dd0 100644
--- a/app/[locale]/[[...slug]]/page.tsx
+++ b/app/[locale]/[[...slug]]/page.tsx
@@ -1,22 +1,67 @@
-'use client';
+// 'use client';
-import LocaleSwitcher from 'components/ui/locale-switcher/locale-switcher';
-import { useTranslations } from 'next-intl';
+import getQueryFromSlug from 'helpers/getQueryFromSlug';
+import { docQuery } from 'lib/sanity/queries';
+import { client } from 'lib/sanity/sanity.client';
+import { groq } from 'next-sanity';
+import HomePage from './home-page';
+import SinglePage from './single-page';
-interface PageProps {
- params: {
+export async function generateStaticParams() {
+ const paths = await client.fetch(groq`${docQuery}`, {
+ next: { revalidate: 10 },
+ })
+
+ // console.log(paths)
+
+ return paths.map((path: {
+ slug: string,
locale: string
- }
+ }) => ({
+ slug: path.slug.split('/').filter((p) => p),
+ locale: path.locale
+ }))
}
-
-export default function Index({params: {locale}} : PageProps) {
- const t = useTranslations('Index');
+/**
+ * Helper function to return the correct version of the document
+ * If we're in "preview mode" and have multiple documents, return the draft
+ */
+function filterDataToSingleItem(data: any, preview = false) {
+ if (!Array.isArray(data)) {
+ return data
+ }
+
+ if (data.length === 1) {
+ return data[0]
+ }
+
+ if (preview) {
+ return data.find((item) => item._id.startsWith(`drafts.`)) || data[0]
+ }
+
+ return data[0]
+}
+
+export default async function Page({
+ params,
+}: {
+ params: { slug: string[], locale: string };
+}) {
+ const { slug, locale } = params;
+
+ const { query, queryParams, docType } = getQueryFromSlug(slug, locale)
+
+ const pageData = await client.fetch(query, queryParams)
+
+ const data = filterDataToSingleItem(pageData, false)
return (
-
{t('title')}
-
+ <>
+ {docType === 'home' && }
+ {docType === 'page' && }
+ >
)
}
\ No newline at end of file
diff --git a/app/[locale]/[[...slug]]/single-page.tsx b/app/[locale]/[[...slug]]/single-page.tsx
new file mode 100644
index 000000000..af08f5fe3
--- /dev/null
+++ b/app/[locale]/[[...slug]]/single-page.tsx
@@ -0,0 +1,19 @@
+'use client'
+
+import dynamic from 'next/dynamic'
+
+const DynamicContentManager = dynamic(
+ () => import('components/ui/dynamic-content-manager')
+)
+
+interface SinglePageProps {
+ data: any
+}
+
+const SinglePage = ({ data }: SinglePageProps) => {
+ return (
+
+ )
+}
+
+export default SinglePage
diff --git a/app/[locale]/globals.css b/app/[locale]/globals.css
index c0eca5423..7a899dedd 100644
--- a/app/[locale]/globals.css
+++ b/app/[locale]/globals.css
@@ -7,3 +7,228 @@
clip-path: inset(0.6px);
}
}
+
+/* BASE */
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+::-moz-selection {
+ /* Code for Firefox */
+ color: #ffffff;
+ background: #333333;
+}
+
+::selection {
+ color: #ffffff;
+ background: #333333;
+}
+
+html,
+body {
+ @apply font-sans h-full bg-white text-high-contrast;
+
+ box-sizing: border-box;
+ touch-action: manipulation;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overscroll-behavior-x: none;
+}
+
+/* COMPONENTS */
+.glider {
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+.glider::-webkit-scrollbar {
+ display: none;
+}
+
+.glider-dots {
+ @apply flex !space-x-[2px] !mt-8;
+}
+
+.glider-dot {
+ @apply !m-0 !rounded-none !w-12 !h-4 !bg-transparent after:content-[''] after:block after:w-12 after:h-[3px] after:bg-ui-border 2xl:!w-16 2xl:after:w-16;
+}
+
+.glider-dot.active {
+ @apply after:!bg-high-contrast;
+}
+
+/* Glider slider. */
+.glider-prev {
+ @apply text-high-contrast !right-12 !-top-10 !left-auto lg:!right-16 lg:!-top-12 2xl:!-top-16 2xl:!right-[100px] !transition-transform !duration-100 hover:!text-high-contrast hover:scale-110;
+}
+
+.glider-next {
+ @apply text-high-contrast !right-4 !-top-10 lg:!right-8 lg:!-top-12 2xl:!-top-16 2xl:!right-16 !transition-transform !duration-100 hover:!text-high-contrast hover:scale-110;
+}
+
+.pdp .glider-prev {
+ @apply text-high-contrast absolute !left-4 !top-1/2 !transition-transform !duration-100 hover:!text-high-contrast hover:scale-100 lg:hidden;
+}
+
+.pdp .glider-next {
+ @apply text-high-contrast absolute !right-4 !top-1/2 !transition-transform !duration-100 hover:!text-high-contrast hover:scale-100 lg:hidden;
+}
+
+/* Dynamic content */
+.dynamic-content > :not(.hero) {
+ @apply my-16 lg:my-24;
+}
+
+.dynamic-content > :first-child {
+ @apply mt-0 md:mt-0 lg:mt-0;
+}
+
+.dynamic-content > :last-child {
+ @apply mb-16 lg:mb-24;
+}
+
+.dynamic-content .dynamic-content {
+ @apply px-0 md:px-0;
+}
+
+.dynamic-content .dynamic-content > {
+ @apply my-0 md:my-0 lg:my-0;
+}
+
+.dynamic-content .dynamic-content > :last-child {
+ @apply my-0 md:my-0 lg:my-0;
+}
+
+/* ALGOLIA SEARCH */
+.ais-SearchBox-form {
+ @apply w-full relative;
+}
+
+.ais-SearchBox-input {
+ @apply h-[44px] pl-10 bg-ui w-full;
+}
+
+.ais-SearchBox-input::placeholder {
+ @apply text-high-contrast text-opacity-50;
+}
+
+.ais-SearchBox-submit {
+ @apply absolute left-0 flex items-center justify-center w-12 h-12 top-1/2 transform -translate-y-1/2;
+}
+
+.ais-SearchBox-submit svg {
+ @apply w-4 h-4;
+}
+
+.ais-SearchBox-reset:not([hidden]) {
+ @apply absolute right-[3px] bg-ui flex items-center justify-center w-12 h-8 top-1/2 transform -translate-y-1/2;
+}
+
+.ais-SearchBox-reset svg {
+ @apply w-3 h-3;
+}
+
+.ais-RefinementList-item {
+ @apply mt-1 first:mt-0;
+}
+.ais-RefinementList-label {
+ @apply flex items-center cursor-pointer text-sm;
+}
+
+.ais-RefinementList-checkbox {
+ @apply w-4 h-4;
+}
+
+.ais-RefinementList-labelText {
+ @apply ml-2;
+}
+
+.ais-RefinementList-count {
+ @apply ml-2 bg-ui h-5 w-5 flex items-center justify-center text-xs rounded-full;
+}
+
+.ais-CurrentRefinements-label {
+ @apply uppercase;
+}
+
+.ais-Hits-list {
+ @apply grid grid-cols-2 gap-4 lg:grid-cols-4 items-start;
+}
+
+.ais-Pagination-list {
+ @apply flex justify-center -space-x-px mt-8;
+}
+
+.ais-Pagination-link {
+ @apply flex h-12 w-12 items-center justify-center bg-white border border-ui-border;
+}
+
+.ais-Pagination-link--selected {
+ @apply bg-ui font-bold;
+}
+
+.ais-ClearRefinements-button {
+ @apply inline-flex py-3 cursor-pointer px-6 border border-ui-border;
+}
+
+.ais-ClearRefinements-button--disabled {
+ @apply opacity-50 cursor-not-allowed;
+}
+
+/* ALGOLIA AUTOCOMPLETE */
+.aa-DetachedContainer {
+ @apply !shadow-none;
+}
+
+.aa-DetachedContainer--modal {
+ @apply lg:!top-4 !rounded-none;
+}
+
+.aa-DetachedFormContainer {
+ @apply !border-none !px-4;
+}
+
+.aa-DetachedSearchButton {
+ @apply !w-10 !h-10 !p-0 cursor-pointer items-center justify-center !border-none;
+}
+
+.aa-DetachedSearchButtonIcon,
+.aa-SubmitIcon {
+ @apply pointer-events-none;
+}
+
+.aa-DetachedSearchButtonPlaceholder {
+ @apply sr-only;
+}
+
+.aa-Form {
+ @apply !rounded-none !bg-white !border-ui-border;
+}
+
+.aa-SubmitButton {
+ @apply !flex !items-center !justify-center !p-0 !h-10 !w-10 lg:!w-10;
+}
+
+.aa-SubmitIcon {
+ @apply !text-high-contrast;
+}
+
+.aa-Panel {
+ @apply !rounded-none border border-ui-border !shadow-none !p-0;
+}
+
+.aa-PanelLayout {
+ @apply !p-0;
+}
+
+.aa-Item {
+ @apply !px-4 !py-2 lg:!px-4;
+}
+
+.aa-Item[aria-selected='true'] {
+ @apply !bg-ui;
+}
+
diff --git a/components/icons/flag-en.tsx b/components/icons/flag-en.tsx
new file mode 100644
index 000000000..2d4005b69
--- /dev/null
+++ b/components/icons/flag-en.tsx
@@ -0,0 +1,18 @@
+
+export default function FlagEn ({ className = "w-6 h-auto" }: { className?: string }) {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/components/icons/flag-sv.tsx b/components/icons/flag-sv.tsx
new file mode 100644
index 000000000..d219dea08
--- /dev/null
+++ b/components/icons/flag-sv.tsx
@@ -0,0 +1,25 @@
+
+export default function FlagSv ({ className = "w-6 h-auto" }: { className?: string }) {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/components/layout/header/header.tsx b/components/layout/header/header.tsx
index ec4d10287..f20752dbd 100644
--- a/components/layout/header/header.tsx
+++ b/components/layout/header/header.tsx
@@ -1,5 +1,6 @@
'use client'
+import LocaleSwitcher from 'components/ui/locale-switcher/locale-switcher'
import Logo from 'components/ui/logo/logo'
import {
NavigationMenu,
@@ -64,6 +65,9 @@ const Header: FC = () => {
+
+
+
diff --git a/components/ui/blurb-section/blurb-section.tsx b/components/ui/blurb-section/blurb-section.tsx
new file mode 100644
index 000000000..3b120754b
--- /dev/null
+++ b/components/ui/blurb-section/blurb-section.tsx
@@ -0,0 +1,115 @@
+import {
+ CarouselItemProps as ItemProps,
+ CarouselProps as Props,
+} from 'components/ui/carousel/carousel'
+import dynamic from 'next/dynamic'
+const Carousel = dynamic(() =>
+ import('components/ui/carousel/carousel').then((mod) => mod.Carousel)
+)
+const CarouselItem = dynamic(() =>
+ import('components/ui/carousel/carousel').then((mod) => mod.CarouselItem)
+)
+const Card = dynamic(() => import('components/ui/card'))
+
+import Text from 'components/ui/text'
+
+interface BlurbSectionProps {
+ blurbs: any
+ title: string
+ mobileLayout: string
+ desktopLayout: string
+ imageFormat: 'square' | 'portrait' | 'landscape'
+}
+
+const BlurbSection = ({
+ title,
+ mobileLayout,
+ desktopLayout,
+ blurbs,
+ imageFormat,
+}: BlurbSectionProps) => {
+ const gridLayout =
+ desktopLayout === '2-column'
+ ? 'lg:grid-cols-2'
+ : desktopLayout === '3-column'
+ ? 'lg:grid-cols-3'
+ : 'lg:grid-cols-4'
+
+ const sliderLayout =
+ desktopLayout === '2-column' ? 2 : desktopLayout === '3-column' ? 3 : 4
+
+ return (
+
+ {title ? (
+
+ {title}
+
+ ) : (
+
+ No title provided yet
+
+ )}
+
+ {blurbs.map((blurb: object | any, index: number) => {
+ return (
+
+
+
+ )
+ })}
+
+
+
+ {blurbs && (
+
+ {blurbs.map((blurb: any, index: number) => (
+
+
+
+ ))}
+
+ )}
+
+
+ )
+}
+
+export default BlurbSection
diff --git a/components/ui/blurb-section/index.ts b/components/ui/blurb-section/index.ts
new file mode 100644
index 000000000..ed14d040f
--- /dev/null
+++ b/components/ui/blurb-section/index.ts
@@ -0,0 +1,2 @@
+export { default } from './blurb-section';
+
diff --git a/components/ui/card/card.tsx b/components/ui/card/card.tsx
new file mode 100644
index 000000000..723c63021
--- /dev/null
+++ b/components/ui/card/card.tsx
@@ -0,0 +1,103 @@
+'use client'
+
+import SanityImage from 'components/ui/sanity-image'
+import { cn } from 'lib/utils'
+import Link from 'next/link'
+import { FC } from 'react'
+
+interface CardProps {
+ className?: string
+ title: string
+ image: object | any
+ link: object | any
+ text?: string
+ imageFormat?: 'square' | 'portrait' | 'landscape'
+}
+
+const placeholderImg = '/product-img-placeholder.svg'
+
+const Card: FC = ({
+ className,
+ title,
+ image,
+ link,
+ text,
+ imageFormat = 'square',
+}) => {
+ const rootClassName = cn('relative', className)
+
+ const { linkType } = link
+
+ const imageWrapperClasses = cn('w-full h-full overflow-hidden relative', {
+ ['aspect-square']: imageFormat === 'square',
+ ['aspect-[3/4]']: imageFormat === 'portrait',
+ ['aspect-[4/3]']: imageFormat === 'landscape',
+ })
+ const imageClasses = cn('object-cover w-full h-full')
+
+ function Card() {
+ if (linkType === 'internal') {
+ return (
+
+
+ {image && (
+
+
+
+ )}
+
+ {title}
+
+ {text && (
+
+ {text}
+
+ )}
+
+
+ )
+ }
+
+ return (
+
+
+ {image && (
+
+
+
+ )}
+
+ {title}
+
+ {text && (
+
+ {text}
+
+ )}
+
+
+ )
+ }
+
+ return
+}
+
+export default Card
diff --git a/components/ui/card/index.tsx b/components/ui/card/index.tsx
new file mode 100644
index 000000000..95cfda28e
--- /dev/null
+++ b/components/ui/card/index.tsx
@@ -0,0 +1 @@
+export { default } from './Card'
diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx
deleted file mode 100644
index 30b6a4733..000000000
--- a/components/ui/carousel.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { getCollectionProducts } from 'lib/shopify';
-import Image from 'next/image';
-import Link from 'next/link';
-
-export async function Carousel() {
- // Collections that start with `hidden-*` are hidden from the search page.
- const products = await getCollectionProducts('hidden-homepage-carousel');
-
- if (!products?.length) return null;
-
- return (
-
-
- {[...products, ...products].map((product, i) => (
-
- {product.featuredImage ? (
-
- ) : null}
-
-
- ))}
-
-
- );
-}
diff --git a/components/ui/carousel/carousel.tsx b/components/ui/carousel/carousel.tsx
new file mode 100644
index 000000000..8aa60edf8
--- /dev/null
+++ b/components/ui/carousel/carousel.tsx
@@ -0,0 +1,62 @@
+'use client'
+
+import 'glider-js/glider.min.css'
+import { ArrowLeft, ArrowRight } from 'lucide-react'
+import React from 'react'
+import Glider from 'react-glider'
+
+export interface CarouselItemProps {
+ children: React.ReactNode
+}
+
+export const CarouselItem: React.FC = ({
+ children,
+}: CarouselItemProps) => {
+ return {children}
+}
+
+export interface CarouselProps {
+ children: JSX.Element | JSX.Element[] | any
+ gliderClasses?: string
+ hasArrows?: boolean
+ hasDots?: boolean
+ gliderItemWrapperClasses?: string
+ slidesToShow?: number
+ slidesToScroll?: number
+ responsive?: any
+}
+
+export const Carousel: React.FC = ({
+ children,
+ gliderClasses,
+ hasArrows = true,
+ hasDots = true,
+ gliderItemWrapperClasses,
+ slidesToShow = 1,
+ slidesToScroll = 1,
+ responsive,
+}) => {
+ return (
+
+
}
+ iconRight={
}
+ responsive={[responsive]}
+ skipTrack
+ >
+
+ {React.Children.map(children, (child) => {
+ return React.cloneElement(child)
+ })}
+
+
+
+ )
+}
diff --git a/components/ui/category-card/category-card.tsx b/components/ui/category-card/category-card.tsx
new file mode 100644
index 000000000..923c30e55
--- /dev/null
+++ b/components/ui/category-card/category-card.tsx
@@ -0,0 +1,46 @@
+'use client'
+
+import SanityImage from 'components/ui/sanity-image'
+import { cn } from 'lib/utils'
+import Link from 'next/link'
+import { FC } from 'react'
+
+
+interface Props {
+ className?: string
+ category: any
+}
+
+const placeholderImg = '/product-img-placeholder.svg'
+
+const CategoryCard: FC = ({ category, className }) => {
+ const rootClassName = cn(
+ 'w-1/2 min-w-0 grow-0 shrink-0 group relative box-border overflow-hidden transition-transform ease-linear cursor-pointer basis-[50%]',
+ className
+ )
+
+ return (
+
+
+
+
+
+ {category.title}
+
+
+
+
+ )
+}
+
+export default CategoryCard
diff --git a/components/ui/category-card/index.ts b/components/ui/category-card/index.ts
new file mode 100644
index 000000000..40185966a
--- /dev/null
+++ b/components/ui/category-card/index.ts
@@ -0,0 +1,2 @@
+export { default } from './category-card';
+
diff --git a/components/ui/dropdown/dropdown.tsx b/components/ui/dropdown/dropdown.tsx
index 76032d320..1d4d37f12 100644
--- a/components/ui/dropdown/dropdown.tsx
+++ b/components/ui/dropdown/dropdown.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { CheckIcon, ChevronRightIcon, CircleIcon } from '@radix-ui/react-icons'
import * as React from 'react'
diff --git a/components/ui/dynamic-content-manager/dynamic-content-manager.tsx b/components/ui/dynamic-content-manager/dynamic-content-manager.tsx
new file mode 100644
index 000000000..87c586f4e
--- /dev/null
+++ b/components/ui/dynamic-content-manager/dynamic-content-manager.tsx
@@ -0,0 +1,79 @@
+'use client'
+
+import { Info } from 'lucide-react'
+import dynamic from 'next/dynamic'
+
+import Hero from 'components/ui/hero'
+const Slider = dynamic(() => import('components/ui/slider'))
+const BlurbSection = dynamic(() => import('components/ui/blurb-section'))
+const FilteredProductList = dynamic(
+ () => import('components/ui/filtered-product-list')
+)
+
+interface getContentComponentProps {
+ _type: string
+ _key: number
+ disabled: boolean
+}
+
+const getContentComponent = ({
+ _type,
+ _key,
+ disabled,
+ ...rest
+}: getContentComponentProps) => {
+ let Component: any
+
+ switch (_type) {
+ case 'hero':
+ Component = Hero
+ break
+ case 'slider':
+ Component = Slider
+ break
+ case 'filteredProductList':
+ Component = FilteredProductList
+ break
+ case 'blurbSection':
+ if (disabled !== true) {
+ Component = BlurbSection
+ } else {
+ return
+ }
+ break
+ default:
+ return (
+
+
+
+ {`No matching component (Type: ${_type})`}
+
+
+ )
+ }
+
+ return Component ? (
+
+ ) : (
+ Something else
+ )
+}
+
+interface dynamicContentManagerProps {
+ content: [] | any
+}
+
+const DynamicContentManager = ({ content }: dynamicContentManagerProps) => {
+ return (
+
+ {content?.map(getContentComponent)}
+
+ )
+}
+
+export default DynamicContentManager
diff --git a/components/ui/dynamic-content-manager/index.tsx b/components/ui/dynamic-content-manager/index.tsx
new file mode 100644
index 000000000..ef80695e1
--- /dev/null
+++ b/components/ui/dynamic-content-manager/index.tsx
@@ -0,0 +1,2 @@
+export { default } from './dynamic-content-manager';
+
diff --git a/components/ui/filtered-product-list/filtered-product-list.tsx b/components/ui/filtered-product-list/filtered-product-list.tsx
new file mode 100644
index 000000000..fb403ea04
--- /dev/null
+++ b/components/ui/filtered-product-list/filtered-product-list.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import Text from 'components/ui/text'
+import dynamic from 'next/dynamic'
+
+const ProductCard = dynamic(() => import('components/ui/product-card'))
+
+interface SliderProps {
+ products: any
+ title: string
+ itemsToShow: number
+}
+
+const FilteredProductList = ({ title, products, itemsToShow }: SliderProps) => {
+ return (
+
+ {title ? (
+
+ {title}
+
+ ) : (
+
+ No title provided yet
+
+ )}
+
+ {products.slice(0, itemsToShow).map((product: any, index: number) => (
+
Product
+ //
+ ))}
+
+
+ )
+}
+
+export default FilteredProductList
diff --git a/components/ui/filtered-product-list/index.ts b/components/ui/filtered-product-list/index.ts
new file mode 100644
index 000000000..bf21a0ae3
--- /dev/null
+++ b/components/ui/filtered-product-list/index.ts
@@ -0,0 +1,2 @@
+export { default } from './filtered-product-list';
+
diff --git a/components/ui/hero/hero.tsx b/components/ui/hero/hero.tsx
new file mode 100644
index 000000000..b7b5ac1dc
--- /dev/null
+++ b/components/ui/hero/hero.tsx
@@ -0,0 +1,82 @@
+'use client'
+
+import dynamic from 'next/dynamic'
+
+const SanityImage = dynamic(() => import('components/ui/sanity-image'))
+const Link = dynamic(() => import('components/ui/link'))
+const Text = dynamic(() => import('components/ui/text'))
+
+interface HeroProps {
+ variant: string
+ text?: string
+ label?: string
+ title: string
+ image: object | any
+ desktopImage: object | any
+ link: {
+ title: string
+ reference: {
+ title: string
+ slug: {
+ current: string
+ }
+ }
+ }
+}
+
+type HeroSize = keyof typeof heroSize
+
+const heroSize = {
+ fullScreen: 'aspect-[3/4] lg:aspect-auto lg:h-[calc(100vh-4rem)]',
+ halfScreen: 'aspect-square max-h-[60vh] lg:aspect-auto lg:min-h-[60vh]',
+}
+
+const Hero = ({ variant, title, text, label, image, link }: HeroProps) => {
+ const heroClass = heroSize[variant as HeroSize] || heroSize.fullScreen
+
+ return (
+
+ {image && (
+
+ )}
+
+ {label && (
+
+ {label}
+
+ )}
+ {title ? (
+ {title}
+ ) : (
+
+ No title provided yet
+
+ )}
+ {text && (
+
+ {label}
+
+ )}
+ {link?.reference && (
+
+ {link?.title ? link.title : link.reference.title}
+
+ )}
+
+
+ )
+}
+
+export default Hero
diff --git a/components/ui/hero/index.ts b/components/ui/hero/index.ts
new file mode 100644
index 000000000..4bcc64419
--- /dev/null
+++ b/components/ui/hero/index.ts
@@ -0,0 +1,2 @@
+export { default } from './hero';
+
diff --git a/components/ui/link/index.ts b/components/ui/link/index.ts
new file mode 100644
index 000000000..f5561bd64
--- /dev/null
+++ b/components/ui/link/index.ts
@@ -0,0 +1,2 @@
+export { default } from './link';
+
diff --git a/components/ui/link/link.tsx b/components/ui/link/link.tsx
new file mode 100644
index 000000000..75b1cff60
--- /dev/null
+++ b/components/ui/link/link.tsx
@@ -0,0 +1,19 @@
+'use client'
+
+import { cn } from 'lib/utils'
+import NextLink, { LinkProps as NextLinkProps } from 'next/link'
+
+const Link: React.FC<
+ NextLinkProps & {
+ children?: React.ReactNode
+ className?: string
+ }
+> = ({ href, children, className, ...props }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+export default Link
diff --git a/components/ui/locale-switcher/I18nWidget.tsx b/components/ui/locale-switcher/I18nWidget.tsx
deleted file mode 100644
index beae0ecac..000000000
--- a/components/ui/locale-switcher/I18nWidget.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import { useState } from 'react'
-import { useRouter } from 'next/router'
-import Link from 'next/link'
-import Image from 'next/image'
-import { cn } from '@lib/utils'
-
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@components/ui/Dropdown/Dropdown'
-
-interface LOCALE_DATA {
- name: string
- img: {
- filename: string
- alt: string
- }
-}
-
-const LOCALES_MAP: Record = {
- sv: {
- name: 'Swedish',
- img: {
- filename: 'flag-sv.svg',
- alt: 'Swedish flag',
- },
- },
- nn: {
- name: 'Norwegian',
- img: {
- filename: 'flag-no.svg',
- alt: 'Norwegian flag',
- },
- },
- en: {
- name: 'English',
- img: {
- filename: 'flag-en.svg',
- alt: 'British flag',
- },
- },
-}
-
-interface I18nWidgetProps {
- translations: [] | any
-}
-
-const I18nWidget = ({ translations }: I18nWidgetProps) => {
- const [isOpen, setIsOpen] = useState(false)
- const { locale, locales, defaultLocale = 'sv' } = useRouter()
- const router = useRouter()
-
- const options: any = locales?.filter((val) => val !== locale)
- const currentLocale = locale || defaultLocale
-
- const handleClick = (e: any, locale: string) => {
- e.preventDefault()
-
- const parent = e.target
-
- if (parent.nodeName !== 'LI') {
- return
- }
-
- let href = '/'
-
- const hasChildLink = parent.querySelector('a').href !== null
-
- if (hasChildLink) {
- href = parent.querySelector('a').href
- }
-
- router.push({ pathname: href }, { pathname: href }, { locale: locale })
-
- setIsOpen(false)
- }
-
- return (
- setIsOpen(!isOpen)}>
-
-
-
-
-
- {options.map((locale: any) => {
- const translationLink = translations?.find(
- (item: object | any) => item.locale === locale
- )
-
- return (
- handleClick(e, locale)}
- >
- -
- setIsOpen(false)}
- >
-
-
-
- {LOCALES_MAP[locale].name}
-
-
-
- )
- })}
-
-
-
- )
-}
-
-export default I18nWidget
diff --git a/components/ui/locale-switcher/locale-switcher.tsx b/components/ui/locale-switcher/locale-switcher.tsx
index 3966f1a08..037142e62 100644
--- a/components/ui/locale-switcher/locale-switcher.tsx
+++ b/components/ui/locale-switcher/locale-switcher.tsx
@@ -1,22 +1,24 @@
'use client'
+import FlagEn from 'components/icons/flag-en';
+import FlagSv from 'components/icons/flag-sv';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
-} from 'components/ui/dropdown/dropdown'
-import Link from 'next/link'
-import { usePathname } from 'next/navigation'
-import { useState } from 'react'
-import { i18n } from '../../../i18n-config'
+} from 'components/ui/dropdown/dropdown';
+import { useLocale } from 'next-intl';
+import Link from 'next/link';
+import { usePathname, useRouter } from 'next/navigation';
+import { useState } from 'react';
+import { i18n } from '../../../i18n-config';
-interface LocaleSwitcherProps {
- currentLocale: string
-}
-
-export default function LocaleSwitcher({currentLocale}: LocaleSwitcherProps) {
+export default function LocaleSwitcher() {
const pathName = usePathname()
+ const locale = useLocale();
+ const [isOpen, setIsOpen] = useState(false)
+ const router = useRouter();
const redirectedPathName = (locale: string) => {
if (!pathName) return '/'
@@ -24,8 +26,28 @@ export default function LocaleSwitcher({currentLocale}: LocaleSwitcherProps) {
segments[1] = locale
return segments.join('/')
}
-
- const [isOpen, setIsOpen] = useState(false)
+
+ const handleClick = (e: any, locale: string) => {
+ e.preventDefault()
+
+ const parent = e.target
+
+ if (parent.nodeName !== 'LI') {
+ return
+ }
+
+ let href = '/'
+
+ const hasChildLink = parent.querySelector('a').href !== null
+
+ if (hasChildLink) {
+ href = parent.querySelector('a').href
+ }
+
+ router.push(`${redirectedPathName(locale)}`)
+
+ setIsOpen(false)
+ }
return (
@@ -34,23 +56,42 @@ export default function LocaleSwitcher({currentLocale}: LocaleSwitcherProps) {
{i18n.locales.map((locale) => {
+ let FlagIcon: any
+
+ FlagIcon = i18n.flags[locale]
+
return (
handleClick(e, locale)}
>
- -
- {locale}
+
-
+ setIsOpen(false)}
+ href={redirectedPathName(locale)}
+ >
+
+ {locale}
+
)
diff --git a/components/ui/product-card/index.ts b/components/ui/product-card/index.ts
new file mode 100644
index 000000000..6eec70851
--- /dev/null
+++ b/components/ui/product-card/index.ts
@@ -0,0 +1,2 @@
+export { default } from './product-card';
+
diff --git a/components/ui/product-card/product-card.tsx b/components/ui/product-card/product-card.tsx
new file mode 100644
index 000000000..d0744bf98
--- /dev/null
+++ b/components/ui/product-card/product-card.tsx
@@ -0,0 +1,80 @@
+'use client'
+
+import { cn } from 'lib/utils'
+import { FC } from 'react'
+// import type { Product } from '@commerce/types/product'
+import dynamic from 'next/dynamic'
+// import usePrice from '@framework/product/use-price'
+
+// const WishlistButton = dynamic(
+// () => import('@components/wishlist/WishlistButton')
+// )
+const ProductTag = dynamic(() => import('components/ui/product-tag'))
+const SanityImage = dynamic(() => import('components/ui/sanity-image'))
+
+interface Props {
+ className?: string
+ // product: Product
+ variant?: 'default'
+}
+
+const ProductCard: FC = ({
+ // product,
+ className,
+ variant = 'default',
+}) => {
+ // const { price } = usePrice({
+ // amount: product.price.value,
+ // baseAmount: product.price.retailPrice,
+ // currencyCode: product.price.currencyCode!,
+ // })
+
+ const rootClassName = cn(
+ 'w-full min-w-0 grow-0 shrink-0 group relative box-border overflow-hidden transition-transform ease-linear basis-[50%]',
+ className
+ )
+
+ return (
+ <>Produyct>
+ //
+ // {variant === 'default' && (
+ // <>
+ //
+ // {/* {process.env.COMMERCE_WISHLIST_ENABLED && (
+ //
+ // )} */}
+ // {/*
+ // {product?.images && (
+ //
+ // )}
+ //
*/}
+ //
+ //
+ // >
+ // )}
+ //
+ )
+}
+
+export default ProductCard
diff --git a/components/ui/product-tag/index.ts b/components/ui/product-tag/index.ts
new file mode 100644
index 000000000..0c00d74ce
--- /dev/null
+++ b/components/ui/product-tag/index.ts
@@ -0,0 +1,2 @@
+export { default } from './product-tag';
+
diff --git a/components/ui/product-tag/product-tag.tsx b/components/ui/product-tag/product-tag.tsx
new file mode 100644
index 000000000..c9ddc319f
--- /dev/null
+++ b/components/ui/product-tag/product-tag.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import { cn } from 'lib/utils'
+import dynamic from 'next/dynamic'
+const Text = dynamic(() => import('components/ui/text'))
+
+interface ProductTagProps {
+ className?: string
+ name: string
+ price: string
+ variant?: 'productView' | 'cardView'
+}
+
+const ProductTag: React.FC = ({
+ name,
+ price,
+ className = '',
+ variant = 'cardView',
+}) => {
+ return (
+
+
+ {name}
+
+
+ {price}
+
+
+ )
+}
+
+export default ProductTag
diff --git a/components/ui/sanity-image/index.tsx b/components/ui/sanity-image/index.tsx
new file mode 100644
index 000000000..515353417
--- /dev/null
+++ b/components/ui/sanity-image/index.tsx
@@ -0,0 +1,2 @@
+export { default } from './sanity-image';
+
diff --git a/components/ui/sanity-image/sanity-image.tsx b/components/ui/sanity-image/sanity-image.tsx
new file mode 100644
index 000000000..805e4b457
--- /dev/null
+++ b/components/ui/sanity-image/sanity-image.tsx
@@ -0,0 +1,67 @@
+'use client'
+
+import { urlForImage } from 'lib/sanity/sanity.image'
+import { cn } from 'lib/utils'
+import Image from 'next/image'
+
+interface SanityImageProps {
+ image: object | any
+ alt: string
+ priority?: boolean
+ width?: number
+ height?: number
+ quality?: number
+ sizes?: string
+ className?: string
+}
+
+const placeholderImg = '/product-img-placeholder.svg'
+
+export default function SanityImage(props: SanityImageProps) {
+ const {
+ image: source,
+ priority = false,
+ quality = 75,
+ alt = '',
+ height = 1080,
+ width = 1080,
+ sizes = '100vw',
+ className,
+ } = props
+
+ const rootClassName = cn('w-full h-auto', className)
+
+ const image = source?.asset?._rev ? (
+ <>
+
+ >
+ ) : (
+ <>
+
+ >
+ )
+
+ return image
+}
diff --git a/components/ui/slider/index.ts b/components/ui/slider/index.ts
new file mode 100644
index 000000000..19a6da94b
--- /dev/null
+++ b/components/ui/slider/index.ts
@@ -0,0 +1 @@
+export { default } from './Slider'
diff --git a/components/ui/slider/slider.tsx b/components/ui/slider/slider.tsx
new file mode 100644
index 000000000..87ab65000
--- /dev/null
+++ b/components/ui/slider/slider.tsx
@@ -0,0 +1,78 @@
+'use client'
+
+import {
+ CarouselItemProps as ItemProps,
+ CarouselProps as Props,
+} from 'components/ui/carousel/carousel'
+import Text from 'components/ui/text'
+import dynamic from 'next/dynamic'
+import { useEffect, useState } from 'react'
+const Carousel = dynamic(() =>
+ import('components/ui/carousel/carousel').then((mod) => mod.Carousel)
+)
+const CarouselItem = dynamic(() =>
+ import('components/ui/carousel/carousel').then((mod) => mod.CarouselItem)
+)
+const ProductCard = dynamic(() => import('components/ui/product-card'))
+const CategoryCard = dynamic(() => import('components/ui/category-card'))
+
+interface SliderProps {
+ products: [] | any
+ title: string
+ categories: [] | any
+ sliderType: String
+}
+
+const Slider = ({ products, categories, title, sliderType }: SliderProps) => {
+ const [items, setItems] = useState([])
+
+ useEffect(() => {
+ if (sliderType === 'products') setItems(products)
+ else if (sliderType === 'categories') setItems(categories)
+ }, [])
+
+ return (
+
+ {title ? (
+
+ {title}
+
+ ) : (
+
+ No title provided yet
+
+ )}
+
+ {items && (
+
+ {items.map((item: any, index: number) => (
+
+ {item.title}
+ {/* {sliderType === 'products' && }
+ {sliderType === 'categories' && } */}
+
+ ))}
+
+ )}
+
+ )
+}
+
+export default Slider
diff --git a/components/ui/text/index.ts b/components/ui/text/index.ts
new file mode 100644
index 000000000..31bf6903a
--- /dev/null
+++ b/components/ui/text/index.ts
@@ -0,0 +1,2 @@
+export { default } from './text';
+
diff --git a/components/ui/text/text.tsx b/components/ui/text/text.tsx
new file mode 100644
index 000000000..54220011a
--- /dev/null
+++ b/components/ui/text/text.tsx
@@ -0,0 +1,88 @@
+'use client'
+
+import { cn } from 'lib/utils'
+import React, {
+ CSSProperties,
+ FunctionComponent,
+ JSXElementConstructor,
+} from 'react'
+
+interface TextProps {
+ variant?: Variant
+ className?: string
+ style?: CSSProperties
+ children?: React.ReactNode | any
+ html?: string
+ onClick?: () => any
+}
+
+type Variant =
+ | 'heading'
+ | 'body'
+ | 'pageHeading'
+ | 'sectionHeading'
+ | 'label'
+ | 'paragraph'
+ | 'listChildHeading'
+
+const Text: FunctionComponent = ({
+ style,
+ className = '',
+ variant = 'body',
+ children,
+ html,
+ onClick,
+}) => {
+ const componentsMap: {
+ [P in Variant]: React.ComponentType | string
+ } = {
+ body: 'div',
+ heading: 'h1',
+ pageHeading: 'h1',
+ sectionHeading: 'h2',
+ listChildHeading: 'h3',
+ label: 'div',
+ paragraph: 'p',
+ }
+
+ const Component:
+ | JSXElementConstructor
+ | React.ReactElement
+ | React.ComponentType
+ | string = componentsMap![variant!]
+
+ const htmlContentProps = html
+ ? {
+ dangerouslySetInnerHTML: { __html: html },
+ }
+ : {}
+
+ return (
+
+ {children}
+
+ )
+}
+
+export default Text
diff --git a/helpers/getQueryFromSlug.ts b/helpers/getQueryFromSlug.ts
index b90ee0292..c0fc0388e 100644
--- a/helpers/getQueryFromSlug.ts
+++ b/helpers/getQueryFromSlug.ts
@@ -4,7 +4,7 @@ import {
pageQuery,
} from '../lib/sanity/queries'
-const getQueryFromSlug = (slugArray = []) => {
+const getQueryFromSlug = (slugArray: string[], locale: string) => {
const docQuery = {
homePage: groq`${homePageQuery}`,
page: groq`${pageQuery}`,
@@ -12,10 +12,10 @@ const getQueryFromSlug = (slugArray = []) => {
let docType = ''
- if (slugArray.length === 0) {
+ if (!slugArray) {
return {
docType: 'home',
- queryParams: {},
+ queryParams: {locale: locale},
query: docQuery.homePage,
}
}
@@ -25,6 +25,7 @@ const getQueryFromSlug = (slugArray = []) => {
// We now have to re-combine the slug array to match our slug in Sanity.
let queryParams = {
slug: `/${slugArray.join('/')}`,
+ locale: locale
}
if (slugStart === 'articles' && slugArray.length === 2) {
diff --git a/i18n-config.ts b/i18n-config.ts
index 0915c37a9..258ff807e 100644
--- a/i18n-config.ts
+++ b/i18n-config.ts
@@ -1,6 +1,13 @@
+import FlagEn from "components/icons/flag-en"
+import FlagSv from "components/icons/flag-sv"
+
export const i18n = {
defaultLocale: 'sv',
- locales: ['sv', 'en', 'nn'],
+ locales: ['sv', 'en'],
+ flags: {
+ sv: FlagSv,
+ en: FlagEn
+ }
} as const
export type Locale = typeof i18n['locales'][number]
\ No newline at end of file
diff --git a/lib/sanity/sanity.image.ts b/lib/sanity/sanity.image.ts
index b41ba8c13..221251499 100644
--- a/lib/sanity/sanity.image.ts
+++ b/lib/sanity/sanity.image.ts
@@ -1,7 +1,7 @@
-import { sanityClient } from './sanity.client'
import createImageUrlBuilder from '@sanity/image-url'
+import { client } from './sanity.client'
-export const imageBuilder = createImageUrlBuilder(sanityClient)
+export const imageBuilder = createImageUrlBuilder(client)
export const urlForImage = (source: any) =>
imageBuilder.image(source).auto('format').fit('crop')
diff --git a/package.json b/package.json
index af8ce4341..8a739d543 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"react": "18.2.0",
"react-cookie": "^4.1.1",
"react-dom": "18.2.0",
+ "react-glider": "^4.0.2",
"sanity": "3",
"styled-components": "^5.2",
"tailwind-merge": "^1.12.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 350e5a525..499e3d053 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -40,6 +40,7 @@ specifiers:
react: 18.2.0
react-cookie: ^4.1.1
react-dom: 18.2.0
+ react-glider: ^4.0.2
sanity: '3'
styled-components: ^5.2
tailwind-merge: ^1.12.0
@@ -71,6 +72,7 @@ dependencies:
react: 18.2.0
react-cookie: 4.1.1_react@18.2.0
react-dom: 18.2.0_react@18.2.0
+ react-glider: 4.0.2_biqbaboplfbrettd7655fr4n2y
sanity: 3.9.1_inskn5v7aqlrr54h6fubgcms5y
styled-components: 5.3.10_7i5myeigehqah43i5u7wbekgba
tailwind-merge: 1.12.0
@@ -4095,6 +4097,10 @@ packages:
- supports-color
dev: false
+ /glider-js/1.7.8:
+ resolution: {integrity: sha512-env4+yfoNYaeHJmEBGygjcZDpZ5LRUjGhCB6Ag+XNQfvuLVD5PhCYQShBVCAd5On7XfknM57O+V2Fwfcsii3Dw==}
+ dev: false
+
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -5966,6 +5972,17 @@ packages:
use-sidecar: 1.1.2_3stiutgnnbnfnf3uowm5cip22i
dev: false
+ /react-glider/4.0.2_biqbaboplfbrettd7655fr4n2y:
+ resolution: {integrity: sha512-nZnkV4x8mSzfhGgrTkMrlL/g07T6hR0bLHg0q1iRumuORwfX+kqxCYoRy4/WXTRhlmurhcFKTaH007Vz2HjeHA==}
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ dependencies:
+ glider-js: 1.7.8
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+ dev: false
+
/react-is/16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}