diff --git a/env.local b/env.local
new file mode 100644
index 00000000..a729d9e1
--- /dev/null
+++ b/env.local
@@ -0,0 +1,8 @@
+NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-WSXY307CRR
+NEXT_PUBLIC_GOOGLE_MAPS_KEY=AIzaSyB3mMuvl8IUlviRZiizBiX7uhsdIqunx94
+
+NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=3b70a0fb51e102381b458316d9fe2c8d
+NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=https://hashflagswag.myshopify.com/api/2022-10/graphql.json
+
+SHOPIFY_STORE_DOMAIN=https://hashflagswag.myshopify.com/api/2022-10/graphql.json
+SHOPIFY_STOREFRONT_ACCESS_TOKEN=3b70a0fb51e102381b458316d9fe2c8d
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 9de26625..94c8c933 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,11 @@
"dotenv": "^16.3.1",
"express": "^4.18.2",
"fast-equals": "3.0.3",
+<<<<<<< HEAD
+ "framer-motion": "^6.5.1",
+ "graphql-request": "^7.1.2",
+=======
+>>>>>>> origin/master
"gray-matter": "^4.0.3",
"lucide-react": "^0.378.0",
"mailchimp-api-v3": "^1.15.0",
@@ -2030,6 +2035,15 @@
"resolved": "https://registry.npmjs.org/@googlemaps/typescript-guards/-/typescript-guards-2.0.3.tgz",
"integrity": "sha512-3iHuO8H0jPehftsMK0kgyJzPYU/g/oiTRw+wu/yltqSZ7wJPt3vfsJHkPiuRpQjbnnWygX+T3mkRGyK/eyZ/lw=="
},
+ "node_modules/@graphql-typed-document-node/core": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
+ "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -10084,6 +10098,28 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
+ "node_modules/graphql": {
+ "version": "16.10.0",
+ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz",
+ "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+ }
+ },
+ "node_modules/graphql-request": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-7.1.2.tgz",
+ "integrity": "sha512-+XE3iuC55C2di5ZUrB4pjgwe+nIQBuXVIK9J98wrVwojzDW3GMdSBZfxUk8l4j9TieIpjpggclxhNEU9ebGF8w==",
+ "license": "MIT",
+ "dependencies": {
+ "@graphql-typed-document-node/core": "^3.2.0"
+ },
+ "peerDependencies": {
+ "graphql": "14 - 16"
+ }
+ },
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
diff --git a/package.json b/package.json
index ab9172c9..e24c634b 100644
--- a/package.json
+++ b/package.json
@@ -20,9 +20,13 @@
"@ai-sdk/azure": "^1.0.7",
"@googlemaps/react-wrapper": "^1.1.35",
"@googlemaps/typescript-guards": "^2.0.1",
+<<<<<<< HEAD
+ "@playwright/test": "^1.49.0",
+=======
"@octokit/rest": "^21.0.2",
"@playwright/test": "^1.49.0",
"@radix-ui/react-dialog": "^1.1.4",
+>>>>>>> origin/master
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^1.1.0",
"@types/express": "^4.17.17",
@@ -37,6 +41,11 @@
"dotenv": "^16.3.1",
"express": "^4.18.2",
"fast-equals": "3.0.3",
+<<<<<<< HEAD
+ "framer-motion": "^6.5.1",
+ "graphql-request": "^7.1.2",
+=======
+>>>>>>> origin/master
"gray-matter": "^4.0.3",
"lucide-react": "^0.378.0",
"mailchimp-api-v3": "^1.15.0",
diff --git a/src/components/product-card/ProductCard.module.css b/src/components/product-card/ProductCard.module.css
new file mode 100644
index 00000000..e97b6892
--- /dev/null
+++ b/src/components/product-card/ProductCard.module.css
@@ -0,0 +1,15 @@
+.card {
+ padding: 1rem 1.2rem;
+}
+
+.image {
+ width: 100%;
+ height: 600px;
+ position: relative;
+ cursor: pointer;
+}
+.content {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 0.5rem;
+}
\ No newline at end of file
diff --git a/src/components/product-card/ProductCard.tsx b/src/components/product-card/ProductCard.tsx
new file mode 100644
index 00000000..4843619b
--- /dev/null
+++ b/src/components/product-card/ProductCard.tsx
@@ -0,0 +1,62 @@
+import Image from 'next/image';
+import Link from 'next/link';
+import styles from './ProductCard.module.css';
+
+interface ProductCardProps {
+ product: {
+ node: {
+ id: string;
+ title: string;
+ handle: string;
+ featuredImage?: {
+ url: string;
+ altText?: string;
+ };
+ priceRange: {
+ minVariantPrice: {
+ amount: string;
+ };
+ };
+ };
+ };
+}
+
+export default function ProductCard({ product }: ProductCardProps) {
+
+ const { node } = product;
+
+ return (
+
+
+
+ {node.featuredImage?.url ? (
+
+ ) : (
+
No Image Available
+ )}
+
+
+
+
+
+
+ {node.title}
+
+
+
+ {new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ }).format(parseFloat(node.priceRange.minVariantPrice.amount))}
+
+
+
+ );
+}
diff --git a/src/components/product-details/product-details.js b/src/components/product-details/product-details.js
new file mode 100644
index 00000000..659a8987
--- /dev/null
+++ b/src/components/product-details/product-details.js
@@ -0,0 +1,51 @@
+import Image from "next/image";
+import Link from "next/link";
+import { useState } from "react";
+import styles from "./ProductDetails.module.css";
+
+export default function ProductDetails({ product }) {
+ const [quantity, setQuantity] = useState(0);
+ const [checkout, setCheckout] = useState(false);
+ const updateQuantity = (e) => {
+ setQuantity(e.target.value);
+ if (quantity == 0) setCheckout(false);
+ };
+
+ const handleAddToCart = async () => {
+ let cartId = sessionStorage.getItem("cartId");
+ if (quantity > 0) {
+ if (cartId) {
+ await updateCart(cartId, product.variants.edges[0].node.id, quantity);
+ setCheckout(true);
+ } else {
+ let data = await addToCart(product.variants.edges[0].node.id, quantity);
+ cartId = data.cartCreate.cart.id;
+ sessionStorage.setItem("cartId", cartId);
+ setCheckout(true);
+ }
+ }
+ };
+ return (
+ <>
+
+
+
+
+
+
+ {product.title}
+ {product.priceRange.minVariantPrice.amount}
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/data/menu.ts b/src/data/menu.ts
index 8f2a5cdc..3caa14cd 100644
--- a/src/data/menu.ts
+++ b/src/data/menu.ts
@@ -107,7 +107,7 @@ const navigation: NavigationItem[] = [
{
id: 62,
label: "Shop",
- path: "https://hashflag.shop/",
+ path: "/shop",
external: true,
},
{
diff --git a/src/pages/shop/home.module.css b/src/pages/shop/home.module.css
new file mode 100644
index 00000000..e00d2610
--- /dev/null
+++ b/src/pages/shop/home.module.css
@@ -0,0 +1,16 @@
+.products {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+}
+@media screen and (max-width: 992px) {
+ .products {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ }
+}
+@media screen and (max-width: 600px) {
+ .products {
+ display: grid;
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/shop/index.js b/src/pages/shop/index.js
new file mode 100644
index 00000000..048a64b3
--- /dev/null
+++ b/src/pages/shop/index.js
@@ -0,0 +1,49 @@
+import Head from "next/head";
+import { getProducts } from "../../utils/shopify";
+import ProductCard from "../../components/product-card/ProductCard";
+import styles from "./home.module.css";
+
+export default function Home({ data }) {
+ const products = data.products.edges;
+
+ return (
+ <>
+
+ Nextjs Shopify
+
+
+
+
+
+
+ {products.length ? (
+ products.map((product) => (
+
+ ))
+ ) : (
+
No products available at the moment.
+ )}
+
+
+ >
+ );
+}
+
+export const getServerSideProps = async () => {
+ try {
+ const data = await getProducts();
+
+ if (!data || !data.products || !data.products.edges) {
+ throw new Error("Invalid data from Shopify API");
+ }
+
+ return {
+ props: { data },
+ };
+ } catch (error) {
+ console.error("Error fetching products:", error);
+ return {
+ props: { data: { products: { edges: [] } } },
+ };
+ }
+};
diff --git a/src/utils/shopify.js b/src/utils/shopify.js
new file mode 100644
index 00000000..8c6a10a9
--- /dev/null
+++ b/src/utils/shopify.js
@@ -0,0 +1,190 @@
+import { gql, GraphQLClient } from "graphql-request";
+
+const storefrontAccessToken = process.env.SHOPIFY_STOREFRONT_ACCESS;
+const endpoint = process.env.SHOPIFY_STORE_DOMAIN;
+
+const graphQLClient = new GraphQLClient(endpoint, {
+ headers: {
+ "X-Shopify-Storefront-Access-Token": storefrontAccessToken,
+ },
+});
+
+export async function getProducts() {
+ const getAllProductsQuery = gql`
+ {
+ products(first: 10) {
+ edges {
+ node {
+ id
+ title
+ handle
+ priceRange {
+ minVariantPrice {
+ amount
+ }
+ }
+ featuredImage {
+ altText
+ url
+ }
+ }
+ }
+ }
+ }
+ `;
+ try {
+ return await graphQLClient.request(getAllProductsQuery);
+ } catch (error) {
+ throw new Error(error);
+ }
+}
+
+export async function addToCart(itemId, quantity) {
+ const createCartMutation = gql`
+ mutation createCart($cartInput: CartInput) {
+ cartCreate(input: $cartInput) {
+ cart {
+ id
+ }
+ }
+ }
+ `;
+ const variables = {
+ cartInput: {
+ lines: [
+ {
+ quantity: parseInt(quantity),
+ merchandiseId: itemId,
+ },
+ ],
+ },
+ };
+ try {
+ return await graphQLClient.request(createCartMutation, variables);
+ } catch (error) {
+ throw new Error(error);
+ }
+}
+
+export async function updateCart(cartId, itemId, quantity) {
+ const updateCartMutation = gql`
+ mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
+ cartLinesAdd(cartId: $cartId, lines: $lines) {
+ cart {
+ id
+ }
+ }
+ }
+ `;
+ const variables = {
+ cartId: cartId,
+ lines: [
+ {
+ quantity: parseInt(quantity),
+ merchandiseId: itemId,
+ },
+ ],
+ };
+ try {
+ return await graphQLClient.request(updateCartMutation, variables);
+ } catch (error) {
+ throw new Error(error);
+ }
+}
+
+export async function retrieveCart(cartId) {
+ const cartQuery = gql`
+ query cartQuery($cartId: ID!) {
+ cart(id: $cartId) {
+ id
+ createdAt
+ updatedAt
+
+ lines(first: 10) {
+ edges {
+ node {
+ id
+ quantity
+ merchandise {
+ ... on ProductVariant {
+ id
+ }
+ }
+ }
+ }
+ }
+ estimatedCost {
+ totalAmount {
+ amount
+ }
+ }
+ }
+ }
+ `;
+ const variables = {
+ cartId,
+ };
+ try {
+ const data = await graphQLClient.request(cartQuery, variables);
+ return data.cart;
+ } catch (error) {
+ throw new Error(error);
+ }
+}
+
+export const getProduct = async (id) => {
+ const productQuery = gql`
+ query getProduct($id: ID!) {
+ product(id: $id) {
+ id
+ handle
+ title
+ description
+ priceRange {
+ minVariantPrice {
+ amount
+ currencyCode
+ }
+ }
+ featuredImage {
+ url
+ altText
+ }
+ variants(first: 10) {
+ edges {
+ node {
+ id
+ }
+ }
+ }
+ }
+ }
+ `;
+
+ const variables = { id };
+
+ try {
+ const data = await graphQLClient.request(productQuery, variables);
+ return data.product; // Return the product object
+ } catch (error) {
+ throw new Error(`Error fetching product by ID: ${error.message}`);
+ }
+};
+
+export const getCheckoutUrl = async (cartId) => {
+ const getCheckoutUrlQuery = gql`
+ query checkoutURL($cartId: ID!) {
+ cart(id: $cartId) {
+ checkoutUrl
+ }
+ }
+ `;
+ const variables = {
+ cartId: cartId,
+ };
+ try {
+ return await graphQLClient.request(getCheckoutUrlQuery, variables);
+ } catch (error) {
+ throw new Error(error);
+ }
+};
diff --git a/tsconfig.json b/tsconfig.json
index 0d43c064..30723769 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -45,6 +45,6 @@
"postcss.config.js",
"next-sitemap.config.js",
"playwright.config.ts"
- ],
+, "src/pages/shop/index.js", "src/utils/shopify.js" ],
"exclude": ["node_modules"]
}
\ No newline at end of file