Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
191 changes: 96 additions & 95 deletions src/components/Product/SingleProduct.component.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
/*eslint complexity: ["error", 20]*/
// Imports
import { useState, useEffect } from 'react';

// Utils
import React, { useState, useEffect } from 'react';
import { filteredVariantPrice, paddedPrice } from '@/utils/functions/functions';

// Components
import AddToCart, { IProductRootObject } from './AddToCart.component';
import AddToCart, { IProduct, IProductRootObject } from './AddToCart.component';
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner.component';
import Button from '@/components/UI/Button.component';

interface SingleProductProps {
product: IProduct;
}

const SingleProduct = ({ product }: IProductRootObject) => {
const SingleProduct: React.FC<SingleProductProps> = ({ product }) => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [selectedVariation, setSelectedVariation] = useState<number>();
const [selectedVariation, setSelectedVariation] = useState<
number | undefined
>();

const placeholderFallBack = 'https://via.placeholder.com/600';

let DESCRIPTION_WITHOUT_HTML;
let DESCRIPTION_WITHOUT_HTML: string | null = null;

useEffect(() => {
setIsLoading(false);
Expand All @@ -28,102 +29,100 @@
let { description, image, name, onSale, price, regularPrice, salePrice } =
product;

// Add padding/empty character after currency symbol here
if (price) {
price = paddedPrice(price, 'kr');
}
if (regularPrice) {
regularPrice = paddedPrice(regularPrice, 'kr');
}
if (salePrice) {
salePrice = paddedPrice(salePrice, 'kr');
}
if (price) price = paddedPrice(price, 'kr');
if (regularPrice) regularPrice = paddedPrice(regularPrice, 'kr');
if (salePrice) salePrice = paddedPrice(salePrice, 'kr');

// Strip out HTML from description
if (process.browser) {
DESCRIPTION_WITHOUT_HTML = new DOMParser().parseFromString(
description,
'text/html',
).body.textContent;
if (typeof window !== 'undefined' && description) {
DESCRIPTION_WITHOUT_HTML =
new DOMParser().parseFromString(description, 'text/html').body
.textContent || '';
}

const handleBuy = () => {
// Implement buy functionality here
console.log('Buy button clicked');
};

return (
<section className="py-8 bg-white mb-12 sm:mb-2">
{/* Show loading spinner while loading, and hide content while loading */}
<section className="bg-white mb-16 sm:mb-24">
{isLoading ? (
<div className="h-56 mt-20">
<p className="text-2xl font-bold text-center">Laster produkt ...</p>
<br />
<LoadingSpinner />
</div>
) : (
<div className="container flex flex-wrap items-center pt-4 pb-12 mx-auto ">
<div className="grid grid-cols-1 gap-4 mt-16 lg:grid-cols-2 xl:grid-cols-2 md:grid-cols-2 sm:grid-cols-2">
{image && (
<img
id="product-image"
src={image.sourceUrl}
alt={name}
className="h-auto p-8 transition duration-500 ease-in-out transform xl:p-2 md:p-2 lg:p-2 md:hover:grow md:hover:scale-105"
/>
)}
{!image && (
<img
id="product-image"
src={
process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL ??
placeholderFallBack
}
alt={name}
className="h-auto p-8 transition duration-500 ease-in-out transform xl:p-2 md:p-2 lg:p-2 md:hover:grow md:hover:shadow-lg md:hover:scale-105"
/>
)}
<div className="ml-8">
<p className="text-3xl font-bold text-left">{name}</p>
<br />
{/* Display sale price when on sale */}
{onSale && (
<div className="flex">
<p className="pt-1 mt-4 text-3xl text-gray-900">
{product.variations && filteredVariantPrice(price, '')}
{!product.variations && salePrice}
</p>
<p className="pt-1 pl-8 mt-4 text-2xl text-gray-900 line-through">
{product.variations && filteredVariantPrice(price, 'right')}
{!product.variations && regularPrice}
</p>
</div>
)}
{/* Display regular price when not on sale */}
{!onSale && (
<p className="pt-1 mt-4 text-2xl text-gray-900"> {price}</p>
<div className="container mx-auto px-4 py-8 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
<div className="w-full">
{image ? (
<img
id="product-image"
src={image.sourceUrl}
alt={name}
className="w-full h-auto object-cover rounded-lg shadow-md"
/>
) : (
<img
id="product-image"
src={
process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL ??
placeholderFallBack
}
alt={name}
className="w-full h-auto object-cover rounded-lg shadow-md"
/>
)}
<br />
<p className="pt-1 mt-4 text-2xl text-gray-900">
</div>
<div className="flex flex-col space-y-4">
<h1 className="text-3xl font-bold text-center md:text-left">
{name}
</h1>
<div className="text-center md:text-left">
{onSale ? (
<div className="flex flex-col md:flex-row items-center md:items-start space-y-2 md:space-y-0 md:space-x-4">
<p className="text-3xl font-bold text-red-600">
{product.variations
? filteredVariantPrice(price, '')
: salePrice}
</p>
<p className="text-xl text-gray-500 line-through">
{product.variations
? filteredVariantPrice(price, 'right')
: regularPrice}
</p>
</div>
) : (
<p className="text-2xl font-bold">{price}</p>
)}
</div>
<p className="text-gray-600 text-center md:text-left">
{DESCRIPTION_WITHOUT_HTML}
</p>
{Boolean(product.stockQuantity) && (
<p
v-if="data.product.stockQuantity"
className="pt-1 mt-4 mb-4 text-2xl text-gray-900"
>
<p className="text-sm font-semibold text-center md:text-left">
{product.stockQuantity} på lager
</p>
)}
{product.variations && (
<p className="pt-1 mt-4 text-xl text-gray-900">
<span className="py-2">Varianter</span>
<div className="w-full">
<label
htmlFor="variant"
className="block text-sm font-medium text-gray-700 mb-2"
>
Varianter
</label>
<select
id="variant"
name="variant"
className="block w-80 px-6 py-2 bg-white border border-gray-500 rounded-lg focus:outline-none focus:shadow-outline"
onChange={(e) => {
setSelectedVariation(Number(e.target.value));
}}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
onChange={(e) =>
setSelectedVariation(Number(e.target.value))
}
>
{product.variations.nodes.map(
({ id, name, databaseId, stockQuantity }) => {
// Remove product name from variation name
const filteredName = name.split('- ').pop();
return (
<option key={id} value={databaseId}>
Expand All @@ -133,20 +132,22 @@
},
)}
</select>
</p>
</div>
)}
<div className="pt-1 mt-2">
{
// Display default AddToCart button if we do not have variations.
// If we do, send the variationId to AddToCart button
}
{product.variations && (
<AddToCart
product={product}
variationId={selectedVariation}
/>
)}
{!product.variations && <AddToCart product={product} />}
<div className="flex flex-col space-y-4 items-center md:items-start">
<Button handleButtonClick={handleBuy} color="blue">
Kjøp nå
</Button>
<div className="w-full md:w-auto">
{product.variations ? (
<AddToCart
product={product}
variationId={selectedVariation}
/>
) : (
<AddToCart product={product} />
)}
</div>
</div>
</div>
</div>
Expand Down
14 changes: 9 additions & 5 deletions src/components/UI/Button.component.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from 'react';

type TButtonColors = 'red' | 'blue';
type TButtonColors = 'red' | 'blue' | 'yellow';

interface IButtonProps {
handleButtonClick?: () => void;
Expand All @@ -14,7 +14,7 @@ interface IButtonProps {
* @function PageTitle
* @param {void} handleButtonClick - Handle button click
* @param {boolean?} buttonDisabled - Is button disabled?
* @param {color?} TButtonColors - Color for button, either red or blue
* @param {color?} TButtonColors - Color for button, either red, blue, or yellow
* @param {ReactNode} children - Children for button
* @returns {JSX.Element} - Rendered component
*/
Expand All @@ -27,11 +27,15 @@ const Button = ({
<button
onClick={handleButtonClick}
disabled={buttonDisabled}
className={`px-2 lg:px-4 py-2 font-bold bg-blue-500 border border-gray-400 border-solid rounded text-white ease-in-out transition-all duration-300 disabled:opacity-50
className={`px-4 py-3 font-bold border border-gray-400 border-solid rounded ease-in-out transition-all duration-300 disabled:opacity-50
${
color === 'blue'
? 'bg-blue-500 hover:bg-blue-600'
: 'bg-red-500 hover:bg-red-600'
? 'bg-blue-500 hover:bg-blue-600 text-white'
: color === 'red'
? 'bg-red-500 hover:bg-red-600 text-white'
: color === 'yellow'
? 'bg-yellow-400 hover:bg-yellow-500 text-black'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}
`}
>
Expand Down
Loading