├── .npmrc ├── src ├── stores │ ├── Currency │ │ ├── types.ts │ │ ├── index.tsx │ │ └── CurrencySelector │ │ │ └── index.tsx │ ├── WishlistStore │ │ └── types.ts │ ├── CartStore │ │ └── types.ts │ ├── CartStateStore │ │ └── index.tsx │ └── WishListStateStore │ │ └── index.tsx ├── access │ ├── anyone.ts │ ├── authenticated.ts │ └── authenticatedOrPublished.ts ├── app │ ├── favicon.ico │ ├── icon1.png │ ├── apple-icon.png │ ├── (frontend) │ │ ├── [locale] │ │ │ ├── (with-cart) │ │ │ │ ├── page.tsx │ │ │ │ ├── account │ │ │ │ │ ├── orders │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── help │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── settings │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── orders-data │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── layout.tsx │ │ │ │ ├── [slug] │ │ │ │ │ └── page.client.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── product │ │ │ │ │ └── [slug] │ │ │ │ │ └── page.tsx │ │ │ ├── (without-cart) │ │ │ │ ├── admin │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── checkout │ │ │ │ │ └── page.tsx │ │ │ │ ├── posts │ │ │ │ │ ├── page.client.tsx │ │ │ │ │ ├── [slug] │ │ │ │ │ │ └── page.client.tsx │ │ │ │ │ └── page │ │ │ │ │ │ └── [pageNumber] │ │ │ │ │ │ └── page.client.tsx │ │ │ │ ├── postSearch │ │ │ │ │ └── page.client.tsx │ │ │ │ ├── register │ │ │ │ │ └── page.tsx │ │ │ │ └── login │ │ │ │ │ └── page.tsx │ │ │ ├── not-found.tsx │ │ │ └── reset-password │ │ │ │ └── page.tsx │ │ ├── next │ │ │ ├── exit-preview │ │ │ │ ├── GET.ts │ │ │ │ └── route.ts │ │ │ ├── verify-email │ │ │ │ └── route.ts │ │ │ ├── seed │ │ │ │ └── route.ts │ │ │ └── wishListProducts │ │ │ │ └── route.ts │ │ ├── not-found.tsx │ │ └── (sitemaps) │ │ │ └── posts-sitemap.xml │ │ │ └── route.ts │ ├── (payload) │ │ ├── api │ │ │ ├── graphql-playground │ │ │ │ └── route.ts │ │ │ ├── graphql │ │ │ │ └── route.ts │ │ │ └── [...slug] │ │ │ │ └── route.ts │ │ ├── admin │ │ │ └── [[...segments]] │ │ │ │ ├── page.tsx │ │ │ │ └── not-found.tsx │ │ └── layout.tsx │ └── manifest.json ├── blocks │ ├── Form │ │ ├── Error │ │ │ └── index.tsx │ │ ├── Width │ │ │ └── index.tsx │ │ ├── Message │ │ │ └── index.tsx │ │ ├── fields.tsx │ │ ├── config.ts │ │ ├── Number │ │ │ └── index.tsx │ │ ├── Text │ │ │ └── index.tsx │ │ ├── Email │ │ │ └── index.tsx │ │ ├── Textarea │ │ │ └── index.tsx │ │ ├── buildInitialFormState.tsx │ │ └── Checkbox │ │ │ └── index.tsx │ ├── MediaBlock │ │ └── config.ts │ ├── Code │ │ ├── Component.tsx │ │ ├── config.ts │ │ ├── CopyButton.tsx │ │ └── Component.client.tsx │ ├── CallToAction │ │ ├── config.ts │ │ └── Component.tsx │ ├── Banner │ │ ├── Component.tsx │ │ └── config.ts │ ├── RelatedPosts │ │ └── Component.tsx │ ├── globals.ts │ └── Accordion │ │ ├── config.ts │ │ └── Component.tsx ├── components │ ├── AdminBar │ │ └── index.scss │ ├── AdminAvatar │ │ └── index.tsx │ ├── BeforeDashboard │ │ ├── SeedButton │ │ │ └── index.scss │ │ └── index.scss │ ├── (ecommerce) │ │ ├── RowLabels │ │ │ ├── DeliveryZonesRowLabel │ │ │ │ └── index.tsx │ │ │ ├── PriceRowLabel │ │ │ │ └── index.tsx │ │ │ ├── WeightRangeRowLabel │ │ │ │ └── index.tsx │ │ │ └── OrderProductsRowLabel │ │ │ │ └── index.tsx │ │ ├── Cart │ │ │ └── SynchronizeCart │ │ │ │ └── index.tsx │ │ ├── AdminDashboard │ │ │ └── components │ │ │ │ ├── views │ │ │ │ └── index.tsx │ │ │ │ └── OverviewCard │ │ │ │ └── index.tsx │ │ ├── PriceClient │ │ │ └── index.tsx │ │ ├── LogoutButton │ │ │ └── index.tsx │ │ ├── AdminDashboardNavLink │ │ │ └── index.tsx │ │ ├── CurrencySelect │ │ │ └── index.tsx │ │ ├── RegisterPage │ │ │ └── WithoutOAuth │ │ │ │ └── index.tsx │ │ └── ListingBreadcrumbs │ │ │ └── index.tsx │ ├── LivePreviewListener │ │ └── index.tsx │ ├── AdminNavbar │ │ ├── NavHamburger │ │ │ └── index.tsx │ │ ├── NavWrapper │ │ │ └── index.tsx │ │ └── getNavPrefs.ts │ ├── ui │ │ ├── AdminInput.tsx │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── checkbox.tsx │ │ ├── popover.tsx │ │ └── radio-group.tsx │ ├── AdminColorPicker │ │ └── index.tsx │ ├── heros │ │ ├── LowImpact │ │ │ └── index.tsx │ │ ├── RenderHero.tsx │ │ ├── MediumImpact │ │ │ └── index.tsx │ │ └── HighImpact │ │ │ └── index.tsx │ ├── Emails │ │ ├── OrderStatusEmail │ │ │ └── index.tsx │ │ ├── WelcomeEmail │ │ │ └── index.tsx │ │ ├── VerifyAccountEmail │ │ │ └── index.tsx │ │ └── ResetPasswordEmail │ │ │ └── index.tsx │ ├── Media │ │ ├── index.tsx │ │ ├── types.ts │ │ └── VideoMedia │ │ │ └── index.tsx │ ├── Logo │ │ └── Logo.tsx │ ├── LocaleSwitch │ │ └── LocaleSwitch.tsx │ ├── CollectionArchive │ │ └── index.tsx │ ├── search │ │ ├── Component.tsx │ │ ├── fieldOverrides.ts │ │ └── beforeSync.ts │ ├── AdminResetPassword │ │ └── index.tsx │ └── PageRange │ │ └── index.tsx ├── utilities │ ├── canUseDOM.ts │ ├── toKebabCase.ts │ ├── cn.ts │ ├── useDebounce.ts │ ├── formatPrices.ts │ ├── mergeOpenGraph.ts │ ├── getRedirects.ts │ ├── formatDateTime.ts │ ├── getURL.ts │ ├── getDocument.ts │ ├── deepMerge.ts │ ├── formatAuthors.ts │ ├── getOrderProducts.ts │ ├── getGlobals.ts │ ├── getCustomer.ts │ ├── generatePreviewPath.ts │ ├── getMeUser.ts │ ├── generateMeta.ts │ ├── nodemailer.ts │ └── getPriceRange.ts ├── i18n │ ├── config.ts │ ├── routing.ts │ └── request.ts ├── providers │ ├── Theme │ │ ├── ThemeSelector │ │ │ ├── types.ts │ │ │ └── index.tsx │ │ ├── types.ts │ │ ├── shared.ts │ │ └── InitTheme │ │ │ └── index.tsx │ ├── index.tsx │ └── HeaderTheme │ │ └── index.tsx ├── fields │ ├── slug │ │ ├── index.scss │ │ └── formatSlug.ts │ ├── currencyField.ts │ ├── backgroundPicker.ts │ ├── countryPickerField.ts │ ├── alignmentField.ts │ ├── courierSettingsFields.ts │ ├── linkGroup.ts │ ├── freeShippingField.ts │ ├── defaultLexical.ts │ ├── noBlocksLexical.ts │ └── courierFields.ts ├── cssVariables.ts ├── collections │ ├── (ecommerce) │ │ ├── Products │ │ │ └── components │ │ │ │ └── RowLabels │ │ │ │ ├── DetailLabel │ │ │ │ └── index.tsx │ │ │ │ ├── OptionLabel │ │ │ │ └── index.tsx │ │ │ │ └── VariantLabel │ │ │ │ └── index.tsx │ │ ├── Orders │ │ │ ├── components │ │ │ │ ├── couriers │ │ │ │ │ └── CourierShipmentMenu.tsx │ │ │ │ ├── inpost-pickup │ │ │ │ │ └── PickupShipmentMenu.tsx │ │ │ │ ├── VariantSelect │ │ │ │ │ └── index.tsx │ │ │ │ ├── OrderTotalPriceField │ │ │ │ │ └── index.tsx │ │ │ │ ├── OrderTotalWithShippingField │ │ │ │ │ └── index.tsx │ │ │ │ ├── ProductTotalPriceField │ │ │ │ │ └── index.tsx │ │ │ │ └── ProductNameField │ │ │ │ │ └── index.tsx │ │ │ ├── hooks │ │ │ │ ├── generateID.ts │ │ │ │ └── sendStatusEmail.ts │ │ │ └── utils │ │ │ │ └── getShippingLabel.ts │ │ ├── Customers │ │ │ ├── ui │ │ │ │ └── RowLabels │ │ │ │ │ └── ShippingAddressRowLabel │ │ │ │ │ └── index.tsx │ │ │ └── hooks │ │ │ │ ├── sendWelcomeEmail.ts │ │ │ │ └── createTokenAndSendEmail.ts │ │ ├── ProductReviews │ │ │ └── index.ts │ │ ├── ProductSubCategories │ │ │ └── index.ts │ │ └── ProductCategories │ │ │ └── index.ts │ ├── Categories.ts │ ├── Administrators │ │ └── index.ts │ ├── Posts │ │ └── hooks │ │ │ ├── populateAuthors.ts │ │ │ └── revalidatePost.ts │ └── Pages │ │ └── hooks │ │ └── revalidatePage.ts ├── middleware.ts ├── hooks │ ├── revalidateRedirects.ts │ ├── revalidateGlobal.ts │ ├── populatePublishedAt.ts │ ├── use-mobile.tsx │ └── formatSlug.ts ├── globals │ ├── Footer │ │ ├── RowLabel.tsx │ │ └── config.ts │ ├── Header │ │ ├── RowLabel.tsx │ │ ├── Component.tsx │ │ └── Component.client.tsx │ └── (ecommerce) │ │ └── Layout │ │ ├── ProductList │ │ └── variants │ │ │ └── filters │ │ │ ├── None.tsx │ │ │ └── WithSidebar │ │ │ ├── stores │ │ │ └── MobileFiltersContext.tsx │ │ │ └── components │ │ │ ├── MobileFiltersDialog.tsx │ │ │ ├── MobileFunnelFiltersButton.tsx │ │ │ ├── MobileFiltersCloseButton.tsx │ │ │ └── SortSelect.tsx │ │ ├── ClientPanel │ │ ├── Help │ │ │ └── Component.tsx │ │ ├── Orders │ │ │ └── Component.tsx │ │ ├── Component.tsx │ │ └── variants │ │ │ └── WithSidebar │ │ │ └── index.tsx │ │ ├── Cart │ │ └── Component.tsx │ │ ├── WishList │ │ └── Component.tsx │ │ ├── ProductDetails │ │ ├── types │ │ │ └── index.ts │ │ └── Component.tsx │ │ └── Checkout │ │ ├── Component.tsx │ │ └── variants │ │ └── OneStepWithSummary │ │ └── index.tsx ├── schemas │ ├── loginForm.schema.ts │ ├── ResetPasswordFormSchema.ts │ ├── changePasswordModalForm.schema.ts │ └── registerForm.schema.ts ├── environment.d.ts └── lib │ ├── getTotalWeight.ts │ ├── couriers │ └── labels │ │ └── getInpostLabel.ts │ ├── getTotal.ts │ └── paywalls │ └── getAutopayPaymentURL.ts ├── postcss.config.js ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── public ├── favicon.ico ├── paczkomat.png ├── storefront.png ├── admin-dashboard.png ├── inpost_courier.png ├── website-template-OG.webp ├── blocksThumbnails │ ├── carousel.png │ └── accordion.png ├── web-app-manifest-192x192.png └── web-app-manifest-512x512.png ├── .gitignore ├── .editorconfig ├── .prettierrc.json ├── next-env.d.ts ├── .prettierignore ├── .env.example ├── components.json ├── redirects.js ├── next-sitemap.config.cjs ├── LICENSE.md ├── next.config.mjs ├── tsconfig.json └── Dockerfile /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | enable-pre-post-scripts=true 3 | -------------------------------------------------------------------------------- /src/stores/Currency/types.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "USD" | "PLN" | "GBP" | "EUR"; 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /src/access/anyone.ts: -------------------------------------------------------------------------------- 1 | import type { Access } from "payload"; 2 | 3 | export const anyone: Access = () => true; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/src/app/icon1.png -------------------------------------------------------------------------------- /public/paczkomat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/paczkomat.png -------------------------------------------------------------------------------- /public/storefront.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/storefront.png -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/src/app/apple-icon.png -------------------------------------------------------------------------------- /public/admin-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/admin-dashboard.png -------------------------------------------------------------------------------- /public/inpost_courier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/inpost_courier.png -------------------------------------------------------------------------------- /public/website-template-OG.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/website-template-OG.webp -------------------------------------------------------------------------------- /public/blocksThumbnails/carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/blocksThumbnails/carousel.png -------------------------------------------------------------------------------- /public/web-app-manifest-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/web-app-manifest-192x192.png -------------------------------------------------------------------------------- /public/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /src/blocks/Form/Error/index.tsx: -------------------------------------------------------------------------------- 1 | export const Error = () => { 2 | return
This field is required
; 3 | }; 4 | -------------------------------------------------------------------------------- /src/components/AdminBar/index.scss: -------------------------------------------------------------------------------- 1 | @import "~@payloadcms/ui/scss"; 2 | 3 | .admin-bar { 4 | @include small-break { 5 | display: none; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/utilities/canUseDOM.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default !!(typeof window !== "undefined" && window.document && window.document.createElement); 3 | -------------------------------------------------------------------------------- /public/blocksThumbnails/accordion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mandala-Software-House/payload-ecommerce-template/HEAD/public/blocksThumbnails/accordion.png -------------------------------------------------------------------------------- /src/components/AdminAvatar/index.tsx: -------------------------------------------------------------------------------- 1 | import { UserCog2 } from "lucide-react"; 2 | 3 | export const AdminAvatar = () => { 4 | return ; 5 | }; 6 | -------------------------------------------------------------------------------- /src/i18n/config.ts: -------------------------------------------------------------------------------- 1 | export type Locale = (typeof locales)[number]; 2 | 3 | export const locales = ["pl", "en"] as const; 4 | export const defaultLocale: Locale = "pl"; 5 | -------------------------------------------------------------------------------- /src/providers/Theme/ThemeSelector/types.ts: -------------------------------------------------------------------------------- 1 | export type Theme = "dark" | "light"; 2 | 3 | export const themeLocalStorageKey = "payload-theme"; 4 | 5 | export const defaultTheme = "light"; 6 | -------------------------------------------------------------------------------- /src/utilities/toKebabCase.ts: -------------------------------------------------------------------------------- 1 | export const toKebabCase = (string: string): string => 2 | string 3 | ?.replace(/([a-z])([A-Z])/g, "$1-$2") 4 | .replace(/\s+/g, "-") 5 | .toLowerCase(); 6 | -------------------------------------------------------------------------------- /src/utilities/cn.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist / media 3 | node_modules 4 | .DS_Store 5 | .env 6 | .next 7 | .vercel 8 | 9 | # Payload default media upload directory 10 | public/media/ 11 | 12 | public/robots.txt 13 | public/sitemap*.xml 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/page.tsx: -------------------------------------------------------------------------------- 1 | import PageTemplate, { generateMetadata } from "./[slug]/page"; 2 | 3 | export const dynamic = "force-dynamic"; 4 | 5 | export default PageTemplate; 6 | 7 | export { generateMetadata }; 8 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | export const dynamic = "force-dynamic"; 4 | 5 | const Page = () => { 6 | redirect("/admin"); 7 | }; 8 | export default Page; 9 | -------------------------------------------------------------------------------- /src/stores/WishlistStore/types.ts: -------------------------------------------------------------------------------- 1 | import { type Product } from "@/payload-types"; 2 | 3 | export type WishListProduct = { 4 | id: Product["id"]; 5 | choosenVariantSlug?: string; 6 | }; 7 | 8 | export type WishList = WishListProduct[]; 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": false, 4 | "trailingComma": "all", 5 | "printWidth": 110, 6 | "tabWidth": 2, 7 | "plugins": ["prettier-plugin-tailwindcss"], 8 | "tailwindConfig": "./tailwind.config.ts" 9 | } 10 | -------------------------------------------------------------------------------- /src/stores/CartStore/types.ts: -------------------------------------------------------------------------------- 1 | import { type Product } from "@/payload-types"; 2 | 3 | export type CartProduct = { 4 | id: Product["id"]; 5 | quantity: number; 6 | choosenVariantSlug?: string; 7 | }; 8 | 9 | export type Cart = CartProduct[]; 10 | -------------------------------------------------------------------------------- /src/app/(frontend)/next/exit-preview/GET.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from "next/headers"; 2 | 3 | export async function GET(): Promise { 4 | const draft = await draftMode(); 5 | draft.disable(); 6 | return new Response("Draft mode is disabled"); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(frontend)/next/exit-preview/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from "next/headers"; 2 | 3 | export async function GET(): Promise { 4 | const draft = await draftMode(); 5 | draft.disable(); 6 | return new Response("Draft mode is disabled"); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/BeforeDashboard/SeedButton/index.scss: -------------------------------------------------------------------------------- 1 | .seedButton { 2 | appearance: none; 3 | background: none; 4 | border: none; 5 | padding: 0; 6 | text-decoration: underline; 7 | 8 | &:hover { 9 | cursor: pointer; 10 | opacity: 0.85; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/fields/slug/index.scss: -------------------------------------------------------------------------------- 1 | .slug-field-component { 2 | .label-wrapper { 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | } 7 | 8 | .lock-button { 9 | margin: 0; 10 | padding-bottom: 0.3125rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/account/orders/page.tsx: -------------------------------------------------------------------------------- 1 | import { Orders } from "@/globals/(ecommerce)/Layout/ClientPanel/Orders/Component"; 2 | 3 | export const dynamic = "force-dynamic"; 4 | 5 | const OrdersPage = () => { 6 | return ; 7 | }; 8 | export default OrdersPage; 9 | -------------------------------------------------------------------------------- /src/cssVariables.ts: -------------------------------------------------------------------------------- 1 | // Keep these in sync with the CSS variables in your tailwind configuration 2 | 3 | export const cssVariables = { 4 | breakpoints: { 5 | "3xl": 1920, 6 | "2xl": 1536, 7 | xl: 1280, 8 | lg: 1024, 9 | md: 768, 10 | sm: 640, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/payload-types.ts 2 | .tmp 3 | **/.git 4 | **/.hg 5 | **/.pnp.* 6 | **/.svn 7 | **/.yarn/** 8 | **/build 9 | **/dist/** 10 | **/node_modules 11 | **/temp 12 | **/docs/** 13 | tsconfig.json 14 | .next 15 | node_modules 16 | package-lock.json 17 | bunb.lock 18 | pnpm-lock.yaml 19 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Products/components/RowLabels/DetailLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const DetailLabel = () => { 6 | const { data } = useRowLabel<{ 7 | title: string; 8 | }>(); 9 | 10 | return

{data?.title}

; 11 | }; 12 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Products/components/RowLabels/OptionLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const OptionLabel = () => { 6 | const { data } = useRowLabel<{ 7 | slug: string; 8 | }>(); 9 | 10 | return

{data.slug}

; 11 | }; 12 | -------------------------------------------------------------------------------- /src/fields/currencyField.ts: -------------------------------------------------------------------------------- 1 | import { type Field } from "payload"; 2 | 3 | export const currencyField: Field = { 4 | name: "currency", 5 | type: "text", 6 | required: true, 7 | admin: { 8 | components: { 9 | Field: "@/components/(ecommerce)/CurrencySelect#CurrencySelect", 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Products/components/RowLabels/VariantLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const VariantLabel = () => { 6 | const { data } = useRowLabel<{ 7 | variantSlug: string; 8 | }>(); 9 | 10 | return

{data.variantSlug}

; 11 | }; 12 | -------------------------------------------------------------------------------- /src/fields/backgroundPicker.ts: -------------------------------------------------------------------------------- 1 | import { type Field } from "payload"; 2 | 3 | export const backgroundPicker: Field = { 4 | name: "background", 5 | label: "Background", 6 | type: "text", 7 | admin: { 8 | components: { 9 | Field: "@/components/AdminColorPicker#AdminColorPicker", 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/RowLabels/DeliveryZonesRowLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const DeliveryZonesRowLabel = () => { 6 | const { data } = useRowLabel<{ 7 | countries?: string[]; 8 | }>(); 9 | 10 | return

{data.countries?.join(", ")}

; 11 | }; 12 | -------------------------------------------------------------------------------- /src/access/authenticated.ts: -------------------------------------------------------------------------------- 1 | import type { Administrator } from "@/payload-types"; 2 | import type { AccessArgs } from "payload"; 3 | 4 | type isAuthenticated = (args: AccessArgs) => boolean; 5 | 6 | export const authenticated: isAuthenticated = ({ req: { user } }) => { 7 | return Boolean(user?.collection === "administrators"); 8 | }; 9 | -------------------------------------------------------------------------------- /src/access/authenticatedOrPublished.ts: -------------------------------------------------------------------------------- 1 | import type { Access } from "payload"; 2 | 3 | export const authenticatedOrPublished: Access = ({ req: { user } }) => { 4 | if (user?.collection === "administrators") { 5 | return true; 6 | } 7 | 8 | return { 9 | _status: { 10 | equals: "published", 11 | }, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Orders/components/couriers/CourierShipmentMenu.tsx: -------------------------------------------------------------------------------- 1 | import { type Order } from "@/payload-types"; 2 | 3 | import { CourierShipmentMenuClient } from "./CourierShipmentMenu.client"; 4 | 5 | export const CourierShipmentMenu = ({ data }: { data: Order }) => { 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Orders/components/inpost-pickup/PickupShipmentMenu.tsx: -------------------------------------------------------------------------------- 1 | import { type Order } from "@/payload-types"; 2 | 3 | import { PickupShipmentMenuClient } from "./PickupShipmentMenu.client"; 4 | 5 | export const PickupShipmentMenu = ({ data }: { data: Order }) => { 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /src/providers/Theme/types.ts: -------------------------------------------------------------------------------- 1 | export type Theme = "dark" | "light"; 2 | 3 | export type ThemeContextType = { 4 | setTheme: (theme: Theme | null) => void; 5 | theme?: Theme | null; 6 | }; 7 | 8 | export function themeIsValid(string: null | string): string is Theme { 9 | return string ? ["dark", "light"].includes(string) : false; 10 | } 11 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import createMiddleware from "next-intl/middleware"; 2 | 3 | import { routing } from "./i18n/routing"; 4 | 5 | export default createMiddleware(routing); 6 | 7 | export const config = { 8 | // Match only internationalized pathnames 9 | matcher: ["/", "/(pl|en)/:path*", "/((?!api|_next|next|admin|route|proxy|.*\\..*).*)"], 10 | }; 11 | -------------------------------------------------------------------------------- /src/app/(payload)/api/graphql-playground/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import '@payloadcms/next/css' 5 | import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' 6 | 7 | export const GET = GRAPHQL_PLAYGROUND_GET(config) 8 | -------------------------------------------------------------------------------- /src/app/(payload)/api/graphql/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes' 5 | 6 | export const POST = GRAPHQL_POST(config) 7 | 8 | export const OPTIONS = REST_OPTIONS(config) 9 | -------------------------------------------------------------------------------- /src/blocks/Form/Width/index.tsx: -------------------------------------------------------------------------------- 1 | export const Width = ({ 2 | children, 3 | className, 4 | width, 5 | }: { 6 | children: React.ReactNode; 7 | className?: string; 8 | width?: number | string; 9 | }) => { 10 | return ( 11 |
12 | {children} 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/Cart/SynchronizeCart/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | 5 | import { useCart } from "@/stores/CartStore"; 6 | 7 | export const SynchronizeCart = () => { 8 | const { synchronizeCart } = useCart(); 9 | useEffect(() => { 10 | void synchronizeCart(); 11 | }, [synchronizeCart]); 12 | return <>; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/RowLabels/PriceRowLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const PriceRowLabel = () => { 6 | const { data } = useRowLabel<{ 7 | currency: string; 8 | value: string; 9 | }>(); 10 | 11 | return ( 12 |

13 | {data.value} {data.currency} 14 |

15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from "react"; 2 | 3 | import { Header } from "@/globals/Header/Component"; 4 | 5 | const WithoutCartLayout = ({ children }: { children: ReactNode }) => { 6 | return ( 7 | <> 8 |
9 | {children} 10 | 11 | ); 12 | }; 13 | export default WithoutCartLayout; 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SERVER_URL=http://localhost:3000 2 | 3 | DATABASE_URI=mongodb://myUser:myPassword@213.76.110.18:27017/payload-ecomm-db 4 | PAYLOAD_SECRET=S0M3R4ND0M5TR1NG 5 | 6 | S3_BUCKET="payload-ecomm" 7 | S3_ENDPOINT="https://payload-ecomm.r2.cloudflarestorage.com" 8 | S3_ACCESS_KEY_ID="myAccessKey" 9 | S3_SECRET_ACCESS_KEY="mySecretKey" 10 | 11 | STRIPE_SECRET_KEY="myStripeSecretKey" -------------------------------------------------------------------------------- /src/hooks/revalidateRedirects.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { revalidateTag } from "next/cache"; 3 | 4 | import type { CollectionAfterChangeHook } from "payload"; 5 | 6 | export const revalidateRedirects: CollectionAfterChangeHook = ({ doc, req: { payload } }) => { 7 | payload.logger.info(`Revalidating redirects`); 8 | 9 | revalidateTag("redirects"); 10 | 11 | return doc; 12 | }; 13 | -------------------------------------------------------------------------------- /src/fields/countryPickerField.ts: -------------------------------------------------------------------------------- 1 | import { type Field } from "payload"; 2 | 3 | import { countryList } from "@/globals/(ecommerce)/Couriers/utils/countryList"; 4 | 5 | export const countryPickerField: Field = { 6 | name: "countries", 7 | type: "select", 8 | label: { 9 | en: "Countries", 10 | pl: "Kraje", 11 | }, 12 | hasMany: true, 13 | options: [...countryList], 14 | required: true, 15 | }; 16 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/(frontend)/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/utilities/cn" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/checkout/page.tsx: -------------------------------------------------------------------------------- 1 | import { Checkout } from "@/globals/(ecommerce)/Layout/Checkout/Component"; 2 | import { type Locale } from "@/i18n/config"; 3 | 4 | export const dynamic = "force-dynamic"; 5 | 6 | const CheckoutPage = async ({ params }: { params: Promise<{ locale: Locale }> }) => { 7 | const { locale } = await params; 8 | return ; 9 | }; 10 | export default CheckoutPage; 11 | -------------------------------------------------------------------------------- /src/blocks/Form/Message/index.tsx: -------------------------------------------------------------------------------- 1 | import { type SerializedEditorState } from "@payloadcms/richtext-lexical/lexical"; 2 | 3 | import RichText from "@/components/RichText"; 4 | 5 | import { Width } from "../Width"; 6 | 7 | export const Message = ({ message }: { message: SerializedEditorState }) => { 8 | return ( 9 | 10 | {message && } 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/BeforeDashboard/index.scss: -------------------------------------------------------------------------------- 1 | @import "~@payloadcms/ui/scss"; 2 | 3 | .dashboard .before-dashboard { 4 | margin-bottom: base(1.5); 5 | 6 | &__banner { 7 | & h4 { 8 | margin: 0; 9 | } 10 | } 11 | 12 | &__instructions { 13 | list-style: decimal; 14 | margin-bottom: base(0.5); 15 | 16 | & li { 17 | width: 100%; 18 | } 19 | } 20 | 21 | & a:hover { 22 | opacity: 0.85; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/blocks/MediaBlock/config.ts: -------------------------------------------------------------------------------- 1 | import { marginFields, paddingFields } from "@/fields/spacingFields"; 2 | 3 | import type { Block } from "payload"; 4 | 5 | export const MediaBlock: Block = { 6 | slug: "mediaBlock", 7 | interfaceName: "MediaBlock", 8 | fields: [ 9 | { 10 | name: "media", 11 | type: "upload", 12 | relationTo: "media", 13 | required: true, 14 | }, 15 | marginFields, 16 | paddingFields, 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/hooks/revalidateGlobal.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { revalidateTag } from "next/cache"; 3 | 4 | import type { GlobalAfterChangeHook } from "payload"; 5 | 6 | export const revalidateGlobal: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => { 7 | if (!context.disableRevalidate) { 8 | payload.logger.info(`Revalidating ${doc.globalType}`); 9 | 10 | revalidateTag(`global_${doc.globalType}`); 11 | } 12 | 13 | return doc; 14 | }; 15 | -------------------------------------------------------------------------------- /src/hooks/populatePublishedAt.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionBeforeChangeHook } from "payload"; 2 | 3 | export const populatePublishedAt: CollectionBeforeChangeHook = ({ data, operation, req }) => { 4 | if (operation === "create" || operation === "update") { 5 | if (req.data && !req.data.publishedAt) { 6 | const now = new Date(); 7 | return { 8 | ...data, 9 | publishedAt: now, 10 | }; 11 | } 12 | } 13 | 14 | return data; 15 | }; 16 | -------------------------------------------------------------------------------- /src/utilities/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export function useDebounce(value: T, delay = 200): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value, delay]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/account/help/page.tsx: -------------------------------------------------------------------------------- 1 | import { setRequestLocale } from "next-intl/server"; 2 | 3 | import { ClientHelp } from "@/globals/(ecommerce)/Layout/ClientPanel/Help/Component"; 4 | import { type Locale } from "@/i18n/config"; 5 | 6 | const HelpPage = async ({ params }: { params: Promise<{ locale: Locale }> }) => { 7 | const { locale } = await params; 8 | setRequestLocale(locale); 9 | return ; 10 | }; 11 | export default HelpPage; 12 | -------------------------------------------------------------------------------- /src/globals/Footer/RowLabel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useRowLabel } from "@payloadcms/ui"; 3 | 4 | import { type Header } from "@/payload-types"; 5 | 6 | export const RowLabel = () => { 7 | const data = useRowLabel[number]>(); 8 | 9 | const label = data?.data?.link?.label 10 | ? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ""}: ${data?.data?.link?.label}` 11 | : "Row"; 12 | 13 | return
{label}
; 14 | }; 15 | -------------------------------------------------------------------------------- /src/globals/Header/RowLabel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useRowLabel } from "@payloadcms/ui"; 3 | 4 | import { type Header } from "@/payload-types"; 5 | 6 | export const RowLabel = () => { 7 | const data = useRowLabel[number]>(); 8 | 9 | const label = data?.data?.link?.label 10 | ? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ""}: ${data?.data?.link?.label}` 11 | : "Row"; 12 | 13 | return
{label}
; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/RowLabels/WeightRangeRowLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const WeightRangeRowLabel = () => { 6 | const { data } = useRowLabel<{ 7 | weightFrom: number; 8 | weightTo: number; 9 | }>(); 10 | 11 | return ( 12 |

13 | {data.weightFrom} 14 | {(data.weightFrom || data.weightFrom === 0) && data.weightTo && " - "} 15 | {data.weightTo} 16 |

17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/[slug]/page.client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | 4 | import { useHeaderTheme } from "@/providers/HeaderTheme"; 5 | 6 | const PageClient = () => { 7 | /* Force the header to be dark mode while we have an image behind it */ 8 | const { setHeaderTheme } = useHeaderTheme(); 9 | 10 | useEffect(() => { 11 | setHeaderTheme("light"); 12 | }, [setHeaderTheme]); 13 | return <>; 14 | }; 15 | 16 | export default PageClient; 17 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/account/page.tsx: -------------------------------------------------------------------------------- 1 | import { setRequestLocale } from "next-intl/server"; 2 | 3 | import { type Locale } from "@/i18n/config"; 4 | import { redirect } from "@/i18n/routing"; 5 | export const dynamic = "force-dynamic"; 6 | 7 | const Page = async ({ params }: { params: Promise<{ locale: Locale }> }) => { 8 | const { locale } = await params; 9 | setRequestLocale(locale); 10 | 11 | return redirect({ locale, href: "/account/orders" }); 12 | }; 13 | 14 | export default Page; 15 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/posts/page.client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | 4 | import { useHeaderTheme } from "@/providers/HeaderTheme"; 5 | 6 | const PageClient = () => { 7 | /* Force the header to be dark mode while we have an image behind it */ 8 | const { setHeaderTheme } = useHeaderTheme(); 9 | 10 | useEffect(() => { 11 | setHeaderTheme("light"); 12 | }, [setHeaderTheme]); 13 | return <>; 14 | }; 15 | 16 | export default PageClient; 17 | -------------------------------------------------------------------------------- /src/components/LivePreviewListener/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | "use client"; 3 | import { RefreshRouteOnSave as PayloadLivePreview } from "@payloadcms/live-preview-react"; 4 | 5 | import { useRouter } from "@/i18n/routing"; 6 | 7 | export const LivePreviewListener = () => { 8 | const router = useRouter(); 9 | return ( 10 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/postSearch/page.client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | 4 | import { useHeaderTheme } from "@/providers/HeaderTheme"; 5 | 6 | const PageClient = () => { 7 | /* Force the header to be dark mode while we have an image behind it */ 8 | const { setHeaderTheme } = useHeaderTheme(); 9 | 10 | useEffect(() => { 11 | setHeaderTheme("light"); 12 | }, [setHeaderTheme]); 13 | return <>; 14 | }; 15 | 16 | export default PageClient; 17 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/posts/[slug]/page.client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | 4 | import { useHeaderTheme } from "@/providers/HeaderTheme"; 5 | 6 | const PageClient = () => { 7 | /* Force the header to be dark mode while we have an image behind it */ 8 | const { setHeaderTheme } = useHeaderTheme(); 9 | 10 | useEffect(() => { 11 | setHeaderTheme("dark"); 12 | }, [setHeaderTheme]); 13 | return <>; 14 | }; 15 | 16 | export default PageClient; 17 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(without-cart)/posts/page/[pageNumber]/page.client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | 4 | import { useHeaderTheme } from "@/providers/HeaderTheme"; 5 | 6 | const PageClient = () => { 7 | /* Force the header to be dark mode while we have an image behind it */ 8 | const { setHeaderTheme } = useHeaderTheme(); 9 | 10 | useEffect(() => { 11 | setHeaderTheme("light"); 12 | }, [setHeaderTheme]); 13 | return <>; 14 | }; 15 | 16 | export default PageClient; 17 | -------------------------------------------------------------------------------- /src/fields/alignmentField.ts: -------------------------------------------------------------------------------- 1 | import { type Field } from "payload"; 2 | 3 | export const AlignmentField: Field = { 4 | name: "alignment", 5 | label: "Alignment", 6 | type: "select", 7 | defaultValue: "center", 8 | options: [ 9 | { 10 | label: "Center", 11 | value: "center", 12 | }, 13 | { 14 | label: "Left", 15 | value: "left", 16 | }, 17 | { 18 | label: "Right", 19 | value: "right", 20 | }, 21 | { label: "Full width", value: "full" }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/ProductList/variants/filters/None.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from "react"; 2 | 3 | export const None = ({ title, children }: { title: string; children: ReactNode }) => { 4 | return ( 5 |
6 |

{title}

7 |
8 | {children} 9 |
10 |
11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/schemas/loginForm.schema.ts: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { z } from "zod"; 3 | 4 | export type LoginFormData = { 5 | email: string; 6 | password: string; 7 | }; 8 | 9 | export const useLoginFormSchema = () => { 10 | const t = useTranslations("LoginForm.errors"); 11 | 12 | const LoginFormSchema = z.object({ 13 | email: z.string().nonempty(t("email-empty")).email(t("email")), 14 | password: z.string().nonempty(t("password")), 15 | }); 16 | 17 | return { LoginFormSchema }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ecommerce", 3 | "short_name": "Ecomm", 4 | "icons": [ 5 | { 6 | "src": "/web-app-manifest-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "maskable" 10 | }, 11 | { 12 | "src": "/web-app-manifest-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | } 17 | ], 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } -------------------------------------------------------------------------------- /src/components/(ecommerce)/RowLabels/OrderProductsRowLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const OrderProductsRowLabel = () => { 6 | const { data } = useRowLabel<{ 7 | productName?: string; 8 | color?: string; 9 | size?: string; 10 | quantity: number; 11 | }>(); 12 | 13 | const label = [data.productName, data.color, data.size].filter(Boolean).join(", "); 14 | 15 | return ( 16 |

17 | {label} x {data.quantity} 18 |

19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/providers/Theme/shared.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from "./types"; 2 | 3 | export const themeLocalStorageKey = "payload-theme"; 4 | 5 | export const defaultTheme = "light"; 6 | 7 | export const getImplicitPreference = (): Theme | null => { 8 | const mediaQuery = "(prefers-color-scheme: dark)"; 9 | const mql = window.matchMedia(mediaQuery); 10 | const hasImplicitPreference = typeof mql.matches === "boolean"; 11 | 12 | if (hasImplicitPreference) { 13 | return mql.matches ? "dark" : "light"; 14 | } 15 | 16 | return null; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utilities/formatPrices.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function that formats a price using Intl.NumberFormat 3 | * @param price - Price to format 4 | * @param currency - Currency to format 5 | * @param locale - Locale to format 6 | * @returns - Formatted price 7 | */ 8 | export const formatPrice = (price: number, currency: string, locale: string) => { 9 | const formattedPrice = new Intl.NumberFormat(`${locale}-${locale.toUpperCase()}`, { 10 | style: "currency", 11 | currency: currency, 12 | }).format(price); 13 | return formattedPrice; 14 | }; 15 | -------------------------------------------------------------------------------- /src/blocks/Code/Component.tsx: -------------------------------------------------------------------------------- 1 | import { Code } from "./Component.client"; 2 | 3 | export type CodeBlockProps = { 4 | code: string; 5 | language?: string; 6 | blockType: "code"; 7 | }; 8 | 9 | type Props = CodeBlockProps & { 10 | className?: string; 11 | }; 12 | 13 | export const CodeBlock = ({ className, code, language }: Props) => { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/AdminNavbar/NavHamburger/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Hamburger, useNav } from "@payloadcms/ui"; 3 | 4 | export const NavHamburger = ({ baseClass }: { baseClass?: string }) => { 5 | const { navOpen, setNavOpen } = useNav(); 6 | 7 | return ( 8 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /redirects.js: -------------------------------------------------------------------------------- 1 | const redirects = async () => { 2 | const internetExplorerRedirect = { 3 | destination: "/ie-incompatible.html", 4 | has: [ 5 | { 6 | type: "header", 7 | key: "user-agent", 8 | value: "(.*Trident.*)", // all ie browsers 9 | }, 10 | ], 11 | permanent: false, 12 | source: "/:path((?!ie-incompatible.html$).*)", // all pages except the incompatibility page 13 | }; 14 | 15 | const redirects = [internetExplorerRedirect]; 16 | 17 | return redirects; 18 | }; 19 | 20 | export default redirects; 21 | -------------------------------------------------------------------------------- /src/app/(frontend)/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | 4 | import { Button } from "@/components/ui/button"; 5 | 6 | export default function NotFound() { 7 | return ( 8 |
9 |
10 |

404

11 |

This page could not be found.

12 |
13 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Customers/ui/RowLabels/ShippingAddressRowLabel/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRowLabel } from "@payloadcms/ui"; 4 | 5 | export const ShippingAddressRowLabel = () => { 6 | const { data } = useRowLabel<{ 7 | name?: string; 8 | address?: string; 9 | city?: string; 10 | postalCode?: string; 11 | phone?: string; 12 | email?: string; 13 | }>(); 14 | 15 | return ( 16 |

17 | {data?.name}, {data?.address} {data?.postalCode} {data?.city} | {data?.phone}, {data?.email} 18 |

19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/ui/AdminInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export type InputProps = {} & React.InputHTMLAttributes; 4 | 5 | const AdminInput = React.forwardRef(({ type, className, ...props }, ref) => { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | ); 13 | }); 14 | 15 | AdminInput.displayName = "AdminInput"; 16 | 17 | export { AdminInput }; 18 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/ProductList/variants/filters/WithSidebar/stores/MobileFiltersContext.tsx: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | type MobileFiltersState = { 4 | mobileFiltersOpen: boolean; 5 | setMobileFiltersOpen: (open: boolean) => void; 6 | }; 7 | 8 | const useMobileFiltersStore = create((set) => ({ 9 | mobileFiltersOpen: false, // domyślna wartość 10 | setMobileFiltersOpen: (open: boolean) => set({ mobileFiltersOpen: open }), 11 | })); 12 | 13 | export const useMobileFilters = () => { 14 | return useMobileFiltersStore(); 15 | }; 16 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | 4 | import { Button } from "@/components/ui/button"; 5 | 6 | export default function NotFound() { 7 | return ( 8 |
9 |
10 |

404

11 |

This page could not be found.

12 |
13 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/i18n/routing.ts: -------------------------------------------------------------------------------- 1 | import { createNavigation } from "next-intl/navigation"; 2 | import { defineRouting } from "next-intl/routing"; 3 | 4 | export const routing = defineRouting({ 5 | // A list of all locales that are supported 6 | locales: ["en", "pl"], 7 | 8 | // Used when no locale matches 9 | defaultLocale: "en", 10 | 11 | localeDetection: true, 12 | }); 13 | 14 | // Lightweight wrappers around Next.js' navigation APIs 15 | // that will consider the routing configuration 16 | export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing); 17 | -------------------------------------------------------------------------------- /src/stores/CartStateStore/index.tsx: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | type CartState = { 4 | isOpen: boolean; 5 | toggleCart: () => void; 6 | setCartState: (isOpen: boolean) => void; 7 | }; 8 | 9 | const useCartStateStore = create((set) => ({ 10 | isOpen: false, 11 | toggleCart: () => set((state) => ({ isOpen: !state.isOpen })), 12 | setCartState: (isOpen) => set({ isOpen }), 13 | })); 14 | 15 | export const useCartState = () => { 16 | const { isOpen, toggleCart, setCartState } = useCartStateStore(); 17 | 18 | return { isOpen, toggleCart, setCartState }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/environment.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import type en from "../translations/en.json"; 3 | 4 | type Messages = typeof en; 5 | 6 | declare global { 7 | namespace NodeJS { 8 | interface ProcessEnv { 9 | PAYLOAD_SECRET: string; 10 | DATABASE_URI: string; 11 | NEXT_PUBLIC_SERVER_URL: string; 12 | VERCEL_PROJECT_PRODUCTION_URL: string; 13 | } 14 | } 15 | interface IntlMessages extends Messages {} 16 | } 17 | 18 | // If this file has no import/export statements (i.e. is a script) 19 | // convert it into a module by adding an empty export statement. 20 | export {}; 21 | -------------------------------------------------------------------------------- /src/globals/Header/Component.tsx: -------------------------------------------------------------------------------- 1 | import { getLocale } from "next-intl/server"; 2 | 3 | import { type Locale } from "@/i18n/config"; 4 | import { getCachedGlobal } from "@/utilities/getGlobals"; 5 | 6 | import { HeaderClient } from "./Component.client"; 7 | 8 | import type { Header } from "@/payload-types"; 9 | 10 | export async function Header({ disableCart }: { disableCart?: boolean }) { 11 | const locale = (await getLocale()) as Locale; 12 | const headerData: Header = await getCachedGlobal("header", locale, 1)(); 13 | 14 | return ; 15 | } 16 | -------------------------------------------------------------------------------- /src/i18n/request.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { getRequestConfig } from "next-intl/server"; 3 | 4 | import { routing } from "./routing"; 5 | 6 | export default getRequestConfig(async ({ requestLocale }) => { 7 | // This typically corresponds to the `[locale]` segment 8 | let locale = await requestLocale; 9 | 10 | // Ensure that a valid locale is used 11 | if (!locale || !routing.locales.includes(locale as any)) { 12 | locale = routing.defaultLocale; 13 | } 14 | 15 | return { 16 | locale, 17 | messages: (await import(`../../translations/${locale}.json`)).default, 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /next-sitemap.config.cjs: -------------------------------------------------------------------------------- 1 | const SITE_URL = 2 | process.env.NEXT_PUBLIC_SERVER_URL || process.env.VERCEL_PROJECT_PRODUCTION_URL || "https://example.com"; 3 | 4 | /** @type {import('next-sitemap').IConfig} */ 5 | module.exports = { 6 | siteUrl: SITE_URL, 7 | generateRobotsTxt: true, 8 | exclude: ["/posts-sitemap.xml", "/pages-sitemap.xml", "/*", "/posts/*"], 9 | robotsTxtOptions: { 10 | policies: [ 11 | { 12 | userAgent: "*", 13 | disallow: "/admin/*", 14 | }, 15 | ], 16 | additionalSitemaps: [`${SITE_URL}/pages-sitemap.xml`, `${SITE_URL}/posts-sitemap.xml`], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/account/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { setRequestLocale } from "next-intl/server"; 2 | 3 | import { Settings } from "@/globals/(ecommerce)/Layout/ClientPanel/Settings"; 4 | import { type Locale } from "@/i18n/config"; 5 | import { getCustomer } from "@/utilities/getCustomer"; 6 | 7 | const SettingsPage = async ({ params }: { params: Promise<{ locale: Locale }> }) => { 8 | const user = await getCustomer(); 9 | const { locale } = await params; 10 | setRequestLocale(locale); 11 | if (!user) return null; 12 | return ; 13 | }; 14 | export default SettingsPage; 15 | -------------------------------------------------------------------------------- /src/blocks/Form/fields.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox } from "./Checkbox"; 2 | import { Country } from "./Country"; 3 | import { Email } from "./Email"; 4 | import { Message } from "./Message"; 5 | import { Number } from "./Number"; 6 | import { Select } from "./Select"; 7 | import { State } from "./State"; 8 | import { Text } from "./Text"; 9 | import { Textarea } from "./Textarea"; 10 | 11 | export const fields = { 12 | checkbox: Checkbox, 13 | country: Country, 14 | email: Email, 15 | message: Message, 16 | number: Number, 17 | select: Select, 18 | state: State, 19 | text: Text, 20 | textarea: Textarea, 21 | }; 22 | -------------------------------------------------------------------------------- /src/fields/courierSettingsFields.ts: -------------------------------------------------------------------------------- 1 | import { type Field } from "payload"; 2 | 3 | export const courierSettingsFields: Field[] = [ 4 | { name: "label", type: "text", label: { en: "Label", pl: "Etykieta" }, localized: true, required: true }, 5 | { 6 | name: "description", 7 | type: "text", 8 | label: { en: "Short description", pl: "Krótki opis" }, 9 | localized: true, 10 | admin: { 11 | description: { 12 | en: "You can provide typical delivery time or any other information", 13 | pl: "Możesz podać typowy czas dostawy lub inne informacje", 14 | }, 15 | }, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/ProductList/variants/filters/WithSidebar/components/MobileFiltersDialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Dialog } from "@headlessui/react"; 4 | import { type ReactNode } from "react"; 5 | 6 | import { useMobileFilters } from "../stores/MobileFiltersContext"; 7 | 8 | export const MobileFiltersDialog = ({ children }: { children: ReactNode }) => { 9 | const { mobileFiltersOpen, setMobileFiltersOpen } = useMobileFilters(); 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/stores/WishListStateStore/index.tsx: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | type WishListState = { 4 | isOpen: boolean; 5 | toggleWishList: () => void; 6 | setWishListState: (isOpen: boolean) => void; 7 | }; 8 | 9 | const useWishListStateStore = create((set) => ({ 10 | isOpen: false, 11 | toggleWishList: () => set((state) => ({ isOpen: !state.isOpen })), 12 | setWishListState: (isOpen) => set({ isOpen }), 13 | })); 14 | 15 | export const useWishListState = () => { 16 | const { isOpen, toggleWishList, setWishListState } = useWishListStateStore(); 17 | 18 | return { isOpen, toggleWishList, setWishListState }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from "react"; 2 | 3 | import { SynchronizeCart } from "@/components/(ecommerce)/Cart/SynchronizeCart"; 4 | import { Cart } from "@/globals/(ecommerce)/Layout/Cart/Component"; 5 | import { WishList } from "@/globals/(ecommerce)/Layout/WishList/Component"; 6 | import { Header } from "@/globals/Header/Component"; 7 | 8 | const CartLayout = ({ children }: { children: ReactNode }) => { 9 | return ( 10 | <> 11 | 12 |
13 | 14 | 15 | {children} 16 | 17 | ); 18 | }; 19 | export default CartLayout; 20 | -------------------------------------------------------------------------------- /src/app/(payload)/api/[...slug]/route.ts: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import config from '@payload-config' 4 | import '@payloadcms/next/css' 5 | import { 6 | REST_DELETE, 7 | REST_GET, 8 | REST_OPTIONS, 9 | REST_PATCH, 10 | REST_POST, 11 | REST_PUT, 12 | } from '@payloadcms/next/routes' 13 | 14 | export const GET = REST_GET(config) 15 | export const POST = REST_POST(config) 16 | export const DELETE = REST_DELETE(config) 17 | export const PATCH = REST_PATCH(config) 18 | export const PUT = REST_PUT(config) 19 | export const OPTIONS = REST_OPTIONS(config) 20 | -------------------------------------------------------------------------------- /src/components/AdminColorPicker/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FieldLabel, useField } from "@payloadcms/ui"; 4 | import { type TextFieldClientComponent } from "payload"; 5 | 6 | import { ColorPicker } from "../ui/colorPicker"; 7 | 8 | export const AdminColorPicker: TextFieldClientComponent = ({ path, field }) => { 9 | const { value, setValue } = useField<{ value: string | undefined }>({ path }); 10 | 11 | return ( 12 |
13 | 14 | 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/ClientPanel/Help/Component.tsx: -------------------------------------------------------------------------------- 1 | import { getLocale } from "next-intl/server"; 2 | 3 | import RichText from "@/components/RichText"; 4 | import { type Locale } from "@/i18n/config"; 5 | import { getCachedGlobal } from "@/utilities/getGlobals"; 6 | 7 | export const ClientHelp = async () => { 8 | const locale = (await getLocale()) as Locale; 9 | const { clientPanel } = await getCachedGlobal("shopLayout", locale, 1)(); 10 | return ( 11 |
12 |

{clientPanel.help?.title}

13 | 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined); 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 12 | }; 13 | mql.addEventListener("change", onChange); 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 15 | return () => mql.removeEventListener("change", onChange); 16 | }, []); 17 | 18 | return !!isMobile; 19 | } 20 | -------------------------------------------------------------------------------- /src/providers/index.tsx: -------------------------------------------------------------------------------- 1 | // import { getLocale } from "next-intl/server"; 2 | import { type ReactNode } from "react"; 3 | 4 | // import { HeaderThemeProvider } from "./HeaderTheme"; 5 | // import { ThemeProvider } from "./Theme"; 6 | 7 | // import { type Locale } from "@/i18n/config"; 8 | // import { type ShopSetting } from "@/payload-types"; 9 | // import { getCachedGlobal } from "@/utilities/getGlobals"; 10 | 11 | export const Providers = async ({ children }: { children: ReactNode }) => { 12 | // const locale = (await getLocale()) as Locale; 13 | // const shopSettings: ShopSetting = await getCachedGlobal("shopSettings", locale, 1)(); 14 | return children; 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/AdminDashboard/components/views/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSearchParams } from "next/navigation"; 4 | import { type ReactNode } from "react"; 5 | 6 | import { Overview } from "./Overview"; 7 | 8 | export const AdminViews = () => { 9 | const searchParams = useSearchParams(); 10 | const view = searchParams.get("view"); 11 | 12 | let ActiveTabComponent: ReactNode | null = null; 13 | 14 | switch (view) { 15 | case "overview": { 16 | ActiveTabComponent = ; 17 | break; 18 | } 19 | default: { 20 | ActiveTabComponent = ; 21 | } 22 | } 23 | return ActiveTabComponent; 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/PriceClient/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useLocale } from "next-intl"; 3 | 4 | import { useCurrency } from "@/stores/Currency"; 5 | import { formatPrice } from "@/utilities/formatPrices"; 6 | 7 | export const PriceClient = ({ 8 | pricing, 9 | }: { 10 | pricing: { 11 | value: number; 12 | currency: string; 13 | }[]; 14 | }) => { 15 | const { currency } = useCurrency(); 16 | const locale = useLocale(); 17 | const price = 18 | pricing.length > 0 19 | ? (pricing.find((price) => price.currency === currency)?.value ?? pricing[0].value) 20 | : 0; 21 | 22 | return <>{formatPrice(price, currency, locale)}; 23 | }; 24 | -------------------------------------------------------------------------------- /src/collections/(ecommerce)/Orders/components/VariantSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import { FieldLabel } from "@payloadcms/ui"; 2 | import { type TextFieldServerComponent } from "payload"; 3 | 4 | import { VariantSelectClient } from "./VariantSelect.client"; 5 | 6 | export type VariantsArr = { 7 | label: string | null | undefined; 8 | value: string | null | undefined; 9 | }[]; 10 | 11 | export const VariantSelect: TextFieldServerComponent = async ({ path }) => { 12 | return ( 13 |
14 | 15 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/Cart/Component.tsx: -------------------------------------------------------------------------------- 1 | import { getLocale } from "next-intl/server"; 2 | import { type ReactNode } from "react"; 3 | 4 | import { type Locale } from "@/i18n/config"; 5 | import { getCachedGlobal } from "@/utilities/getGlobals"; 6 | 7 | import { SlideOver } from "./variants/SlideOver"; 8 | 9 | export const Cart = async () => { 10 | const locale = (await getLocale()) as Locale; 11 | const { cartAndWishlist } = await getCachedGlobal("shopLayout", locale, 1)(); 12 | 13 | let CartComponent: ReactNode = null; 14 | switch (cartAndWishlist.type) { 15 | case "slideOver": 16 | CartComponent = ; 17 | break; 18 | } 19 | 20 | return CartComponent; 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/heros/LowImpact/index.tsx: -------------------------------------------------------------------------------- 1 | import RichText from "@/components/RichText"; 2 | 3 | import type { Page } from "@/payload-types"; 4 | 5 | type LowImpactHeroType = 6 | | { 7 | children?: React.ReactNode; 8 | richText?: never; 9 | } 10 | | (Omit & { 11 | children?: never; 12 | richText?: Page["hero"]["richText"]; 13 | }); 14 | 15 | export const LowImpactHero = ({ children, richText }: LowImpactHeroType) => { 16 | return ( 17 |
18 |
19 | {children ?? (richText && )} 20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utilities/mergeOpenGraph.ts: -------------------------------------------------------------------------------- 1 | import { getServerSideURL } from "./getURL"; 2 | 3 | import type { Metadata } from "next"; 4 | 5 | const defaultOpenGraph: Metadata["openGraph"] = { 6 | type: "website", 7 | description: "An open-source website built with Payload and Next.js.", 8 | images: [ 9 | { 10 | url: `${getServerSideURL()}/website-template-OG.webp`, 11 | }, 12 | ], 13 | siteName: "Payload Ecommerce Template", 14 | title: "Payload Ecommerce Template", 15 | }; 16 | 17 | export const mergeOpenGraph = (og?: Metadata["openGraph"]): Metadata["openGraph"] => { 18 | return { 19 | ...defaultOpenGraph, 20 | ...og, 21 | images: og?.images ?? defaultOpenGraph.images, 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/reset-password/page.tsx: -------------------------------------------------------------------------------- 1 | import { ResetPasswordForm } from "@/components/ResetPasswordForm"; 2 | import { redirect } from "@/i18n/routing"; 3 | 4 | export const dynamic = "force-dynamic"; 5 | 6 | const ResetPassword = async ({ 7 | params, 8 | searchParams, 9 | }: { 10 | params: Promise<{ locale: string }>; 11 | searchParams: Promise<{ token?: string; collection: string }>; 12 | }) => { 13 | const { token, collection } = await searchParams; 14 | const { locale } = await params; 15 | if (!token) { 16 | return redirect({ href: "/", locale }); 17 | } 18 | return ; 19 | }; 20 | export default ResetPassword; 21 | -------------------------------------------------------------------------------- /src/components/heros/RenderHero.tsx: -------------------------------------------------------------------------------- 1 | import { HighImpactHero } from "@/components/heros/HighImpact"; 2 | import { LowImpactHero } from "@/components/heros/LowImpact"; 3 | import { MediumImpactHero } from "@/components/heros/MediumImpact"; 4 | 5 | import type { Page } from "@/payload-types"; 6 | 7 | const heroes = { 8 | highImpact: HighImpactHero, 9 | lowImpact: LowImpactHero, 10 | mediumImpact: MediumImpactHero, 11 | }; 12 | 13 | export const RenderHero = (props: Page["hero"]) => { 14 | const { type } = props || {}; 15 | 16 | if (!type || type === "none") return null; 17 | 18 | const HeroToRender = heroes[type]; 19 | 20 | if (!HeroToRender) return null; 21 | 22 | return ; 23 | }; 24 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/WishList/Component.tsx: -------------------------------------------------------------------------------- 1 | import { getLocale } from "next-intl/server"; 2 | import { type ReactNode } from "react"; 3 | 4 | import { type Locale } from "@/i18n/config"; 5 | import { getCachedGlobal } from "@/utilities/getGlobals"; 6 | 7 | import { SlideOver } from "./variants/SlideOver"; 8 | 9 | export const WishList = async () => { 10 | const locale = (await getLocale()) as Locale; 11 | const { cartAndWishlist } = await getCachedGlobal("shopLayout", locale, 1)(); 12 | 13 | let WishListComponent: ReactNode = null; 14 | switch (cartAndWishlist.type) { 15 | case "slideOver": 16 | WishListComponent = ; 17 | break; 18 | } 19 | 20 | return WishListComponent; 21 | }; 22 | -------------------------------------------------------------------------------- /src/fields/linkGroup.ts: -------------------------------------------------------------------------------- 1 | import deepMerge from "@/utilities/deepMerge"; 2 | 3 | import { link, type LinkAppearances } from "./link"; 4 | 5 | import type { ArrayField, Field } from "payload"; 6 | 7 | type LinkGroupType = (options?: { 8 | appearances?: LinkAppearances[] | false; 9 | overrides?: Partial; 10 | }) => Field; 11 | 12 | export const linkGroup: LinkGroupType = ({ appearances, overrides = {} } = {}) => { 13 | const generatedLinkGroup: Field = { 14 | name: "links", 15 | type: "array", 16 | fields: [ 17 | link({ 18 | appearances, 19 | }), 20 | ], 21 | admin: { 22 | initCollapsed: true, 23 | }, 24 | }; 25 | 26 | return deepMerge(generatedLinkGroup, overrides); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/Emails/OrderStatusEmail/index.tsx: -------------------------------------------------------------------------------- 1 | import { Html } from "@react-email/components"; 2 | import { type ReactNode } from "react"; 3 | 4 | import { type Locale } from "@/i18n/config"; 5 | import { type Order } from "@/payload-types"; 6 | import { getCachedGlobal } from "@/utilities/getGlobals"; 7 | 8 | import { Default } from "./variants/Default"; 9 | 10 | export const OrderStatusEmail = async ({ order, locale }: { order: Order; locale: Locale }) => { 11 | const { messages } = await getCachedGlobal("emailMessages", locale, 1)(); 12 | 13 | let Email: ReactNode = ; 14 | 15 | switch (messages.template) { 16 | case "default": 17 | Email = ; 18 | } 19 | return Email; 20 | }; 21 | -------------------------------------------------------------------------------- /src/fields/slug/formatSlug.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import type { FieldHook } from "payload"; 3 | 4 | export const formatSlug = (val: string): string => 5 | val 6 | .replace(/ /g, "-") 7 | .replace(/[^\w-]+/g, "") 8 | .toLowerCase(); 9 | 10 | export const formatSlugHook = 11 | (fallback: string): FieldHook => 12 | ({ data, operation, value }) => { 13 | if (typeof value === "string") { 14 | return formatSlug(value); 15 | } 16 | 17 | if (operation === "create" || !data?.slug) { 18 | const fallbackData = data?.[fallback] || data?.[fallback]; 19 | 20 | if (fallbackData && typeof fallbackData === "string") { 21 | return formatSlug(fallbackData); 22 | } 23 | } 24 | 25 | return value; 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/Emails/WelcomeEmail/index.tsx: -------------------------------------------------------------------------------- 1 | import { Html } from "@react-email/components"; 2 | import { type ReactNode } from "react"; 3 | 4 | import { type Locale } from "@/i18n/config"; 5 | import { type Customer } from "@/payload-types"; 6 | import { getCachedGlobal } from "@/utilities/getGlobals"; 7 | 8 | import { Default } from "./variants/Default"; 9 | 10 | export const WelcomeEmail = async ({ customer, locale }: { customer: Customer; locale: Locale }) => { 11 | const { messages } = await getCachedGlobal("emailMessages", locale, 1)(); 12 | 13 | let Email: ReactNode = ; 14 | 15 | switch (messages.template) { 16 | case "default": 17 | Email = ; 18 | } 19 | return Email; 20 | }; 21 | -------------------------------------------------------------------------------- /src/hooks/formatSlug.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import type { FieldHook } from "payload"; 3 | 4 | const format = (val: string): string => 5 | val 6 | .replace(/ /g, "-") 7 | .replace(/[^\w-]+/g, "") 8 | .toLowerCase(); 9 | 10 | const formatSlug = 11 | (fallback: string): FieldHook => 12 | ({ data, operation, originalDoc, value }) => { 13 | if (typeof value === "string") { 14 | return format(value); 15 | } 16 | 17 | if (operation === "create") { 18 | const fallbackData = data?.[fallback] || originalDoc?.[fallback]; 19 | 20 | if (fallbackData && typeof fallbackData === "string") { 21 | return format(fallbackData); 22 | } 23 | } 24 | 25 | return value; 26 | }; 27 | 28 | export default formatSlug; 29 | -------------------------------------------------------------------------------- /src/blocks/Code/config.ts: -------------------------------------------------------------------------------- 1 | import type { Block } from "payload"; 2 | 3 | export const Code: Block = { 4 | slug: "code", 5 | interfaceName: "CodeBlock", 6 | fields: [ 7 | { 8 | name: "language", 9 | type: "select", 10 | defaultValue: "typescript", 11 | options: [ 12 | { 13 | label: "Typescript", 14 | value: "typescript", 15 | }, 16 | { 17 | label: "Javascript", 18 | value: "javascript", 19 | }, 20 | { 21 | label: "CSS", 22 | value: "css", 23 | }, 24 | ], 25 | }, 26 | { 27 | name: "code", 28 | type: "code", 29 | label: false, 30 | required: true, 31 | localized: true, 32 | }, 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/Media/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React, { Fragment } from "react"; 3 | 4 | import { ImageMedia } from "./ImageMedia"; 5 | import { VideoMedia } from "./VideoMedia"; 6 | 7 | import type { Props } from "./types"; 8 | 9 | export const Media = (props: Props) => { 10 | const { className, htmlElement = "div", resource } = props; 11 | 12 | const isVideo = typeof resource === "object" && resource?.mimeType?.includes("video"); 13 | const Tag = (htmlElement as any) || Fragment; 14 | 15 | return ( 16 | 23 | {isVideo ? : } 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/ProductDetails/types/index.ts: -------------------------------------------------------------------------------- 1 | import { type Media } from "@/payload-types"; 2 | 3 | export type FilledVariant = { 4 | color: 5 | | { 6 | label: string; 7 | slug: string; 8 | colorValue?: string | null; 9 | id?: string | null; 10 | } 11 | | undefined; 12 | size: 13 | | { 14 | label: string; 15 | slug: string; 16 | id?: string | null; 17 | } 18 | | undefined; 19 | slug: string | null | undefined; 20 | stock: number; 21 | image: Media | null | undefined; 22 | pricing: 23 | | { 24 | value: number; 25 | currency: "USD" | "EUR" | "GBP" | "PLN"; 26 | id?: string | null; 27 | }[] 28 | | null 29 | | undefined; 30 | }; 31 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/Checkout/Component.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | import { type ReactNode } from "react"; 3 | 4 | import { type Locale } from "@/i18n/config"; 5 | import { getCachedGlobal } from "@/utilities/getGlobals"; 6 | 7 | import { OneStepWithSummary } from "./variants/OneStepWithSummary"; 8 | 9 | export const Checkout = async ({ locale }: { locale: Locale }) => { 10 | const { checkout } = await getCachedGlobal("shopLayout", locale, 1)(); 11 | 12 | let CheckoutComponent: ReactNode = null; 13 | switch (checkout.type) { 14 | case "OneStepWithSummary": 15 | CheckoutComponent = ; 16 | break; 17 | } 18 | 19 | if (!CheckoutComponent) { 20 | notFound(); 21 | } 22 | 23 | return CheckoutComponent; 24 | }; 25 | -------------------------------------------------------------------------------- /src/lib/getTotalWeight.ts: -------------------------------------------------------------------------------- 1 | import { type Cart } from "@/stores/CartStore/types"; 2 | 3 | import { type FilledProduct } from "./getFilledProducts"; 4 | 5 | export const getTotalWeight = (filledProducts: FilledProduct[], cart: Cart) => 6 | filledProducts.reduce((acc, product) => { 7 | if (product.enableVariantWeights && product.variants) { 8 | const variantWeight = product.variants 9 | .filter((variant) => 10 | cart.some( 11 | (cartProduct) => 12 | cartProduct.id === product.id && cartProduct.choosenVariantSlug === variant.variantSlug, 13 | ), 14 | ) 15 | .reduce((varAcc, variant) => varAcc + (variant.weight ?? 0), 0); 16 | 17 | return acc + variantWeight; 18 | } 19 | 20 | return acc + (product.weight ?? 0); 21 | }, 0); 22 | -------------------------------------------------------------------------------- /src/schemas/ResetPasswordFormSchema.ts: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { z } from "zod"; 3 | 4 | export type ResetPasswordFormData = { 5 | newPassword: string; 6 | confirmPassword: string; 7 | }; 8 | 9 | export const useResetPasswordForm = () => { 10 | const t = useTranslations("ResetPasswordForm.errors"); 11 | 12 | const ResetPasswordForm = z 13 | .object({ 14 | newPassword: z.string().nonempty(t("password-length")).min(8, t("password-length")), 15 | confirmPassword: z.string().nonempty(t("password-length")).min(8, t("password-length")), 16 | }) 17 | .refine((data) => data.newPassword === data.confirmPassword, { 18 | message: t("passwords-mismatch"), 19 | path: ["confirmPassword"], 20 | }); 21 | 22 | return { ResetPasswordForm }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/utilities/getRedirects.ts: -------------------------------------------------------------------------------- 1 | import { unstable_cache } from "next/cache"; 2 | import { getPayload } from "payload"; 3 | 4 | import configPromise from "@payload-config"; 5 | 6 | export async function getRedirects(depth = 1) { 7 | const payload = await getPayload({ config: configPromise }); 8 | 9 | const { docs: redirects } = await payload.find({ 10 | collection: "redirects", 11 | depth, 12 | limit: 0, 13 | pagination: false, 14 | }); 15 | 16 | return redirects; 17 | } 18 | 19 | /** 20 | * Returns a unstable_cache function mapped with the cache tag for 'redirects'. 21 | * 22 | * Cache all redirects together to avoid multiple fetches. 23 | */ 24 | export const getCachedRedirects = () => 25 | unstable_cache(async () => getRedirects(), ["redirects"], { 26 | tags: ["redirects"], 27 | }); 28 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import { type VariantProps, cva } from "class-variance-authority"; 5 | import * as React from "react"; 6 | 7 | import { cn } from "src/utilities/cn"; 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & VariantProps 16 | >(({ className, ...props }, ref) => ( 17 | 18 | )); 19 | Label.displayName = LabelPrimitive.Root.displayName; 20 | 21 | export { Label }; 22 | -------------------------------------------------------------------------------- /src/components/Logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | 3 | type Props = { 4 | className?: string; 5 | loading?: "lazy" | "eager"; 6 | priority?: "auto" | "high" | "low"; 7 | }; 8 | 9 | export const Logo = (props: Props) => { 10 | const { loading: loadingFromProps, priority: priorityFromProps, className } = props; 11 | 12 | const loading = loadingFromProps ?? "lazy"; 13 | const priority = priorityFromProps ?? "low"; 14 | 15 | return ( 16 | /* eslint-disable @next/next/no-img-element */ 17 | Mandala Software House logo 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/Media/types.ts: -------------------------------------------------------------------------------- 1 | import type { Media as MediaType } from "@/payload-types"; 2 | import type { StaticImageData } from "next/image"; 3 | import type { ElementType, Ref } from "react"; 4 | 5 | export type Props = { 6 | alt?: string; 7 | className?: string; 8 | fill?: boolean; // for NextImage only 9 | htmlElement?: ElementType | null; 10 | imgClassName?: string; 11 | onClick?: () => void; 12 | onLoad?: () => void; 13 | loading?: "lazy" | "eager"; // for NextImage only 14 | priority?: boolean; // for NextImage only 15 | ref?: Ref; 16 | resource?: MediaType | string | number; // for Payload media 17 | size?: string; // for NextImage only 18 | src?: StaticImageData; // for static media 19 | videoClassName?: string; 20 | placeholder?: "blur" | "empty"; 21 | }; 22 | -------------------------------------------------------------------------------- /src/globals/(ecommerce)/Layout/ProductList/variants/filters/WithSidebar/components/MobileFunnelFiltersButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FunnelIcon } from "@heroicons/react/20/solid"; 4 | import { useTranslations } from "next-intl"; 5 | 6 | import { useMobileFilters } from "../stores/MobileFiltersContext"; 7 | 8 | export const MobileFunnelFiltersButton = () => { 9 | const { setMobileFiltersOpen } = useMobileFilters(); 10 | const t = useTranslations("ProductList"); 11 | return ( 12 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/app/(frontend)/[locale]/(with-cart)/account/orders-data/page.tsx: -------------------------------------------------------------------------------- 1 | import { revalidateTag } from "next/cache"; 2 | import { setRequestLocale } from "next-intl/server"; 3 | 4 | import { OrdersData } from "@/globals/(ecommerce)/Layout/ClientPanel/OrdersData/Component"; 5 | import { type Locale } from "@/i18n/config"; 6 | import { getCustomer } from "@/utilities/getCustomer"; 7 | 8 | async function updateCustomerData() { 9 | "use server"; 10 | revalidateTag("user-auth"); 11 | } 12 | 13 | const OrdersDataPage = async ({ params }: { params: Promise<{ locale: Locale }> }) => { 14 | const user = await getCustomer(); 15 | const { locale } = await params; 16 | setRequestLocale(locale); 17 | if (!user) return null; 18 | return ; 19 | }; 20 | export default OrdersDataPage; 21 | -------------------------------------------------------------------------------- /src/components/(ecommerce)/LogoutButton/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import { type ReactNode } from "react"; 5 | 6 | import { Button } from "@/components/ui/button"; 7 | import { useRouter } from "@/i18n/routing"; 8 | 9 | export const LogoutButton = ({ 10 | className, 11 | children, 12 | ...props 13 | }: { 14 | children: ReactNode; 15 | className?: string; 16 | [key: string]: unknown; 17 | }) => { 18 | const router = useRouter(); 19 | const handleLogout = async () => { 20 | try { 21 | await axios.post("/api/customers/logout"); 22 | router.refresh(); 23 | } catch (error) { 24 | console.log(error); 25 | } 26 | }; 27 | 28 | return ( 29 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/blocks/CallToAction/config.ts: -------------------------------------------------------------------------------- 1 | import { defaultLexical } from "@/fields/defaultLexical"; 2 | import { linkGroup } from "@/fields/linkGroup"; 3 | import { marginFields, paddingFields } from "@/fields/spacingFields"; 4 | 5 | import type { Block } from "payload"; 6 | 7 | export const CallToAction: Block = { 8 | slug: "cta", 9 | interfaceName: "CallToActionBlock", 10 | fields: [ 11 | { 12 | name: "richText", 13 | type: "richText", 14 | editor: defaultLexical, 15 | localized: true, 16 | label: false, 17 | }, 18 | linkGroup({ 19 | appearances: ["default", "outline"], 20 | overrides: { 21 | maxRows: 2, 22 | }, 23 | }), 24 | marginFields, 25 | paddingFields, 26 | ], 27 | labels: { 28 | plural: "Calls to Action", 29 | singular: "Call to Action", 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/(payload)/admin/[[...segments]]/page.tsx: -------------------------------------------------------------------------------- 1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ 2 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ 3 | import type { Metadata } from 'next' 4 | 5 | import config from '@payload-config' 6 | import { RootPage, generatePageMetadata } from '@payloadcms/next/views' 7 | import { importMap } from '../importMap' 8 | 9 | type Args = { 10 | params: Promise<{ 11 | segments: string[] 12 | }> 13 | searchParams: Promise<{ 14 | [key: string]: string | string[] 15 | }> 16 | } 17 | 18 | export const generateMetadata = ({ params, searchParams }: Args): Promise => 19 | generatePageMetadata({ config, params, searchParams }) 20 | 21 | const Page = ({ params, searchParams }: Args) => 22 | RootPage({ config, params, searchParams, importMap }) 23 | 24 | export default Page 25 | -------------------------------------------------------------------------------- /src/components/LocaleSwitch/LocaleSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { useLocale, useTranslations } from "next-intl"; 2 | import { Suspense } from "react"; 3 | 4 | import { SelectItem } from "@/components/ui/select"; 5 | import { routing } from "@/i18n/routing"; 6 | 7 | import { LocaleSwitchSelect } from "./LocaleSwitchSelect"; 8 | 9 | export function LocaleSwitch() { 10 | const t = useTranslations("LocaleSwitch"); 11 | const locale = useLocale(); 12 | 13 | return ( 14 | //TODO; better fallback 15 | 16 | 17 | {routing.locales.map((cur) => ( 18 | 19 | {t("locale", { locale: cur })} 20 | 21 | ))} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "src/utilities/cn"; 4 | 5 | export type TextareaProps = {} & React.TextareaHTMLAttributes; 6 | 7 | const Textarea = React.forwardRef(({ className, ...props }, ref) => { 8 | return ( 9 |