= ({ html, className }) => {
return (
new RegExp(text, 'gim');
-
-test('should be able to open and close cart', async ({ page }) => {
- let cart;
-
- await page.goto('/');
-
- await page.getByTestId('open-cart').click();
- cart = await page.getByTestId('cart');
- await expect(cart).toBeVisible();
- await expect(cart).toHaveText(regex('your cart is empty'));
-
- await page.getByTestId('close-cart').click();
- cart = await page.getByTestId('cart');
- await expect(cart).toBeHidden();
-});
-
-test('should be able to add item to cart, without selecting a variant, assuming first variant', async ({
- page
-}) => {
- await page.goto('/');
- await page.getByTestId('homepage-products').locator('a').first().click();
-
- const productName = await page.getByTestId('product-name').first().innerText();
- const firstVariant = await page.getByTestId('variant').first().innerText();
-
- await page.getByRole('button', { name: regex('add to cart') }).click();
-
- const cart = await page.getByTestId('cart');
-
- await expect(cart).toBeVisible();
-
- const cartItems = await page.getByTestId('cart-item').all();
- let isItemInCart = false;
-
- for (const item of cartItems) {
- const cartProductName = await item.getByTestId('cart-product-name').innerText();
- const cartProductVariant = await item.getByTestId('cart-product-variant').innerText();
-
- if (cartProductName === productName && cartProductVariant === firstVariant) {
- isItemInCart = true;
- break;
- }
- }
-
- await expect(isItemInCart).toBe(true);
-});
-
-test('should be able to add item to cart by selecting a variant', async ({ page }) => {
- await page.goto('/');
- await page.getByTestId('homepage-products').locator('a').first().click();
-
- const selectedProductName = await page.getByTestId('product-name').first().innerText();
- const secondVariant = await page.getByTestId('variant').nth(1);
-
- await secondVariant.click();
- const selectedProductVariant = await page.getByTestId('selected-variant').innerText();
-
- await page.getByRole('button', { name: regex('add to cart') }).click();
-
- const cart = await page.getByTestId('cart');
-
- await expect(cart).toBeVisible();
-
- const cartItem = await page.getByTestId('cart-item').first();
- const cartItemProductName = await cartItem.getByTestId('cart-product-name').innerText();
- const cartItemProductVariant = await cartItem.getByTestId('cart-product-variant').innerText();
-
- await expect(cartItemProductName).toBe(selectedProductName);
- await expect(cartItemProductVariant).toBe(selectedProductVariant);
-});
diff --git a/e2e/mobile-menu.spec.ts b/e2e/mobile-menu.spec.ts
deleted file mode 100644
index 0765f4d19..000000000
--- a/e2e/mobile-menu.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-test.use({ viewport: { width: 600, height: 900 } });
-test('should be able to open and close mobile menu', async ({ page }) => {
- let mobileMenu;
-
- await page.goto('/');
-
- await page.getByTestId('open-mobile-menu').click();
- mobileMenu = await page.getByTestId('mobile-menu');
- await expect(mobileMenu).toBeVisible();
-
- await page.getByTestId('close-mobile-menu').click();
- mobileMenu = await page.getByTestId('mobile-menu');
- await expect(mobileMenu).toBeHidden();
-});
diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts
index 2e23a7f78..a8804d045 100644
--- a/lib/shopify/index.ts
+++ b/lib/shopify/index.ts
@@ -1,5 +1,8 @@
import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
import { isShopifyError } from 'lib/type-guards';
+import { revalidateTag } from 'next/cache';
+import { headers } from 'next/headers';
+import { NextRequest, NextResponse } from 'next/server';
import {
addToCartMutation,
createCartMutation,
@@ -23,6 +26,7 @@ import {
Cart,
Collection,
Connection,
+ Image,
Menu,
Page,
Product,
@@ -151,6 +155,18 @@ const reshapeCollections = (collections: ShopifyCollection[]) => {
return reshapedCollections;
};
+const reshapeImages = (images: Connection, productTitle: string) => {
+ const flattened = removeEdgesAndNodes(images);
+
+ return flattened.map((image) => {
+ const filename = image.url.match(/.*\/(.*)\..*/)[1];
+ return {
+ ...image,
+ altText: image.altText || `${productTitle} - ${filename}`
+ };
+ });
+};
+
const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean = true) => {
if (!product || (filterHiddenProducts && product.tags.includes(HIDDEN_PRODUCT_TAG))) {
return undefined;
@@ -160,7 +176,7 @@ const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean =
return {
...rest,
- images: removeEdgesAndNodes(images),
+ images: reshapeImages(images, product.title),
variants: removeEdgesAndNodes(variants)
};
};
@@ -234,15 +250,16 @@ export async function updateCart(
return reshapeCart(res.body.data.cartLinesUpdate.cart);
}
-export async function getCart(cartId: string): Promise {
+export async function getCart(cartId: string): Promise {
const res = await shopifyFetch({
query: getCartQuery,
variables: { cartId },
cache: 'no-store'
});
+ // Old carts becomes `null` when you checkout.
if (!res.body.data.cart) {
- return null;
+ return undefined;
}
return reshapeCart(res.body.data.cart);
@@ -275,7 +292,7 @@ export async function getCollectionProducts({
variables: {
handle: collection,
reverse,
- sortKey
+ sortKey: sortKey === 'CREATED_AT' ? 'CREATED' : sortKey
}
});
@@ -394,3 +411,35 @@ export async function getProducts({
return reshapeProducts(removeEdgesAndNodes(res.body.data.products));
}
+
+// This is called from `app/api/revalidate.ts` so providers can control revalidation logic.
+export async function revalidate(req: NextRequest): Promise {
+ // We always need to respond with a 200 status code to Shopify,
+ // otherwise it will continue to retry the request.
+ const collectionWebhooks = ['collections/create', 'collections/delete', 'collections/update'];
+ const productWebhooks = ['products/create', 'products/delete', 'products/update'];
+ const topic = headers().get('x-shopify-topic') || 'unknown';
+ const secret = req.nextUrl.searchParams.get('secret');
+ const isCollectionUpdate = collectionWebhooks.includes(topic);
+ const isProductUpdate = productWebhooks.includes(topic);
+
+ if (!secret || secret !== process.env.SHOPIFY_REVALIDATION_SECRET) {
+ console.error('Invalid revalidation secret.');
+ return NextResponse.json({ status: 200 });
+ }
+
+ if (!isCollectionUpdate && !isProductUpdate) {
+ // We don't need to revalidate anything for any other topics.
+ return NextResponse.json({ status: 200 });
+ }
+
+ if (isCollectionUpdate) {
+ revalidateTag(TAGS.collections);
+ }
+
+ if (isProductUpdate) {
+ revalidateTag(TAGS.products);
+ }
+
+ return NextResponse.json({ status: 200, revalidated: true, now: Date.now() });
+}
diff --git a/lib/shopware/api-extended.ts b/lib/shopware/api-extended.ts
index 99b67c1d7..87db588fa 100644
--- a/lib/shopware/api-extended.ts
+++ b/lib/shopware/api-extended.ts
@@ -10,6 +10,7 @@ type operationsWithoutOriginal = Omit<
| 'readProductCrossSellings'
| 'readProductListing'
| 'searchPage'
+ | 'addLineItem'
| 'readCart'
| 'deleteLineItem'
>;
@@ -21,6 +22,7 @@ export type extendedPaths =
| 'readProductCrossSellings post /product/{productId}/cross-selling'
| 'readProductListing post /product-listing/{categoryId}'
| 'searchPage post /search'
+ | 'addLineItem post /checkout/cart/line-item'
| 'readCart get /checkout/cart?name'
| 'deleteLineItem delete /checkout/cart/line-item?id[]={ids}'
| operationPaths;
@@ -32,6 +34,7 @@ export type extendedOperations = operationsWithoutOriginal & {
readProductCrossSellings: extendedReadProductCrossSellings;
readProductListing: extendedReadProductListing;
searchPage: extendedSearchPage;
+ addLineItem: extendedAddLineItem;
readCart: extendedReadCart;
deleteLineItem: extendedDeleteLineItem;
};
@@ -340,6 +343,26 @@ type extendedReadProductListing = {
};
};
+type extendedCartItems = components['schemas']['ArrayStruct'] & {
+ items?: Partial[];
+};
+
+type extendedAddLineItem = {
+ requestBody?: {
+ content: {
+ 'application/json': extendedCartItems;
+ };
+ };
+ responses: {
+ /** The updated cart. */
+ 200: {
+ content: {
+ 'application/json': ExtendedCart;
+ };
+ };
+ };
+};
+
type extendedReadCart = {
parameters: {
query?: {
diff --git a/package.json b/package.json
index 5376c0e6a..bff1c09d5 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,9 @@
{
"private": true,
+ "packageManager": "pnpm@8.2.0",
"engines": {
"node": ">=18",
- "pnpm": ">=8"
+ "pnpm": ">=7"
},
"scripts": {
"build": "next build",
@@ -12,29 +13,26 @@
"prettier": "prettier --write --ignore-unknown .",
"prettier:check": "prettier --check --ignore-unknown .",
"start": "next start",
- "test": "pnpm lint && pnpm prettier:check",
- "test:e2e": "playwright test"
+ "test": "pnpm lint && pnpm prettier:check"
},
"lint-staged": {
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@headlessui/react": "^1.7.15",
+ "@heroicons/react": "^2.0.18",
"@shopware/api-client": "0.0.0-canary-20230725064840",
- "@vercel/og": "^0.5.9",
- "clsx": "^1.2.1",
- "is-empty-iterable": "^3.0.0",
+ "clsx": "^2.0.0",
"next": "13.4.12",
"react": "18.2.0",
- "react-cookie": "^4.1.1",
"react-dom": "18.2.0",
"react-paginate": "^8.2.0"
},
"devDependencies": {
- "@playwright/test": "^1.36.1",
+ "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.9",
- "@types/node": "^20.4.4",
- "@types/react": "18.2.15",
+ "@types/node": "20.4.4",
+ "@types/react": "18.2.16",
"@types/react-dom": "18.2.7",
"@vercel/git-hooks": "^1.0.0",
"autoprefixer": "^10.4.14",
diff --git a/playwright.config.ts b/playwright.config.ts
deleted file mode 100644
index f98a725b5..000000000
--- a/playwright.config.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { PlaywrightTestConfig, devices } from '@playwright/test';
-import path from 'path';
-
-const baseURL = `http://localhost:${process.env.PORT || 3000}`;
-const config: PlaywrightTestConfig = {
- testDir: path.join(__dirname, 'e2e'),
- retries: 2,
- outputDir: '.playwright',
- webServer: {
- command: 'pnpm build && pnpm start',
- url: baseURL,
- timeout: 120 * 1000,
- reuseExistingServer: !process.env.CI
- },
- use: {
- baseURL,
- trace: 'retry-with-trace'
- },
- projects: [
- {
- name: 'Desktop Chrome',
- use: {
- ...devices['Desktop Chrome']
- }
- },
- {
- name: 'Desktop Safari',
- use: {
- ...devices['Desktop Safari']
- }
- },
- {
- name: 'Mobile Chrome',
- use: {
- ...devices['Pixel 5']
- }
- },
- {
- name: 'Mobile Safari',
- use: devices['iPhone 12']
- }
- ]
-};
-
-export default config;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5cff2f7e5..3297d0d9c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,27 +8,21 @@ dependencies:
'@headlessui/react':
specifier: ^1.7.15
version: 1.7.15(react-dom@18.2.0)(react@18.2.0)
+ '@heroicons/react':
+ specifier: ^2.0.18
+ version: 2.0.18(react@18.2.0)
'@shopware/api-client':
specifier: 0.0.0-canary-20230725064840
version: 0.0.0-canary-20230725064840
- '@vercel/og':
- specifier: ^0.5.9
- version: 0.5.9
clsx:
- specifier: ^1.2.1
- version: 1.2.1
- is-empty-iterable:
- specifier: ^3.0.0
- version: 3.0.0
+ specifier: ^2.0.0
+ version: 2.0.0
next:
specifier: 13.4.12
version: 13.4.12(react-dom@18.2.0)(react@18.2.0)
react:
specifier: 18.2.0
version: 18.2.0
- react-cookie:
- specifier: ^4.1.1
- version: 4.1.1(react@18.2.0)
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
@@ -37,18 +31,18 @@ dependencies:
version: 8.2.0(react@18.2.0)
devDependencies:
- '@playwright/test':
- specifier: ^1.36.1
- version: 1.36.1
+ '@tailwindcss/container-queries':
+ specifier: ^0.1.1
+ version: 0.1.1(tailwindcss@3.3.3)
'@tailwindcss/typography':
specifier: ^0.5.9
version: 0.5.9(tailwindcss@3.3.3)
'@types/node':
- specifier: ^20.4.4
+ specifier: 20.4.4
version: 20.4.4
'@types/react':
- specifier: 18.2.15
- version: 18.2.15
+ specifier: 18.2.16
+ version: 18.2.16
'@types/react-dom':
specifier: 18.2.7
version: 18.2.7
@@ -181,6 +175,14 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@heroicons/react@2.0.18(react@18.2.0):
+ resolution: {integrity: sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==}
+ peerDependencies:
+ react: '>= 16'
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'}
@@ -359,22 +361,6 @@ packages:
tslib: 2.6.0
dev: true
- /@playwright/test@1.36.1:
- resolution: {integrity: sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==}
- engines: {node: '>=16'}
- hasBin: true
- dependencies:
- '@types/node': 20.4.4
- playwright-core: 1.36.1
- optionalDependencies:
- fsevents: 2.3.2
- dev: true
-
- /@resvg/resvg-wasm@2.4.1:
- resolution: {integrity: sha512-yi6R0HyHtsoWTRA06Col4WoDs7SvlXU3DLMNP2bdAgs7HK18dTEVl1weXgxRzi8gwLteGUbIg29zulxIB3GSdg==}
- engines: {node: '>= 10'}
- dev: false
-
/@rushstack/eslint-patch@1.3.2:
resolution: {integrity: sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==}
dev: true
@@ -385,21 +371,20 @@ packages:
ofetch: 1.1.1
dev: false
- /@shuding/opentype.js@1.4.0-beta.0:
- resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==}
- engines: {node: '>= 8.0.0'}
- hasBin: true
- dependencies:
- fflate: 0.7.4
- string.prototype.codepointat: 0.2.1
- dev: false
-
/@swc/helpers@0.5.1:
resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==}
dependencies:
tslib: 2.6.0
dev: false
+ /@tailwindcss/container-queries@0.1.1(tailwindcss@3.3.3):
+ resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==}
+ peerDependencies:
+ tailwindcss: '>=3.2.0'
+ dependencies:
+ tailwindcss: 3.3.3
+ dev: true
+
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.3):
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
peerDependencies:
@@ -412,17 +397,6 @@ packages:
tailwindcss: 3.3.3
dev: true
- /@types/cookie@0.3.3:
- resolution: {integrity: sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==}
- dev: false
-
- /@types/hoist-non-react-statics@3.3.1:
- resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==}
- dependencies:
- '@types/react': 18.2.15
- hoist-non-react-statics: 3.3.2
- dev: false
-
/@types/json5@0.0.29:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true
@@ -437,22 +411,25 @@ packages:
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
+ dev: true
/@types/react-dom@18.2.7:
resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==}
dependencies:
- '@types/react': 18.2.15
+ '@types/react': 18.2.16
dev: true
- /@types/react@18.2.15:
- resolution: {integrity: sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==}
+ /@types/react@18.2.16:
+ resolution: {integrity: sha512-LLFWr12ZhBJ4YVw7neWLe6Pk7Ey5R9OCydfuMsz1L8bZxzaawJj2p06Q8/EFEHDeTBQNFLF62X+CG7B2zIyu0Q==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3
csstype: 3.1.2
+ dev: true
/@types/scheduler@0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
+ dev: true
/@typescript-eslint/parser@5.62.0(eslint@8.45.0)(typescript@5.1.6):
resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==}
@@ -521,15 +498,6 @@ packages:
requiresBuild: true
dev: true
- /@vercel/og@0.5.9:
- resolution: {integrity: sha512-CtjaV/BVHtNCjRtxGqn8Q6AKFLqcG34Byxr91+mY+4eqyp/09LVe9jEeY9WXjbaKvu8syWPMteTpY+YQUQYzSg==}
- engines: {node: '>=16'}
- dependencies:
- '@resvg/resvg-wasm': 2.4.1
- satori: 0.10.1
- yoga-wasm-web: 0.3.3
- dev: false
-
/acorn-jsx@5.3.2(acorn@8.10.0):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -733,11 +701,6 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
- /base64-js@0.0.8:
- resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
- engines: {node: '>= 0.4'}
- dev: false
-
/big-integer@1.6.51:
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
engines: {node: '>=0.6'}
@@ -816,10 +779,6 @@ packages:
engines: {node: '>= 6'}
dev: true
- /camelize@1.0.1:
- resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
- dev: false
-
/caniuse-lite@1.0.30001517:
resolution: {integrity: sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==}
@@ -904,8 +863,8 @@ packages:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
- /clsx@1.2.1:
- resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
+ /clsx@2.0.0:
+ resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
engines: {node: '>=6'}
dev: false
@@ -928,6 +887,7 @@ packages:
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ dev: true
/colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@@ -947,11 +907,6 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
- /cookie@0.4.2:
- resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
- engines: {node: '>= 0.6'}
- dev: false
-
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@@ -961,27 +916,6 @@ packages:
which: 2.0.2
dev: true
- /css-background-parser@0.1.0:
- resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==}
- dev: false
-
- /css-box-shadow@1.0.0-3:
- resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==}
- dev: false
-
- /css-color-keywords@1.0.0:
- resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
- engines: {node: '>=4'}
- dev: false
-
- /css-to-react-native@3.2.0:
- resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
- dependencies:
- camelize: 1.0.1
- css-color-keywords: 1.0.0
- postcss-value-parser: 4.2.0
- dev: false
-
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -990,6 +924,7 @@ packages:
/csstype@3.1.2:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
+ dev: true
/damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -1099,10 +1034,6 @@ packages:
resolution: {integrity: sha512-6M1qyhaJOt7rQtNti1lBA0GwclPH+oKCmsra/hkcWs5INLxfXXD/dtdnaKUYQu/pjOBP/8Osoe4mAcNvvzoFag==}
dev: true
- /emoji-regex@10.2.1:
- resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==}
- dev: false
-
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
@@ -1199,10 +1130,6 @@ packages:
engines: {node: '>=6'}
dev: true
- /escape-html@1.0.3:
- resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
- dev: false
-
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@@ -1577,10 +1504,6 @@ packages:
reusify: 1.0.4
dev: true
- /fflate@0.7.4:
- resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==}
- dev: false
-
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -1836,17 +1759,6 @@ packages:
function-bind: 1.1.1
dev: true
- /hex-rgb@4.3.0:
- resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
- engines: {node: '>=6'}
- dev: false
-
- /hoist-non-react-statics@3.3.2:
- resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
- dependencies:
- react-is: 16.13.1
- dev: false
-
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
@@ -1980,11 +1892,6 @@ packages:
hasBin: true
dev: true
- /is-empty-iterable@3.0.0:
- resolution: {integrity: sha512-ZXVNGZrRvda9spnGVME3nTYTyDNjCTrmRy3DfDjBaMQ7aftcPsy/vkJoLL47IwcAbgioIfGvjQJWdit8GiggPg==}
- engines: {node: '>=12'}
- dev: false
-
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -2181,13 +2088,6 @@ packages:
engines: {node: '>=10'}
dev: true
- /linebreak@1.1.0:
- resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
- dependencies:
- base64-js: 0.0.8
- unicode-trie: 2.0.0
- dev: false
-
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
@@ -2590,10 +2490,6 @@ packages:
engines: {node: '>=6'}
dev: true
- /pako@0.2.9:
- resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
- dev: false
-
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2601,13 +2497,6 @@ packages:
callsites: 3.1.0
dev: true
- /parse-css-color@0.2.1:
- resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
- dependencies:
- color-name: 1.1.4
- hex-rgb: 4.3.0
- dev: false
-
/parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
@@ -2671,12 +2560,6 @@ packages:
engines: {node: '>= 6'}
dev: true
- /playwright-core@1.36.1:
- resolution: {integrity: sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==}
- engines: {node: '>=16'}
- hasBin: true
- dev: true
-
/pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -2749,6 +2632,7 @@ packages:
/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ dev: true
/postcss@8.4.14:
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
@@ -2850,17 +2734,6 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
- /react-cookie@4.1.1(react@18.2.0):
- resolution: {integrity: sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==}
- peerDependencies:
- react: '>= 16.3.0'
- dependencies:
- '@types/hoist-non-react-statics': 3.3.1
- hoist-non-react-statics: 3.3.2
- react: 18.2.0
- universal-cookie: 4.0.4
- dev: false
-
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
@@ -3035,22 +2908,6 @@ packages:
is-regex: 1.1.4
dev: true
- /satori@0.10.1:
- resolution: {integrity: sha512-F4bTCkDp931tLb7+UCNPBuSQwXhikrUkI4fBQo6fA8lF0Evqqgg3nDyUpRktQpR5Ry1DIiIVqLyEwkAms87ykg==}
- engines: {node: '>=16'}
- dependencies:
- '@shuding/opentype.js': 1.4.0-beta.0
- css-background-parser: 0.1.0
- css-box-shadow: 1.0.0-3
- css-to-react-native: 3.2.0
- emoji-regex: 10.2.1
- escape-html: 1.0.3
- linebreak: 1.1.0
- parse-css-color: 0.2.1
- postcss-value-parser: 4.2.0
- yoga-wasm-web: 0.3.3
- dev: false
-
/scheduler@0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
@@ -3189,10 +3046,6 @@ packages:
strip-ansi: 7.1.0
dev: true
- /string.prototype.codepointat@0.2.1:
- resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==}
- dev: false
-
/string.prototype.matchall@4.0.8:
resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==}
dependencies:
@@ -3387,10 +3240,6 @@ packages:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true
- /tiny-inflate@1.0.3:
- resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
- dev: false
-
/titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
engines: {node: '>=12'}
@@ -3517,20 +3366,6 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
- /unicode-trie@2.0.0:
- resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
- dependencies:
- pako: 0.2.9
- tiny-inflate: 1.0.3
- dev: false
-
- /universal-cookie@4.0.4:
- resolution: {integrity: sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==}
- dependencies:
- '@types/cookie': 0.3.3
- cookie: 0.4.2
- dev: false
-
/untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
@@ -3637,10 +3472,6 @@ packages:
engines: {node: '>=10'}
dev: true
- /yoga-wasm-web@0.3.3:
- resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==}
- dev: false
-
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
dev: false
diff --git a/tailwind.config.js b/tailwind.config.js
index 643aad5a8..0f10cf955 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,26 +1,13 @@
const plugin = require('tailwindcss/plugin');
-const colors = require('tailwindcss/colors');
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: [
- './pages/**/*.{js,ts,jsx,tsx}',
- './components/**/*.{js,ts,jsx,tsx}',
- './icons/**/*.{js,ts,jsx,tsx}',
- './app/**/*.{js,ts,jsx,tsx}'
- ],
+ content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)']
},
- colors: {
- gray: colors.neutral,
- hotPink: '#FF1966',
- dark: '#111111',
- light: '#FAFAFA',
- violetDark: '#4c2889'
- },
keyframes: {
fadeIn: {
from: { opacity: 0 },
@@ -47,6 +34,7 @@ module.exports = {
hoverOnlyWhenSupported: true
},
plugins: [
+ require('@tailwindcss/container-queries'),
require('@tailwindcss/typography'),
plugin(({ matchUtilities, theme }) => {
matchUtilities(