├── .eslintrc.json ├── src ├── core │ ├── constants.ts │ ├── server │ │ ├── useTranslations.ts │ │ ├── getMenuTranslation.ts │ │ ├── parseJSONText.ts │ │ ├── useHook.ts │ │ ├── renderJSONText.tsx │ │ ├── getTranslations.ts │ │ ├── useTranslationValues.ts │ │ ├── locale.ts │ │ └── checkout.tsx │ └── client │ │ ├── isLocalUrl.ts │ │ ├── useProductTranslation.tsx │ │ ├── useApp.tsx │ │ └── useProductSelection.tsx ├── gql │ ├── fragments │ │ ├── money.graphql │ │ ├── checkoutQuantity.graphql │ │ ├── collection.graphql │ │ ├── headerMenuItem.gql │ │ ├── taxedMoney.graphql │ │ ├── menu.graphql │ │ ├── pricing.graphql │ │ ├── menuItem.graphql │ │ ├── productAttributes.graphql │ │ ├── productVariant.graphql │ │ ├── galleryProduct.graphql │ │ ├── pdproduct.graphql │ │ └── productDetails.graphql │ ├── query │ │ ├── checkoutQuantity.graphql │ │ ├── menu.graphql │ │ ├── collections.graphql │ │ ├── page.graphql │ │ ├── product.graphql │ │ └── products.graphql │ ├── index.ts │ └── mutation │ │ ├── tokenCreate.graphql │ │ ├── accountRegister.graphql │ │ ├── checkoutCreate.graphql │ │ └── checkoutLinesAdd.graphql ├── app │ ├── [locale] │ │ ├── globals.css │ │ ├── cart │ │ │ ├── trashcan.png │ │ │ ├── apple-juice.webp │ │ │ ├── shoe-balance.webp │ │ │ ├── components │ │ │ │ ├── CartHeadline.tsx │ │ │ │ ├── ProductImage.tsx │ │ │ │ ├── QuantityCounter.tsx │ │ │ │ ├── CartItem.tsx │ │ │ │ └── OrderSummary.tsx │ │ │ ├── trashcan.svg │ │ │ └── page.tsx │ │ ├── (components) │ │ │ ├── (header) │ │ │ │ ├── hamburger.png │ │ │ │ ├── data.tsx │ │ │ │ ├── navItemMobile.tsx │ │ │ │ ├── drawerIcon.tsx │ │ │ │ ├── navMobile.tsx │ │ │ │ ├── primaryNav.tsx │ │ │ │ ├── navDesktop.tsx │ │ │ │ └── logo.svg │ │ │ ├── requiredLabel.tsx │ │ │ ├── productReviewSummary.tsx │ │ │ ├── productImageGridSkeleton.tsx │ │ │ ├── productCardSkeleton.tsx │ │ │ ├── linkButton.tsx │ │ │ ├── navbarMenu.tsx │ │ │ ├── logo.tsx │ │ │ ├── root.tsx │ │ │ ├── cartIcon.tsx │ │ │ ├── textField.tsx │ │ │ ├── quantitySelector.tsx │ │ │ ├── navbarMenuCategories.tsx │ │ │ ├── cartIconWithCount.tsx │ │ │ ├── localeMenu.tsx │ │ │ ├── serverSubmitHandlers.tsx │ │ │ ├── productHero.tsx │ │ │ ├── link.tsx │ │ │ ├── productDescription.tsx │ │ │ ├── productImageGrid.tsx │ │ │ ├── productPrice.tsx │ │ │ ├── footer.tsx │ │ │ ├── theme.tsx │ │ │ ├── productCardVariantList.tsx │ │ │ ├── addToCartButton.tsx │ │ │ ├── productRating.tsx │ │ │ ├── productGallery.tsx │ │ │ ├── addToCartConfirmation.tsx │ │ │ ├── placeholderImage.tsx │ │ │ ├── productCard.tsx │ │ │ ├── productVariantSelector.tsx │ │ │ └── searchFilter.tsx │ │ ├── [channel] │ │ │ ├── (home) │ │ │ │ └── page.tsx │ │ │ ├── about │ │ │ │ └── page.tsx │ │ │ ├── p │ │ │ │ └── [slug] │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ └── c │ │ │ │ └── all │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ ├── login │ │ │ ├── page.tsx │ │ │ ├── login.tsx │ │ │ └── createAccount.tsx │ │ └── layout.tsx │ ├── favicon.ico │ ├── translations │ │ ├── productGallery.ts │ │ ├── productCard.ts │ │ ├── productDetails.ts │ │ ├── addToCart.ts │ │ └── searchFilter.ts │ ├── types.d.ts │ └── daisyui │ │ ├── drawer │ │ ├── drawerSidebar.tsx │ │ ├── drawerToggle.tsx │ │ ├── drawerContent.tsx │ │ └── index.tsx │ │ ├── types.d.ts │ │ ├── menu-title │ │ └── index.tsx │ │ ├── card-title │ │ └── index.tsx │ │ ├── breadcrumbs │ │ └── index.tsx │ │ ├── select │ │ └── index.tsx │ │ ├── menu │ │ └── index.tsx │ │ ├── card-body │ │ └── index.tsx │ │ ├── text-input │ │ └── index.tsx │ │ ├── button │ │ └── index.tsx │ │ ├── card-actions │ │ └── index.tsx │ │ ├── badge │ │ └── index.tsx │ │ ├── card-media │ │ └── index.tsx │ │ ├── card │ │ └── index.tsx │ │ ├── collapse │ │ └── index.tsx │ │ ├── indicator │ │ └── index.tsx │ │ └── util │ │ └── index.ts ├── channel-config.ts ├── translation-dictionaries.ts ├── locale-config.ts ├── dictionaries │ ├── it-it.json │ ├── fr-fr.json │ ├── es-mx.json │ └── en-us.json └── middleware.ts ├── public ├── favicon.ico ├── robots.txt ├── placeholder.svg ├── placeholderImage.svg └── logo.svg ├── postcss.config.js ├── .vscode ├── settings.json └── extensions.json ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug.yml │ ├── task.yml │ └── epic.yml └── workflows │ └── deploy.yml ├── .gitignore ├── tsconfig.json ├── package.json ├── codegen.ts ├── next.config.js ├── README.md ├── tailwind.config.js └── docs ├── proposal.md └── translations.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/core/constants.ts: -------------------------------------------------------------------------------- 1 | export const checkoutStorageKey = "CheckoutID"; 2 | -------------------------------------------------------------------------------- /src/gql/fragments/money.graphql: -------------------------------------------------------------------------------- 1 | fragment MoneyFragment on Money { 2 | amount 3 | } -------------------------------------------------------------------------------- /src/app/[locale]/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limlabs/saleor-storefront-starter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limlabs/saleor-storefront-starter/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /cart 3 | 4 | # Allow all crawlers 5 | User-agent: * 6 | Allow: / -------------------------------------------------------------------------------- /src/gql/fragments/checkoutQuantity.graphql: -------------------------------------------------------------------------------- 1 | fragment CheckoutQuantityFragment on Checkout { 2 | quantity 3 | } -------------------------------------------------------------------------------- /src/gql/fragments/collection.graphql: -------------------------------------------------------------------------------- 1 | fragment CollectionFragment on Collection { 2 | id 3 | name 4 | slug 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/app/translations/productGallery.ts: -------------------------------------------------------------------------------- 1 | export const productGalleryTranslationKeys = [ 2 | "next", 3 | "prev", 4 | ] as const; -------------------------------------------------------------------------------- /src/app/[locale]/cart/trashcan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limlabs/saleor-storefront-starter/HEAD/src/app/[locale]/cart/trashcan.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bradlc.vscode-tailwindcss", 4 | "kumar-harsh.graphql-for-vscode" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/app/[locale]/cart/apple-juice.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limlabs/saleor-storefront-starter/HEAD/src/app/[locale]/cart/apple-juice.webp -------------------------------------------------------------------------------- /src/app/[locale]/cart/shoe-balance.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limlabs/saleor-storefront-starter/HEAD/src/app/[locale]/cart/shoe-balance.webp -------------------------------------------------------------------------------- /src/gql/query/checkoutQuantity.graphql: -------------------------------------------------------------------------------- 1 | query CheckoutQuantity($id: ID) { 2 | checkout(id: $id) { 3 | ...CheckoutQuantityFragment 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/(header)/hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limlabs/saleor-storefront-starter/HEAD/src/app/[locale]/(components)/(header)/hamburger.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /src/app/translations/productCard.ts: -------------------------------------------------------------------------------- 1 | export const productCardTranslationKeys = ["sale"] as const; 2 | 3 | export type ProductCardTranslations = 4 | (typeof productCardTranslationKeys)[number]; 5 | -------------------------------------------------------------------------------- /src/app/translations/productDetails.ts: -------------------------------------------------------------------------------- 1 | export const productDetailsTranslationKeys = ["rating"] as const; 2 | 3 | export type ProductDetailsTranslations = 4 | (typeof productDetailsTranslationKeys)[number]; 5 | -------------------------------------------------------------------------------- /src/gql/fragments/headerMenuItem.gql: -------------------------------------------------------------------------------- 1 | fragment HeaderMenuItemFragment on MenuItem { 2 | page { 3 | slug 4 | } 5 | children { 6 | { 7 | page { 8 | slug 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/gql/query/menu.graphql: -------------------------------------------------------------------------------- 1 | query Menu( 2 | $channel: String = "default-channel" 3 | $slug: String! 4 | $languageCode: LanguageCodeEnum! 5 | ) { 6 | menu(channel: $channel, slug: $slug) { 7 | ...MenuFragment 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/gql/fragments/taxedMoney.graphql: -------------------------------------------------------------------------------- 1 | fragment TaxedMoneyFragment on TaxedMoney { 2 | currency 3 | gross { 4 | ...MoneyFragment 5 | } 6 | net { 7 | ...MoneyFragment 8 | } 9 | tax { 10 | ...MoneyFragment 11 | } 12 | } -------------------------------------------------------------------------------- /src/gql/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLClient } from "graphql-request"; 2 | import { getSdk } from "./sdk"; 3 | 4 | const saleorClient = new GraphQLClient(process.env.SALEOR_ENDPOINT as string); 5 | 6 | export const gqlClient = getSdk(saleorClient); 7 | 8 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/(header)/data.tsx: -------------------------------------------------------------------------------- 1 | export interface Navlink { 2 | name: string; 3 | path: string; 4 | } 5 | 6 | export const navLinks: Navlink[] = [ 7 | { name: "Home", path: "/" }, 8 | { name: "Shop", path: "/c/all" }, 9 | ]; 10 | -------------------------------------------------------------------------------- /src/gql/query/collections.graphql: -------------------------------------------------------------------------------- 1 | query Collections($channel: String = "default-channel", $first: Int!) { 2 | collections(channel: $channel, first: $first) { 3 | edges { 4 | node { 5 | ...CollectionFragment 6 | } 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/gql/fragments/menu.graphql: -------------------------------------------------------------------------------- 1 | fragment MenuFragment on Menu { 2 | id 3 | items { 4 | ...MenuItemFragment 5 | children { 6 | ...MenuItemFragment 7 | children { 8 | ...MenuItemFragment 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/gql/query/page.graphql: -------------------------------------------------------------------------------- 1 | query Page($slug: String!, $languageCode: LanguageCodeEnum! ) { 2 | page(slug: $slug) { 3 | title 4 | content 5 | translation(languageCode: $languageCode) { 6 | title 7 | content 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/gql/query/product.graphql: -------------------------------------------------------------------------------- 1 | query Product( 2 | $channel: String = "default-channel" 3 | $slug: String! 4 | $languageCode: LanguageCodeEnum = EN_US 5 | ) { 6 | product(channel: $channel, slug: $slug) { 7 | ...ProductDetailsFragment 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/gql/fragments/pricing.graphql: -------------------------------------------------------------------------------- 1 | fragment PricingFragment on ProductPricingInfo { 2 | onSale 3 | discount { 4 | ...TaxedMoneyFragment 5 | } 6 | displayGrossPrices 7 | priceRange { 8 | start { 9 | ...TaxedMoneyFragment 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/gql/mutation/tokenCreate.graphql: -------------------------------------------------------------------------------- 1 | mutation tokenCreate($email: String!, $password: String!) { 2 | tokenCreate(email: $email, password: $password) { 3 | token 4 | refreshToken 5 | errors { 6 | field 7 | message 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/translations/addToCart.ts: -------------------------------------------------------------------------------- 1 | export const addToCartTranslationKeys = [ 2 | "add to cart", 3 | "add to cart confirmation", 4 | "checkout", 5 | "continue shopping", 6 | ] as const; 7 | 8 | export type AddToCartTranslations = (typeof addToCartTranslationKeys)[number]; 9 | -------------------------------------------------------------------------------- /src/channel-config.ts: -------------------------------------------------------------------------------- 1 | export const channelConfig = { 2 | defaultChannel: "default-channel", 3 | list: ["default-channel", "mx", "channel-pln"], 4 | } as const; 5 | 6 | export type ChannelConfig = typeof channelConfig; 7 | 8 | export type Channel = ChannelConfig["list"][number]; 9 | -------------------------------------------------------------------------------- /src/translation-dictionaries.ts: -------------------------------------------------------------------------------- 1 | import { localeConfig } from "./locale-config";; 2 | 3 | export const dictionaries = Object.fromEntries( 4 | Object.keys(localeConfig.locales).map( 5 | (key) => [key, async () => (await import(`@/dictionaries/${key}.json`)).default])); 6 | -------------------------------------------------------------------------------- /src/app/translations/searchFilter.ts: -------------------------------------------------------------------------------- 1 | export const searchFilterTranslationKeys = [ 2 | "price range", 3 | "is available", 4 | "search", 5 | "filter", 6 | "apply", 7 | ] as const; 8 | 9 | export type SearchFilterTranslationKeys = typeof searchFilterTranslationKeys[number]; -------------------------------------------------------------------------------- /src/core/server/useTranslations.ts: -------------------------------------------------------------------------------- 1 | import { useHook } from "./useHook"; 2 | import { getTranslations } from "./getTranslations"; 3 | import type { Locale } from "@/locale-config"; 4 | 5 | export const useTranslations = (locale?: Locale) => { 6 | return useHook("useTranslations", getTranslations(locale)); 7 | }; 8 | -------------------------------------------------------------------------------- /src/core/server/getMenuTranslation.ts: -------------------------------------------------------------------------------- 1 | import { cache } from "react"; 2 | import { IMenuItemFragment } from "@/gql/sdk"; 3 | 4 | export const getMenuTranslation = cache( 5 | (item: T) => { 6 | return { 7 | name: item.translation?.name ?? item.category?.name, 8 | }; 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /src/core/server/parseJSONText.ts: -------------------------------------------------------------------------------- 1 | import { EditorJSObject, JSONString } from "@/app/types"; 2 | 3 | export function parseJSONText( 4 | data: JSONString | null | undefined 5 | ): EditorJSObject | null { 6 | try { 7 | if (data) { 8 | return JSON.parse(data); 9 | } 10 | } catch {} 11 | return null; 12 | } 13 | -------------------------------------------------------------------------------- /src/gql/mutation/accountRegister.graphql: -------------------------------------------------------------------------------- 1 | mutation accountRegister($input: AccountRegisterInput!) { 2 | accountRegister(input: $input) { 3 | accountErrors { 4 | field 5 | message 6 | } 7 | user { 8 | id 9 | email 10 | firstName 11 | lastName 12 | isActive 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/requiredLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface RequiredLabelProps { 4 | label: string; 5 | } 6 | 7 | const RequiredLabel = ({ label }: RequiredLabelProps) => ( 8 | 11 | ); 12 | 13 | export default RequiredLabel; 14 | -------------------------------------------------------------------------------- /src/gql/fragments/menuItem.graphql: -------------------------------------------------------------------------------- 1 | fragment MenuItemFragment on MenuItem { 2 | level 3 | name 4 | url 5 | page { 6 | slug 7 | } 8 | category { 9 | id 10 | name 11 | } 12 | children { 13 | page { 14 | slug 15 | } 16 | } 17 | translation(languageCode: $languageCode) { 18 | name 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/[locale]/cart/components/CartHeadline.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CartHeadline = () => { 4 | return ( 5 |

6 | Your Items 7 |

8 | ); 9 | }; 10 | 11 | export default CartHeadline; -------------------------------------------------------------------------------- /src/gql/fragments/productAttributes.graphql: -------------------------------------------------------------------------------- 1 | fragment ProductDetailsAttributeFragment on SelectedAttribute { 2 | attribute { 3 | id 4 | name 5 | translation(languageCode: $languageCode) { 6 | name 7 | } 8 | } 9 | values { 10 | id 11 | name 12 | translation(languageCode: $languageCode) { 13 | name 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/types.d.ts: -------------------------------------------------------------------------------- 1 | type JSONString = string; 2 | 3 | interface EditorJSBlock { 4 | id: string; 5 | data: { 6 | text: string; 7 | }; 8 | type: "paragraph"; 9 | } 10 | 11 | interface EditorJSObject { 12 | time: number; 13 | blocks: EditorJSBlock[]; 14 | } 15 | 16 | export type MenuItem = NonNullable< 17 | import("@/gql/sdk").IMenuFragment["items"] 18 | >[number]; 19 | -------------------------------------------------------------------------------- /src/locale-config.ts: -------------------------------------------------------------------------------- 1 | export const localeConfig = { 2 | defaultLocale: "en-us", 3 | locales: { 4 | "en-us": "English 🇺🇸", 5 | "es-mx": "Español 🇲🇽", 6 | "fr-fr": "Français 🇫🇷", 7 | "it-it": "Italiano 🇮🇹", 8 | }, 9 | } as const; 10 | 11 | export type LocaleConfig = typeof localeConfig; 12 | export type Locale = keyof (typeof localeConfig)["locales"]; 13 | -------------------------------------------------------------------------------- /src/core/server/useHook.ts: -------------------------------------------------------------------------------- 1 | import { use } from "react"; 2 | 3 | export const useHook = (hookName: string, promise: Promise) => { 4 | try { 5 | return use(promise); 6 | } catch (error) { 7 | if (error instanceof TypeError) { 8 | throw new Error(`${hookName} is not callable within an async component`) 9 | } 10 | throw error; 11 | } 12 | } -------------------------------------------------------------------------------- /src/app/daisyui/drawer/drawerSidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ReactNode } from "react"; 4 | import { useDrawer } from "."; 5 | 6 | export const DrawerSidebar = ({ children }: { children: ReactNode }) => { 7 | const { sidebarOpen } = useDrawer(); 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/core/client/isLocalUrl.ts: -------------------------------------------------------------------------------- 1 | import NextLink from 'next/link'; 2 | import type { ComponentProps } from 'react'; 3 | 4 | type Url = ComponentProps['href']; 5 | 6 | export const isLocalUrl = (url: Url) => { 7 | if (typeof url === 'object') { 8 | return url.host == null && url.hostname == null; 9 | } 10 | const hasProtocol = /^[a-z]+:/i.test(url); 11 | return !hasProtocol; 12 | } -------------------------------------------------------------------------------- /src/gql/mutation/checkoutCreate.graphql: -------------------------------------------------------------------------------- 1 | mutation checkoutCreate($channel: String = "default-channel", $variantID: ID!, $quantity: Int!) { 2 | checkoutCreate( 3 | input: { 4 | channel: $channel 5 | lines: [{ variantId: $variantID, quantity: $quantity }] 6 | } 7 | ) { 8 | errors { 9 | message 10 | } 11 | checkout { 12 | id 13 | quantity 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/daisyui/types.d.ts: -------------------------------------------------------------------------------- 1 | type BoolOrSize = boolean | "sm" | "md" | "lg" | "xl"; 2 | 3 | type Size = "xs" | "sm" | "md" | "lg"; 4 | 5 | type BGBlendMode = 6 | | "normal" 7 | | "multiply" 8 | | "screen" 9 | | "overlay" 10 | | "darken" 11 | | "lighten"; 12 | 13 | type Variant = 14 | | "primary" 15 | | "secondary" 16 | | "accent" 17 | | "info" 18 | | "success" 19 | | "warning" 20 | | "error"; 21 | -------------------------------------------------------------------------------- /src/app/daisyui/drawer/drawerToggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useDrawer } from "."; 4 | 5 | export const DrawerToggle = () => { 6 | const { setSidebarOpen, sidebarOpen } = useDrawer(); 7 | return ( 8 | setSidebarOpen(!sidebarOpen)} 13 | checked={sidebarOpen} 14 | /> 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/(header)/navItemMobile.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useDrawer } from "@/app/daisyui/drawer"; 4 | import { PropsWithChildren } from "react"; 5 | 6 | export const NavItemMobile = ({ children }: PropsWithChildren) => { 7 | const { setSidebarOpen } = useDrawer(); 8 | return ( 9 |
  • setSidebarOpen(false)}> 10 | {children} 11 |
  • 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Community Help 4 | url: https://discord.com/channels/1087268228943855658/1129655542416146522 5 | about: Ask and answer questions here! 6 | - name: Discussion 7 | url: https://discord.com/channels/1087268228943855658/1123018420158480435 8 | about: Provide feedback, talk about new feature ideas, and interact with community members doing similar things 9 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/productReviewSummary.tsx: -------------------------------------------------------------------------------- 1 | import { productDetailsTranslationKeys } from "@/app/translations/productDetails"; 2 | import { useTranslationValues } from "@/core/server/useTranslationValues"; 3 | 4 | export const ProductReviewSummary = ({ rating }: { rating: number }) => { 5 | const t = useTranslationValues(productDetailsTranslationKeys, "component"); 6 | return ( 7 |
    8 | {t["rating"]} {rating} 9 | /5 10 |
    11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/app/daisyui/menu-title/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface MenuTitleProps { 5 | className?: string; 6 | } 7 | 8 | const MenuTitle = React.forwardRef< 9 | HTMLLIElement, 10 | PropsWithChildren 11 | >(function MenuTitle({ children, className }, ref) { 12 | const classNames = clsx("menu-title", className); 13 | 14 | return ( 15 |
  • 16 | {children} 17 |
  • 18 | ); 19 | }); 20 | 21 | export default MenuTitle; 22 | -------------------------------------------------------------------------------- /src/gql/mutation/checkoutLinesAdd.graphql: -------------------------------------------------------------------------------- 1 | mutation checkoutLinesAdd($variantID: ID!, $checkoutID: ID!, $quantity: Int!) { 2 | checkoutLinesAdd( 3 | id: $checkoutID 4 | lines: [{ variantId: $variantID, quantity: $quantity }] 5 | ) { 6 | checkout { 7 | id 8 | lines { 9 | id 10 | variant { 11 | name 12 | } 13 | quantity 14 | } 15 | totalPrice { 16 | gross { 17 | currency 18 | amount 19 | } 20 | } 21 | quantity 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/daisyui/card-title/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, forwardRef } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface CardTitleProps { 5 | className?: string; 6 | } 7 | 8 | const CardTitle = forwardRef< 9 | HTMLHeadingElement, 10 | PropsWithChildren 11 | >(function CardTitle({ children, className }, ref) { 12 | const classNames = clsx("card-title", className); 13 | 14 | return ( 15 |

    16 | {children} 17 |

    18 | ); 19 | }); 20 | 21 | export default CardTitle; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | yarn.lock 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /src/core/server/renderJSONText.tsx: -------------------------------------------------------------------------------- 1 | import { EditorJSBlock, EditorJSObject } from "@/app/types"; 2 | 3 | function renderEditorJSBlock(block: EditorJSBlock) { 4 | switch (block.type) { 5 | case "paragraph": 6 | return ( 7 |

    11 | ); 12 | default: 13 | return null; 14 | } 15 | } 16 | 17 | export function renderEditorJsObject(editorJSObj: EditorJSObject) { 18 | return editorJSObj.blocks.map(renderEditorJSBlock); 19 | } 20 | -------------------------------------------------------------------------------- /src/gql/fragments/productVariant.graphql: -------------------------------------------------------------------------------- 1 | fragment ProductVariantFragment on ProductVariant { 2 | id 3 | name 4 | quantityAvailable 5 | weight { 6 | unit 7 | value 8 | } 9 | media { 10 | url 11 | alt 12 | type 13 | } 14 | attributes { 15 | attribute { 16 | id 17 | name 18 | translation(languageCode: $languageCode) { 19 | name 20 | } 21 | } 22 | values { 23 | id 24 | name 25 | translation(languageCode: $languageCode) { 26 | name 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/daisyui/breadcrumbs/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface BreadcrumbsProps { 5 | className?: string; 6 | } 7 | 8 | const Breadcrumbs = React.forwardRef< 9 | HTMLDivElement, 10 | PropsWithChildren 11 | >(function Breadcrumbs({ children, className }, ref) { 12 | const classNames = clsx("breadcrumbs", className); 13 | 14 | return ( 15 |

    16 | {children} 17 |
    18 | ); 19 | }); 20 | 21 | export default Breadcrumbs; 22 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/(header)/drawerIcon.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FC } from "react"; 4 | import Image from "next/image"; 5 | import { useDrawer } from "../../../daisyui/drawer"; 6 | import hamburger from "./hamburger.png"; 7 | 8 | export const DrawerIcon: FC = () => { 9 | const { sidebarOpen } = useDrawer(); 10 | return ( 11 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/[locale]/cart/components/ProductImage.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image, { StaticImageData } from "next/image"; 3 | 4 | interface ProductImageProps { 5 | productImage: StaticImageData; 6 | } 7 | 8 | const ProductImage: React.FC = ({ productImage }) => { 9 | return ( 10 |
    11 |
    12 | Product Image 13 |
    14 |
    15 | ); 16 | }; 17 | 18 | export default ProductImage; -------------------------------------------------------------------------------- /src/app/daisyui/select/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, forwardRef, SelectHTMLAttributes } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface SelectProps extends SelectHTMLAttributes{ 5 | className?: string; 6 | } 7 | 8 | const Select = forwardRef>( 9 | function Select({ children, className, ...props }, ref) { 10 | const classNames = clsx("select", className); 11 | return ( 12 | 15 | ); 16 | } 17 | ); 18 | 19 | export default Select; 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 2 | title: "Bug Name" 3 | description: "Unintentional behavior affecting the template or development process" 4 | labels: bug 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: "Provide a brief overview of the epic and its goals. Include any relevant background information or context." 11 | validations: 12 | required: true 13 | - id: stepsToReproduce 14 | type: textarea 15 | attributes: 16 | label: Steps to Reproduce 17 | placeholder: | 18 | 1. 19 | 2. 20 | 3. 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy_website 2 | on: 3 | push: 4 | branches: main 5 | pull_request: 6 | branches: main 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: amondnet/vercel-action@v20 #deploy 13 | with: 14 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required 15 | vercel-org-id: ${{ vars.VERCEL_ORG_ID}} #Required 16 | vercel-args: ${{ github.ref_name == 'main' && '--prod' || '--debug'}} 17 | vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}} #Required 18 | vercel-project-name: saleor-storefront-starter 19 | -------------------------------------------------------------------------------- /src/app/daisyui/drawer/drawerContent.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import clsx from "clsx"; 4 | import { ReactNode } from "react"; 5 | import { useDrawer } from "."; 6 | 7 | export const DrawerContent = ({ children }: { children: ReactNode }) => { 8 | const { sidebarOpen } = useDrawer(); 9 | return ( 10 |
    20 | {children} 21 |
    22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/gql/fragments/galleryProduct.graphql: -------------------------------------------------------------------------------- 1 | fragment GalleryProductFragment on Product { 2 | id 3 | slug 4 | name 5 | translation(languageCode: $languageCode) { 6 | name 7 | description 8 | } 9 | 10 | thumbnail(size: $thumbnailSize) { 11 | url 12 | alt 13 | } 14 | rating 15 | category { 16 | id 17 | name 18 | translation(languageCode: $languageCode) { 19 | name 20 | } 21 | } 22 | defaultVariant { 23 | id 24 | } 25 | variants { 26 | id 27 | name 28 | translation(languageCode: $languageCode) { 29 | id 30 | name 31 | } 32 | } 33 | pricing { 34 | ...PricingFragment 35 | } 36 | } -------------------------------------------------------------------------------- /src/app/daisyui/menu/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface MenuProps { 5 | className?: string; 6 | horizontal?: boolean; 7 | } 8 | 9 | const Menu = React.forwardRef>( 10 | function Menu({ children, className, horizontal }, ref) { 11 | const classNames = clsx( 12 | "menu", 13 | { 14 | "menu-horizontal": horizontal, 15 | }, 16 | className 17 | ); 18 | 19 | return ( 20 |
      21 | {children} 22 |
    23 | ); 24 | } 25 | ); 26 | 27 | export default Menu; 28 | -------------------------------------------------------------------------------- /src/gql/query/products.graphql: -------------------------------------------------------------------------------- 1 | query Products( 2 | $channel: String = "default-channel" 3 | $languageCode: LanguageCodeEnum = EN_US 4 | $thumbnailSize: Int = 300 5 | $first: Int 6 | $last: Int 7 | $after: String 8 | $before: String 9 | $filter: ProductFilterInput 10 | ) { 11 | products( 12 | channel: $channel 13 | first: $first 14 | last: $last 15 | after: $after 16 | before: $before 17 | filter: $filter 18 | ) { 19 | pageInfo { 20 | endCursor 21 | startCursor 22 | hasNextPage 23 | hasPreviousPage 24 | } 25 | edges { 26 | node { 27 | ...GalleryProductFragment 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /public/placeholder.svg: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/app/daisyui/card-body/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface CardBodyProps { 5 | centerItems?: boolean; 6 | className?: string; 7 | } 8 | 9 | const CardBody = React.forwardRef< 10 | HTMLDivElement, 11 | PropsWithChildren 12 | >(function CardBody({ centerItems, children, className }, ref) { 13 | const classNames = clsx( 14 | "card-body", 15 | { 16 | "items-center": centerItems, 17 | }, 18 | className 19 | ); 20 | 21 | return ( 22 |
    23 | {children} 24 |
    25 | ); 26 | }); 27 | 28 | export default CardBody; 29 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/productImageGridSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | /** 4 | * Considerations 5 | * 6 | * - What if less than 4 (or 6) images? 7 | * - What if no images (less important / likely)? 8 | */ 9 | 10 | const SingleImageSkeleton: FC = () => { 11 | return ( 12 |
    13 |
    14 |
    15 | ); 16 | }; 17 | 18 | export const ProductImageGridSkeleton: FC = () => { 19 | return ( 20 |
    21 | 22 |
    23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/app/daisyui/drawer/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import clsx from "clsx"; 4 | import { createContext, ReactNode, useContext, useState } from "react"; 5 | 6 | export const DrawerContext = createContext({ 7 | sidebarOpen: false, 8 | setSidebarOpen: (sidebarOpen: boolean) => {}, 9 | }); 10 | 11 | export const DrawerContainer = ({ children }: { children: ReactNode }) => { 12 | const [sidebarOpen, setSidebarOpen] = useState(false); 13 | 14 | return ( 15 | 16 |
    {children}
    17 |
    18 | ); 19 | }; 20 | 21 | export const useDrawer = () => { 22 | return useContext(DrawerContext); 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/daisyui/text-input/index.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, InputHTMLAttributes } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface TextInputProps extends InputHTMLAttributes { 5 | bordered?: boolean; 6 | ghost?: boolean; 7 | } 8 | 9 | const TextInput = forwardRef( 10 | function TextInput({ bordered, ghost, className, ...inputProps }, ref) { 11 | const classNames = clsx( 12 | "input", 13 | { 14 | "input-bordered": bordered, 15 | "input-ghost": ghost, 16 | }, 17 | className 18 | ); 19 | 20 | return ( 21 | 22 | ); 23 | } 24 | ); 25 | 26 | export default TextInput; 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/app/[locale]/[channel]/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import { withTranslations } from "@/core/server/locale"; 2 | import { useTranslations } from "@/core/server/useTranslations"; 3 | import type { Locale } from "@/locale-config"; 4 | 5 | interface HomePageProps { 6 | params: { 7 | locale: Locale; 8 | channel: string; 9 | }; 10 | } 11 | 12 | export default withTranslations(function HomePage() { 13 | const t = useTranslations(); 14 | 15 | return ( 16 |
    17 |

    18 | {t("Home.welcome")} 19 |

    20 |
    21 | ); 22 | }); 23 | -------------------------------------------------------------------------------- /src/app/daisyui/button/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface ButtonProps { 5 | variant?: "primary" | "secondary" | "neutral"; 6 | glass?: boolean; 7 | disabled?: boolean; 8 | } 9 | 10 | const Button = React.forwardRef< 11 | HTMLButtonElement, 12 | PropsWithChildren 13 | >(function Button({ children, variant, glass, disabled }, ref) { 14 | const classNames = clsx("btn", { 15 | glass: glass === true, 16 | "btn-secondary": variant === "secondary", 17 | "btn-primary": variant === "primary", 18 | "w-full": true, 19 | }); 20 | 21 | return ( 22 | 25 | ); 26 | }); 27 | 28 | export default Button; 29 | -------------------------------------------------------------------------------- /src/gql/fragments/pdproduct.graphql: -------------------------------------------------------------------------------- 1 | fragment PDProductFragment on Product { 2 | id 3 | seoTitle 4 | name 5 | description 6 | slug 7 | rating 8 | isAvailable 9 | 10 | translation(languageCode: $languageCode) { 11 | name 12 | description 13 | } 14 | category { 15 | id 16 | name 17 | translation(languageCode: $languageCode) { 18 | name 19 | } 20 | } 21 | thumbnail(size: 1200) { 22 | alt 23 | url 24 | } 25 | productType { 26 | name 27 | slug 28 | } 29 | weight { 30 | unit 31 | value 32 | } 33 | pricing { 34 | ...PricingFragment 35 | } 36 | variants { 37 | id 38 | name 39 | weight { 40 | unit 41 | value 42 | } 43 | media { 44 | url 45 | alt 46 | type 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/app/daisyui/card-actions/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | 4 | interface CardActionsProps { 5 | justify?: "end" | "start" | "center"; 6 | className?: string; 7 | } 8 | 9 | const CardActions = React.forwardRef< 10 | HTMLDivElement, 11 | PropsWithChildren 12 | >(function CardActions({ children, justify, className }, ref) { 13 | const classNames = clsx( 14 | "card-actions", 15 | { 16 | "justify-end": justify === "end", 17 | "justify-center": justify === "center", 18 | "justify-start": justify === "start", 19 | }, 20 | className 21 | ); 22 | 23 | return ( 24 |
    25 | {children} 26 |
    27 | ); 28 | }); 29 | 30 | export default CardActions; 31 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/productCardSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import Badge from "@/app/daisyui/badge"; 2 | import type { FC } from "react"; 3 | 4 | export const ProductCardSkeleton: FC = () => { 5 | return ( 6 |
    7 |
    8 |
    9 | 10 | 11 |
    12 |
    13 | 14 |
    15 |
    16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/core/client/useProductTranslation.tsx: -------------------------------------------------------------------------------- 1 | import { IGalleryProductFragment, IProduct } from "@/gql/sdk"; 2 | import { useMemo } from "react"; 3 | 4 | type Translatable = Pick< 5 | IGalleryProductFragment, 6 | "name" | "translation" | "category" | "variants" 7 | > & 8 | Pick; 9 | 10 | export function useProductTranslation(product: Translatable) { 11 | return useMemo(() => { 12 | const { name, description, translation, category, variants } = product; 13 | return Object.assign( 14 | {}, 15 | translation ?? { name, description }, 16 | { 17 | category: category?.translation ?? category, 18 | }, 19 | { variants: variants?.map((v) => v.translation ?? v) } 20 | ); 21 | }, [product]); 22 | } 23 | 24 | 25 | export type ProductTranslation = ReturnType; -------------------------------------------------------------------------------- /src/app/[locale]/cart/components/QuantityCounter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | const QuanitityCounter: React.FC = () => { 4 | const [quantity, setQuantity] = useState(1); 5 | 6 | const DecreaseQuantity = () => { 7 | if (quantity > 0) { 8 | setQuantity(quantity - 1); 9 | } 10 | }; 11 | 12 | const IncreaseQuantity = () => { 13 | setQuantity(quantity + 1); 14 | }; 15 | 16 | return ( 17 |
    18 | 21 | {quantity} 22 | 25 |
    26 | ); 27 | }; 28 | 29 | export default QuanitityCounter; -------------------------------------------------------------------------------- /src/app/daisyui/badge/index.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react"; 2 | import clsx from "clsx"; 3 | import type { HTMLAttributes, PropsWithChildren } from "react"; 4 | 5 | export interface BadgeProps extends HTMLAttributes { 6 | ghost?: boolean; 7 | outline?: boolean; 8 | className?: string; 9 | } 10 | 11 | const Badge = forwardRef>( 12 | function Badge({ ghost, outline, className, children, ...divProps }, ref) { 13 | const classNames = clsx( 14 | "badge", 15 | { 16 | "badge-outline": outline, 17 | "badge-ghost": ghost, 18 | }, 19 | className 20 | ); 21 | 22 | return ( 23 |
    24 | {children} 25 |
    26 | ); 27 | } 28 | ); 29 | 30 | export default Badge; 31 | -------------------------------------------------------------------------------- /src/core/server/getTranslations.ts: -------------------------------------------------------------------------------- 1 | import { cache } from "react"; 2 | import { dictionaries } from "@/translation-dictionaries"; 3 | import { getLocaleContext } from "./locale"; 4 | import type { Locale } from "@/locale-config"; 5 | 6 | export const getTranslations = cache( 7 | async (locale: Locale = getLocaleContext().get("locale")) => { 8 | const translations = await dictionaries[locale]?.(); 9 | return (path: string) => { 10 | const pathParts = path.split("."); 11 | let prop = pathParts.pop(); 12 | let namespace = translations; 13 | const error = `(${path})`; 14 | 15 | for (let part of pathParts) { 16 | namespace = namespace?.[part]; 17 | if (namespace !== undefined) continue; 18 | return error; 19 | } 20 | return namespace[prop as string] ?? error; 21 | }; 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/linkButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | import clsx from "clsx"; 3 | import { Link } from "./link"; 4 | import type { LinkProps } from "./link"; 5 | 6 | interface LinkButtonProps extends LinkProps { 7 | disabled?: boolean; 8 | } 9 | 10 | export const LinkButton: FC> = ({ 11 | disabled, 12 | children, 13 | className="", 14 | ...props 15 | }) => { 16 | const classNames = clsx("text-base-content/50", "flex", "items-center", "uppercase", { 17 | btn: !disabled, 18 | "btn-outline": !disabled, 19 | "cursor-not-allowed": disabled, 20 | }, {[className]: !disabled}); 21 | 22 | const btn = {children}; 23 | 24 | if (!disabled) { 25 | return {btn}; 26 | } 27 | 28 | return <>{btn}; 29 | }; 30 | -------------------------------------------------------------------------------- /src/app/[locale]/cart/trashcan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/navbarMenu.tsx: -------------------------------------------------------------------------------- 1 | import Menu from "@/app/daisyui/menu"; 2 | import MenuTitle from "@/app/daisyui/menu-title"; 3 | import { useTranslations } from "@/core/server/useTranslations"; 4 | import { NavbarMenuCategory } from "./navbarMenuCategories"; 5 | import { IMenuFragment } from "@/gql/sdk"; 6 | import type { FC } from "react"; 7 | 8 | interface NavbarMenuProps { 9 | menu?: IMenuFragment | null; 10 | } 11 | 12 | export const NavbarMenu: FC = ({ menu }) => { 13 | const t = useTranslations(); 14 | return ( 15 | 16 | {t("menu.categories")} 17 | {menu?.items 18 | ? menu.items.map((item) => ( 19 | 20 | )) 21 | : null} 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/core/server/useTranslationValues.ts: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "./useTranslations"; 2 | import type { FC } from "react"; 3 | 4 | type TranslationsDictionary = { [k in K]: string }; 5 | 6 | interface TranslationsProp { 7 | t: TranslationsDictionary; 8 | } 9 | /** 10 | * Translations Function Component 11 | */ 12 | export type TFC = FC

    >; 13 | 14 | export const useTranslationValues = ( 15 | translationKeys: readonly Keys[], 16 | namespace?: string, 17 | ) => { 18 | const t = useTranslations(); 19 | const keys = namespace 20 | ? translationKeys.map((k) => `${namespace}.${k}`) 21 | : translationKeys; 22 | return Object.fromEntries( 23 | keys.map((k, idx) => [translationKeys[idx], t(k)]) 24 | ) as { [k in (typeof translationKeys)[number]]: string }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/logo.tsx: -------------------------------------------------------------------------------- 1 | export const LogoSVG = () => { 2 | return ( 3 | 11 | Liminal Labs Logo 12 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/[locale]/(components)/root.tsx: -------------------------------------------------------------------------------- 1 | import { DrawerContainer } from "@/app/daisyui/drawer"; 2 | import { DrawerContent } from "@/app/daisyui/drawer/drawerContent"; 3 | import { DrawerSidebar } from "@/app/daisyui/drawer/drawerSidebar"; 4 | import type { PropsWithChildren, ReactNode } from "react"; 5 | import { PrimaryNav } from "./(header)/primaryNav"; 6 | import { MobilePrimaryNav } from "./(header)/navMobile"; 7 | import Footer from "./footer"; 8 | import { DrawerToggle } from "@/app/daisyui/drawer/drawerToggle"; 9 | 10 | export default function AppRoot({ children }: PropsWithChildren) { 11 | return ( 12 | 13 | 14 | 15 | 16 | {children} 17 |