Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Nuxt Apollo
- Pinia with pinia-plugin-persistedstate for cart and state management
- Responsive design
- Swiper integration for Hero section
- Support for simple and variable products
- Stock quantity on simple and variable products
- CSS animations and transitions
Expand Down
75 changes: 23 additions & 52 deletions components/Cart/CartContents.vue
Original file line number Diff line number Diff line change
@@ -1,59 +1,30 @@
<template>
<div>
<div v-if="data.cart?.contents?.nodes?.length">
<h1 class="h-10 p-6 text-3xl font-bold text-center">Cart</h1>
<section class="mt-10">
<div
v-for="products in data.cart.contents.nodes"
:key="products.id"
class="container mx-auto mt-4 flex-container"
>
<div class="item">
<span class="block mt-2 font-extrabold">Remove: <br /></span>
<span class="item-content">
<nuxt-img
class="mt-2 ml-4 cursor-pointer"
:class="{ removing: isRemoving }"
alt="Remove icon"
aria-label="Remove"
src="/svg/Remove.svg"
@click="handleRemoveProduct(products)"
/>
</span>
</div>
<div class="item">
<span class="block mt-2 font-extrabold">Name: <br /></span>
<span class="item-content">{{ products.product.node.name }}</span>
</div>
<div class="item">
<span class="block mt-2 font-extrabold">Quantity: <br /> </span>
<span class="item-content">
{{ products.quantity }}
</span>
</div>
<div class="item">
<span class="block mt-2 font-extrabold">Subtotal: <br /></span>
<span class="item-content"> {{ products.total }} </span>
</div>
</div>
</section>
</div>
<h2
v-if="!data.cart?.contents?.nodes?.length"
class="mt-64 text-3xl text-center"
>
Cart is currently empty
</h2>
<CommonButton
link-to="/checkout"
v-if="showCheckoutButton && data.cart?.contents?.nodes?.length"
center-button
>CHECKOUT</CommonButton
>
</div>
<template v-if="data.cart?.contents?.nodes?.length">
<h1 class="h-10 p-6 text-3xl font-bold text-center">Cart</h1>
<section class="mt-10">
<CartItem
v-for="product in data.cart.contents.nodes"
:key="product.id"
:product="product"
@remove="handleRemoveProduct"
/>
</section>
<CommonButton link-to="/checkout" v-if="showCheckoutButton" center-button>
CHECKOUT
</CommonButton>
</template>
<h2 v-else class="mt-64 text-3xl text-center">Cart is currently empty</h2>
</template>

<script setup>
/**
* Vue.js component for handling the logic of removing a product from the cart and updating the cart state.
*
* @module CartContents
* @param {Object} props - Object containing the component's properties.
* @param {Boolean} props.showCheckoutButton - Determines whether the checkout button should be shown or not.
* @returns {Object} The Vue.js component object.
*/
import GET_CART_QUERY from "@/apollo/queries/GET_CART_QUERY.gql";
import UPDATE_CART_MUTATION from "@/apollo/mutations/UPDATE_CART_MUTATION.gql";

Expand Down
95 changes: 95 additions & 0 deletions components/Cart/CartItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div class="container mx-auto mt-4 flex-container">
<div class="item">
<span class="block mt-2 font-extrabold">Remove: <br /></span>
<span class="item-content">
<nuxt-img
class="mt-2 ml-4 cursor-pointer"
:class="{ removing: isRemoving }"
alt="Remove icon"
aria-label="Remove"
src="/svg/Remove.svg"
@click="emitRemove"
/>
</span>
</div>
<div class="item">
<span class="block mt-2 font-extrabold">Name: <br /></span>
<span class="item-content">{{ product.product.node.name }}</span>
</div>
<div class="item">
<span class="block mt-2 font-extrabold">Quantity: <br /></span>
<span class="item-content">
{{ product.quantity }}
</span>
</div>
<div class="item">
<span class="block mt-2 font-extrabold">Subtotal: <br /></span>
<span class="item-content"
>{{ formatPrice(`${product.total}`) }}</span
>
</div>
</div>
</template>

<script setup>
/**
* Vue component representing a product item in a shopping cart.
*
* @component CartItem
*
* @prop {Object} product - The product object containing information about the product.
* @prop {string} product.name - The name of the product.
* @prop {number} product.quantity - The quantity of the product.
* @prop {number} product.total - The subtotal of the product.
*
* @emits CartItem#remove - Emitted when the remove button is clicked.
*/

import { defineProps, defineEmits } from "vue";

import { formatPrice } from "@/utils/functions";

const isRemoving = useState("isRemoving", () => false);

const props = defineProps({
product: {
type: Object,
required: true,
},
});

const emit = defineEmits(["remove"]);

/**
* Emits a "remove" event with the `product` prop as the payload.
*/
const emitRemove = () => {
emit("remove", props.product);
};
</script>

<style scoped>
.flex-container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-around;
align-items: center;
align-content: center;
max-width: 1380px;
@apply border border-gray-300 rounded-lg shadow;
}

.item {
@apply lg:m-2 xl:m-4 xl:w-1/6 lg:w-1/6 sm:m-2 w-auto;
}

.item-content {
@apply inline-block mt-4 w-20 h-12 md:w-full lg:w-full xl:w-full;
}

.removing {
@apply animate-spin cursor-not-allowed;
}
</style>
95 changes: 82 additions & 13 deletions components/Index/IndexHero.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,93 @@
<template>
<div>
<nuxt-img
class="mx-auto"
alt="Image slider slide #1"
src="/images/Hero.jpg"
/>
<div
class="flex flex-col items-start justify-center w-full mx-auto tracking-wide lg:w-1/2"
>
<span
class="w-full p-4 mt-4 text-center text-white bg-black text-md lg:text-2xl lg:-mt-64"
>
Modern Interior Sample
</span>
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<nuxt-img
class="mx-auto"
alt="Image slider slide #1"
src="/images/Hero.jpg"
/>
<div
class="flex flex-col items-start justify-center w-full mx-auto tracking-wide lg:w-1/2"
>
<span
class="w-full p-4 mt-4 text-center text-white bg-black text-md lg:text-2xl lg:-mt-64"
>
Modern Interior Sample #1
</span>
</div>
</div>

<div class="swiper-slide">
<nuxt-img
class="mx-auto"
alt="Image slider slide #1"
src="/images/Hero2.jpg"
/>
<div
class="flex flex-col items-start justify-center w-full mx-auto tracking-wide lg:w-1/2"
>
<span
class="w-full p-4 mt-4 text-center text-white bg-black text-md lg:text-2xl lg:-mt-64"
>
Modern Interior Sample #2
</span>
</div>
</div>
<div class="swiper-slide">
<nuxt-img
class="mx-auto"
alt="Image slider slide #1"
src="/images/Hero3.jpg"
/>
<div
class="flex flex-col items-start justify-center w-full mx-auto tracking-wide lg:w-1/2"
>
<span
class="w-full p-4 mt-4 text-center text-white bg-black text-md lg:text-2xl lg:-mt-64"
>
Modern Interior Sample #3
</span>
</div>
</div>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</div>
</template>

<script setup>
import { onMounted } from "vue";
import Swiper from "swiper";
import { Navigation, Pagination } from "swiper/modules";

import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";

onMounted(() => {
new Swiper(".swiper", {
modules: [Navigation, Pagination],
pagination: {
el: ".swiper-pagination",
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
</script>

<style scoped>
#hero {
max-width: 1350px;
}

.swiper {
width: 100%;
}
</style>
Loading