From 3777ec9980d91d528c5a8bdd007649d39dd70543 Mon Sep 17 00:00:00 2001 From: Paul Popus Date: Mon, 28 Oct 2024 23:53:57 -0600 Subject: [PATCH] chore(templates): remove old ecommerce template --- templates/ecommerce/.editorconfig | 10 - templates/ecommerce/.env.example | 31 - templates/ecommerce/.eslintrc.cjs | 38 - templates/ecommerce/.gitignore | 8 - templates/ecommerce/.prettierignore | 14 - templates/ecommerce/.prettierrc.json | 6 - templates/ecommerce/.vscode/launch.json | 14 - templates/ecommerce/Dockerfile | 27 - templates/ecommerce/README.md | 329 - templates/ecommerce/csp.js | 36 - templates/ecommerce/docker-compose.yml | 31 - templates/ecommerce/eject.ts | 36 - templates/ecommerce/next-env.d.ts | 5 - templates/ecommerce/next.config.js | 50 - templates/ecommerce/nodemon.json | 8 - templates/ecommerce/package.json | 65 - templates/ecommerce/public/favicon.ico | Bin 17223 -> 0 bytes templates/ecommerce/public/favicon.svg | 15 - templates/ecommerce/public/static-image.jpg | Bin 93586 -> 0 bytes templates/ecommerce/redirects.js | 81 - .../ecommerce/src/app/(pages)/[slug]/page.tsx | 98 - .../account/AccountForm/index.module.scss | 24 - .../app/(pages)/account/AccountForm/index.tsx | 161 - .../src/app/(pages)/account/index.module.scss | 25 - .../src/app/(pages)/account/page.tsx | 115 - .../(pages)/cart/CartPage/index.module.scss | 112 - .../src/app/(pages)/cart/CartPage/index.tsx | 157 - .../src/app/(pages)/cart/index.module.scss | 5 - .../ecommerce/src/app/(pages)/cart/page.tsx | 119 - .../checkout/CheckoutForm/index.module.scss | 13 - .../(pages)/checkout/CheckoutForm/index.tsx | 115 - .../checkout/CheckoutPage/index.module.scss | 81 - .../(pages)/checkout/CheckoutPage/index.tsx | 196 - .../app/(pages)/checkout/index.module.scss | 5 - .../src/app/(pages)/checkout/page.tsx | 117 - .../CreateAccountForm/index.module.scss | 22 - .../CreateAccountForm/index.tsx | 120 - .../(pages)/create-account/index.module.scss | 5 - .../src/app/(pages)/create-account/page.tsx | 35 - .../(pages)/login/LoginForm/index.module.scss | 22 - .../src/app/(pages)/login/LoginForm/index.tsx | 85 - .../src/app/(pages)/login/index.module.scss | 9 - .../ecommerce/src/app/(pages)/login/page.tsx | 33 - .../app/(pages)/logout/LogoutPage/index.tsx | 53 - .../src/app/(pages)/logout/index.module.scss | 3 - .../ecommerce/src/app/(pages)/logout/page.tsx | 39 - .../ecommerce/src/app/(pages)/not-found.tsx | 15 - .../OrderConfirmationPage/index.module.scss | 7 - .../OrderConfirmationPage/index.tsx | 58 - .../order-confirmation/index.module.scss | 5 - .../app/(pages)/order-confirmation/page.tsx | 27 - .../app/(pages)/orders/[id]/index.module.scss | 122 - .../src/app/(pages)/orders/[id]/page.tsx | 143 - .../src/app/(pages)/orders/index.module.scss | 65 - .../ecommerce/src/app/(pages)/orders/page.tsx | 100 - .../src/app/(pages)/products/[slug]/page.tsx | 114 - .../RecoverPasswordForm/index.module.scss | 22 - .../RecoverPasswordForm/index.tsx | 87 - .../recover-password/index.module.scss | 5 - .../src/app/(pages)/recover-password/page.tsx | 25 - .../ResetPasswordForm/index.module.scss | 13 - .../ResetPasswordForm/index.tsx | 86 - .../(pages)/reset-password/index.module.scss | 3 - .../src/app/(pages)/reset-password/page.tsx | 27 - .../app/(pages)/styleguide/buttons/page.tsx | 43 - .../styleguide/call-to-action/page.tsx | 100 - .../(pages)/styleguide/content-block/page.tsx | 48 - .../(pages)/styleguide/media-block/page.tsx | 45 - .../app/(pages)/styleguide/message/page.tsx | 44 - .../src/app/(pages)/styleguide/page.tsx | 40 - .../(pages)/styleguide/typography/page.tsx | 55 - templates/ecommerce/src/app/_api/fetchDoc.ts | 66 - templates/ecommerce/src/app/_api/fetchDocs.ts | 59 - .../ecommerce/src/app/_api/fetchGlobals.ts | 103 - templates/ecommerce/src/app/_api/getMe.ts | 50 - templates/ecommerce/src/app/_api/shared.ts | 3 - templates/ecommerce/src/app/_api/token.ts | 1 - .../_blocks/ArchiveBlock/index.module.scss | 13 - .../src/app/_blocks/ArchiveBlock/index.tsx | 46 - .../src/app/_blocks/ArchiveBlock/types.ts | 3 - .../_blocks/CallToAction/index.module.scss | 61 - .../src/app/_blocks/CallToAction/index.tsx | 38 - .../src/app/_blocks/Content/index.module.scss | 42 - .../src/app/_blocks/Content/index.tsx | 37 - .../app/_blocks/MediaBlock/index.module.scss | 8 - .../src/app/_blocks/MediaBlock/index.tsx | 42 - .../_blocks/RelatedProducts/index.module.scss | 58 - .../src/app/_blocks/RelatedProducts/index.tsx | 52 - .../AddToCartButton/index.module.scss | 14 - .../app/_components/AddToCartButton/index.tsx | 59 - .../_components/AdminBar/index.module.scss | 53 - .../src/app/_components/AdminBar/index.tsx | 64 - .../BackgroundColor/index.module.scss | 11 - .../app/_components/BackgroundColor/index.tsx | 20 - .../src/app/_components/Blocks/index.tsx | 86 - .../app/_components/Button/index.module.scss | 72 - .../src/app/_components/Button/index.tsx | 78 - .../app/_components/Card/index.module.scss | 93 - .../src/app/_components/Card/index.tsx | 120 - .../_components/CartLink/index.module.scss | 9 - .../src/app/_components/CartLink/index.tsx | 30 - .../src/app/_components/Chevron/index.tsx | 26 - .../CollectionArchive/index.module.scss | 73 - .../_components/CollectionArchive/index.tsx | 210 - .../app/_components/Footer/index.module.scss | 43 - .../src/app/_components/Footer/index.tsx | 58 - .../app/_components/Gutter/index.module.scss | 13 - .../src/app/_components/Gutter/index.tsx | 35 - .../src/app/_components/HR/index.module.scss | 8 - .../src/app/_components/HR/index.tsx | 11 - .../_components/Header/Nav/index.module.scss | 20 - .../src/app/_components/Header/Nav/index.tsx | 42 - .../app/_components/Header/index.module.scss | 22 - .../src/app/_components/Header/index.tsx | 48 - .../src/app/_components/Hero/index.tsx | 25 - .../app/_components/Input/index.module.scss | 61 - .../src/app/_components/Input/index.tsx | 61 - .../app/_components/Label/index.module.scss | 5 - .../src/app/_components/Label/index.tsx | 7 - .../_components/LargeBody/index.module.scss | 5 - .../src/app/_components/LargeBody/index.tsx | 7 - .../src/app/_components/Link/index.tsx | 67 - .../LoadingShimmer/index.module.scss | 29 - .../app/_components/LoadingShimmer/index.tsx | 18 - .../_components/Media/Image/index.module.scss | 7 - .../src/app/_components/Media/Image/index.tsx | 77 - .../_components/Media/Video/index.module.scss | 11 - .../src/app/_components/Media/Video/index.tsx | 45 - .../src/app/_components/Media/index.tsx | 27 - .../src/app/_components/Media/types.ts | 20 - .../app/_components/Message/index.module.scss | 46 - .../src/app/_components/Message/index.tsx | 33 - .../_components/PageRange/index.module.scss | 21 - .../src/app/_components/PageRange/index.tsx | 56 - .../_components/Pagination/index.module.scss | 29 - .../src/app/_components/Pagination/index.tsx | 45 - .../app/_components/PaywallBlocks/index.tsx | 121 - .../app/_components/Price/index.module.scss | 29 - .../src/app/_components/Price/index.tsx | 80 - .../RemoveFromCartButton/index.module.scss | 7 - .../RemoveFromCartButton/index.tsx | 33 - .../_components/RenderParams/Component.tsx | 51 - .../RenderParams/index.module.scss | 9 - .../app/_components/RenderParams/index.tsx | 17 - .../_components/RichText/index.module.scss | 8 - .../src/app/_components/RichText/index.tsx | 18 - .../app/_components/RichText/serialize.tsx | 110 - .../VerticalPadding/index.module.scss | 15 - .../app/_components/VerticalPadding/index.tsx | 29 - .../app/_components/icons/Chevron/index.tsx | 9 - .../src/app/_components/icons/Menu/index.tsx | 11 - templates/ecommerce/src/app/_css/app.scss | 123 - templates/ecommerce/src/app/_css/colors.scss | 105 - templates/ecommerce/src/app/_css/common.scss | 2 - templates/ecommerce/src/app/_css/queries.scss | 30 - templates/ecommerce/src/app/_css/theme.scss | 237 - templates/ecommerce/src/app/_css/type.scss | 110 - .../ecommerce/src/app/_graphql/blocks.ts | 74 - templates/ecommerce/src/app/_graphql/cart.ts | 13 - .../ecommerce/src/app/_graphql/categories.ts | 8 - .../ecommerce/src/app/_graphql/globals.ts | 43 - templates/ecommerce/src/app/_graphql/link.ts | 20 - templates/ecommerce/src/app/_graphql/me.ts | 14 - templates/ecommerce/src/app/_graphql/media.ts | 12 - templates/ecommerce/src/app/_graphql/meta.ts | 9 - .../ecommerce/src/app/_graphql/orders.ts | 27 - templates/ecommerce/src/app/_graphql/pages.ts | 41 - .../ecommerce/src/app/_graphql/products.ts | 56 - .../app/_heros/HighImpact/index.module.scss | 55 - .../src/app/_heros/HighImpact/index.tsx | 43 - .../app/_heros/LowImpact/index.module.scss | 4 - .../src/app/_heros/LowImpact/index.tsx | 20 - .../app/_heros/MediumImpact/index.module.scss | 62 - .../src/app/_heros/MediumImpact/index.tsx | 35 - .../src/app/_heros/Product/index.module.scss | 84 - .../src/app/_heros/Product/index.tsx | 94 - .../src/app/_providers/Auth/index.tsx | 218 - .../src/app/_providers/Cart/index.tsx | 278 - .../src/app/_providers/Cart/reducer.ts | 122 - .../app/_providers/Theme/InitTheme/index.tsx | 49 - .../Theme/ThemeSelector/index.module.scss | 52 - .../_providers/Theme/ThemeSelector/index.tsx | 55 - .../_providers/Theme/ThemeSelector/types.ts | 5 - .../src/app/_providers/Theme/index.tsx | 57 - .../src/app/_providers/Theme/shared.ts | 17 - .../src/app/_providers/Theme/types.ts | 10 - .../ecommerce/src/app/_providers/index.tsx | 19 - .../ecommerce/src/app/_utilities/canUseDOM.ts | 1 - .../src/app/_utilities/formatDateTime.ts | 20 - .../src/app/_utilities/generateMeta.ts | 32 - .../ecommerce/src/app/_utilities/getMeUser.ts | 41 - .../src/app/_utilities/mergeOpenGraph.ts | 21 - .../src/app/_utilities/toKebabCase.ts | 5 - templates/ecommerce/src/app/cssVariables.js | 17 - templates/ecommerce/src/app/layout.tsx | 42 - .../src/app/next/exit-preview/route.ts | 6 - .../ecommerce/src/app/next/preview/route.ts | 49 - .../src/app/next/revalidate/route.ts | 28 - templates/ecommerce/src/app/page.tsx | 5 - .../ecommerce/src/payload/access/admins.ts | 11 - .../src/payload/access/adminsOrLoggedIn.ts | 13 - .../ecommerce/src/payload/access/anyone.ts | 3 - .../src/payload/blocks/ArchiveBlock/index.ts | 100 - .../src/payload/blocks/CallToAction/index.ts | 23 - .../src/payload/blocks/Content/index.ts | 55 - .../src/payload/blocks/MediaBlock/index.ts | 31 - .../src/payload/collections/Categories.ts | 19 - .../src/payload/collections/Media.ts | 30 - .../Orders/access/adminsOrOrderedBy.ts | 15 - .../collections/Orders/hooks/clearUserCart.ts | 30 - .../Orders/hooks/populateOrderedBy.ts | 11 - .../Orders/hooks/updateUserPurchases.ts | 35 - .../src/payload/collections/Orders/index.ts | 76 - .../Orders/ui/LinkToPaymentIntent.tsx | 56 - .../Pages/access/adminsOrPublished.ts | 15 - .../collections/Pages/hooks/revalidatePage.ts | 15 - .../src/payload/collections/Pages/index.ts | 86 - .../Products/access/checkUserPurchases.ts | 25 - .../Products/hooks/beforeChange.ts | 56 - .../Products/hooks/deleteProductFromCarts.ts | 36 - .../Products/hooks/revalidateProduct.ts | 15 - .../src/payload/collections/Products/index.ts | 159 - .../collections/Products/ui/ProductSelect.tsx | 120 - .../collections/Users/access/adminsAndUser.ts | 21 - .../payload/collections/Users/checkRole.ts | 16 - .../collections/Users/endpoints/customer.ts | 80 - .../Users/hooks/createStripeCustomer.ts | 42 - .../Users/hooks/ensureFirstUserIsAdmin.ts | 22 - .../Users/hooks/loginAfterCreate.ts | 29 - .../Users/hooks/resolveDuplicatePurchases.ts | 15 - .../src/payload/collections/Users/index.ts | 158 - .../collections/Users/ui/CustomerSelect.tsx | 119 - .../BeforeDashboard/SeedButton/index.tsx | 40 - .../components/BeforeDashboard/index.scss | 24 - .../components/BeforeDashboard/index.tsx | 99 - .../payload/components/BeforeLogin/index.scss | 0 .../payload/components/BeforeLogin/index.tsx | 16 - templates/ecommerce/src/payload/dotenv.js | 3 - .../ecommerce/src/payload/emptyModuleMock.js | 4 - .../endpoints/create-payment-intent.ts | 110 - .../src/payload/endpoints/customers.ts | 34 - .../src/payload/endpoints/products.ts | 34 - .../ecommerce/src/payload/endpoints/seed.ts | 21 - .../ecommerce/src/payload/fields/hero.ts | 59 - .../src/payload/fields/invertBackground.ts | 6 - .../ecommerce/src/payload/fields/link.ts | 150 - .../ecommerce/src/payload/fields/linkGroup.ts | 28 - .../src/payload/fields/richText/elements.ts | 18 - .../src/payload/fields/richText/index.ts | 109 - .../fields/richText/label/Button/index.tsx | 16 - .../fields/richText/label/Element/index.scss | 9 - .../fields/richText/label/Element/index.tsx | 16 - .../fields/richText/label/Icon/index.tsx | 18 - .../payload/fields/richText/label/index.ts | 14 - .../payload/fields/richText/label/plugin.ts | 21 - .../richText/largeBody/Button/index.tsx | 16 - .../richText/largeBody/Element/index.scss | 5 - .../richText/largeBody/Element/index.tsx | 16 - .../fields/richText/largeBody/Icon/index.tsx | 18 - .../fields/richText/largeBody/index.ts | 14 - .../fields/richText/largeBody/plugin.ts | 21 - .../src/payload/fields/richText/leaves.ts | 5 - .../ecommerce/src/payload/fields/slug.ts | 23 - .../src/payload/generated-schema.graphql | 10545 ---------------- .../ecommerce/src/payload/globals/Footer.ts | 22 - .../ecommerce/src/payload/globals/Header.ts | 22 - .../ecommerce/src/payload/globals/Settings.ts | 22 - .../src/payload/hooks/populateArchiveBlock.ts | 62 - .../ecommerce/src/payload/payload-types.ts | 518 - .../ecommerce/src/payload/payload.config.ts | 148 - .../ecommerce/src/payload/seed/cart-page.ts | 118 - .../ecommerce/src/payload/seed/cart-static.ts | 109 - .../ecommerce/src/payload/seed/home-static.ts | 126 - templates/ecommerce/src/payload/seed/home.ts | 505 - .../ecommerce/src/payload/seed/image-1.jpg | Bin 93586 -> 0 bytes .../ecommerce/src/payload/seed/image-1.ts | 40 - .../ecommerce/src/payload/seed/image-2.jpg | Bin 349328 -> 0 bytes .../ecommerce/src/payload/seed/image-2.ts | 40 - .../ecommerce/src/payload/seed/image-3.jpg | Bin 248185 -> 0 bytes .../ecommerce/src/payload/seed/image-3.ts | 40 - templates/ecommerce/src/payload/seed/index.ts | 233 - .../ecommerce/src/payload/seed/product-1.ts | 38 - .../ecommerce/src/payload/seed/product-2.ts | 76 - .../ecommerce/src/payload/seed/product-3.ts | 76 - .../src/payload/seed/products-page.ts | 61 - .../payload/stripe/webhooks/priceUpdated.ts | 69 - .../payload/stripe/webhooks/productUpdated.ts | 81 - .../src/payload/utilities/deepMerge.ts | 34 - .../src/payload/utilities/formatSlug.ts | 27 - .../src/payload/utilities/revalidate.ts | 27 - templates/ecommerce/src/server.default.ts | 44 - templates/ecommerce/src/server.ts | 60 - templates/ecommerce/tsconfig.json | 32 - templates/ecommerce/tsconfig.server.json | 10 - templates/ecommerce/yarn.lock | 9870 --------------- 295 files changed, 35150 deletions(-) delete mode 100644 templates/ecommerce/.editorconfig delete mode 100644 templates/ecommerce/.env.example delete mode 100644 templates/ecommerce/.eslintrc.cjs delete mode 100644 templates/ecommerce/.gitignore delete mode 100644 templates/ecommerce/.prettierignore delete mode 100644 templates/ecommerce/.prettierrc.json delete mode 100644 templates/ecommerce/.vscode/launch.json delete mode 100644 templates/ecommerce/Dockerfile delete mode 100644 templates/ecommerce/README.md delete mode 100644 templates/ecommerce/csp.js delete mode 100644 templates/ecommerce/docker-compose.yml delete mode 100644 templates/ecommerce/eject.ts delete mode 100644 templates/ecommerce/next-env.d.ts delete mode 100644 templates/ecommerce/next.config.js delete mode 100644 templates/ecommerce/nodemon.json delete mode 100644 templates/ecommerce/package.json delete mode 100644 templates/ecommerce/public/favicon.ico delete mode 100644 templates/ecommerce/public/favicon.svg delete mode 100644 templates/ecommerce/public/static-image.jpg delete mode 100644 templates/ecommerce/redirects.js delete mode 100644 templates/ecommerce/src/app/(pages)/[slug]/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/account/AccountForm/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/account/AccountForm/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/account/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/account/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/cart/CartPage/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/cart/CartPage/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/cart/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/cart/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/checkout/CheckoutForm/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/checkout/CheckoutForm/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/checkout/CheckoutPage/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/checkout/CheckoutPage/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/checkout/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/checkout/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/create-account/CreateAccountForm/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/create-account/CreateAccountForm/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/create-account/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/create-account/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/login/LoginForm/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/login/LoginForm/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/login/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/login/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/logout/LogoutPage/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/logout/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/logout/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/not-found.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/order-confirmation/OrderConfirmationPage/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/order-confirmation/OrderConfirmationPage/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/order-confirmation/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/order-confirmation/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/orders/[id]/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/orders/[id]/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/orders/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/orders/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/products/[slug]/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/recover-password/RecoverPasswordForm/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/recover-password/RecoverPasswordForm/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/recover-password/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/recover-password/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/reset-password/ResetPasswordForm/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/reset-password/ResetPasswordForm/index.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/reset-password/index.module.scss delete mode 100644 templates/ecommerce/src/app/(pages)/reset-password/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/buttons/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/call-to-action/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/content-block/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/media-block/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/message/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/page.tsx delete mode 100644 templates/ecommerce/src/app/(pages)/styleguide/typography/page.tsx delete mode 100644 templates/ecommerce/src/app/_api/fetchDoc.ts delete mode 100644 templates/ecommerce/src/app/_api/fetchDocs.ts delete mode 100644 templates/ecommerce/src/app/_api/fetchGlobals.ts delete mode 100644 templates/ecommerce/src/app/_api/getMe.ts delete mode 100644 templates/ecommerce/src/app/_api/shared.ts delete mode 100644 templates/ecommerce/src/app/_api/token.ts delete mode 100644 templates/ecommerce/src/app/_blocks/ArchiveBlock/index.module.scss delete mode 100644 templates/ecommerce/src/app/_blocks/ArchiveBlock/index.tsx delete mode 100644 templates/ecommerce/src/app/_blocks/ArchiveBlock/types.ts delete mode 100644 templates/ecommerce/src/app/_blocks/CallToAction/index.module.scss delete mode 100644 templates/ecommerce/src/app/_blocks/CallToAction/index.tsx delete mode 100644 templates/ecommerce/src/app/_blocks/Content/index.module.scss delete mode 100644 templates/ecommerce/src/app/_blocks/Content/index.tsx delete mode 100644 templates/ecommerce/src/app/_blocks/MediaBlock/index.module.scss delete mode 100644 templates/ecommerce/src/app/_blocks/MediaBlock/index.tsx delete mode 100644 templates/ecommerce/src/app/_blocks/RelatedProducts/index.module.scss delete mode 100644 templates/ecommerce/src/app/_blocks/RelatedProducts/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/AddToCartButton/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/AddToCartButton/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/AdminBar/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/AdminBar/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/BackgroundColor/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/BackgroundColor/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Blocks/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Button/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Button/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Card/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Card/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/CartLink/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/CartLink/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Chevron/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/CollectionArchive/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/CollectionArchive/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Footer/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Footer/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Gutter/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Gutter/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/HR/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/HR/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Header/Nav/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Header/Nav/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Header/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Header/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Hero/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Input/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Input/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Label/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Label/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/LargeBody/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/LargeBody/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Link/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/LoadingShimmer/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/LoadingShimmer/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Media/Image/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Media/Image/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Media/Video/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Media/Video/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Media/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Media/types.ts delete mode 100644 templates/ecommerce/src/app/_components/Message/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Message/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/PageRange/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/PageRange/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Pagination/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Pagination/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/PaywallBlocks/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/Price/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/Price/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/RemoveFromCartButton/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/RemoveFromCartButton/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/RenderParams/Component.tsx delete mode 100644 templates/ecommerce/src/app/_components/RenderParams/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/RenderParams/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/RichText/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/RichText/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/RichText/serialize.tsx delete mode 100644 templates/ecommerce/src/app/_components/VerticalPadding/index.module.scss delete mode 100644 templates/ecommerce/src/app/_components/VerticalPadding/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/icons/Chevron/index.tsx delete mode 100644 templates/ecommerce/src/app/_components/icons/Menu/index.tsx delete mode 100644 templates/ecommerce/src/app/_css/app.scss delete mode 100644 templates/ecommerce/src/app/_css/colors.scss delete mode 100644 templates/ecommerce/src/app/_css/common.scss delete mode 100644 templates/ecommerce/src/app/_css/queries.scss delete mode 100644 templates/ecommerce/src/app/_css/theme.scss delete mode 100644 templates/ecommerce/src/app/_css/type.scss delete mode 100644 templates/ecommerce/src/app/_graphql/blocks.ts delete mode 100644 templates/ecommerce/src/app/_graphql/cart.ts delete mode 100644 templates/ecommerce/src/app/_graphql/categories.ts delete mode 100644 templates/ecommerce/src/app/_graphql/globals.ts delete mode 100644 templates/ecommerce/src/app/_graphql/link.ts delete mode 100644 templates/ecommerce/src/app/_graphql/me.ts delete mode 100644 templates/ecommerce/src/app/_graphql/media.ts delete mode 100644 templates/ecommerce/src/app/_graphql/meta.ts delete mode 100644 templates/ecommerce/src/app/_graphql/orders.ts delete mode 100644 templates/ecommerce/src/app/_graphql/pages.ts delete mode 100644 templates/ecommerce/src/app/_graphql/products.ts delete mode 100644 templates/ecommerce/src/app/_heros/HighImpact/index.module.scss delete mode 100644 templates/ecommerce/src/app/_heros/HighImpact/index.tsx delete mode 100644 templates/ecommerce/src/app/_heros/LowImpact/index.module.scss delete mode 100644 templates/ecommerce/src/app/_heros/LowImpact/index.tsx delete mode 100644 templates/ecommerce/src/app/_heros/MediumImpact/index.module.scss delete mode 100644 templates/ecommerce/src/app/_heros/MediumImpact/index.tsx delete mode 100644 templates/ecommerce/src/app/_heros/Product/index.module.scss delete mode 100644 templates/ecommerce/src/app/_heros/Product/index.tsx delete mode 100644 templates/ecommerce/src/app/_providers/Auth/index.tsx delete mode 100644 templates/ecommerce/src/app/_providers/Cart/index.tsx delete mode 100644 templates/ecommerce/src/app/_providers/Cart/reducer.ts delete mode 100644 templates/ecommerce/src/app/_providers/Theme/InitTheme/index.tsx delete mode 100644 templates/ecommerce/src/app/_providers/Theme/ThemeSelector/index.module.scss delete mode 100644 templates/ecommerce/src/app/_providers/Theme/ThemeSelector/index.tsx delete mode 100644 templates/ecommerce/src/app/_providers/Theme/ThemeSelector/types.ts delete mode 100644 templates/ecommerce/src/app/_providers/Theme/index.tsx delete mode 100644 templates/ecommerce/src/app/_providers/Theme/shared.ts delete mode 100644 templates/ecommerce/src/app/_providers/Theme/types.ts delete mode 100644 templates/ecommerce/src/app/_providers/index.tsx delete mode 100644 templates/ecommerce/src/app/_utilities/canUseDOM.ts delete mode 100644 templates/ecommerce/src/app/_utilities/formatDateTime.ts delete mode 100644 templates/ecommerce/src/app/_utilities/generateMeta.ts delete mode 100644 templates/ecommerce/src/app/_utilities/getMeUser.ts delete mode 100644 templates/ecommerce/src/app/_utilities/mergeOpenGraph.ts delete mode 100644 templates/ecommerce/src/app/_utilities/toKebabCase.ts delete mode 100644 templates/ecommerce/src/app/cssVariables.js delete mode 100644 templates/ecommerce/src/app/layout.tsx delete mode 100644 templates/ecommerce/src/app/next/exit-preview/route.ts delete mode 100644 templates/ecommerce/src/app/next/preview/route.ts delete mode 100644 templates/ecommerce/src/app/next/revalidate/route.ts delete mode 100644 templates/ecommerce/src/app/page.tsx delete mode 100644 templates/ecommerce/src/payload/access/admins.ts delete mode 100644 templates/ecommerce/src/payload/access/adminsOrLoggedIn.ts delete mode 100644 templates/ecommerce/src/payload/access/anyone.ts delete mode 100644 templates/ecommerce/src/payload/blocks/ArchiveBlock/index.ts delete mode 100644 templates/ecommerce/src/payload/blocks/CallToAction/index.ts delete mode 100644 templates/ecommerce/src/payload/blocks/Content/index.ts delete mode 100644 templates/ecommerce/src/payload/blocks/MediaBlock/index.ts delete mode 100644 templates/ecommerce/src/payload/collections/Categories.ts delete mode 100644 templates/ecommerce/src/payload/collections/Media.ts delete mode 100644 templates/ecommerce/src/payload/collections/Orders/access/adminsOrOrderedBy.ts delete mode 100644 templates/ecommerce/src/payload/collections/Orders/hooks/clearUserCart.ts delete mode 100644 templates/ecommerce/src/payload/collections/Orders/hooks/populateOrderedBy.ts delete mode 100644 templates/ecommerce/src/payload/collections/Orders/hooks/updateUserPurchases.ts delete mode 100644 templates/ecommerce/src/payload/collections/Orders/index.ts delete mode 100644 templates/ecommerce/src/payload/collections/Orders/ui/LinkToPaymentIntent.tsx delete mode 100644 templates/ecommerce/src/payload/collections/Pages/access/adminsOrPublished.ts delete mode 100644 templates/ecommerce/src/payload/collections/Pages/hooks/revalidatePage.ts delete mode 100644 templates/ecommerce/src/payload/collections/Pages/index.ts delete mode 100644 templates/ecommerce/src/payload/collections/Products/access/checkUserPurchases.ts delete mode 100644 templates/ecommerce/src/payload/collections/Products/hooks/beforeChange.ts delete mode 100644 templates/ecommerce/src/payload/collections/Products/hooks/deleteProductFromCarts.ts delete mode 100644 templates/ecommerce/src/payload/collections/Products/hooks/revalidateProduct.ts delete mode 100644 templates/ecommerce/src/payload/collections/Products/index.ts delete mode 100644 templates/ecommerce/src/payload/collections/Products/ui/ProductSelect.tsx delete mode 100644 templates/ecommerce/src/payload/collections/Users/access/adminsAndUser.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/checkRole.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/endpoints/customer.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/hooks/createStripeCustomer.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/hooks/ensureFirstUserIsAdmin.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/hooks/loginAfterCreate.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/hooks/resolveDuplicatePurchases.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/index.ts delete mode 100644 templates/ecommerce/src/payload/collections/Users/ui/CustomerSelect.tsx delete mode 100644 templates/ecommerce/src/payload/components/BeforeDashboard/SeedButton/index.tsx delete mode 100644 templates/ecommerce/src/payload/components/BeforeDashboard/index.scss delete mode 100644 templates/ecommerce/src/payload/components/BeforeDashboard/index.tsx delete mode 100644 templates/ecommerce/src/payload/components/BeforeLogin/index.scss delete mode 100644 templates/ecommerce/src/payload/components/BeforeLogin/index.tsx delete mode 100644 templates/ecommerce/src/payload/dotenv.js delete mode 100644 templates/ecommerce/src/payload/emptyModuleMock.js delete mode 100644 templates/ecommerce/src/payload/endpoints/create-payment-intent.ts delete mode 100644 templates/ecommerce/src/payload/endpoints/customers.ts delete mode 100644 templates/ecommerce/src/payload/endpoints/products.ts delete mode 100644 templates/ecommerce/src/payload/endpoints/seed.ts delete mode 100644 templates/ecommerce/src/payload/fields/hero.ts delete mode 100644 templates/ecommerce/src/payload/fields/invertBackground.ts delete mode 100644 templates/ecommerce/src/payload/fields/link.ts delete mode 100644 templates/ecommerce/src/payload/fields/linkGroup.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/elements.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/index.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/label/Button/index.tsx delete mode 100644 templates/ecommerce/src/payload/fields/richText/label/Element/index.scss delete mode 100644 templates/ecommerce/src/payload/fields/richText/label/Element/index.tsx delete mode 100644 templates/ecommerce/src/payload/fields/richText/label/Icon/index.tsx delete mode 100644 templates/ecommerce/src/payload/fields/richText/label/index.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/label/plugin.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/largeBody/Button/index.tsx delete mode 100644 templates/ecommerce/src/payload/fields/richText/largeBody/Element/index.scss delete mode 100644 templates/ecommerce/src/payload/fields/richText/largeBody/Element/index.tsx delete mode 100644 templates/ecommerce/src/payload/fields/richText/largeBody/Icon/index.tsx delete mode 100644 templates/ecommerce/src/payload/fields/richText/largeBody/index.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/largeBody/plugin.ts delete mode 100644 templates/ecommerce/src/payload/fields/richText/leaves.ts delete mode 100644 templates/ecommerce/src/payload/fields/slug.ts delete mode 100644 templates/ecommerce/src/payload/generated-schema.graphql delete mode 100644 templates/ecommerce/src/payload/globals/Footer.ts delete mode 100644 templates/ecommerce/src/payload/globals/Header.ts delete mode 100644 templates/ecommerce/src/payload/globals/Settings.ts delete mode 100644 templates/ecommerce/src/payload/hooks/populateArchiveBlock.ts delete mode 100644 templates/ecommerce/src/payload/payload-types.ts delete mode 100644 templates/ecommerce/src/payload/payload.config.ts delete mode 100644 templates/ecommerce/src/payload/seed/cart-page.ts delete mode 100644 templates/ecommerce/src/payload/seed/cart-static.ts delete mode 100644 templates/ecommerce/src/payload/seed/home-static.ts delete mode 100644 templates/ecommerce/src/payload/seed/home.ts delete mode 100644 templates/ecommerce/src/payload/seed/image-1.jpg delete mode 100644 templates/ecommerce/src/payload/seed/image-1.ts delete mode 100644 templates/ecommerce/src/payload/seed/image-2.jpg delete mode 100644 templates/ecommerce/src/payload/seed/image-2.ts delete mode 100644 templates/ecommerce/src/payload/seed/image-3.jpg delete mode 100644 templates/ecommerce/src/payload/seed/image-3.ts delete mode 100644 templates/ecommerce/src/payload/seed/index.ts delete mode 100644 templates/ecommerce/src/payload/seed/product-1.ts delete mode 100644 templates/ecommerce/src/payload/seed/product-2.ts delete mode 100644 templates/ecommerce/src/payload/seed/product-3.ts delete mode 100644 templates/ecommerce/src/payload/seed/products-page.ts delete mode 100644 templates/ecommerce/src/payload/stripe/webhooks/priceUpdated.ts delete mode 100644 templates/ecommerce/src/payload/stripe/webhooks/productUpdated.ts delete mode 100644 templates/ecommerce/src/payload/utilities/deepMerge.ts delete mode 100644 templates/ecommerce/src/payload/utilities/formatSlug.ts delete mode 100644 templates/ecommerce/src/payload/utilities/revalidate.ts delete mode 100644 templates/ecommerce/src/server.default.ts delete mode 100644 templates/ecommerce/src/server.ts delete mode 100644 templates/ecommerce/tsconfig.json delete mode 100644 templates/ecommerce/tsconfig.server.json delete mode 100644 templates/ecommerce/yarn.lock diff --git a/templates/ecommerce/.editorconfig b/templates/ecommerce/.editorconfig deleted file mode 100644 index d8e085abcb2..00000000000 --- a/templates/ecommerce/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -end_of_line = lf -max_line_length = null diff --git a/templates/ecommerce/.env.example b/templates/ecommerce/.env.example deleted file mode 100644 index 6803b5ad261..00000000000 --- a/templates/ecommerce/.env.example +++ /dev/null @@ -1,31 +0,0 @@ -# Run on a specific port -PORT=3000 - -# Database connection string -DATABASE_URI=mongodb://127.0.0.1/payload-template-ecommerce - -# Used to encrypt JWT tokens -PAYLOAD_SECRET=YOUR_SECRET_HERE - -# Used to format links and URLs -PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000 -NEXT_PUBLIC_SERVER_URL=http://localhost:3000 - -# Enable Stripe integration -STRIPE_SECRET_KEY= -PAYLOAD_PUBLIC_STRIPE_IS_TEST_KEY=true -NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= - -# Enable Stripe webhooks (optional) -STRIPE_WEBHOOKS_SIGNING_SECRET= - -# Allow robots to index the site (optional) -NEXT_PUBLIC_IS_LIVE= - -# Used to preview drafts -PAYLOAD_PUBLIC_DRAFT_SECRET=demo-draft-secret -NEXT_PRIVATE_DRAFT_SECRET=demo-draft-secret - -# Used to revalidate static pages -REVALIDATION_KEY=demo-revalation-key -NEXT_PRIVATE_REVALIDATION_KEY=demo-revalation-key diff --git a/templates/ecommerce/.eslintrc.cjs b/templates/ecommerce/.eslintrc.cjs deleted file mode 100644 index cbb655ef53a..00000000000 --- a/templates/ecommerce/.eslintrc.cjs +++ /dev/null @@ -1,38 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -module.exports = { - extends: ['plugin:@next/next/recommended', '@payloadcms'], - ignorePatterns: ['**/payload-types.ts'], - overrides: [ - { - extends: ['plugin:@typescript-eslint/disable-type-checked'], - files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'], - }, - { - files: ['package.json', 'tsconfig.json'], - rules: { - 'perfectionist/sort-array-includes': 'off', - 'perfectionist/sort-astro-attributes': 'off', - 'perfectionist/sort-classes': 'off', - 'perfectionist/sort-enums': 'off', - 'perfectionist/sort-exports': 'off', - 'perfectionist/sort-imports': 'off', - 'perfectionist/sort-interfaces': 'off', - 'perfectionist/sort-jsx-props': 'off', - 'perfectionist/sort-keys': 'off', - 'perfectionist/sort-maps': 'off', - 'perfectionist/sort-named-exports': 'off', - 'perfectionist/sort-named-imports': 'off', - 'perfectionist/sort-object-types': 'off', - 'perfectionist/sort-objects': 'off', - 'perfectionist/sort-svelte-attributes': 'off', - 'perfectionist/sort-union-types': 'off', - 'perfectionist/sort-vue-attributes': 'off', - }, - }, - ], - parserOptions: { - project: ['./tsconfig.json'], - tsconfigRootDir: __dirname, - }, - root: true, -} diff --git a/templates/ecommerce/.gitignore b/templates/ecommerce/.gitignore deleted file mode 100644 index 1bbe951cc3e..00000000000 --- a/templates/ecommerce/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -build -dist -/media -node_modules -.DS_Store -.env -.next -.vercel diff --git a/templates/ecommerce/.prettierignore b/templates/ecommerce/.prettierignore deleted file mode 100644 index 996b10e1585..00000000000 --- a/templates/ecommerce/.prettierignore +++ /dev/null @@ -1,14 +0,0 @@ -**/payload-types.ts -.tmp -**/.git -**/.hg -**/.pnp.* -**/.svn -**/.yarn/** -**/build -**/dist/** -**/node_modules -**/temp -**/docs/** -tsconfig.json - diff --git a/templates/ecommerce/.prettierrc.json b/templates/ecommerce/.prettierrc.json deleted file mode 100644 index cb8ee2671df..00000000000 --- a/templates/ecommerce/.prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "printWidth": 100, - "semi": false -} diff --git a/templates/ecommerce/.vscode/launch.json b/templates/ecommerce/.vscode/launch.json deleted file mode 100644 index 35316d56d8d..00000000000 --- a/templates/ecommerce/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "command": "yarn dev", - "name": "Debug Ecommerce", - "request": "launch", - "type": "node-terminal" - } - ] -} diff --git a/templates/ecommerce/Dockerfile b/templates/ecommerce/Dockerfile deleted file mode 100644 index 27ace7fb4ca..00000000000 --- a/templates/ecommerce/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM node:18.8-alpine as base - -FROM base as builder - -WORKDIR /home/node/app -COPY package*.json ./ - -COPY . . -RUN yarn install -RUN yarn build - -FROM base as runtime - -ENV NODE_ENV=production -ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js - -WORKDIR /home/node/app -COPY package*.json ./ -COPY yarn.lock ./ - -RUN yarn install --production -COPY --from=builder /home/node/app/dist ./dist -COPY --from=builder /home/node/app/build ./build - -EXPOSE 3000 - -CMD ["node", "dist/server.js"] diff --git a/templates/ecommerce/README.md b/templates/ecommerce/README.md deleted file mode 100644 index 9fb2481aab7..00000000000 --- a/templates/ecommerce/README.md +++ /dev/null @@ -1,329 +0,0 @@ -# Payload E-Commerce Template - -This is the official [Payload E-Commerce Template](https://github.com/payloadcms/payload/blob/main/templates/ecommerce). Use it to power e-commerce businesses and online stores of all sizes. This repo includes a fully-working backend, enterprise-grade admin panel, and a beautifully designed, production-ready website. - -This template is right for you if you are selling: - -- Physical products like clothing or merchandise -- Digital assets like ebooks or videos -- Access to content like courses or premium articles - -Core features: - -- [Pre-configured Payload Config](#how-it-works) -- [Authentication](#users-authentication) -- [Access Control](#access-control) -- [Shopping Cart](#shopping-cart) -- [Checkout](#checkout) -- [Paywall](#paywall) -- [Layout Builder](#layout-builder) -- [SEO](#seo) -- [Website](#website) - -## Quick Start - -To spin up this example locally, follow these steps: - -### Clone - -If you have not done so already, you need to have standalone copy of this repo on your machine. If you've already cloned this repo, skip to [Development](#development). - -#### Method 1 (recommended) - -Go to Payload Cloud and [clone this template](https://payloadcms.com/new/clone/ecommerce). This will create a new repository on your GitHub account with this template's code which you can then clone to your own machine. - -#### Method 2 - -Use the `create-payload-app` CLI to clone this template directly to your machine: - - npx create-payload-app@latest my-project -t ecommerce - -#### Method 3 - -Use the `git` CLI to clone this template directly to your machine: - - git clone -n --depth=1 --filter=tree:0 https://github.com/payloadcms/payload my-project && cd my-project && git sparse-checkout set --no-cone templates/ecommerce && git checkout && rm -rf .git && git init && git add . && git mv -f templates/ecommerce/{.,}* . && git add . && git commit -m "Initial commit" - -### Development - -1. First [clone the repo](#clone) if you have not done so already -1. `cd my-project && cp .env.example .env` to copy the example environment variables -1. `yarn && yarn dev` to install dependencies and start the dev server -1. `open http://localhost:3000` to open the app in your browser - -That's it! Changes made in `./src` will be reflected in your app. Follow the on-screen instructions to login and create your first admin user. To begin accepting payment, follow the [Stripe](#stripe) guide. Then check out [Production](#production) once you're ready to build and serve your app, and [Deployment](#deployment) when you're ready to go live. - -## How it works - -The Payload config is tailored specifically to the needs of most e-commerce businesses. It is pre-configured in the following ways: - -### Collections - -See the [Collections](https://payloadcms.com/docs/configuration/collections) docs for details on how to extend this functionality. - -- #### Users (Authentication) - - Users are auth-enabled and encompass both admins and customers based on the value of their `roles` field. Only `admin` users can access your admin panel to manage your store whereas `customer` can authenticate on your front-end to create [shopping carts](#shopping-cart) and place [orders](#orders) but have limited access to the platform. See [Access Control](#access-control) for more details. - - For additional help, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth) or the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs. - -- #### Products - - Products are linked to Stripe via a custom select field that is dynamically populated in the sidebar of each product. This field fetches all available products in the background and displays them as options. Once a product has been selected, prices get automatically synced between Stripe and Payload through [Payload Hooks](https://payloadcms.com/docs/hooks) and [Stripe Webhooks](https://stripe.com/docs/webhooks). See [Stripe](#stripe) for more details. - - All products are layout builder enabled so you can generate unique pages for each product using layout building blocks, see [Layout Builder](#layout-builder) for more details. - - Products can also restrict access to content or digital assets behind a paywall (gated content), see [Paywall](#paywall) for more details. - -- #### Orders - - Orders are created when a user successfully completes a checkout. They contain all the data about the order including the products purchased, the total price, and the user who placed the order. See [Checkout](#checkout) for more details. - -- #### Pages - - All pages are layout builder enabled so you can generate unique layouts for each page using layout-building blocks, see [Layout Builder](#layout-builder) for more details. - -- #### Media - - This is the uploads enabled collection used by products and pages to contain media like images, videos, downloads, and other assets. - -- #### Categories - - A taxonomy used to group products together. Categories can be nested inside of one another, for example "Courses > Technology". See the official [Payload Nested Docs Plugin](https://github.com/payloadcms/plugin-nested-docs) for more details. - -### Globals - -See the [Globals](https://payloadcms.com/docs/configuration/globals) docs for details on how to extend this functionality. - -- `Header` - - The data required by the header on your front-end like nav links. - -- `Footer` - - Same as above but for the footer of your site. - -## Access control - -Basic role-based access control is setup to determine what users can and cannot do based on their roles, which are: - -- `admin`: They can access the Payload admin panel to manage your store. They can see all data and make all operations. -- `customer`: They cannot access the Payload admin panel and can perform limited operations based on their user (see below). - -This applies to each collection in the following ways: - -- `users`: Only admins and the user themselves can access their profile. Anyone can create a user but only admins can delete users. -- `products`: Everyone can access products, but only admins can create, update, or delete them. Paywall-enabled products may also have content that is only accessible to only users who have purchased the product. See [Paywall](#paywall) for more details. - -For more details on how to extend this functionality, see the [Payload Access Control](https://payloadcms.com/docs/access-control/overview#access-control) docs. - -## Shopping cart - -Logged-in users can have their shopping carts saved to their profiles as they shop. This way they can continue shopping at a later date or on another device. When not logged in, the cart can be saved to local storage and synced to Payload on the next login. This works by maintaining a `cart` field on the `user`: - -```ts -{ - name: 'cart', - label: 'Shopping Cart', - type: 'object', - fields: [ - { - name: 'items', - label: 'Items', - type: 'array', - fields: [ - // product, quantity, etc - ] - }, - // other metadata like `createdOn`, etc - ] -} -``` - -## Stripe - -Payload itself handles no currency exchange. All payments are processed and billed using [Stripe](https://stripe.com). This means you must have access to a Stripe account via an API key, see [Connect Stripe](#connect-stripe) for how to get one. When you create a product in Payload that you wish to sell, it must be connected to a Stripe product by selecting one from the field in the product's sidebar, see [Products](#products) for more details. Once set, data is automatically synced between the two platforms in the following ways: - -1. Stripe to Payload using [Stripe Webhooks](https://stripe.com/docs/webhooks): - - - `product.created` - - `product.updated` - - `price.updated` - -1. Payload to Stripe using [Payload Hooks](https://payloadcms.com/docs/hooks/overview): - - `user.create` - -For more details on how to extend this functionality, see the the official [Payload Stripe Plugin](https://github.com/payloadcms/plugin-stripe). - -### Connect Stripe - -To integrate with Stripe, follow these steps: - -1. You will first need to create a [Stripe](https://stripe.com) account if you do not already have one. -1. Retrieve your [Stripe API keys](https://dashboard.stripe.com/test/apikeys) and paste them into your `env`: - ```bash - STRIPE_SECRET_KEY= - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= - ``` -1. In another terminal, listen for webhooks (optional): - ```bash - stripe login # follow the prompts - yarn stripe:webhooks - ``` -1. Paste the given webhook signing secret into your `env`: - ```bash - STRIPE_WEBHOOKS_SIGNING_SECRET= - ``` -1. Reboot Payload to ensure that Stripe connects and the webhooks are registered. - -## Checkout - -A custom endpoint is opened at `POST /api/create-payment-intent` which initiates the checkout process. This endpoint totals your cart and creates a [Stripe Payment Intent](https://stripe.com/docs/payments/payment-intents). The total price is recalculated on the server to ensure accuracy and security, and once completed, passes the `client_secret` back in the response for your front-end to finalize the payment. Once the payment has succeeded, an [Order](#orders) will be created in Payload with a `stripePaymentIntentID`. Each purchased product will be recorded to the user's profile, and the user's cart will be automatically cleared. - -## Paywall - -Products can optionally restrict access to content or digital assets behind a paywall. This will require the product to be purchased before it's data and resources are accessible. To do this, a `purchases` field is maintained on each user to track their purchase history: - -```ts -{ - name: 'purchases', - label: 'Purchases', - type: 'array', - fields: [ - { - name: 'product', - label: 'Product', - type: 'relationship', - relationTo: 'products', - }, - // other metadata like `createdOn`, etc - ] -} -``` - -Then, a `paywall` field is added to the `product` with `read` access control set to check for associated purchases. Every time a user requests a product, this will only return data to those who have purchased it: - -```ts -{ - name: 'paywall', - label: 'Paywall', - type: 'blocks', - access: { - read: checkUserPurchases, - }, - fields: [ - // assets - ] -} -``` - -## Layout Builder - -Create unique product and page layouts for any type fo content using a powerful layout builder. This template comes pre-configured with the following layout building blocks: - -- Hero -- Content -- Media -- Call To Action -- Archive - -Each block is fully designed and built into the front-end website that comes with this template. See [Website](#website) for more details. - -## Draft Preview - -All pages and products are draft-enabled so you can preview them before publishing them to your website. To do this, these collections use [Versions](https://payloadcms.com/docs/configuration/collections#versions) with `drafts` set to `true`. This means that when you create a new page or product, it will be saved as a draft and will not be visible on your website until you publish it. This also means that you can preview your draft before publishing it to your website. To do this, we automatically format a custom URL which redirects to your front-end to securely fetch the draft version of your content. - -Since the front-end of this template is statically generated, this also means that pages and products will need to be regenerated as changes are made to published documents. To do this, we use an `afterChange` hook to regenerate the front-end when a document has changed and its `_status` is `published`. - -For more details on how to extend this functionality, see the official [Draft Preview Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview). - -## SEO - -This template comes pre-configured with the official [Payload SEO Plugin](https://github.com/payloadcms/plugin-seo) for complete SEO control from the admin panel. All SEO data is fully integrated into the front-end website that comes with this template. See [Website](#website) for more details. - -## Redirects - -If you are migrating an existing site or moving content to a new URL, you can use the `redirects` collection to create a proper redirect from old URLs to new ones. This will ensure that proper request status codes are returned to search engines and that your users are not left with a broken link. This template comes pre-configured with the official [Payload Redirects Plugin](https://github.com/payloadcms/plugin-redirects) for complete redirect control from the admin panel. All redirects are fully integrated into the front-end website that comes with this template. See [Website](#website) for more details. - -## Website - -This template includes a beautifully designed, production-ready front-end built with the [Next.js App Router](https://nextjs.org), served right alongside your Payload app in a single Express server. This makes is so that you can deploy both apps simultaneously and host them together. If you prefer a different front-end framework, this pattern works for any framework that supports a custom server. If you prefer to host your website separately from Payload, you can easily [Eject](#eject) the front-end out from this template to swap in your own, or to use it as a standalone CMS. For more details, see the official [Custom Server Example](https://github.com/payloadcms/payload/tree/main/examples/custom-server). - -Core features: - -- [Next.js App Router](https://nextjs.org) -- [Stripe](https://stripe.com) -- [GraphQL](https://graphql.org) -- [TypeScript](https://www.typescriptlang.org) -- [React Hook Form](https://react-hook-form.com) -- [Payload Admin Bar](https://github.com/payloadcms/payload-admin-bar) -- Authentication -- Publication workflow -- Shopping cart -- Checkout -- Customer accounts -- Dark mode -- Pre-made layout building blocks -- SEO -- Redirects -- Paywall - -### Cache - -Although Next.js includes a robust set of caching strategies out of the box, Payload Cloud proxies and caches all files through Cloudflare using the [Official Cloud Plugin](https://github.com/payloadcms/plugin-cloud). This means that Next.js caching is not needed and is disabled by default. If you are hosting your app outside of Payload Cloud, you can easily reenable the Next.js caching mechanisms by removing the `no-store` directive from all fetch requests in `./src/app/_api` and then removing all instances of `export const dynamic = 'force-dynamic'` from pages files, such as `./src/app/(pages)/[slug]/page.tsx`. For more details, see the official [Next.js Caching Docs](https://nextjs.org/docs/app/building-your-application/caching). - -### Eject - -If you prefer another front-end framework or would like to use Payload as a standalone CMS, you can easily eject the front-end from this template. To eject, simply run `yarn eject`. This will uninstall all Next.js related dependencies and delete all files and folders related to the Next.js front-end. It also removes all custom routing from your `server.ts` file and updates your `eslintrc.js`. - -> Note: Your eject script may not work as expected if you've made significant modifications to your project. If you run into any issues, compare your project's dependencies and file structure with this template. See [./src/eject](./src/eject) for full details. - -For more details on how setup a custom server, see the official [Custom Server Example](https://github.com/payloadcms/payload/tree/main/examples/custom-server). - -## Development - -To spin up this example locally, follow the [Quick Start](#quick-start). Then [Connect Stripe](#connect-stripe) to enable payments, and [Seed](#seed) the database with a few products and pages. - -### Docker - -Alternatively, you can use [Docker](https://www.docker.com) to spin up this template locally. To do so, follow these steps: - -1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root -1. Next run `docker-compose up` -1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user - -That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. - -### Seed - -To seed the database with a few products and pages you can run `yarn seed`. This template also comes with a `GET /api/seed` endpoint you can use to seed the database from the admin panel. - -> NOTICE: seeding the database is destructive because it drops your current database to populate a fresh one from the seed template. Only run this command if you are starting a new project or can afford to lose your current data. - -### Conflicting routes - -> In a monorepo when routes are bootstrapped to the same host, they can conflict with Payload's own routes if they have the same name. In our template we've named the Nextjs API routes to `next` to avoid this conflict. -> -> This can happen with any other routes conflicting with Payload such as `admin` and we recommend using different names for custom routes. -> Alternatively you can also rename Payload's own routes via the [configuration](https://payloadcms.com/docs/configuration/overview). - -## Production - -To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps: - -1. Invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle. -1. Finally run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory. -1. When you're ready to go live, see [Deployment](#deployment) for more details. - -### Deployment - -Before deploying your app, you need to: - -1. Switch [your Stripe account to live mode](https://stripe.com/docs/test-mode) and update your [Stripe API keys](https://dashboard.stripe.com/test/apikeys). See [Connect Stripe](#connect-stripe) for more details. -1. Ensure your app builds and serves in production. See [Production](#production) for more details. - -The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details. - -## Questions - -If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions). diff --git a/templates/ecommerce/csp.js b/templates/ecommerce/csp.js deleted file mode 100644 index 84b18d17bd3..00000000000 --- a/templates/ecommerce/csp.js +++ /dev/null @@ -1,36 +0,0 @@ -const policies = { - 'child-src': ["'self'"], - 'connect-src': [ - "'self'", - 'https://checkout.stripe.com', - 'https://api.stripe.com', - 'https://maps.googleapis.com', - ], - 'default-src': ["'self'"], - 'font-src': ["'self'"], - 'frame-src': [ - "'self'", - 'https://checkout.stripe.com', - 'https://js.stripe.com', - 'https://hooks.stripe.com', - ], - 'img-src': ["'self'", 'https://*.stripe.com', 'https://raw.githubusercontent.com'], - 'script-src': [ - "'self'", - "'unsafe-inline'", - "'unsafe-eval'", - 'https://checkout.stripe.com', - 'https://js.stripe.com', - 'https://maps.googleapis.com', - ], - 'style-src': ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'], -} - -module.exports = Object.entries(policies) - .map(([key, value]) => { - if (Array.isArray(value)) { - return `${key} ${value.join(' ')}` - } - return '' - }) - .join('; ') diff --git a/templates/ecommerce/docker-compose.yml b/templates/ecommerce/docker-compose.yml deleted file mode 100644 index 4c9fc519402..00000000000 --- a/templates/ecommerce/docker-compose.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: '3' - -services: - payload: - image: node:18-alpine - ports: - - '3000:3000' - volumes: - - .:/home/node/app - - node_modules:/home/node/app/node_modules - working_dir: /home/node/app/ - command: sh -c "yarn install && yarn dev" - depends_on: - - mongo - env_file: - - .env - - mongo: - image: mongo:latest - ports: - - '27017:27017' - command: - - --storageEngine=wiredTiger - volumes: - - data:/data/db - logging: - driver: none - -volumes: - data: - node_modules: diff --git a/templates/ecommerce/eject.ts b/templates/ecommerce/eject.ts deleted file mode 100644 index f3f119dd5bd..00000000000 --- a/templates/ecommerce/eject.ts +++ /dev/null @@ -1,36 +0,0 @@ -import fs from 'fs' -import path from 'path' - -// Run this script to eject the front-end from this template -// This will remove all template-specific files and directories -// See `yarn eject` in `package.json` for the exact command -// See `./README.md#eject` for more information - -const files = ['./next.config.js', './next-env.d.ts', './redirects.js'] -const directories = ['./src/app'] - -const eject = async (): Promise => { - files.forEach((file) => { - fs.unlinkSync(path.join(__dirname, file)) - }) - - directories.forEach((directory) => { - fs.rm(path.join(__dirname, directory), { recursive: true }, (err) => { - if (err) throw err - }) - }) - - // create a new `./src/server.ts` file - // use contents from `./src/server.default.ts` - const serverFile = path.join(__dirname, './src/server.ts') - const serverDefaultFile = path.join(__dirname, './src/server.default.ts') - fs.copyFileSync(serverDefaultFile, serverFile) - - // remove `'plugin:@next/next/recommended', ` from `./.eslintrc.js` - const eslintConfigFile = path.join(__dirname, './.eslintrc.js') - const eslintConfig = fs.readFileSync(eslintConfigFile, 'utf8') - const updatedEslintConfig = eslintConfig.replace(`'plugin:@next/next/recommended', `, '') - fs.writeFileSync(eslintConfigFile, updatedEslintConfig, 'utf8') -} - -eject() diff --git a/templates/ecommerce/next-env.d.ts b/templates/ecommerce/next-env.d.ts deleted file mode 100644 index 4f11a03dc6c..00000000000 --- a/templates/ecommerce/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/templates/ecommerce/next.config.js b/templates/ecommerce/next.config.js deleted file mode 100644 index 4f068b477ae..00000000000 --- a/templates/ecommerce/next.config.js +++ /dev/null @@ -1,50 +0,0 @@ -/** @type {import('next').NextConfig} */ -const ContentSecurityPolicy = require('./csp') -const redirects = require('./redirects') - -const nextConfig = { - async headers() { - const headers = [] - - // Prevent search engines from indexing the site if it is not live - // This is useful for staging environments before they are ready to go live - // To allow robots to crawl the site, use the `NEXT_PUBLIC_IS_LIVE` env variable - // You may want to also use this variable to conditionally render any tracking scripts - if (!process.env.NEXT_PUBLIC_IS_LIVE) { - headers.push({ - headers: [ - { - key: 'X-Robots-Tag', - value: 'noindex', - }, - ], - source: '/:path*', - }) - } - - // Set the `Content-Security-Policy` header as a security measure to prevent XSS attacks - // It works by explicitly whitelisting trusted sources of content for your website - // This will block all inline scripts and styles except for those that are allowed - headers.push({ - headers: [ - { - key: 'Content-Security-Policy', - value: ContentSecurityPolicy, - }, - ], - source: '/(.*)', - }) - - return headers - }, - images: { - domains: ['localhost', process.env.NEXT_PUBLIC_SERVER_URL] - .filter(Boolean) - .map((url) => url.replace(/https?:\/\//, '')), - }, - reactStrictMode: true, - redirects, - swcMinify: true, -} - -module.exports = nextConfig diff --git a/templates/ecommerce/nodemon.json b/templates/ecommerce/nodemon.json deleted file mode 100644 index f94c3a7eeff..00000000000 --- a/templates/ecommerce/nodemon.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/nodemon.json", - "exec": "ts-node --project tsconfig.server.json src/server.ts -- -I", - "ext": "js ts", - "ignore": ["src/app"], - "stdin": false, - "watch": ["server.ts"] -} diff --git a/templates/ecommerce/package.json b/templates/ecommerce/package.json deleted file mode 100644 index 3ff124cc289..00000000000 --- a/templates/ecommerce/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "@payloadcms/template-ecommerce", - "version": "1.0.0", - "description": "E-commerce template for Payload", - "license": "MIT", - "main": "dist/server.js", - "scripts": { - "build": "cross-env NODE_ENV=production yarn build:payload && yarn build:server && yarn copyfiles && yarn build:next", - "build:next": "cross-env PAYLOAD_CONFIG_PATH=dist/payload/payload.config.js NEXT_BUILD=true node dist/server.js", - "build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload/payload.config.ts payload build", - "build:server": "tsc --project tsconfig.server.json", - "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,js}\" dist/", - "dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload/payload.config.ts nodemon", - "eject": "yarn remove next react react-dom @next/eslint-plugin-next && ts-node eject.ts", - "generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload/payload.config.ts payload generate:graphQLSchema", - "generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload/payload.config.ts payload generate:types", - "lint": "eslint src", - "lint:fix": "eslint --fix --ext .ts,.tsx src", - "payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload/payload.config.ts payload", - "seed": "rm -rf media && cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload/payload.config.ts ts-node src/server.ts", - "serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload/payload.config.js NODE_ENV=production node dist/server.js", - "stripe:webhooks": "stripe listen --forward-to localhost:8000/stripe/webhooks" - }, - "dependencies": { - "@payloadcms/bundler-webpack": "^1.0.0", - "@payloadcms/db-mongodb": "^1.0.0", - "@payloadcms/plugin-cloud": "^3.0.0", - "@payloadcms/plugin-nested-docs": "^1.0.8", - "@payloadcms/plugin-redirects": "^1.0.0", - "@payloadcms/plugin-seo": "^1.0.10", - "@payloadcms/plugin-stripe": "^0.0.19", - "@payloadcms/richtext-slate": "^1.0.0", - "@stripe/react-stripe-js": "^1.16.3", - "@stripe/stripe-js": "^1.46.0", - "cross-env": "^7.0.3", - "dotenv": "^8.2.0", - "escape-html": "^1.0.3", - "express": "^4.17.1", - "next": "13.5.2", - "payload": "^2.0.7", - "payload-admin-bar": "^1.0.6", - "qs": "6.11.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-hook-form": "7.45.4", - "react-router-dom": "5.3.4", - "stripe": "^10.2.0" - }, - "devDependencies": { - "@next/eslint-plugin-next": "^13.1.6", - "@payloadcms/eslint-config": "^1.1.1", - "@swc/core": "1.3.76", - "@types/escape-html": "^1.0.2", - "@types/express": "^4.17.9", - "@types/node": "18.11.3", - "@types/qs": "^6.9.8", - "@types/react": "18.0.21", - "copyfiles": "^2.4.1", - "nodemon": "^2.0.6", - "prettier": "^3.0.3", - "slate": "0.91.4", - "ts-node": "10.9.1", - "typescript": "^4.8.4" - } -} diff --git a/templates/ecommerce/public/favicon.ico b/templates/ecommerce/public/favicon.ico deleted file mode 100644 index 601b77718ca588b59f2ff9da3cb5039856844e5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17223 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 diff --git a/templates/ecommerce/public/favicon.svg b/templates/ecommerce/public/favicon.svg deleted file mode 100644 index 86dc2defc49..00000000000 --- a/templates/ecommerce/public/favicon.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/templates/ecommerce/public/static-image.jpg b/templates/ecommerce/public/static-image.jpg deleted file mode 100644 index ae614855dc6633efe9fe1ff7da0de732becc945b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93586 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 diff --git a/templates/ecommerce/redirects.js b/templates/ecommerce/redirects.js deleted file mode 100644 index 9e81b7095c3..00000000000 --- a/templates/ecommerce/redirects.js +++ /dev/null @@ -1,81 +0,0 @@ -// Note: This will not work in dev mode and will throw an error upon startup -// This is because the Payload APIs are not yet running when the Next.js server starts -// This is not a problem in production as Payload is booted up before building Next.js -// For this reason the errors can be silently ignored in dev mode - -module.exports = async () => { - const internetExplorerRedirect = { - destination: '/ie-incompatible.html', - has: [ - { - type: 'header', - key: 'user-agent', - value: '(.*Trident.*)', // all ie browsers - }, - ], - permanent: false, - source: '/:path((?!ie-incompatible.html$).*)', // all pages except the incompatibility page - } - - try { - const redirectsRes = await fetch( - `${process.env.NEXT_PUBLIC_SERVER_URL}/api/redirects?limit=1000&depth=1`, - ) - - const redirectsData = await redirectsRes.json() - const { docs } = redirectsData - - let dynamicRedirects = [] - - if (docs) { - docs.forEach((doc) => { - const { from, to: { type, reference, url } = {} } = doc - - let source = from - .replace(process.env.NEXT_PUBLIC_SERVER_URL, '') - .split('?')[0] - .toLowerCase() - - if (source.endsWith('/')) source = source.slice(0, -1) // a trailing slash will break this redirect - - let destination = '/' - - if (type === 'custom' && url) { - destination = url.replace(process.env.NEXT_PUBLIC_SERVER_URL, '') - } - - if ( - type === 'reference' && - typeof reference.value === 'object' && - reference?.value?._status === 'published' - ) { - destination = `${process.env.NEXT_PUBLIC_SERVER_URL}/${ - reference.relationTo !== 'pages' ? `${reference.relationTo}/` : '' - }${reference.value.slug}` - } - - const redirect = { - destination, - permanent: true, - source, - } - - if (source.startsWith('/') && destination && source !== destination) { - return dynamicRedirects.push(redirect) - } - - return - }) - } - - const redirects = [internetExplorerRedirect, ...dynamicRedirects] - - return redirects - } catch (error) { - if (process.env.NODE_ENV === 'production') { - console.error(`Error configuring redirects: ${error}`) // eslint-disable-line no-console - } - - return [] - } -} diff --git a/templates/ecommerce/src/app/(pages)/[slug]/page.tsx b/templates/ecommerce/src/app/(pages)/[slug]/page.tsx deleted file mode 100644 index 97ce360bf9d..00000000000 --- a/templates/ecommerce/src/app/(pages)/[slug]/page.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import type { Metadata } from 'next' - -import { draftMode } from 'next/headers' -import { notFound } from 'next/navigation' -import React from 'react' - -import type { Page } from '../../../payload/payload-types' - -import { staticHome } from '../../../payload/seed/home-static' -import { fetchDoc } from '../../_api/fetchDoc' -import { fetchDocs } from '../../_api/fetchDocs' -import { Blocks } from '../../_components/Blocks' -import { Hero } from '../../_components/Hero' -import { generateMeta } from '../../_utilities/generateMeta' - -// Payload Cloud caches all files through Cloudflare, so we don't need Next.js to cache them as well -// This means that we can turn off Next.js data caching and instead rely solely on the Cloudflare CDN -// To do this, we include the `no-cache` header on the fetch requests used to get the data for this page -// But we also need to force Next.js to dynamically render this page on each request for preview mode to work -// See https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic -// If you are not using Payload Cloud then this line can be removed, see `../../../README.md#cache` -export const dynamic = 'force-dynamic' - -export default async function Page({ params: { slug = 'home' } }) { - const { isEnabled: isDraftMode } = draftMode() - - let page: Page | null = null - - try { - page = await fetchDoc({ - slug, - collection: 'pages', - draft: isDraftMode, - }) - } catch (error) { - // when deploying this template on Payload Cloud, this page needs to build before the APIs are live - // so swallow the error here and simply render the page with fallback data where necessary - // in production you may want to redirect to a 404 page or at least log the error somewhere - // console.error(error) - } - - // if no `home` page exists, render a static one using dummy content - // you should delete this code once you have a home page in the CMS - // this is really only useful for those who are demoing this template - if (!page && slug === 'home') { - page = staticHome - } - - if (!page) { - return notFound() - } - - const { hero, layout } = page - - return ( - - - - - ) -} - -export async function generateStaticParams() { - try { - const pages = await fetchDocs('pages') - return pages?.map(({ slug }) => slug) - } catch (error) { - return [] - } -} - -export async function generateMetadata({ params: { slug = 'home' } }): Promise { - const { isEnabled: isDraftMode } = draftMode() - - let page: Page | null = null - - try { - page = await fetchDoc({ - slug, - collection: 'pages', - draft: isDraftMode, - }) - } catch (error) { - // don't throw an error if the fetch fails - // this is so that we can render a static home page for the demo - // when deploying this template on Payload Cloud, this page needs to build before the APIs are live - // in production you may want to redirect to a 404 page or at least log the error somewhere - } - - if (!page && slug === 'home') { - page = staticHome - } - - return generateMeta({ doc: page }) -} diff --git a/templates/ecommerce/src/app/(pages)/account/AccountForm/index.module.scss b/templates/ecommerce/src/app/(pages)/account/AccountForm/index.module.scss deleted file mode 100644 index 9798e6489c2..00000000000 --- a/templates/ecommerce/src/app/(pages)/account/AccountForm/index.module.scss +++ /dev/null @@ -1,24 +0,0 @@ -@import '../../../_css/common'; - -.form { - margin-bottom: var(--base); - display: flex; - flex-direction: column; - gap: calc(var(--base) / 2); - align-items: flex-start; - width: 66.66%; - - @include mid-break { - width: 100%; - } -} - -.changePassword { - all: unset; - cursor: pointer; - text-decoration: underline; -} - -.submit { - margin-top: calc(var(--base) / 2); -} diff --git a/templates/ecommerce/src/app/(pages)/account/AccountForm/index.tsx b/templates/ecommerce/src/app/(pages)/account/AccountForm/index.tsx deleted file mode 100644 index 95c7783c5c8..00000000000 --- a/templates/ecommerce/src/app/(pages)/account/AccountForm/index.tsx +++ /dev/null @@ -1,161 +0,0 @@ -'use client' - -import { useRouter } from 'next/navigation' -import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' -import { useForm } from 'react-hook-form' - -import { Button } from '../../../_components/Button' -import { Input } from '../../../_components/Input' -import { Message } from '../../../_components/Message' -import { useAuth } from '../../../_providers/Auth' -import classes from './index.module.scss' - -type FormData = { - email: string - name: string - password: string - passwordConfirm: string -} - -const AccountForm: React.FC = () => { - const [error, setError] = useState('') - const [success, setSuccess] = useState('') - const { setUser, user } = useAuth() - const [changePassword, setChangePassword] = useState(false) - - const { - formState: { errors, isLoading }, - handleSubmit, - register, - reset, - watch, - } = useForm() - - const password = useRef({}) - password.current = watch('password', '') - - const router = useRouter() - - const onSubmit = useCallback( - async (data: FormData) => { - if (user) { - const response = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/${user.id}`, { - // Make sure to include cookies with fetch - body: JSON.stringify(data), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'PATCH', - }) - - if (response.ok) { - const json = await response.json() - setUser(json.doc) - setSuccess('Successfully updated account.') - setError('') - setChangePassword(false) - reset({ - name: json.doc.name, - email: json.doc.email, - password: '', - passwordConfirm: '', - }) - } else { - setError('There was a problem updating your account.') - } - } - }, - [user, setUser, reset], - ) - - useEffect(() => { - if (user === null) { - router.push( - `/login?error=${encodeURIComponent( - 'You must be logged in to view this page.', - )}&redirect=${encodeURIComponent('/account')}`, - ) - } - - // Once user is loaded, reset form to have default values - if (user) { - reset({ - name: user.name, - email: user.email, - password: '', - passwordConfirm: '', - }) - } - }, [user, router, reset, changePassword]) - - return ( -

- - {!changePassword ? ( - -

- {'Change your account details below, or '} - - {' to change your password.'} -

- - -
- ) : ( - -

- {'Change your password below, or '} - - . -

- - value === password.current || 'The passwords do not match'} - /> -
- )} -
-
- - Page {page} of {totalPages} - -
- - - ) -} diff --git a/templates/ecommerce/src/app/_components/PaywallBlocks/index.tsx b/templates/ecommerce/src/app/_components/PaywallBlocks/index.tsx deleted file mode 100644 index 2589464766a..00000000000 --- a/templates/ecommerce/src/app/_components/PaywallBlocks/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -'use client' - -import Link from 'next/link' -import React, { useEffect } from 'react' - -import type { Page } from '../../../payload/payload-types' - -import { PRODUCT_PAYWALL } from '../../_graphql/products' -import { useAuth } from '../../_providers/Auth' -import { Blocks } from '../Blocks' -import { Gutter } from '../Gutter' -import { LoadingShimmer } from '../LoadingShimmer' -import { Message } from '../Message' -import { VerticalPadding } from '../VerticalPadding' - -export const PaywallBlocks: React.FC<{ - disableTopPadding?: boolean - productSlug: string -}> = (props) => { - const { disableTopPadding, productSlug } = props - const { user } = useAuth() - - const [isLoading, setIsLoading] = React.useState(false) - const [blocks, setBlocks] = React.useState() - const hasInitialized = React.useRef(false) - const isRequesting = React.useRef(false) - - useEffect(() => { - if (!user || hasInitialized.current || isRequesting.current) return - hasInitialized.current = true - isRequesting.current = true - - const start = Date.now() - - const getPaywallContent = async () => { - setIsLoading(true) - - try { - const paywall = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/graphql`, { - body: JSON.stringify({ - query: PRODUCT_PAYWALL, - variables: { - slug: productSlug, - }, - }), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }) - ?.then((res) => res.json()) - ?.then((res) => res?.data?.Products.docs[0]?.paywall) - - if (paywall) { - setBlocks(paywall) - } - - // wait before setting `isLoading` to `false` to give the illusion of loading - // this is to prevent a flash of the loading shimmer on fast networks - const end = Date.now() - if (end - start < 1000) { - await new Promise((resolve) => setTimeout(resolve, 500 - (end - start))) - } - - setIsLoading(false) - } catch (error) { - console.error(error) // eslint-disable-line no-console - setIsLoading(false) - } - } - - getPaywallContent() - }, [user, productSlug]) - - if (user === undefined) { - return null - } - - if (user === null) { - return ( - - - - {`This content is gated behind a paywall. You must be `} - - logged in - - {` as an admin or have purchased this product to view this content.`} - - } - /> - - - ) - } - - if (isLoading) { - return ( - - - - - - ) - } - - if (!blocks || blocks.length === 0) { - return ( - - - - - - ) - } - - return -} diff --git a/templates/ecommerce/src/app/_components/Price/index.module.scss b/templates/ecommerce/src/app/_components/Price/index.module.scss deleted file mode 100644 index 1b31033fe76..00000000000 --- a/templates/ecommerce/src/app/_components/Price/index.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import '../../_css/common'; - -.actions { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: calc(var(--base) / 2); - - @include mid-break { - flex-direction: column; - align-items: flex-start; - } -} - -.price { - font-weight: 600; - line-height: 1; - display: flex; - flex-direction: column; - gap: 4px; - - > * { - margin: 0; - } -} - -.priceBreakdown { - color: var(--theme-elevation-500); -} diff --git a/templates/ecommerce/src/app/_components/Price/index.tsx b/templates/ecommerce/src/app/_components/Price/index.tsx deleted file mode 100644 index 5dc31f5b57c..00000000000 --- a/templates/ecommerce/src/app/_components/Price/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client' - -import React, { useEffect, useState } from 'react' - -import type { Product } from '../../../payload/payload-types' - -import { AddToCartButton } from '../AddToCartButton' -import { RemoveFromCartButton } from '../RemoveFromCartButton' -import classes from './index.module.scss' - -export const priceFromJSON = (priceJSON: string, quantity: number = 1, raw?: boolean): string => { - let price = '' - - if (priceJSON) { - try { - const parsed = JSON.parse(priceJSON)?.data[0] - const priceValue = parsed.unit_amount * quantity - const priceType = parsed.type - - if (raw) return priceValue.toString() - - price = (priceValue / 100).toLocaleString('en-US', { - currency: 'USD', // TODO: use `parsed.currency` - style: 'currency', - }) - - if (priceType === 'recurring') { - price += `/${ - parsed.recurring.interval_count > 1 - ? `${parsed.recurring.interval_count} ${parsed.recurring.interval}` - : parsed.recurring.interval - }` - } - } catch (e) { - console.error(`Cannot parse priceJSON`) // eslint-disable-line no-console - } - } - - return price -} - -export const Price: React.FC<{ - button?: 'addToCart' | 'removeFromCart' | false - product: Product - quantity?: number -}> = (props) => { - const { button = 'addToCart', product, product: { priceJSON } = {}, quantity } = props - - const [price, setPrice] = useState<{ - actualPrice: string - withQuantity: string - }>(() => ({ - actualPrice: priceFromJSON(priceJSON), - withQuantity: priceFromJSON(priceJSON, quantity), - })) - - useEffect(() => { - setPrice({ - actualPrice: priceFromJSON(priceJSON), - withQuantity: priceFromJSON(priceJSON, quantity), - }) - }, [priceJSON, quantity]) - - return ( -
- {typeof price?.actualPrice !== 'undefined' && price?.withQuantity !== '' && ( -
-

{price?.withQuantity}

- {quantity > 1 && ( - {`${price.actualPrice} x ${quantity}`} - )} -
- )} - {button && button === 'addToCart' && ( - - )} - {button && button === 'removeFromCart' && } -
- ) -} diff --git a/templates/ecommerce/src/app/_components/RemoveFromCartButton/index.module.scss b/templates/ecommerce/src/app/_components/RemoveFromCartButton/index.module.scss deleted file mode 100644 index 2519bfea484..00000000000 --- a/templates/ecommerce/src/app/_components/RemoveFromCartButton/index.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../_css/common'; - -.removeFromCartButton { - all: unset; - cursor: pointer; - @extend %label; -} diff --git a/templates/ecommerce/src/app/_components/RemoveFromCartButton/index.tsx b/templates/ecommerce/src/app/_components/RemoveFromCartButton/index.tsx deleted file mode 100644 index b27e4ce4fab..00000000000 --- a/templates/ecommerce/src/app/_components/RemoveFromCartButton/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' - -import type { Product } from '../../../payload/payload-types' - -import { useCart } from '../../_providers/Cart' -import classes from './index.module.scss' - -export const RemoveFromCartButton: React.FC<{ - className?: string - product: Product -}> = (props) => { - const { className, product } = props - - const { deleteItemFromCart, isProductInCart } = useCart() - - const productIsInCart = isProductInCart(product) - - if (!productIsInCart) { - return
Item is not in the cart
- } - - return ( - - ) -} diff --git a/templates/ecommerce/src/app/_components/RenderParams/Component.tsx b/templates/ecommerce/src/app/_components/RenderParams/Component.tsx deleted file mode 100644 index f911d357869..00000000000 --- a/templates/ecommerce/src/app/_components/RenderParams/Component.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client' - -import { useSearchParams } from 'next/navigation' -import { useEffect } from 'react' - -import { Message } from '../Message' -import classes from './index.module.scss' - -export type Props = { - className?: string - message?: string - onParams?: (paramValues: ((null | string | undefined) | string[])[]) => void - params?: string[] -} - -export const RenderParamsComponent: React.FC = ({ - className, - onParams, - params = ['error', 'warning', 'success', 'message'], -}) => { - const searchParams = useSearchParams() - const paramValues = params.map((param) => searchParams?.get(param)) - - useEffect(() => { - if (paramValues.length && onParams) { - onParams(paramValues) - } - }, [paramValues, onParams]) - - if (paramValues.length) { - return ( -
- {paramValues.map((paramValue, index) => { - if (!paramValue) return null - - return ( - - ) - })} -
- ) - } - - return null -} diff --git a/templates/ecommerce/src/app/_components/RenderParams/index.module.scss b/templates/ecommerce/src/app/_components/RenderParams/index.module.scss deleted file mode 100644 index 69f05b94908..00000000000 --- a/templates/ecommerce/src/app/_components/RenderParams/index.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import '../../_css/common.scss'; - -.renderParams { - margin-bottom: calc(var(--base) * 2); - - @include mid-break { - margin-bottom: var(--base); - } -} diff --git a/templates/ecommerce/src/app/_components/RenderParams/index.tsx b/templates/ecommerce/src/app/_components/RenderParams/index.tsx deleted file mode 100644 index dd8b1f95175..00000000000 --- a/templates/ecommerce/src/app/_components/RenderParams/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Suspense } from 'react' - -import type { Props } from './Component' - -import { RenderParamsComponent } from './Component' - -// Using `useSearchParams` from `next/navigation` causes the entire route to de-optimize into client-side rendering -// To fix this, we wrap the component in a `Suspense` component -// See https://nextjs.org/docs/messages/deopted-into-client-rendering for more info - -export const RenderParams: React.FC = (props) => { - return ( - - - - ) -} diff --git a/templates/ecommerce/src/app/_components/RichText/index.module.scss b/templates/ecommerce/src/app/_components/RichText/index.module.scss deleted file mode 100644 index ef45c8d9036..00000000000 --- a/templates/ecommerce/src/app/_components/RichText/index.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.richText { - :first-child { - margin-top: 0; - } - :last-child { - margin-bottom: 0; - } -} diff --git a/templates/ecommerce/src/app/_components/RichText/index.tsx b/templates/ecommerce/src/app/_components/RichText/index.tsx deleted file mode 100644 index b0b28080cb9..00000000000 --- a/templates/ecommerce/src/app/_components/RichText/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' - -import classes from './index.module.scss' -import serialize from './serialize' - -const RichText: React.FC<{ className?: string; content: any }> = ({ className, content }) => { - if (!content) { - return null - } - - return ( -
- {serialize(content)} -
- ) -} - -export default RichText diff --git a/templates/ecommerce/src/app/_components/RichText/serialize.tsx b/templates/ecommerce/src/app/_components/RichText/serialize.tsx deleted file mode 100644 index b071b813d9e..00000000000 --- a/templates/ecommerce/src/app/_components/RichText/serialize.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import escapeHTML from 'escape-html' -import Link from 'next/link' -import React, { Fragment } from 'react' -import { Text } from 'slate' - -import { Label } from '../Label' -import { LargeBody } from '../LargeBody' -import { CMSLink } from '../Link' - -// eslint-disable-next-line no-use-before-define -type Children = Leaf[] - -type Leaf = { - [key: string]: unknown - children?: Children - type: string - url?: string - value?: { - alt: string - url: string - } -} - -const serialize = (children?: Children): React.ReactNode[] => - children?.map((node, i) => { - if (Text.isText(node)) { - let text = - - if (node.bold) { - text = {text} - } - - if (node.code) { - text = {text} - } - - if (node.italic) { - text = {text} - } - - if (node.underline) { - text = ( - - {text} - - ) - } - - if (node.strikethrough) { - text = ( - - {text} - - ) - } - - return {text} - } - - if (!node) { - return null - } - - switch (node.type) { - case 'h1': - return

{serialize(node?.children)}

- case 'h2': - return

{serialize(node?.children)}

- case 'h3': - return

{serialize(node?.children)}

- case 'h4': - return

{serialize(node?.children)}

- case 'h5': - return
{serialize(node?.children)}
- case 'h6': - return
{serialize(node?.children)}
- case 'quote': - return
{serialize(node?.children)}
- case 'ul': - return
    {serialize(node?.children)}
- case 'ol': - return
    {serialize(node.children)}
- case 'li': - return
  • {serialize(node.children)}
  • - case 'link': - return ( - - {serialize(node?.children)} - - ) - - case 'label': - return - - case 'large-body': { - return {serialize(node?.children)} - } - - default: - return

    {serialize(node?.children)}

    - } - }) || [] - -export default serialize diff --git a/templates/ecommerce/src/app/_components/VerticalPadding/index.module.scss b/templates/ecommerce/src/app/_components/VerticalPadding/index.module.scss deleted file mode 100644 index a9db566e8cf..00000000000 --- a/templates/ecommerce/src/app/_components/VerticalPadding/index.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -.top-large { - padding-top: var(--block-padding); -} - -.top-medium { - padding-top: calc(var(--block-padding) / 2); -} - -.bottom-large { - padding-bottom: var(--block-padding); -} - -.bottom-medium { - padding-bottom: calc(var(--block-padding) / 2); -} diff --git a/templates/ecommerce/src/app/_components/VerticalPadding/index.tsx b/templates/ecommerce/src/app/_components/VerticalPadding/index.tsx deleted file mode 100644 index d17a341b110..00000000000 --- a/templates/ecommerce/src/app/_components/VerticalPadding/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' - -import classes from './index.module.scss' - -export type VerticalPaddingOptions = 'large' | 'medium' | 'none' - -type Props = { - bottom?: VerticalPaddingOptions - children: React.ReactNode - className?: string - top?: VerticalPaddingOptions -} - -export const VerticalPadding: React.FC = ({ - bottom = 'medium', - children, - className, - top = 'medium', -}) => { - return ( -
    - {children} -
    - ) -} diff --git a/templates/ecommerce/src/app/_components/icons/Chevron/index.tsx b/templates/ecommerce/src/app/_components/icons/Chevron/index.tsx deleted file mode 100644 index 9e07b0d94ef..00000000000 --- a/templates/ecommerce/src/app/_components/icons/Chevron/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -export const Chevron: React.FC = () => { - return ( - - - - ) -} diff --git a/templates/ecommerce/src/app/_components/icons/Menu/index.tsx b/templates/ecommerce/src/app/_components/icons/Menu/index.tsx deleted file mode 100644 index c513d7ba79c..00000000000 --- a/templates/ecommerce/src/app/_components/icons/Menu/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' - -export const MenuIcon: React.FC = () => { - return ( - - - - - - ) -} diff --git a/templates/ecommerce/src/app/_css/app.scss b/templates/ecommerce/src/app/_css/app.scss deleted file mode 100644 index da67c2347fa..00000000000 --- a/templates/ecommerce/src/app/_css/app.scss +++ /dev/null @@ -1,123 +0,0 @@ -@use './queries.scss' as *; -@use './colors.scss' as *; -@use './type.scss' as *; -@import './theme.scss'; - -:root { - --base: 24px; - --font-body: system-ui; - --font-mono: 'Roboto Mono', monospace; - - --gutter-h: 180px; - --block-padding: 120px; - - @include large-break { - --gutter-h: 144px; - --block-padding: 96px; - } - - @include mid-break { - --gutter-h: 24px; - --block-padding: 60px; - } -} - -* { - box-sizing: border-box; -} - -html { - @extend %body; - background: var(--theme-bg); - -webkit-font-smoothing: antialiased; - opacity: 0; - - &[data-theme='dark'], - &[data-theme='light'] { - opacity: initial; - } -} - -html, -body, -#app { - height: 100%; -} - -body { - font-family: var(--font-body); - margin: 0; - color: var(--theme-text); -} - -::selection { - background: var(--theme-success-500); - color: var(--color-base-800); -} - -::-moz-selection { - background: var(--theme-success-500); - color: var(--color-base-800); -} - -img { - max-width: 100%; - height: auto; - display: block; -} - -h1 { - @extend %h1; -} - -h2 { - @extend %h2; -} - -h3 { - @extend %h3; -} - -h4 { - @extend %h4; -} - -h5 { - @extend %h5; -} - -h6 { - @extend %h6; -} - -p { - margin: var(--base) 0; - - @include mid-break { - margin: calc(var(--base) * 0.75) 0; - } -} - -ul, -ol { - padding-left: var(--base); - margin: 0 0 var(--base); -} - -a { - color: currentColor; - - &:focus { - opacity: 0.8; - outline: none; - } - - &:active { - opacity: 0.7; - outline: none; - } -} - -svg { - vertical-align: middle; -} diff --git a/templates/ecommerce/src/app/_css/colors.scss b/templates/ecommerce/src/app/_css/colors.scss deleted file mode 100644 index ce7655db303..00000000000 --- a/templates/ecommerce/src/app/_css/colors.scss +++ /dev/null @@ -1,105 +0,0 @@ -// Keep these in sync with the colors exported in '../cssVariables.js' - -:root { - --color-base-0: rgb(255, 255, 255); - --color-base-50: rgb(245, 245, 245); - --color-base-100: rgb(235, 235, 235); - --color-base-150: rgb(221, 221, 221); - --color-base-200: rgb(208, 208, 208); - --color-base-250: rgb(195, 195, 195); - --color-base-300: rgb(181, 181, 181); - --color-base-350: rgb(168, 168, 168); - --color-base-400: rgb(154, 154, 154); - --color-base-450: rgb(141, 141, 141); - --color-base-500: rgb(128, 128, 128); - --color-base-550: rgb(114, 114, 114); - --color-base-600: rgb(101, 101, 101); - --color-base-650: rgb(87, 87, 87); - --color-base-700: rgb(74, 74, 74); - --color-base-750: rgb(60, 60, 60); - --color-base-800: rgb(47, 47, 47); - --color-base-850: rgb(34, 34, 34); - --color-base-900: rgb(20, 20, 20); - --color-base-950: rgb(7, 7, 7); - --color-base-1000: rgb(0, 0, 0); - - --color-success-50: rgb(237, 245, 249); - --color-success-100: rgb(218, 237, 248); - --color-success-150: rgb(188, 225, 248); - --color-success-200: rgb(156, 216, 253); - --color-success-250: rgb(125, 204, 248); - --color-success-300: rgb(97, 190, 241); - --color-success-350: rgb(65, 178, 236); - --color-success-400: rgb(36, 164, 223); - --color-success-450: rgb(18, 148, 204); - --color-success-500: rgb(21, 135, 186); - --color-success-550: rgb(12, 121, 168); - --color-success-600: rgb(11, 110, 153); - --color-success-650: rgb(11, 97, 135); - --color-success-700: rgb(17, 88, 121); - --color-success-750: rgb(17, 76, 105); - --color-success-800: rgb(18, 66, 90); - --color-success-850: rgb(18, 56, 76); - --color-success-900: rgb(19, 44, 58); - --color-success-950: rgb(22, 33, 39); - - --color-error-50: rgb(250, 241, 240); - --color-error-100: rgb(252, 229, 227); - --color-error-150: rgb(247, 208, 204); - --color-error-200: rgb(254, 193, 188); - --color-error-250: rgb(253, 177, 170); - --color-error-300: rgb(253, 154, 146); - --color-error-350: rgb(253, 131, 123); - --color-error-400: rgb(246, 109, 103); - --color-error-450: rgb(234, 90, 86); - --color-error-500: rgb(218, 75, 72); - --color-error-550: rgb(200, 62, 61); - --color-error-600: rgb(182, 54, 54); - --color-error-650: rgb(161, 47, 47); - --color-error-700: rgb(144, 44, 43); - --color-error-750: rgb(123, 41, 39); - --color-error-800: rgb(105, 39, 37); - --color-error-850: rgb(86, 36, 33); - --color-error-900: rgb(64, 32, 29); - --color-error-950: rgb(44, 26, 24); - - --color-warning-50: rgb(249, 242, 237); - --color-warning-100: rgb(248, 232, 219); - --color-warning-150: rgb(243, 212, 186); - --color-warning-200: rgb(243, 200, 162); - --color-warning-250: rgb(240, 185, 136); - --color-warning-300: rgb(238, 166, 98); - --color-warning-350: rgb(234, 148, 58); - --color-warning-400: rgb(223, 132, 17); - --color-warning-450: rgb(204, 120, 15); - --color-warning-500: rgb(185, 108, 13); - --color-warning-550: rgb(167, 97, 10); - --color-warning-600: rgb(150, 87, 11); - --color-warning-650: rgb(134, 78, 11); - --color-warning-700: rgb(120, 70, 13); - --color-warning-750: rgb(105, 61, 13); - --color-warning-800: rgb(90, 55, 19); - --color-warning-850: rgb(73, 47, 21); - --color-warning-900: rgb(56, 38, 20); - --color-warning-950: rgb(38, 29, 21); - - --color-blue-50: rgb(237, 245, 249); - --color-blue-100: rgb(218, 237, 248); - --color-blue-150: rgb(188, 225, 248); - --color-blue-200: rgb(156, 216, 253); - --color-blue-250: rgb(125, 204, 248); - --color-blue-300: rgb(97, 190, 241); - --color-blue-350: rgb(65, 178, 236); - --color-blue-400: rgb(36, 164, 223); - --color-blue-450: rgb(18, 148, 204); - --color-blue-500: rgb(21, 135, 186); - --color-blue-550: rgb(12, 121, 168); - --color-blue-600: rgb(11, 110, 153); - --color-blue-650: rgb(11, 97, 135); - --color-blue-700: rgb(17, 88, 121); - --color-blue-750: rgb(17, 76, 105); - --color-blue-800: rgb(18, 66, 90); - --color-blue-850: rgb(18, 56, 76); - --color-blue-900: rgb(19, 44, 58); - --color-blue-950: rgb(22, 33, 39); -} diff --git a/templates/ecommerce/src/app/_css/common.scss b/templates/ecommerce/src/app/_css/common.scss deleted file mode 100644 index e66be0183db..00000000000 --- a/templates/ecommerce/src/app/_css/common.scss +++ /dev/null @@ -1,2 +0,0 @@ -@forward './queries.scss'; -@forward './type.scss'; diff --git a/templates/ecommerce/src/app/_css/queries.scss b/templates/ecommerce/src/app/_css/queries.scss deleted file mode 100644 index b5f8c282cdc..00000000000 --- a/templates/ecommerce/src/app/_css/queries.scss +++ /dev/null @@ -1,30 +0,0 @@ -// Keep these in sync with the breakpoints exported in '../cssVariables.js' - -$breakpoint-xs-width: 400px; -$breakpoint-s-width: 768px; -$breakpoint-m-width: 1024px; -$breakpoint-l-width: 1440px; - -@mixin extra-small-break { - @media (max-width: #{$breakpoint-xs-width}) { - @content; - } -} - -@mixin small-break { - @media (max-width: #{$breakpoint-s-width}) { - @content; - } -} - -@mixin mid-break { - @media (max-width: #{$breakpoint-m-width}) { - @content; - } -} - -@mixin large-break { - @media (max-width: #{$breakpoint-l-width}) { - @content; - } -} diff --git a/templates/ecommerce/src/app/_css/theme.scss b/templates/ecommerce/src/app/_css/theme.scss deleted file mode 100644 index 17bd7caa9aa..00000000000 --- a/templates/ecommerce/src/app/_css/theme.scss +++ /dev/null @@ -1,237 +0,0 @@ -[data-theme='light'] { - --theme-success-50: var(--color-success-50); - --theme-success-100: var(--color-success-100); - --theme-success-150: var(--color-success-150); - --theme-success-200: var(--color-success-200); - --theme-success-250: var(--color-success-250); - --theme-success-300: var(--color-success-300); - --theme-success-350: var(--color-success-350); - --theme-success-400: var(--color-success-400); - --theme-success-450: var(--color-success-450); - --theme-success-500: var(--color-success-500); - --theme-success-550: var(--color-success-550); - --theme-success-600: var(--color-success-600); - --theme-success-650: var(--color-success-650); - --theme-success-700: var(--color-success-700); - --theme-success-750: var(--color-success-750); - --theme-success-800: var(--color-success-800); - --theme-success-850: var(--color-success-850); - --theme-success-900: var(--color-success-900); - --theme-success-950: var(--color-success-950); - - --theme-warning-50: var(--color-warning-50); - --theme-warning-100: var(--color-warning-100); - --theme-warning-150: var(--color-warning-150); - --theme-warning-200: var(--color-warning-200); - --theme-warning-250: var(--color-warning-250); - --theme-warning-300: var(--color-warning-300); - --theme-warning-350: var(--color-warning-350); - --theme-warning-400: var(--color-warning-400); - --theme-warning-450: var(--color-warning-450); - --theme-warning-500: var(--color-warning-500); - --theme-warning-550: var(--color-warning-550); - --theme-warning-600: var(--color-warning-600); - --theme-warning-650: var(--color-warning-650); - --theme-warning-700: var(--color-warning-700); - --theme-warning-750: var(--color-warning-750); - --theme-warning-800: var(--color-warning-800); - --theme-warning-850: var(--color-warning-850); - --theme-warning-900: var(--color-warning-900); - --theme-warning-950: var(--color-warning-950); - - --theme-error-50: var(--color-error-50); - --theme-error-100: var(--color-error-100); - --theme-error-150: var(--color-error-150); - --theme-error-200: var(--color-error-200); - --theme-error-250: var(--color-error-250); - --theme-error-300: var(--color-error-300); - --theme-error-350: var(--color-error-350); - --theme-error-400: var(--color-error-400); - --theme-error-450: var(--color-error-450); - --theme-error-500: var(--color-error-500); - --theme-error-550: var(--color-error-550); - --theme-error-600: var(--color-error-600); - --theme-error-650: var(--color-error-650); - --theme-error-700: var(--color-error-700); - --theme-error-750: var(--color-error-750); - --theme-error-800: var(--color-error-800); - --theme-error-850: var(--color-error-850); - --theme-error-900: var(--color-error-900); - --theme-error-950: var(--color-error-950); - - --theme-elevation-0: var(--color-base-0); - --theme-elevation-50: var(--color-base-50); - --theme-elevation-100: var(--color-base-100); - --theme-elevation-150: var(--color-base-150); - --theme-elevation-200: var(--color-base-200); - --theme-elevation-250: var(--color-base-250); - --theme-elevation-300: var(--color-base-300); - --theme-elevation-350: var(--color-base-350); - --theme-elevation-400: var(--color-base-400); - --theme-elevation-450: var(--color-base-450); - --theme-elevation-500: var(--color-base-500); - --theme-elevation-550: var(--color-base-550); - --theme-elevation-600: var(--color-base-600); - --theme-elevation-650: var(--color-base-650); - --theme-elevation-700: var(--color-base-700); - --theme-elevation-750: var(--color-base-750); - --theme-elevation-800: var(--color-base-800); - --theme-elevation-850: var(--color-base-850); - --theme-elevation-900: var(--color-base-900); - --theme-elevation-950: var(--color-base-950); - --theme-elevation-1000: var(--color-base-1000); - - --theme-bg: var(--theme-elevation-0); - --theme-input-bg: var(--theme-elevation-50); - --theme-text: var(--theme-elevation-750); - --theme-border-color: var(--theme-elevation-150); - - color-scheme: light; - color: var(--theme-text); - - --highlight-default-bg-color: var(--theme-success-400); - --highlight-default-text-color: var(--theme-text); - - --highlight-danger-bg-color: var(--theme-error-150); - --highlight-danger-text-color: var(--theme-text); - - h1 a, - h2 a, - h3 a, - h4 a, - h5 a, - h6 a { - color: var(--theme-elevation-750); - - &:hover { - color: var(--theme-elevation-800); - } - - &:visited { - color: var(--theme-elevation-750); - - &:hover { - color: var(--theme-elevation-800); - } - } - } -} - -[data-theme='dark'] { - --theme-elevation-0: var(--color-base-1000); - --theme-elevation-50: var(--color-base-950); - --theme-elevation-100: var(--color-base-900); - --theme-elevation-150: var(--color-base-850); - --theme-elevation-200: var(--color-base-800); - --theme-elevation-250: var(--color-base-750); - --theme-elevation-300: var(--color-base-700); - --theme-elevation-350: var(--color-base-650); - --theme-elevation-400: var(--color-base-600); - --theme-elevation-450: var(--color-base-550); - --theme-elevation-500: var(--color-base-500); - --theme-elevation-550: var(--color-base-450); - --theme-elevation-600: var(--color-base-400); - --theme-elevation-650: var(--color-base-350); - --theme-elevation-700: var(--color-base-300); - --theme-elevation-750: var(--color-base-250); - --theme-elevation-800: var(--color-base-200); - --theme-elevation-850: var(--color-base-150); - --theme-elevation-900: var(--color-base-100); - --theme-elevation-950: var(--color-base-50); - --theme-elevation-1000: var(--color-base-0); - - --theme-success-50: var(--color-success-950); - --theme-success-100: var(--color-success-900); - --theme-success-150: var(--color-success-850); - --theme-success-200: var(--color-success-800); - --theme-success-250: var(--color-success-750); - --theme-success-300: var(--color-success-700); - --theme-success-350: var(--color-success-650); - --theme-success-400: var(--color-success-600); - --theme-success-450: var(--color-success-550); - --theme-success-500: var(--color-success-500); - --theme-success-550: var(--color-success-450); - --theme-success-600: var(--color-success-400); - --theme-success-650: var(--color-success-350); - --theme-success-700: var(--color-success-300); - --theme-success-750: var(--color-success-250); - --theme-success-800: var(--color-success-200); - --theme-success-850: var(--color-success-150); - --theme-success-900: var(--color-success-100); - --theme-success-950: var(--color-success-50); - - --theme-warning-50: var(--color-warning-950); - --theme-warning-100: var(--color-warning-900); - --theme-warning-150: var(--color-warning-850); - --theme-warning-200: var(--color-warning-800); - --theme-warning-250: var(--color-warning-750); - --theme-warning-300: var(--color-warning-700); - --theme-warning-350: var(--color-warning-650); - --theme-warning-400: var(--color-warning-600); - --theme-warning-450: var(--color-warning-550); - --theme-warning-500: var(--color-warning-500); - --theme-warning-550: var(--color-warning-450); - --theme-warning-600: var(--color-warning-400); - --theme-warning-650: var(--color-warning-350); - --theme-warning-700: var(--color-warning-300); - --theme-warning-750: var(--color-warning-250); - --theme-warning-800: var(--color-warning-200); - --theme-warning-850: var(--color-warning-150); - --theme-warning-900: var(--color-warning-100); - --theme-warning-950: var(--color-warning-50); - - --theme-error-50: var(--color-error-950); - --theme-error-100: var(--color-error-900); - --theme-error-150: var(--color-error-850); - --theme-error-200: var(--color-error-800); - --theme-error-250: var(--color-error-750); - --theme-error-300: var(--color-error-700); - --theme-error-350: var(--color-error-650); - --theme-error-400: var(--color-error-600); - --theme-error-450: var(--color-error-550); - --theme-error-500: var(--color-error-500); - --theme-error-550: var(--color-error-450); - --theme-error-600: var(--color-error-400); - --theme-error-650: var(--color-error-350); - --theme-error-700: var(--color-error-300); - --theme-error-750: var(--color-error-250); - --theme-error-800: var(--color-error-200); - --theme-error-850: var(--color-error-150); - --theme-error-900: var(--color-error-100); - --theme-error-950: var(--color-error-50); - - --theme-bg: var(--theme-elevation-100); - --theme-text: var(--theme-elevation-900); - --theme-input-bg: var(--theme-elevation-150); - --theme-border-color: var(--theme-elevation-250); - - color-scheme: dark; - color: var(--theme-text); - - --highlight-default-bg-color: var(--theme-success-100); - --highlight-default-text-color: var(--theme-success-600); - - --highlight-danger-bg-color: var(--theme-error-100); - --highlight-danger-text-color: var(--theme-error-550); - - h1 a, - h2 a, - h3 a, - h4 a, - h5 a, - h6 a { - color: var(--theme-success-600); - - &:hover { - color: var(--theme-success-400); - } - - &:visited { - color: var(--theme-success-700); - - &:hover { - color: var(--theme-success-500); - } - } - } -} diff --git a/templates/ecommerce/src/app/_css/type.scss b/templates/ecommerce/src/app/_css/type.scss deleted file mode 100644 index f8d1d07162e..00000000000 --- a/templates/ecommerce/src/app/_css/type.scss +++ /dev/null @@ -1,110 +0,0 @@ -@use 'queries' as *; - -%h1, -%h2, -%h3, -%h4, -%h5, -%h6 { - font-weight: 700; -} - -%h1 { - margin: 40px 0; - font-size: 64px; - line-height: 70px; - font-weight: bold; - - @include mid-break { - margin: 24px 0; - font-size: 42px; - line-height: 42px; - } -} - -%h2 { - margin: 28px 0; - font-size: 48px; - line-height: 54px; - font-weight: bold; - - @include mid-break { - margin: 22px 0; - font-size: 32px; - line-height: 40px; - } -} - -%h3 { - margin: 24px 0; - font-size: 32px; - line-height: 40px; - font-weight: bold; - - @include mid-break { - margin: 20px 0; - font-size: 26px; - line-height: 32px; - } -} - -%h4 { - margin: 20px 0; - font-size: 26px; - line-height: 32px; - font-weight: bold; - - @include mid-break { - font-size: 22px; - line-height: 30px; - } -} - -%h5 { - margin: 20px 0; - font-size: 22px; - line-height: 30px; - font-weight: bold; - - @include mid-break { - font-size: 18px; - line-height: 24px; - } -} - -%h6 { - margin: 20px 0; - font-size: inherit; - line-height: inherit; - font-weight: bold; -} - -%body { - font-size: 18px; - line-height: 32px; - - @include mid-break { - font-size: 15px; - line-height: 24px; - } -} - -%large-body { - font-size: 25px; - line-height: 32px; - - @include mid-break { - font-size: 22px; - line-height: 30px; - } -} - -%label { - font-size: 16px; - line-height: 24px; - text-transform: uppercase; - - @include mid-break { - font-size: 13px; - } -} diff --git a/templates/ecommerce/src/app/_graphql/blocks.ts b/templates/ecommerce/src/app/_graphql/blocks.ts deleted file mode 100644 index 6f3aea1d7e7..00000000000 --- a/templates/ecommerce/src/app/_graphql/blocks.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { CATEGORIES } from './categories' -import { LINK_FIELDS } from './link' -import { MEDIA } from './media' -import { META } from './meta' - -export const CALL_TO_ACTION = ` -...on Cta { - blockType - invertBackground - richText - links { - link ${LINK_FIELDS()} - } -} -` - -export const CONTENT = ` -...on Content { - blockType - invertBackground - columns { - size - richText - enableLink - link ${LINK_FIELDS()} - } -} -` - -export const MEDIA_BLOCK = ` -...on MediaBlock { - blockType - invertBackground - position - ${MEDIA} -} -` - -export const ARCHIVE_BLOCK = ` -...on Archive { - blockType - introContent - populateBy - relationTo - ${CATEGORIES} - limit - selectedDocs { - relationTo - value { - ...on Product { - id - slug - title - priceJSON - ${META} - } - } - } - populatedDocs { - relationTo - value { - ...on Product { - id - slug - title - priceJSON - ${CATEGORIES} - ${META} - } - } - } - populatedDocsTotal -} -` diff --git a/templates/ecommerce/src/app/_graphql/cart.ts b/templates/ecommerce/src/app/_graphql/cart.ts deleted file mode 100644 index 84d0b131ed2..00000000000 --- a/templates/ecommerce/src/app/_graphql/cart.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { META } from './meta' - -export const CART = `cart { - items { - product { - id - slug - priceJSON - ${META} - } - quantity - } -}` diff --git a/templates/ecommerce/src/app/_graphql/categories.ts b/templates/ecommerce/src/app/_graphql/categories.ts deleted file mode 100644 index df8cbe9a154..00000000000 --- a/templates/ecommerce/src/app/_graphql/categories.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const CATEGORIES = `categories { - title - id - breadcrumbs { - id - label - } -}` diff --git a/templates/ecommerce/src/app/_graphql/globals.ts b/templates/ecommerce/src/app/_graphql/globals.ts deleted file mode 100644 index d43c5978581..00000000000 --- a/templates/ecommerce/src/app/_graphql/globals.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { LINK_FIELDS } from './link' - -export const HEADER = ` - Header { - navItems { - link ${LINK_FIELDS({ disableAppearance: true })} - } - } -` - -export const HEADER_QUERY = ` -query Header { - ${HEADER} -} -` - -export const FOOTER = ` - Footer { - navItems { - link ${LINK_FIELDS({ disableAppearance: true })} - } - } -` - -export const FOOTER_QUERY = ` -query Footer { - ${FOOTER} -} -` - -export const SETTINGS = ` - Settings { - productsPage { - slug - } - } -` - -export const SETTINGS_QUERY = ` -query Settings { - ${SETTINGS} -} -` diff --git a/templates/ecommerce/src/app/_graphql/link.ts b/templates/ecommerce/src/app/_graphql/link.ts deleted file mode 100644 index 9c211b41891..00000000000 --- a/templates/ecommerce/src/app/_graphql/link.ts +++ /dev/null @@ -1,20 +0,0 @@ -interface Args { - disableAppearance?: true - disableLabel?: true -} - -export const LINK_FIELDS = ({ disableAppearance, disableLabel }: Args = {}): string => `{ - ${!disableLabel ? 'label' : ''} - ${!disableAppearance ? 'appearance' : ''} - type - newTab - url - reference { - relationTo - value { - ...on Page { - slug - } - } - } -}` diff --git a/templates/ecommerce/src/app/_graphql/me.ts b/templates/ecommerce/src/app/_graphql/me.ts deleted file mode 100644 index 8154ae2331c..00000000000 --- a/templates/ecommerce/src/app/_graphql/me.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CART } from './cart' - -export const ME_QUERY = `query { - meUser { - user { - id - email - name - ${CART} - roles - } - exp - } -}` diff --git a/templates/ecommerce/src/app/_graphql/media.ts b/templates/ecommerce/src/app/_graphql/media.ts deleted file mode 100644 index fbf8e526b66..00000000000 --- a/templates/ecommerce/src/app/_graphql/media.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const MEDIA_FIELDS = ` -mimeType -filename -width -height -alt -caption -` - -export const MEDIA = `media { - ${MEDIA_FIELDS} -}` diff --git a/templates/ecommerce/src/app/_graphql/meta.ts b/templates/ecommerce/src/app/_graphql/meta.ts deleted file mode 100644 index fc87374928e..00000000000 --- a/templates/ecommerce/src/app/_graphql/meta.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MEDIA_FIELDS } from './media' - -export const META = `meta { - title - image { - ${MEDIA_FIELDS} - } - description -}` diff --git a/templates/ecommerce/src/app/_graphql/orders.ts b/templates/ecommerce/src/app/_graphql/orders.ts deleted file mode 100644 index da57fb5d51b..00000000000 --- a/templates/ecommerce/src/app/_graphql/orders.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { PRODUCT } from './products' - -export const ORDERS = ` - query Orders { - Orders(limit: 300) { - docs { - id - } - } - } -` - -export const ORDER = ` - query Order($id: String ) { - Orders(where: { id: { equals: $id}}) { - docs { - id - orderedBy - items { - product ${PRODUCT} - title - priceJSON - } - } - } - } -` diff --git a/templates/ecommerce/src/app/_graphql/pages.ts b/templates/ecommerce/src/app/_graphql/pages.ts deleted file mode 100644 index 67aaa7f07a1..00000000000 --- a/templates/ecommerce/src/app/_graphql/pages.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ARCHIVE_BLOCK, CALL_TO_ACTION, CONTENT, MEDIA_BLOCK } from './blocks' -import { LINK_FIELDS } from './link' -import { MEDIA } from './media' -import { META } from './meta' - -export const PAGES = ` - query Pages { - Pages(limit: 300, where: { slug: { not_equals: "cart" } }) { - docs { - slug - } - } - } -` - -export const PAGE = ` - query Page($slug: String, $draft: Boolean) { - Pages(where: { AND: [{ slug: { equals: $slug }}] }, limit: 1, draft: $draft) { - docs { - id - title - hero { - type - richText - links { - link ${LINK_FIELDS()} - } - ${MEDIA} - } - layout { - ${CONTENT} - ${CALL_TO_ACTION} - ${CONTENT} - ${MEDIA_BLOCK} - ${ARCHIVE_BLOCK} - } - ${META} - } - } - } -` diff --git a/templates/ecommerce/src/app/_graphql/products.ts b/templates/ecommerce/src/app/_graphql/products.ts deleted file mode 100644 index 13e579811a8..00000000000 --- a/templates/ecommerce/src/app/_graphql/products.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ARCHIVE_BLOCK, CALL_TO_ACTION, CONTENT, MEDIA_BLOCK } from './blocks' -import { CATEGORIES } from './categories' -import { META } from './meta' - -export const PRODUCTS = ` - query Products { - Products(limit: 300) { - docs { - slug - } - } - } -` - -export const PRODUCT = ` - query Product($slug: String, $draft: Boolean) { - Products(where: { slug: { equals: $slug}}, limit: 1, draft: $draft) { - docs { - id - title - stripeProductID - ${CATEGORIES} - layout { - ${CALL_TO_ACTION} - ${CONTENT} - ${MEDIA_BLOCK} - ${ARCHIVE_BLOCK} - } - priceJSON - enablePaywall - relatedProducts { - id - slug - title - ${META} - } - ${META} - } - } - } -` - -export const PRODUCT_PAYWALL = ` - query Product($slug: String, $draft: Boolean) { - Products(where: { slug: { equals: $slug}}, limit: 1, draft: $draft) { - docs { - paywall { - ${CALL_TO_ACTION} - ${CONTENT} - ${MEDIA_BLOCK} - ${ARCHIVE_BLOCK} - } - } - } - } -` diff --git a/templates/ecommerce/src/app/_heros/HighImpact/index.module.scss b/templates/ecommerce/src/app/_heros/HighImpact/index.module.scss deleted file mode 100644 index d6bffc2a230..00000000000 --- a/templates/ecommerce/src/app/_heros/HighImpact/index.module.scss +++ /dev/null @@ -1,55 +0,0 @@ -@import '../../_css/queries.scss'; - -.hero { - padding-top: calc(var(--base) * 2); - position: relative; - overflow: hidden; - - @include mid-break { - padding-top: var(--gutter-h); - } -} - -.media { - width: calc(100% + var(--gutter-h)); - left: calc(var(--gutter-h) / -2); - margin-top: calc(var(--base) * 3); - position: relative; - - @include mid-break { - left: 0; - margin-top: var(--base); - margin-left: calc(var(--gutter-h) * -1); - width: calc(100% + var(--gutter-h) * 2); - } -} - -.links { - list-style: none; - margin: 0; - padding: 0; - display: flex; - padding-top: var(--base); - flex-wrap: wrap; - margin: calc(var(--base) * -0.5); - - & > * { - margin: calc(var(--base) / 2); - } -} - -.caption { - margin-top: var(--base); - color: var(--theme-elevation-500); - left: calc(var(--gutter-h) / 2); - width: calc(100% - var(--gutter-h)); - position: relative; - - @include mid-break { - left: var(--gutter-h); - } -} - -.content { - position: relative; -} diff --git a/templates/ecommerce/src/app/_heros/HighImpact/index.tsx b/templates/ecommerce/src/app/_heros/HighImpact/index.tsx deleted file mode 100644 index 109616da9ec..00000000000 --- a/templates/ecommerce/src/app/_heros/HighImpact/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Fragment } from 'react' - -import type { Page } from '../../../payload/payload-types' - -import { Gutter } from '../../_components/Gutter' -import { CMSLink } from '../../_components/Link' -import { Media } from '../../_components/Media' -import RichText from '../../_components/RichText' -import classes from './index.module.scss' - -export const HighImpactHero: React.FC = ({ links, media, richText }) => { - return ( - -
    - - {Array.isArray(links) && links.length > 0 && ( -
      - {links.map(({ link }, i) => { - return ( -
    • - -
    • - ) - })} -
    - )} -
    -
    - {typeof media === 'object' && ( - - - {media?.caption && } - - )} -
    -
    - ) -} diff --git a/templates/ecommerce/src/app/_heros/LowImpact/index.module.scss b/templates/ecommerce/src/app/_heros/LowImpact/index.module.scss deleted file mode 100644 index 6b6fbea13da..00000000000 --- a/templates/ecommerce/src/app/_heros/LowImpact/index.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -@use '../../_css/type.scss' as *; - -.lowImpactHero { -} diff --git a/templates/ecommerce/src/app/_heros/LowImpact/index.tsx b/templates/ecommerce/src/app/_heros/LowImpact/index.tsx deleted file mode 100644 index 3b1a34c5988..00000000000 --- a/templates/ecommerce/src/app/_heros/LowImpact/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' - -import type { Page } from '../../../payload/payload-types' - -import { Gutter } from '../../_components/Gutter' -import RichText from '../../_components/RichText' -import { VerticalPadding } from '../../_components/VerticalPadding' -import classes from './index.module.scss' - -export const LowImpactHero: React.FC = ({ richText }) => { - return ( - -
    - - - -
    -
    - ) -} diff --git a/templates/ecommerce/src/app/_heros/MediumImpact/index.module.scss b/templates/ecommerce/src/app/_heros/MediumImpact/index.module.scss deleted file mode 100644 index a0b58fef569..00000000000 --- a/templates/ecommerce/src/app/_heros/MediumImpact/index.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use '../../_css/common.scss' as *; - -.hero { - padding-top: calc(var(--base) * 3); - - @include mid-break { - padding-top: var(--base); - } -} - -.richText { - position: relative; - - &::after { - content: ''; - display: block; - position: absolute; - width: 100vw; - left: calc(var(--gutter-h) * -1); - height: 200px; - background: linear-gradient(to bottom, var(--theme-elevation-100), transparent); - top: calc(100% + (var(--base) * 2)); - right: 0; - - @include mid-break { - display: none; - } - } -} - -.links { - position: relative; - list-style: none; - margin: 0; - padding: 0; - display: flex; - margin-top: calc(var(--base) * 4); - - li { - margin-right: 12px; - } - - @include mid-break { - display: block; - margin-top: var(--base); - - li { - margin-right: 0; - } - } -} - -.link { - @include mid-break { - width: 100%; - } -} - -.media { - position: relative; - width: calc(100% + var(--gutter-h)); -} diff --git a/templates/ecommerce/src/app/_heros/MediumImpact/index.tsx b/templates/ecommerce/src/app/_heros/MediumImpact/index.tsx deleted file mode 100644 index 980844a51be..00000000000 --- a/templates/ecommerce/src/app/_heros/MediumImpact/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' - -import type { Page } from '../../../payload/payload-types' - -import { Gutter } from '../../_components/Gutter' -import { CMSLink } from '../../_components/Link' -import { Media } from '../../_components/Media' -import RichText from '../../_components/RichText' -import classes from './index.module.scss' - -export const MediumImpactHero: React.FC = (props) => { - const { links, media, richText } = props - - return ( - -
    - - {Array.isArray(links) && ( -
      - {links.map(({ link }, i) => { - return ( -
    • - -
    • - ) - })} -
    - )} -
    -
    - {typeof media === 'object' && } -
    -
    - ) -} diff --git a/templates/ecommerce/src/app/_heros/Product/index.module.scss b/templates/ecommerce/src/app/_heros/Product/index.module.scss deleted file mode 100644 index 50c9439a910..00000000000 --- a/templates/ecommerce/src/app/_heros/Product/index.module.scss +++ /dev/null @@ -1,84 +0,0 @@ -@use '../../_css/common.scss' as *; - -.productHero { - display: flex; - gap: calc(var(--base) * 2); - - @include mid-break { - flex-direction: column; - gap: var(--base); - } -} - -.content { - width: 50%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - gap: var(--base); - - @include mid-break { - width: 100%; - gap: calc(var(--base) / 2); - } -} - -.categories { - @extend %label; -} - -.title { - margin: 0; -} - -.warning { - margin-bottom: calc(var(--base) * 1.5); -} - -.description { - margin: 0; -} - -.media { - width: 50%; - - @include mid-break { - width: 100%; - } -} - -.mediaWrapper { - text-decoration: none; - display: block; - position: relative; - aspect-ratio: 5 / 4; - margin-bottom: calc(var(--base) / 2); - width: calc(100% + calc(var(--gutter-h) / 2)); - - @include mid-break { - margin-left: calc(var(--gutter-h) * -1); - width: calc(100% + var(--gutter-h) * 2); - } -} - -.image { - object-fit: cover; -} - -.placeholder { - background-color: var(--theme-elevation-50); - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.caption { - color: var(--theme-elevation-500); -} - -.addToCartButton { - margin-top: calc(var(--base) / 2); -} diff --git a/templates/ecommerce/src/app/_heros/Product/index.tsx b/templates/ecommerce/src/app/_heros/Product/index.tsx deleted file mode 100644 index 4a7ab2f25f7..00000000000 --- a/templates/ecommerce/src/app/_heros/Product/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import Link from 'next/link' -import React, { Fragment } from 'react' - -import type { Product } from '../../../payload/payload-types' - -import { AddToCartButton } from '../../_components/AddToCartButton' -import { Gutter } from '../../_components/Gutter' -import { Media } from '../../_components/Media' -import { Message } from '../../_components/Message' -import { Price } from '../../_components/Price' -import RichText from '../../_components/RichText' -import classes from './index.module.scss' - -export const ProductHero: React.FC<{ - product: Product -}> = ({ product }) => { - const { - id, - categories, - meta: { description, image: metaImage } = {}, - stripeProductID, - title, - } = product - - return ( - - {!stripeProductID && ( - - - {'This product is not yet connected to Stripe. To link this product, '} - - edit this product in the admin panel - - . - - } - /> - - )} - -
    -
    - {categories?.map((category, index) => { - if (typeof category === 'object' && category !== null) { - const { title: categoryTitle } = category - - const titleToUse = categoryTitle || 'Untitled category' - - const isLast = index === categories.length - 1 - - return ( - - {titleToUse} - {!isLast && ,  } - - ) - } - - return null - })} -
    -

    {title}

    -
    -

    - {`${description ? `${description} ` : ''}To edit this product, `} - - navigate to the admin dashboard - - . -

    -
    - - -
    -
    -
    - {!metaImage &&
    No image
    } - {metaImage && typeof metaImage !== 'string' && ( - - )} -
    - {metaImage && typeof metaImage !== 'string' && metaImage?.caption && ( - - )} -
    -
    - - ) -} diff --git a/templates/ecommerce/src/app/_providers/Auth/index.tsx b/templates/ecommerce/src/app/_providers/Auth/index.tsx deleted file mode 100644 index 19dc73cf2cf..00000000000 --- a/templates/ecommerce/src/app/_providers/Auth/index.tsx +++ /dev/null @@ -1,218 +0,0 @@ -'use client' - -import React, { createContext, useCallback, useContext, useEffect, useState } from 'react' - -import type { User } from '../../../payload/payload-types' - -// eslint-disable-next-line no-unused-vars -type ResetPassword = (args: { - password: string - passwordConfirm: string - token: string -}) => Promise - -type ForgotPassword = (args: { email: string }) => Promise // eslint-disable-line no-unused-vars - -type Create = (args: { email: string; password: string; passwordConfirm: string }) => Promise // eslint-disable-line no-unused-vars - -type Login = (args: { email: string; password: string }) => Promise // eslint-disable-line no-unused-vars - -type Logout = () => Promise - -type AuthContext = { - create: Create - forgotPassword: ForgotPassword - login: Login - logout: Logout - resetPassword: ResetPassword - setUser: (user: User | null) => void // eslint-disable-line no-unused-vars - status: 'loggedIn' | 'loggedOut' | undefined - user?: User | null -} - -const Context = createContext({} as AuthContext) - -export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [user, setUser] = useState() - - // used to track the single event of logging in or logging out - // useful for `useEffect` hooks that should only run once - const [status, setStatus] = useState<'loggedIn' | 'loggedOut' | undefined>() - const create = useCallback(async (args) => { - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/create`, { - body: JSON.stringify({ - email: args.email, - password: args.password, - passwordConfirm: args.passwordConfirm, - }), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }) - - if (res.ok) { - const { data, errors } = await res.json() - if (errors) throw new Error(errors[0].message) - setUser(data?.loginUser?.user) - setStatus('loggedIn') - } else { - throw new Error('Invalid login') - } - } catch (e) { - throw new Error('An error occurred while attempting to login.') - } - }, []) - - const login = useCallback(async (args) => { - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/login`, { - body: JSON.stringify({ - email: args.email, - password: args.password, - }), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }) - - if (res.ok) { - const { errors, user } = await res.json() - if (errors) throw new Error(errors[0].message) - setUser(user) - setStatus('loggedIn') - return user - } - - throw new Error('Invalid login') - } catch (e) { - throw new Error('An error occurred while attempting to login.') - } - }, []) - - const logout = useCallback(async () => { - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/logout`, { - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }) - - if (res.ok) { - setUser(null) - setStatus('loggedOut') - } else { - throw new Error('An error occurred while attempting to logout.') - } - } catch (e) { - throw new Error('An error occurred while attempting to logout.') - } - }, []) - - useEffect(() => { - const fetchMe = async () => { - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/me`, { - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'GET', - }) - - if (res.ok) { - const { user: meUser } = await res.json() - setUser(meUser || null) - setStatus(meUser ? 'loggedIn' : undefined) - } else { - throw new Error('An error occurred while fetching your account.') - } - } catch (e) { - setUser(null) - throw new Error('An error occurred while fetching your account.') - } - } - - fetchMe() - }, []) - - const forgotPassword = useCallback(async (args) => { - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/forgot-password`, { - body: JSON.stringify({ - email: args.email, - }), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }) - - if (res.ok) { - const { data, errors } = await res.json() - if (errors) throw new Error(errors[0].message) - setUser(data?.loginUser?.user) - } else { - throw new Error('Invalid login') - } - } catch (e) { - throw new Error('An error occurred while attempting to login.') - } - }, []) - - const resetPassword = useCallback(async (args) => { - try { - const res = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/reset-password`, { - body: JSON.stringify({ - password: args.password, - passwordConfirm: args.passwordConfirm, - token: args.token, - }), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }) - - if (res.ok) { - const { data, errors } = await res.json() - if (errors) throw new Error(errors[0].message) - setUser(data?.loginUser?.user) - setStatus(data?.loginUser?.user ? 'loggedIn' : undefined) - } else { - throw new Error('Invalid login') - } - } catch (e) { - throw new Error('An error occurred while attempting to login.') - } - }, []) - - return ( - - {children} - - ) -} - -type UseAuth = () => AuthContext // eslint-disable-line no-unused-vars - -export const useAuth: UseAuth = () => useContext(Context) diff --git a/templates/ecommerce/src/app/_providers/Cart/index.tsx b/templates/ecommerce/src/app/_providers/Cart/index.tsx deleted file mode 100644 index 5f0a890ba2a..00000000000 --- a/templates/ecommerce/src/app/_providers/Cart/index.tsx +++ /dev/null @@ -1,278 +0,0 @@ -'use client' - -import React, { - createContext, - useCallback, - useContext, - useEffect, - useReducer, - useRef, - useState, -} from 'react' - -import type { Product, User } from '../../../payload/payload-types' -import type { CartItem } from './reducer' - -import { useAuth } from '../Auth' -import { cartReducer } from './reducer' - -export type CartContext = { - addItemToCart: (item: CartItem) => void - cart: User['cart'] - cartIsEmpty: boolean | undefined - cartTotal: { - formatted: string - raw: number - } - clearCart: () => void - deleteItemFromCart: (product: Product) => void - hasInitializedCart: boolean - isProductInCart: (product: Product) => boolean -} - -const Context = createContext({} as CartContext) - -export const useCart = () => useContext(Context) - -const arrayHasItems = (array) => Array.isArray(array) && array.length > 0 - -/** - * ensure that cart items are fully populated, filter out any items that are not - * this will prevent discontinued products from appearing in the cart - */ -const flattenCart = (cart: User['cart']): User['cart'] => ({ - ...cart, - items: cart.items - .map((item) => { - if (!item?.product || typeof item?.product !== 'object') { - return null - } - - return { - ...item, - // flatten relationship to product - product: item?.product?.id, - quantity: typeof item?.quantity === 'number' ? item?.quantity : 0, - } - }) - .filter(Boolean) as CartItem[], -}) - -// Step 1: Check local storage for a cart -// Step 2: If there is a cart, fetch the products and hydrate the cart -// Step 3: Authenticate the user -// Step 4: If the user is authenticated, merge the user's cart with the local cart -// Step 4B: Sync the cart to Payload and clear local storage -// Step 5: If the user is logged out, sync the cart to local storage only - -export const CartProvider = (props) => { - // const { setTimedNotification } = useNotifications(); - const { children } = props - const { status: authStatus, user } = useAuth() - - const [cart, dispatchCart] = useReducer(cartReducer, {}) - - const [total, setTotal] = useState<{ - formatted: string - raw: number - }>({ - formatted: '0.00', - raw: 0, - }) - - const hasInitialized = useRef(false) - const [hasInitializedCart, setHasInitialized] = useState(false) - - // Check local storage for a cart - // If there is a cart, fetch the products and hydrate the cart - useEffect(() => { - // wait for the user to be defined before initializing the cart - if (user === undefined) return - if (!hasInitialized.current) { - hasInitialized.current = true - - const syncCartFromLocalStorage = async () => { - const localCart = localStorage.getItem('cart') - - const parsedCart = JSON.parse(localCart || '{}') - - if (parsedCart?.items && parsedCart?.items?.length > 0) { - const initialCart = await Promise.all( - parsedCart.items.map(async ({ product, quantity }) => { - const res = await fetch( - `${process.env.NEXT_PUBLIC_SERVER_URL}/api/products/${product}`, - ) - const data = await res.json() - return { - product: data, - quantity, - } - }), - ) - - dispatchCart({ - type: 'SET_CART', - payload: { - items: initialCart, - }, - }) - } else { - dispatchCart({ - type: 'SET_CART', - payload: { - items: [], - }, - }) - } - } - - syncCartFromLocalStorage() - } - }, [user]) - - // authenticate the user and if logged in, merge the user's cart with local state - // only do this after we have initialized the cart to ensure we don't lose any items - useEffect(() => { - if (!hasInitialized.current) return - - if (authStatus === 'loggedIn') { - // merge the user's cart with the local state upon logging in - dispatchCart({ - type: 'MERGE_CART', - payload: user?.cart, - }) - } - - if (authStatus === 'loggedOut') { - // clear the cart from local state after logging out - dispatchCart({ - type: 'CLEAR_CART', - }) - } - }, [user, authStatus]) - - // every time the cart changes, determine whether to save to local storage or Payload based on authentication status - // upon logging in, merge and sync the existing local cart to Payload - useEffect(() => { - // wait until we have attempted authentication (the user is either an object or `null`) - if (!hasInitialized.current || user === undefined || !cart.items) return - - const flattenedCart = flattenCart(cart) - - if (user) { - // prevent updating the cart when the cart hasn't changed - if (JSON.stringify(flattenCart(user.cart)) === JSON.stringify(flattenedCart)) { - setHasInitialized(true) - return - } - - try { - const syncCartToPayload = async () => { - const req = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/${user.id}`, { - // Make sure to include cookies with fetch - body: JSON.stringify({ - cart: flattenedCart, - }), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - method: 'PATCH', - }) - - if (req.ok) { - localStorage.setItem('cart', '[]') - } - } - - syncCartToPayload() - } catch (e) { - console.error('Error while syncing cart to Payload.') // eslint-disable-line no-console - } - } else { - localStorage.setItem('cart', JSON.stringify(flattenedCart)) - } - - setHasInitialized(true) - }, [user, cart]) - - const isProductInCart = useCallback( - (incomingProduct: Product): boolean => { - let isInCart = false - const { items: itemsInCart } = cart || {} - if (Array.isArray(itemsInCart) && itemsInCart.length > 0) { - isInCart = Boolean( - itemsInCart.find(({ product }) => - typeof product === 'string' - ? product === incomingProduct.id - : product?.id === incomingProduct.id, - ), // eslint-disable-line function-paren-newline - ) - } - return isInCart - }, - [cart], - ) - - // this method can be used to add new items AND update existing ones - const addItemToCart = useCallback((incomingItem) => { - dispatchCart({ - type: 'ADD_ITEM', - payload: incomingItem, - }) - }, []) - - const deleteItemFromCart = useCallback((incomingProduct: Product) => { - dispatchCart({ - type: 'DELETE_ITEM', - payload: incomingProduct, - }) - }, []) - - const clearCart = useCallback(() => { - dispatchCart({ - type: 'CLEAR_CART', - }) - }, []) - - // calculate the new cart total whenever the cart changes - useEffect(() => { - if (!hasInitialized) return - - const newTotal = - cart?.items?.reduce((acc, item) => { - return ( - acc + - (typeof item.product === 'object' - ? JSON.parse(item?.product?.priceJSON || '{}')?.data?.[0]?.unit_amount * - (typeof item?.quantity === 'number' ? item?.quantity : 0) - : 0) - ) - }, 0) || 0 - - setTotal({ - formatted: (newTotal / 100).toLocaleString('en-US', { - currency: 'USD', - style: 'currency', - }), - raw: newTotal, - }) - }, [cart, hasInitialized]) - - return ( - - {children && children} - - ) -} diff --git a/templates/ecommerce/src/app/_providers/Cart/reducer.ts b/templates/ecommerce/src/app/_providers/Cart/reducer.ts deleted file mode 100644 index 04171b8e427..00000000000 --- a/templates/ecommerce/src/app/_providers/Cart/reducer.ts +++ /dev/null @@ -1,122 +0,0 @@ -import type { CartItems, Product, User } from '../../../payload/payload-types' - -export type CartItem = CartItems[0] - -type CartType = User['cart'] - -type CartAction = - | { - payload: CartItem - type: 'ADD_ITEM' - } - | { - payload: CartType - type: 'MERGE_CART' - } - | { - payload: CartType - type: 'SET_CART' - } - | { - payload: Product - type: 'DELETE_ITEM' - } - | { - type: 'CLEAR_CART' - } - -export const cartReducer = (cart: CartType, action: CartAction): CartType => { - switch (action.type) { - case 'SET_CART': { - return action.payload - } - - case 'MERGE_CART': { - const { payload: incomingCart } = action - - const syncedItems: CartItem[] = [ - ...(cart?.items || []), - ...(incomingCart?.items || []), - ].reduce((acc: CartItem[], item) => { - // remove duplicates - const productId = typeof item.product === 'string' ? item.product : item?.product?.id - - const indexInAcc = acc.findIndex(({ product }) => - typeof product === 'string' ? product === productId : product?.id === productId, - ) // eslint-disable-line function-paren-newline - - if (indexInAcc > -1) { - acc[indexInAcc] = { - ...acc[indexInAcc], - // customize the merge logic here, e.g.: - // quantity: acc[indexInAcc].quantity + item.quantity - } - } else { - acc.push(item) - } - return acc - }, []) - - return { - ...cart, - items: syncedItems, - } - } - - case 'ADD_ITEM': { - // if the item is already in the cart, increase the quantity - const { payload: incomingItem } = action - const productId = - typeof incomingItem.product === 'string' ? incomingItem.product : incomingItem?.product?.id - - const indexInCart = cart?.items?.findIndex(({ product }) => - typeof product === 'string' ? product === productId : product?.id === productId, - ) // eslint-disable-line function-paren-newline - - const withAddedItem = [...(cart?.items || [])] - - if (indexInCart === -1) { - withAddedItem.push(incomingItem) - } - - if (typeof indexInCart === 'number' && indexInCart > -1) { - withAddedItem[indexInCart] = { - ...withAddedItem[indexInCart], - quantity: (incomingItem.quantity || 0) > 0 ? incomingItem.quantity : undefined, - } - } - - return { - ...cart, - items: withAddedItem, - } - } - - case 'DELETE_ITEM': { - const { payload: incomingProduct } = action - const withDeletedItem = { ...cart } - - const indexInCart = cart?.items?.findIndex(({ product }) => - typeof product === 'string' - ? product === incomingProduct.id - : product?.id === incomingProduct.id, - ) // eslint-disable-line function-paren-newline - - if (typeof indexInCart === 'number' && withDeletedItem.items && indexInCart > -1) - withDeletedItem.items.splice(indexInCart, 1) - - return withDeletedItem - } - - case 'CLEAR_CART': { - return { - ...cart, - items: [], - } - } - - default: { - return cart - } - } -} diff --git a/templates/ecommerce/src/app/_providers/Theme/InitTheme/index.tsx b/templates/ecommerce/src/app/_providers/Theme/InitTheme/index.tsx deleted file mode 100644 index 206d655ee5b..00000000000 --- a/templates/ecommerce/src/app/_providers/Theme/InitTheme/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import Script from 'next/script' - -import { defaultTheme, themeLocalStorageKey } from '../ThemeSelector/types' - -export const InitTheme: React.FC = () => { - return ( - // eslint-disable-next-line @next/next/no-before-interactive-script-outside-document -