diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-09-25 14:07:26 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-09-25 14:07:26 +0700 |
| commit | e7313b4d7006bed37a408d26f15028892839b73a (patch) | |
| tree | d0d6c9455ca6aac305efc094639dd6886b34fb14 /src/pages | |
| parent | d1c0e083ac8f64dfaa8505fc11e30728dbd5a58d (diff) | |
| parent | e8f640d3fd4984fe5854c2faf7ead9b3b5aebbf2 (diff) | |
Merge branch 'new-release' into bug-product
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/_document.jsx | 8 | ||||
| -rw-r--r-- | src/pages/api/activation-request.js | 26 | ||||
| -rw-r--r-- | src/pages/api/shop/brands.js | 2 | ||||
| -rw-r--r-- | src/pages/api/shop/finish-checkout.js | 84 | ||||
| -rw-r--r-- | src/pages/api/shop/product-homepage.js | 3 | ||||
| -rw-r--r-- | src/pages/api/shop/promo.js | 204 | ||||
| -rw-r--r-- | src/pages/api/shop/search.js | 57 | ||||
| -rw-r--r-- | src/pages/google_merchant/products/[page].js | 4 | ||||
| -rw-r--r-- | src/pages/index.jsx | 77 | ||||
| -rw-r--r-- | src/pages/login.jsx | 12 | ||||
| -rw-r--r-- | src/pages/my/address/[id]/edit.jsx | 29 | ||||
| -rw-r--r-- | src/pages/my/profile.jsx | 69 | ||||
| -rw-r--r-- | src/pages/shop/brands/[slug].jsx | 7 | ||||
| -rw-r--r-- | src/pages/shop/category/[slug].jsx | 9 | ||||
| -rw-r--r-- | src/pages/shop/lob/[slug].jsx | 48 | ||||
| -rw-r--r-- | src/pages/shop/product/variant/[slug].jsx | 2 | ||||
| -rw-r--r-- | src/pages/shop/promo/[slug].jsx | 394 | ||||
| -rw-r--r-- | src/pages/shop/promo/[slug].tsx | 523 | ||||
| -rw-r--r-- | src/pages/sitemap/brands.xml.js | 4 | ||||
| -rw-r--r-- | src/pages/sitemap/products/[page].js | 2 | ||||
| -rw-r--r-- | src/pages/tracking-order.jsx | 27 |
21 files changed, 923 insertions, 668 deletions
diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx index cd60bd89..6af6294f 100644 --- a/src/pages/_document.jsx +++ b/src/pages/_document.jsx @@ -41,13 +41,13 @@ export default function MyDocument() { content='328wmjs7hcnz74rwsqzxvq50rmbtm2' /> - <Script + {/* <Script async strategy='beforeInteractive' src='https://www.googletagmanager.com/gtag/js?id=UA-10501937-1' - /> + /> */} - <Script + {/* <Script async id='google-analytics-ua' strategy='beforeInteractive' @@ -59,7 +59,7 @@ export default function MyDocument() { gtag('config', 'UA-10501937-1'); `, }} - /> + /> */} <Script async diff --git a/src/pages/api/activation-request.js b/src/pages/api/activation-request.js index 61dbb597..2b8ccec3 100644 --- a/src/pages/api/activation-request.js +++ b/src/pages/api/activation-request.js @@ -1,27 +1,29 @@ -import odooApi from '@/core/api/odooApi' -import mailer from '@/core/utils/mailer' +import odooApi from '@/core/api/odooApi'; +import mailer from '@/core/utils/mailer'; export default async function handler(req, res) { try { - const { email } = req.body - let result = await odooApi('POST', '/api/v1/user/activation-request', { email }) + const { email } = req.body; + let result = await odooApi('POST', '/api/v1/user/activation-request', { + email, + }); if (result.activationRequest) { mailer.sendMail({ - from: 'sales@indoteknik.com', + from: 'noreply@indoteknik.com', to: result.user.email, subject: 'Permintaan Aktivasi Akun Indoteknik', html: ` <h1>Permintaan Aktivasi Akun Indoteknik</h1> <br> <p>Aktivasi akun anda melalui link berikut: <a href="${process.env.NEXT_PUBLIC_SELF_HOST}/activate?token=${result.token}">Aktivasi Akun</a></p> - ` - }) + `, + }); } - delete result.user - delete result.token - res.status(200).json(result) + delete result.user; + delete result.token; + res.status(200).json(result); } catch (error) { - console.log(error) - res.status(400).json({ error: error.message }) + console.log(error); + res.status(400).json({ error: error.message }); } } diff --git a/src/pages/api/shop/brands.js b/src/pages/api/shop/brands.js index cc64a7e7..9c2824b3 100644 --- a/src/pages/api/shop/brands.js +++ b/src/pages/api/shop/brands.js @@ -24,6 +24,8 @@ export default async function handler(req, res) { params = `name_s:${req.query.params}`.toLowerCase(); } } + if(req.query.rows) rows = req.query.rows; + const url = `${SOLR_HOST}/solr/brands/select?q=${params}&q.op=OR&indent=true&rows=${rows}&${sort}`; let brands = await axios(url); let dataBrands = responseMap(brands.data.response.docs); diff --git a/src/pages/api/shop/finish-checkout.js b/src/pages/api/shop/finish-checkout.js index 9eaa36db..4dcc915c 100644 --- a/src/pages/api/shop/finish-checkout.js +++ b/src/pages/api/shop/finish-checkout.js @@ -1,60 +1,66 @@ -import odooApi from '@/core/api/odooApi' -import mailer from '@/core/utils/mailer' -import FinishCheckoutEmail from '@/lib/checkout/email/FinishCheckoutEmail' -import { render } from '@react-email/render' -import axios from 'axios' -import camelcaseObjectDeep from 'camelcase-object-deep' +import odooApi from '@/core/api/odooApi'; +import mailer from '@/core/utils/mailer'; +import FinishCheckoutEmail from '@/lib/checkout/email/FinishCheckoutEmail'; +import { render } from '@react-email/render'; +import axios from 'axios'; +import camelcaseObjectDeep from 'camelcase-object-deep'; export default async function handler(req, res) { - const { orderName = null } = req.query + const { orderName = null } = req.query; if (!orderName) { - return res.status(422).json({ error: 'parameter missing' }) + return res.status(422).json({ error: 'parameter missing' }); } - let { auth } = req.cookies + let { auth } = req.cookies; if (!auth) { - return res.status(401).json({ error: 'Unauthorized' }) + return res.status(401).json({ error: 'Unauthorized' }); } - auth = JSON.parse(auth) + auth = JSON.parse(auth); - const midtransAuthKey = btoa(process.env.MIDTRANS_SERVER_KEY + ':') + const midtransAuthKey = btoa(process.env.MIDTRANS_SERVER_KEY + ':'); const midtransHeaders = { Accept: 'application/json', 'Content-Type': 'application/json', - Authorization: `Basic ${midtransAuthKey}` - } - let midtransStatus = {} + Authorization: `Basic ${midtransAuthKey}`, + }; + let midtransStatus = {}; try { - midtransStatus = await axios.get(`${process.env.MIDTRANS_HOST}/v2/${orderName}/status`, { - headers: midtransHeaders - }) - midtransStatus = camelcaseObjectDeep(midtransStatus.data) + midtransStatus = await axios.get( + `${process.env.MIDTRANS_HOST}/v2/${orderName}/status`, + { + headers: midtransHeaders, + } + ); + midtransStatus = camelcaseObjectDeep(midtransStatus.data); } catch (error) { - console.log(error) + console.log(error); } - let statusPayment = 'manual' + let statusPayment = 'manual'; if (midtransStatus?.orderId) { - const transactionStatus = midtransStatus.transactionStatus - statusPayment = 'failed' + const transactionStatus = midtransStatus.transactionStatus; + statusPayment = 'failed'; if (['capture', 'settlement'].includes(transactionStatus)) { - statusPayment = 'success' + statusPayment = 'success'; } else if (transactionStatus == 'pending') { - statusPayment = 'pending' + statusPayment = 'pending'; } } - const query = `name=${orderName.replaceAll('-', '/')}&limit=1&context=quotation` + const query = `name=${orderName.replaceAll( + '-', + '/' + )}&limit=1&context=quotation`; const searchTransaction = await odooApi( 'GET', `/api/v1/partner/${auth.partnerId}/sale_order?${query}`, {}, { Token: auth.token } - ) + ); if (searchTransaction.saleOrderTotal == 0) { - return res.status(400).json({ error: 'Transaction Not Found' }) + return res.status(400).json({ error: 'Transaction Not Found' }); } let transaction = await odooApi( @@ -62,17 +68,17 @@ export default async function handler(req, res) { `/api/v1/partner/${auth.partnerId}/sale_order/${searchTransaction.saleOrders[0].id}`, {}, { Token: auth.token } - ) + ); if (!transaction?.id) { - return res.status(400).json({ error: 'Transaction Detail Not Found' }) + return res.status(400).json({ error: 'Transaction Detail Not Found' }); } - transaction.subtotal = 0 - transaction.discountTotal = 0 + transaction.subtotal = 0; + transaction.discountTotal = 0; for (const product of transaction.products) { - transaction.subtotal += product.price.price * product.quantity + transaction.subtotal += product.price.price * product.quantity; transaction.discountTotal -= - (product.price.price - product.price.priceDiscount) * product.quantity + (product.price.price - product.price.priceDiscount) * product.quantity; } const emailMessage = render( @@ -81,14 +87,14 @@ export default async function handler(req, res) { payment={midtransStatus} statusPayment={statusPayment} /> - ) + ); mailer.sendMail({ - from: 'sales@indoteknik.com', + from: 'noreply@indoteknik.com', to: transaction.address.customer.email, subject: 'Pembelian di Indoteknik.com', - html: emailMessage - }) + html: emailMessage, + }); - return res.status(200).json({ description: 'success' }) + return res.status(200).json({ description: 'success' }); } diff --git a/src/pages/api/shop/product-homepage.js b/src/pages/api/shop/product-homepage.js index 02c01ee0..61732c77 100644 --- a/src/pages/api/shop/product-homepage.js +++ b/src/pages/api/shop/product-homepage.js @@ -36,7 +36,8 @@ const respoonseMap = (productHomepage, products) => { name: productHomepage.name_s, image: productHomepage.image_s, url: productHomepage.url_s, - products: products + products: products, + categoryIds: productHomepage.category_id_ids, } return productMapped diff --git a/src/pages/api/shop/promo.js b/src/pages/api/shop/promo.js new file mode 100644 index 00000000..f90c8559 --- /dev/null +++ b/src/pages/api/shop/promo.js @@ -0,0 +1,204 @@ +import { productMappingSolr, promoMappingSolr } from '@/utils/solrMapping'; +import axios from 'axios'; +import camelcaseObjectDeep from 'camelcase-object-deep'; + +export default async function handler(req, res) { + const { + q = '*', + page = 1, + brand = '', + category = '', + priceFrom = 0, + priceTo = 0, + orderBy = 'if(exists(sequence_i),0,1) asc, sequence_i asc,', + operation = 'AND', + fq = '', + limit = 30, + } = req.query; + + let { stock = '' } = req.query; + + let paramOrderBy = 'if(exists(sequence_i),0,1) asc, sequence_i asc,'; + switch (orderBy) { + case 'price-asc': + paramOrderBy += 'price_tier1_v2_f ASC'; + break; + case 'price-desc': + paramOrderBy += 'price_tier1_v2_f DESC'; + break; + case 'popular': + paramOrderBy += 'product_rating_f DESC, search_rank_i DESC,'; + break; + case 'popular-weekly': + paramOrderBy += 'search_rank_weekly_i DESC'; + break; + case 'stock': + paramOrderBy += 'product_rating_f DESC, stock_total_f DESC'; + break; + case 'flashsale-price-asc': + paramOrderBy += 'flashsale_price_f ASC'; + break; + default: + paramOrderBy += ''; + break; + } + + let checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/); + let newQ = checkQ.length > 1 ? escapeSolrQuery(q) + '*' : escapeSolrQuery(q); + + let offset = (page - 1) * limit; + let parameter = [ + 'facet.field=manufacture_name_s', + 'facet.field=category_name', + 'facet=true', + 'indent=true', + `facet.limit=-1`, + // `facet.query=${escapeSolrQuery(q)}`, + `q.op=${operation}`, + `q=${q}`, + // 'qf=name_s', + `start=${parseInt(offset)}`, + `rows=${limit}`, + `sort=${paramOrderBy}`, + `fq=product_ids:[* TO *]`, + `fq=active_b:true`, + ]; + + if (priceFrom > 0 || priceTo > 0) { + parameter.push( + `fq=price_tier1_v2_f:[${priceFrom == '' ? '*' : priceFrom} TO ${ + priceTo == '' ? '*' : priceTo + }]` + ); + } + + let { auth } = req.cookies; + if (auth) { + auth = JSON.parse(auth); + if (auth.feature.onlyReadyStock) stock = true; + } + + if (brand) + parameter.push( + `fq=${brand + .split(',') + .map( + (manufacturer) => + `manufacture_name_s:"${encodeURIComponent(manufacturer)}"` + ) + .join(' OR ')}` + ); + if (category) + parameter.push( + `fq=${category + .split(',') + .map((cat) => `category_name:"${encodeURIComponent(cat)}"`) + .join(' OR ')}` + ); + // if (category) parameter.push(`fq=category_name:${capitalizeFirstLetter(category.replace(/,/g, ' OR '))}`) + if (stock) parameter.push(`fq=stock_total_f:{1 TO *}`); + + // Single fq in url params + if (typeof fq === 'string') parameter.push(`fq=${fq}`); + // Multi fq in url params + if (Array.isArray(fq)) + parameter = parameter.concat(fq.map((val) => `fq=${val}`)); + + let result = await axios( + process.env.SOLR_HOST + '/solr/promotion_program_lines/select?' + parameter.join('&') + ); + try { + result.data.response.products = promoMappingSolr( + result.data.response.docs + ); + + result.data.responseHeader.params.start = parseInt( + result.data.responseHeader.params.start + ); + result.data.responseHeader.params.rows = parseInt( + result.data.responseHeader.params.rows + ); + delete result.data.response.docs; + // result.data = camelcaseObjectDeep(result.data); + result.data = result.data; + res.status(200).json(result.data); + } catch (error) { + res.status(400).json({ error: error.message }); + } +} + +const escapeSolrQuery = (query) => { + if (query == '*') return query; + + query = query.replace(/-/g, ' '); + + const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; + const words = query.split(/\s+/); + const escapedWords = words.map((word) => { + if (specialChars.test(word)) { + return word.replace(specialChars, '\\$1'); + } + return word; + }); + + return escapedWords.join(' '); +}; + + +/*const productResponseMap = (products, pricelist) => { + return products.map((product) => { + let price = product.price_tier1_v2_f || 0 + let priceDiscount = product.price_discount_f || 0 + let discountPercentage = product.discount_f || 0 + + if (pricelist) { + // const pricelistDiscount = product?.[`price_${pricelist}_f`] || false + // const pricelistDiscountPerc = product?.[`discount_${pricelist}_f`] || false + + // if (pricelistDiscount && pricelistDiscount > 0) priceDiscount = pricelistDiscount + // if (pricelistDiscountPerc && pricelistDiscountPerc > 0) + // discountPercentage = pricelistDiscountPerc + + price = product?.[`price_${pricelist}_f`] || 0 + } + + if (product?.flashsale_id_i > 0) { + price = product?.flashsale_base_price_f || 0 + priceDiscount = product?.flashsale_price_f || 0 + discountPercentage = product?.flashsale_discount_f || 0 + } + + let productMapped = { + id: product.product_id_i || '', + image: product.image_s || '', + code: product.default_code_s || '', + name: product.name_s || '', + lowestPrice: { price, priceDiscount, discountPercentage }, + variantTotal: product.variant_total_i || 0, + stockTotal: product.stock_total_f || 0, + weight: product.weight_f || 0, + manufacture: {}, + categories: [], + flashSale: { + id: product?.flashsale_id_i, + name: product?.product?.flashsale_name_s, + tag : product?.flashsale_tag_s || 'FLASH SALE' + } + } + + if (product.manufacture_id_i && product.manufacture_name_s) { + productMapped.manufacture = { + id: product.manufacture_id_i || '', + name: product.manufacture_name_s || '' + } + } + + productMapped.categories = [ + { + id: product.category_id_i || '', + name: product.category_name_s || '' + } + ] + return productMapped + }) +}*/ diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 6f98efcb..6269d3ed 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -1,6 +1,8 @@ import { productMappingSolr } from '@/utils/solrMapping'; import axios from 'axios'; import camelcaseObjectDeep from 'camelcase-object-deep'; +import searchSuggestApi from '@/core/api/searchSuggestApi'; +import { ECDH } from 'crypto'; export default async function handler(req, res) { const { @@ -14,6 +16,7 @@ export default async function handler(req, res) { operation = 'AND', fq = '', limit = 30, + source = '', } = req.query; let { stock = '' } = req.query; @@ -42,10 +45,40 @@ export default async function handler(req, res) { paramOrderBy += ''; break; } - + + // let suggestWord = null; + // let keywords = q; + // let checkQ = null; + + // if (q != '*') { + // checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/); + // if (checkQ.length > 1) { + // const dataSearchSuggest = await axios( + // `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/suggest?q=${checkQ[1]}` + // ); + // suggestWord = dataSearchSuggest.data.suggestions[0]; + // } + // if (suggestWord && suggestWord?.term.split(' ').length <= 1) { + // keywords = `"${escapeSolrQuery(checkQ[0] + ' ' + suggestWord?.term)}"`; + // } + // } + + // let newQ = keywords; + let checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/); - let newQ = checkQ.length > 1 ? escapeSolrQuery(q) + '*' : escapeSolrQuery(q); + let newQ = escapeSolrQuery(q); + + const formattedQuery = `(${newQ.split(' ').map(term => `${term}*`).join(' ') })`; + const mm = checkQ.length > 2 ? checkQ.length > 5 ? '55%' : '85%' : `${checkQ.length}`; + + const filterQueries = [ + '-publish_b:false', + 'product_rating_f:[8 TO *]', + 'price_tier1_v2_f:[1 TO *]' + ]; + const fq_ = filterQueries.join('AND '); + let offset = (page - 1) * limit; let parameter = [ 'facet.field=manufacture_name_s', @@ -53,13 +86,15 @@ export default async function handler(req, res) { 'facet=true', 'indent=true', `facet.query=${escapeSolrQuery(q)}`, - `q.op=${operation}`, - `q=${newQ}`, - 'qf=name_s', + `q.op=OR`, + `q=${source == 'similar' || checkQ.length < 3 ? checkQ.length < 2 ? newQ : newQ + '*' : formattedQuery }`, + `defType=edismax`, + 'qf=name_s description_clean_t category_name manufacture_name_s variants_code_t variants_name_t category_id_ids default_code_s', `start=${parseInt(offset)}`, `rows=${limit}`, `sort=${paramOrderBy}`, - `fq=-publish_b:false, product_rating_f:[8 TO *], price_tier1_v2_f:[1 TO *]`, + `fq=${encodeURIComponent(fq_)}`, + `mm=${encodeURIComponent(mm)}`, ]; if (priceFrom > 0 || priceTo > 0) { @@ -97,14 +132,15 @@ export default async function handler(req, res) { if (stock) parameter.push(`fq=stock_total_f:{1 TO *}`); // Single fq in url params - if (typeof fq === 'string') parameter.push(`fq=${fq}`); + if (typeof fq === 'string') parameter.push(`fq=${encodeURIComponent(fq)}`); // Multi fq in url params if (Array.isArray(fq)) - parameter = parameter.concat(fq.map((val) => `fq=${val}`)); - + parameter = parameter.concat(fq.map((val) => `fq=${encodeURIComponent(val)}`)); + let result = await axios( process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&') ); + try { result.data.response.products = productMappingSolr( result.data.response.docs, @@ -126,7 +162,7 @@ export default async function handler(req, res) { const escapeSolrQuery = (query) => { if (query == '*') return query; - + query = query.replace(/-/g, ' '); const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; @@ -141,7 +177,6 @@ const escapeSolrQuery = (query) => { return escapedWords.join(' '); }; - /*const productResponseMap = (products, pricelist) => { return products.map((product) => { let price = product.price_tier1_v2_f || 0 diff --git a/src/pages/google_merchant/products/[page].js b/src/pages/google_merchant/products/[page].js index 6e0eb703..0c2cf3c5 100644 --- a/src/pages/google_merchant/products/[page].js +++ b/src/pages/google_merchant/products/[page].js @@ -50,7 +50,7 @@ export async function getServerSideProps({ res, query }) { let categoryId = null; if (brandId && brandId in brandsData) { - categoryId = brandsData[brandId].category_ids?.[0] ?? null; + categoryId = brandsData[brandId]?.category_ids?.[0] ?? null; } else { const solrBrand = await getBrandById(brandId); brandsData[brandId] = solrBrand; @@ -58,7 +58,7 @@ export async function getServerSideProps({ res, query }) { } if (categoryId && categoryId in categoriesData) { - categoryName = categoriesData[categoryId].name_s ?? null; + categoryName = categoriesData[categoryId]?.name_s ?? null; } else { const solrCategory = await getCategoryById(categoryId); categoriesData[categoryId] = solrCategory; diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 4493fe31..6077c192 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,6 +1,5 @@ import dynamic from 'next/dynamic'; -import { useRef } from 'react'; - +import { useEffect, useRef, useState } from 'react'; import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton'; import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton'; import Seo from '@/core/components/Seo'; @@ -11,12 +10,15 @@ import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton'; import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton'; import BannerPromoSkeleton from '@/lib/home/components/Skeleton/BannerPromoSkeleton'; import PromotinProgram from '@/lib/promotinProgram/components/HomePage'; -import PagePopupIformation from '~/modules/popup-information'; -import useProductDetail from '~/modules/product-detail/stores/useProductDetail'; +import PagePopupIformation from '~/modules/popup-information'; // need change to dynamic and ssr : false +import CategoryPilihan from '../lib/home/components/CategoryPilihan'; +import odooApi from '@/core/api/odooApi'; import { getAuth } from '~/libs/auth'; +// import { getAuth } from '~/libs/auth'; +import useProductDetail from '~/modules/product-detail/stores/useProductDetail'; const BasicLayout = dynamic(() => - import('@/core/components/layouts/BasicLayout') + import('@/core/components/layouts/BasicLayout'),{ssr: false} ); const HeroBanner = dynamic(() => import('@/components/ui/HeroBanner'), { loading: () => <HeroBannerSkeleton />, @@ -53,17 +55,28 @@ const ProgramPromotion = dynamic(() => ); const BannerSection = dynamic(() => - import('@/lib/home/components/BannerSection') -); + import('@/lib/home/components/BannerSection'), {ssr: false} +); const CategoryHomeId = dynamic(() => - import('@/lib/home/components/CategoryHomeId') + import('@/lib/home/components/CategoryHomeId'), {ssr: false} ); -const CustomerReviews = dynamic(() => - import('@/lib/review/components/CustomerReviews') + +const CategoryDynamic = dynamic(() => + import('@/lib/home/components/CategoryDynamic'), {ssr: false} +); + +const CategoryDynamicMobile = dynamic(() => +import('@/lib/home/components/CategoryDynamicMobile'), {ssr: false} ); -const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList')); -export default function Home() { +const CustomerReviews = dynamic(() => + import('@/lib/review/components/CustomerReviews'), {ssr: false} +); // need to ssr:false +const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList'), {ssr: false}); // need to ssr: false + + + +export default function Home({categoryId}) { const bannerRef = useRef(null); const wrapperRef = useRef(null); @@ -74,7 +87,19 @@ export default function Home() { bannerRef.current?.querySelector(':first-child')?.clientHeight + 'px'; }; + useEffect(() => { + const loadCategories = async () => { + const getCategories = await odooApi('GET', '/api/v1/category/child?partner_id='+{categoryId}) + if(getCategories){ + setDataCategories(getCategories) + } + } + loadCategories() + }, []) + + const [dataCategories, setDataCategories] = useState([]) return ( + <> <BasicLayout> <Seo title='Indoteknik.com: B2B Industrial Supply & Solution' @@ -82,11 +107,9 @@ export default function Home() { additionalMetaTags={[ { name: 'keywords', - content: - 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air', + content: 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air', }, - ]} - /> + ]} /> <PagePopupIformation /> @@ -125,19 +148,22 @@ export default function Home() { </DelayRender> </> )} - <PromotinProgram /> + {/* <PromotinProgram /> */} + {dataCategories &&( + <CategoryPilihan categories={dataCategories} /> + )} + <CategoryDynamic /> <CategoryHomeId /> <BannerSection /> <CustomerReviews /> </div> </div> - </DesktopView> - - <MobileView> + </DesktopView> + <MobileView> <DelayRender renderAfter={200}> <HeroBanner /> </DelayRender> - <div className='flex flex-col gap-y-12 my-6'> + <div className='flex flex-col gap-y-4 my-6'> <DelayRender renderAfter={400}> <ServiceList /> </DelayRender> @@ -157,7 +183,13 @@ export default function Home() { </> )} <DelayRender renderAfter={600}> - <PromotinProgram /> + {/* <PromotinProgram /> */} + </DelayRender> + <DelayRender renderAfter={600}> + {dataCategories &&( + <CategoryPilihan categories={dataCategories} /> + )} + <CategoryDynamicMobile /> </DelayRender> <DelayRender renderAfter={800}> <PopularProduct /> @@ -172,5 +204,6 @@ export default function Home() { </div> </MobileView> </BasicLayout> + </> ); }
\ No newline at end of file diff --git a/src/pages/login.jsx b/src/pages/login.jsx index 9a1aa85b..07d13784 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.jsx @@ -1,3 +1,5 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; import Seo from '@/core/components/Seo'; import SimpleFooter from '@/core/components/elements/Footer/SimpleFooter'; import BasicLayout from '@/core/components/layouts/BasicLayout'; @@ -5,8 +7,18 @@ import DesktopView from '@/core/components/views/DesktopView'; import MobileView from '@/core/components/views/MobileView'; import LoginComponent from '@/lib/auth/components/Login'; import AccountActivation from '~/modules/account-activation'; +import useAuth from '@/core/hooks/useAuth'; export default function Login() { + const router = useRouter(); + const auth = useAuth(); + + useEffect(() => { + if (auth) { + router.push('/'); + } + }, [auth, router]); + return ( <> <Seo title='Login - Indoteknik.com' /> diff --git a/src/pages/my/address/[id]/edit.jsx b/src/pages/my/address/[id]/edit.jsx index bd680b90..c552659b 100644 --- a/src/pages/my/address/[id]/edit.jsx +++ b/src/pages/my/address/[id]/edit.jsx @@ -1,11 +1,11 @@ -import Seo from '@/core/components/Seo' -import AppLayout from '@/core/components/layouts/AppLayout' -import BasicLayout from '@/core/components/layouts/BasicLayout' -import DesktopView from '@/core/components/views/DesktopView' -import MobileView from '@/core/components/views/MobileView' -import addressApi from '@/lib/address/api/addressApi' -import EditAddressComponent from '@/lib/address/components/EditAddress' -import IsAuth from '@/lib/auth/components/IsAuth' +import Seo from '@/core/components/Seo'; +import AppLayout from '@/core/components/layouts/AppLayout'; +import BasicLayout from '@/core/components/layouts/BasicLayout'; +import DesktopView from '@/core/components/views/DesktopView'; +import MobileView from '@/core/components/views/MobileView'; +import addressApi from '@/lib/address/api/addressApi'; +import EditAddressComponent from '@/lib/address/components/EditAddress'; +import IsAuth from '@/lib/auth/components/IsAuth'; export default function EditAddress({ id, defaultValues }) { return ( @@ -24,12 +24,12 @@ export default function EditAddress({ id, defaultValues }) { </BasicLayout> </DesktopView> </IsAuth> - ) + ); } export async function getServerSideProps(context) { - const { id } = context.query - const address = await addressApi({ id }) + const { id } = context.query; + const address = await addressApi({ id }); const defaultValues = { type: address.type, name: address.name, @@ -41,7 +41,8 @@ export async function getServerSideProps(context) { oldDistrict: address.district?.id || '', district: '', oldSubDistrict: address.subDistrict?.id || '', - subDistrict: '' - } - return { props: { id, defaultValues } } + subDistrict: '', + business_name: '', + }; + return { props: { id, defaultValues } }; } diff --git a/src/pages/my/profile.jsx b/src/pages/my/profile.jsx index 25c3a608..7cf1bcbb 100644 --- a/src/pages/my/profile.jsx +++ b/src/pages/my/profile.jsx @@ -1,41 +1,44 @@ -import Divider from '@/core/components/elements/Divider/Divider' -import AppLayout from '@/core/components/layouts/AppLayout' -import BasicLayout from '@/core/components/layouts/BasicLayout' -import DesktopView from '@/core/components/views/DesktopView' -import MobileView from '@/core/components/views/MobileView' -import useAuth from '@/core/hooks/useAuth' -import CompanyProfile from '@/lib/auth/components/CompanyProfile' -import IsAuth from '@/lib/auth/components/IsAuth' -import Menu from '@/lib/auth/components/Menu' -import PersonalProfile from '@/lib/auth/components/PersonalProfile' +import Divider from '@/core/components/elements/Divider/Divider'; +import AppLayout from '@/core/components/layouts/AppLayout'; +import BasicLayout from '@/core/components/layouts/BasicLayout'; +import DesktopView from '@/core/components/views/DesktopView'; +import MobileView from '@/core/components/views/MobileView'; +import useAuth from '@/core/hooks/useAuth'; +import CompanyProfile from '@/lib/auth/components/CompanyProfile'; +import IsAuth from '@/lib/auth/components/IsAuth'; +import Menu from '@/lib/auth/components/Menu'; +import PersonalProfile from '@/lib/auth/components/PersonalProfile'; +import Seo from '@/core/components/Seo'; export default function Profile() { - const auth = useAuth() + const auth = useAuth(); return ( - <IsAuth> - <MobileView> - <AppLayout title='Akun Saya'> - <PersonalProfile /> - <Divider /> - {auth?.parentId && <CompanyProfile />} - </AppLayout> - </MobileView> - - <DesktopView> - <BasicLayout> - <div className='container mx-auto flex py-10'> - <div className='w-3/12 pr-4'> - <Menu /> - </div> - <div className='w-9/12 bg-white border border-gray_r-6 rounded'> + <> + <Seo title='Profile - Indoteknik.com' /> + <IsAuth> + <MobileView> + <AppLayout title='Akun Saya'> <PersonalProfile /> <Divider /> {auth?.parentId && <CompanyProfile />} + </AppLayout> + </MobileView> - </div> - </div> - </BasicLayout> - </DesktopView> - </IsAuth> - ) + <DesktopView> + <BasicLayout> + <div className='container mx-auto flex py-10'> + <div className='w-3/12 pr-4'> + <Menu /> + </div> + <div className='w-9/12 bg-white border border-gray_r-6 rounded'> + <PersonalProfile /> + <Divider /> + {auth?.parentId && <CompanyProfile />} + </div> + </div> + </BasicLayout> + </DesktopView> + </IsAuth> + </> + ); } diff --git a/src/pages/shop/brands/[slug].jsx b/src/pages/shop/brands/[slug].jsx index e786ef78..ed6724ea 100644 --- a/src/pages/shop/brands/[slug].jsx +++ b/src/pages/shop/brands/[slug].jsx @@ -18,9 +18,10 @@ export default function BrandDetail() { const brandName = getNameFromSlug(slug) const id = getIdFromSlug(slug) const {brand} = useBrand({id}) - if (!brand || !brand.data || _.isEmpty(brand.data)) { - return <PageNotFound />; - } + // if ( !brand.isLoading && _.isEmpty(brand.data)) { + // console.log('ini masuk pak') + // return <PageNotFound />; + // } return ( <BasicLayout> <Seo diff --git a/src/pages/shop/category/[slug].jsx b/src/pages/shop/category/[slug].jsx index 1afe30bf..11840d47 100644 --- a/src/pages/shop/category/[slug].jsx +++ b/src/pages/shop/category/[slug].jsx @@ -5,6 +5,8 @@ import { useRouter } from 'next/router'; import Seo from '@/core/components/Seo'; import { getIdFromSlug, getNameFromSlug } from '@/core/utils/slug'; import Breadcrumb from '@/lib/category/components/Breadcrumb'; +import { useEffect, useState } from 'react'; +import odooApi from '@/core/api/odooApi'; const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout') @@ -12,10 +14,14 @@ const BasicLayout = dynamic(() => const ProductSearch = dynamic(() => import('@/lib/product/components/ProductSearch') ); +const CategorySection = dynamic(() => + import('@/lib/product/components/CategorySection') +) export default function CategoryDetail() { const router = useRouter(); const { slug = '', page = 1 } = router.query; + const [dataCategories, setDataCategories] = useState([]) const categoryName = getNameFromSlug(slug); const categoryId = getIdFromSlug(slug); @@ -43,8 +49,9 @@ export default function CategoryDetail() { <Breadcrumb categoryId={categoryId} /> + {!_.isEmpty(router.query) && ( - <ProductSearch query={query} prefixUrl={`/shop/category/${slug}`} /> + <ProductSearch query={query} categories ={categoryId} prefixUrl={`/shop/category/${slug}`} /> )} </BasicLayout> ); diff --git a/src/pages/shop/lob/[slug].jsx b/src/pages/shop/lob/[slug].jsx new file mode 100644 index 00000000..d939c25c --- /dev/null +++ b/src/pages/shop/lob/[slug].jsx @@ -0,0 +1,48 @@ +import _ from 'lodash'; +import dynamic from 'next/dynamic'; +import { useRouter } from 'next/router'; +import Seo from '@/core/components/Seo'; +import { getIdFromSlug, getNameFromSlug } from '@/core/utils/slug'; +import Breadcrumb from '../../../lib/lob/components/Breadcrumb'; +import { useEffect, useState } from 'react'; +import odooApi from '@/core/api/odooApi'; +import { div } from 'lodash-contrib'; + +const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout')); +const ProductSearch = dynamic(() => import('@/lib/product/components/ProductSearch')); +const CategorySection = dynamic(() => import('@/lib/product/components/CategorySection')); + +export default function CategoryDetail() { + const router = useRouter(); + const { slug = '', page = 1 } = router.query; + const [dataLob, setDataLob] = useState([]); + const [finalQuery, setFinalQuery] = useState({}); + const [dataCategoriesProduct, setDataCategoriesProduct] = useState([]) + const [data, setData] = useState([]) + const dataIdCategories = [] + + const categoryName = getNameFromSlug(slug); + const lobId = getIdFromSlug(slug); + const q = router?.query.q || null; + + return ( + <BasicLayout> + <Seo + title={`Beli ${categoryName} di Indoteknik`} + description={`Jual ${categoryName} Kirim Jakarta Surabaya Semarang Makassar Manado Denpasar Balikpapan Medan Palembang Lampung Bali Bandung Makassar Manado.`} + additionalMetaTags={[ + { + property: 'keywords', + content: `Jual ${categoryName}, harga ${categoryName}, ${categoryName} murah, toko ${categoryName}, ${categoryName} jakarta, ${categoryName} surabaya`, + }, + ]} + /> + + <Breadcrumb categoryId={getIdFromSlug(slug)} /> + + {!_.isEmpty(router.query) && ( + <ProductSearch query={router.query} categories={getIdFromSlug(slug)} prefixUrl={`/shop/lob/${slug}`} /> + )} + </BasicLayout> + ); +} diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx index cb335e0a..42f38774 100644 --- a/src/pages/shop/product/variant/[slug].jsx +++ b/src/pages/shop/product/variant/[slug].jsx @@ -69,6 +69,8 @@ export default function ProductDetail({ product }) { <Seo title={product?.name || '' + ' - Indoteknik.com' || ''} description='Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.' + noindex={true} + nofollow={true} openGraph={{ url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, images: [ diff --git a/src/pages/shop/promo/[slug].jsx b/src/pages/shop/promo/[slug].jsx new file mode 100644 index 00000000..cfb2c841 --- /dev/null +++ b/src/pages/shop/promo/[slug].jsx @@ -0,0 +1,394 @@ +import dynamic from 'next/dynamic' +import NextImage from 'next/image'; +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import Seo from '../../../core/components/Seo.jsx' +import Promocrumb from '../../../lib/promo/components/Promocrumb.jsx' +import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx' +import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card.tsx' +import React from 'react' +import DesktopView from '../../../core/components/views/DesktopView.jsx'; +import MobileView from '../../../core/components/views/MobileView.jsx'; +import 'swiper/swiper-bundle.css'; +import useDevice from '../../../core/hooks/useDevice.js' +import ProductFilterDesktop from '../../../lib/product/components/ProductFilterDesktopPromotion.jsx'; +import ProductFilter from '../../../lib/product/components/ProductFilter.jsx'; +import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react'; +import { formatCurrency } from '../../../core/utils/formatValue.js'; +import Pagination from '../../../core/components/elements/Pagination/Pagination.js'; +import whatsappUrl from '../../../core/utils/whatsappUrl.js'; +import _ from 'lodash'; +import useActive from '../../../core/hooks/useActive.js'; +import useProductSearch from '../../../lib/promo/hooks/usePromotionSearch.js'; + +const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout.jsx')) + +export default function PromoDetail() { + const router = useRouter() + const { slug = '', brand ='', category='', page = '1' } = router.query + const [currentPage, setCurrentPage] = useState(parseInt(10) || 1); + const [orderBy, setOrderBy] = useState(router.query?.orderBy); + const popup = useActive(); + const prefixUrl = `/shop/promo/${slug}` + const [queryFinal, setQueryFinal] = useState({}); + const [limit, setLimit] = useState(30); + const [q, setQ] = useState('*'); + const [finalQuery, setFinalQuery] = useState({}); + const [products, setProducts] = useState(null); + const [brandValues, setBrand] = useState( + !router.pathname.includes('brands') + ? router.query.brand + ? router.query.brand.split(',') + : [] + : [] + ); + + const [categoryValues, setCategory] = useState( + router.query?.category?.split(',') || router.query?.category?.split(',') + ); + + const [priceFrom, setPriceFrom] = useState(router.query?.priceFrom || null); + const [priceTo, setPriceTo] = useState(router.query?.priceTo || null); + + + useEffect(() => { + const newQuery = { + fq: `type_value_s:${slug}`, + page : router.query.page? router.query.page : 1, + brand : router.query.brand? router.query.brand : '', + category : router.query.category? router.query.category : '', + priceFrom : router.query.priceFrom? router.query.priceFrom : '', + priceTo : router.query.priceTo? router.query.priceTo : '', + limit : router.query.limit? router.query.limit : '', + orderBy : router.query.orderBy? router.query.orderBy : '' + }; + setFinalQuery(newQuery); +}, [router.query, prefixUrl, slug, brand, category, priceFrom, priceTo, currentPage]); + useEffect(() => { + setQueryFinal({ ...finalQuery, q, limit, orderBy }); + }, [router.query, prefixUrl, slug, brand, category, priceFrom, priceTo, currentPage, finalQuery]); + + const { productSearch } = useProductSearch({ + query: queryFinal, + operation: 'OR', + }); + + + const pageCount = Math.ceil(productSearch.data?.response.numFound / limit); + const productStart = productSearch.data?.responseHeader.params.start; + const productRows = limit; + const productFound = productSearch.data?.response.numFound; + + useEffect(() => { + setProducts(productSearch.data?.response?.products); + }, [productSearch]); + + const brands = []; + for ( + let i = 0; + i < productSearch.data?.facet_counts?.facet_fields?.manufacture_name_s.length; + i += 2 + ) { + const brand = + productSearch.data?.facet_counts?.facet_fields?.manufacture_name_s[i]; + const qty = + productSearch.data?.facet_counts?.facet_fields?.manufacture_name_s[i + 1]; + if (qty > 0) { + brands.push({ brand, qty }); + } + } + + const categories = []; + for ( + let i = 0; + i < productSearch.data?.facet_counts?.facet_fields?.category_name.length; + i += 2 + ) { + const name = productSearch.data?.facet_counts?.facet_fields?.category_name[i]; + const qty = + productSearch.data?.facet_counts?.facet_fields?.category_name[i + 1]; + if (qty > 0) { + categories.push({ name, qty }); + } + } + + function capitalizeFirstLetter(string) { + string = string.replace(/_/g, ' '); + return string.replace(/(^\w|\s\w)/g, function(match) { + return match.toUpperCase(); + }); + } + + const handleDeleteFilter = async (source, value) => { + let params = { + q: router.query.q, + orderBy: '', + brand: brandValues.join(','), + category: categoryValues?.join(','), + priceFrom: priceFrom || '', + priceTo: priceTo || '', + }; + + let brands = brandValues; + let catagories = categoryValues; + switch (source) { + case 'brands': + brands = brandValues.filter((item) => item !== value); + params.brand = brands.join(','); + await setBrandValues(brands); + break; + case 'category': + catagories = categoryValues.filter((item) => item !== value); + params.category = catagories.join(','); + await setCategoryValues(catagories); + break; + case 'price': + params.priceFrom = ''; + params.priceTo = ''; + break; + case 'delete': + params = { + q: router.query.q, + orderBy: '', + brand: '', + category: '', + priceFrom: '', + priceTo: '', + }; + break; + } + + handleSubmitFilter(params); + }; + const handleSubmitFilter = (params) => { + params = _.pickBy(params, _.identity); + params = toQuery(params); + router.push(`${slug}?${params}`); + }; + + + const toQuery = (obj) => { + const str = Object.keys(obj) + .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) + .join('&') + return str + } + + const whatPromo = capitalizeFirstLetter(slug) + const queryWithoutSlug = _.omit(router.query, ['slug']) + + return ( + <BasicLayout> + <Seo + title={`Promo ${Array.isArray(slug) ? slug[0] : slug} Terkini`} + description='B2B Marketplace MRO & Industri dengan Layanan Pembayaran Tempo, Faktur Pajak, Online Quotation, Garansi Resmi & Harga Kompetitif' + /> + <Promocrumb brandName={whatPromo} /> + <MobileView> + <div className='p-4 pt-0'> + <h1 className='mb-2 font-semibold text-h-sm'>Promo {whatPromo}</h1> + + <FilterChoicesComponent + brandValues={brandValues} + categoryValues={categoryValues} + priceFrom={priceFrom} + priceTo={priceTo} + handleDeleteFilter={handleDeleteFilter} + /> + {products?.length >= 1 && ( + <div className='flex items-center gap-x-2 mb-5 justify-between'> + <div> + <button + className='btn-light py-2 px-5 h-[40px]' + onClick={popup.activate} + > + Filter + </button> + </div> + </div> + )} + {productSearch.isLoading && <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> + </div>} + {products && ( + <> + <div className='grid grid-cols-1 gap-x-1 gap-y-1'> + {products?.map((promotion) => ( + <div key={promotion.id} className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4 "> + <ProductPromoCard promotion={promotion}/> + </div> + ))} + </div> + </> + ) } + + <Pagination + pageCount={pageCount} + currentPage={parseInt(page)} + url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`} + className='mt-6 mb-2' + /> + <ProductFilter + active={popup.active} + close={popup.deactivate} + brands={brands || []} + categories={categories || []} + prefixUrl={router.asPath.includes('?') ? `${router.asPath}` : `${router.asPath}?`} + defaultBrand={null} + /> + </div> + + </MobileView> + <DesktopView> + <div className='container mx-auto flex mb-3 flex-col'> + <div className='w-full pl-6'> + <h1 className='text-2xl mb-2 font-semibold'>Promo {whatPromo}</h1> + <div className=' w-full h-full flex flex-row items-center '> + + <div className='detail-filter w-1/2 flex justify-start items-center mt-4'> + + <FilterChoicesComponent + brandValues={brandValues} + categoryValues={categoryValues} + priceFrom={priceFrom} + priceTo={priceTo} + handleDeleteFilter={handleDeleteFilter} + /> + </div> + <div className='Filter w-1/2 flex flex-col'> + + <ProductFilterDesktop + brands={brands || []} + categories={categories || []} + prefixUrl={'/shop/promo'} + // defaultBrand={null} + /> + </div> + </div> + {productSearch.isLoading ? ( + <div className='container flex justify-center my-4'> + <LogoSpinner width={48} height={48} /> + </div> + ) : products && products.length >= 1 ? ( + <> + <div className='grid grid-cols-1 gap-x-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'> + {products?.map((promotion) => ( + <div key={promotion.id} className="min-w-[400px] max-w-[400px] mb-[20px] sm:min-w-[350px] md:min-w-[380px] lg:min-w-[400px] xl:min-w-[400px] "> + <ProductPromoCard promotion={promotion}/> + </div> + ))} + </div> + </> + ) : ( + <div className="text-center my-8"> + <p>Belum ada promo pada kategori ini</p> + </div> + )} + <div className='flex justify-between items-center mt-6 mb-2'> + <div className='pt-2 pb-6 flex items-center gap-x-3'> + <NextImage + src='/images/logo-question.png' + alt='Logo Question Indoteknik' + width={60} + height={60} + /> + <div className='text-gray_r-12/90'> + <span> + Barang yang anda cari tidak ada?{' '} + <a + href={ + router.query?.q + ? whatsappUrl('productSearch', { + name: router.query.q, + }) + : whatsappUrl() + } + className='text-danger-500' + > + Hubungi Kami + </a> + </span> + </div> + </div> + + + + <Pagination + pageCount={pageCount} + currentPage={parseInt(page)} + url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`} + className='mt-6 mb-2' + /> + </div> + + </div> + </div> + </DesktopView> + </BasicLayout> + ) + } + +const FilterChoicesComponent = ({ + brandValues, + categoryValues, + priceFrom, + priceTo, + handleDeleteFilter, + }) => ( + <div className='flex items-center mb-4'> + <HStack spacing={2} className='flex-wrap'> + {brandValues?.map((value, index) => ( + <Tag + size='lg' + key={index} + borderRadius='lg' + variant='outline' + colorScheme='gray' + > + <TagLabel>{value}</TagLabel> + <TagCloseButton onClick={() => handleDeleteFilter('brands', value)} /> + </Tag> + ))} + + {categoryValues?.map((value, index) => ( + <Tag + size='lg' + key={index} + borderRadius='lg' + variant='outline' + colorScheme='gray' + > + <TagLabel>{value}</TagLabel> + <TagCloseButton + onClick={() => handleDeleteFilter('category', value)} + /> + </Tag> + ))} + {priceFrom && priceTo && ( + <Tag size='lg' borderRadius='lg' variant='outline' colorScheme='gray'> + <TagLabel> + {formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)} + </TagLabel> + <TagCloseButton + onClick={() => handleDeleteFilter('price', priceFrom)} + /> + </Tag> + )} + {brandValues?.length > 0 || + categoryValues?.length > 0 || + priceFrom || + priceTo ? ( + <span> + <button + className='btn-transparent py-2 px-5 h-[40px] text-red-700' + onClick={() => handleDeleteFilter('delete')} + > + Hapus Semua + </button> + </span> + ) : ( + '' + )} + </HStack> + </div> +); diff --git a/src/pages/shop/promo/[slug].tsx b/src/pages/shop/promo/[slug].tsx deleted file mode 100644 index aaee1249..00000000 --- a/src/pages/shop/promo/[slug].tsx +++ /dev/null @@ -1,523 +0,0 @@ -import dynamic from 'next/dynamic' -import NextImage from 'next/image'; -import { useEffect, useState } from 'react' -import { useRouter } from 'next/router' -import Seo from '../../../core/components/Seo' -import Promocrumb from '../../../lib/promo/components/Promocrumb' -import { fetchPromoItemsSolr, fetchVariantSolr } from '../../../api/promoApi' -import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx' -import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card' -import { IPromotion } from '../../../../src-migrate/types/promotion' -import React from 'react' -import { SolrResponse } from "../../../../src-migrate/types/solr.ts"; -import DesktopView from '../../../core/components/views/DesktopView'; -import MobileView from '../../../core/components/views/MobileView'; -import 'swiper/swiper-bundle.css'; -import useDevice from '../../../core/hooks/useDevice' -import ProductFilterDesktop from '../../../lib/product/components/ProductFilterDesktopPromotion'; -import ProductFilter from '../../../lib/product/components/ProductFilter'; -import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react'; -import { formatCurrency } from '../../../core/utils/formatValue'; -import Pagination from '../../../core/components/elements/Pagination/Pagination'; -import SideBanner from '../../../../src-migrate/modules/side-banner'; -import whatsappUrl from '../../../core/utils/whatsappUrl'; -import { cons, toQuery } from 'lodash-contrib'; -import _ from 'lodash'; -import useActive from '../../../core/hooks/useActive'; - -const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout')) - -export default function PromoDetail() { - const router = useRouter() - const { slug = '', brand ='', category='', priceFrom = '', priceTo = '', page = '1' } = router.query - const [promoItems, setPromoItems] = useState<any[]>([]) - const [promoData, setPromoData] = useState<IPromotion[] | null>(null) - const [currentPage, setCurrentPage] = useState(parseInt(page as string, 10) || 1); - const itemsPerPage = 12; // Jumlah item yang ingin ditampilkan per halaman - const [loading, setLoading] = useState(true); - const { isMobile, isDesktop } = useDevice() - const [brands, setBrands] = useState<Brand[]>([]); - const [categories, setCategories] = useState<Category[]>([]); - const [brandValues, setBrandValues] = useState<string[]>([]); - const [categoryValues, setCategoryValues] = useState<string[]>([]); - const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular'); - const popup = useActive(); - const prefixUrl = `/shop/promo/${slug}` - - useEffect(() => { - if (router.query.brand) { - let brandsArray: string[] = []; - if (Array.isArray(router.query.brand)) { - brandsArray = router.query.brand; - } else if (typeof router.query.brand === 'string') { - brandsArray = router.query.brand.split(',').map((brand) => brand.trim()); - } - setBrandValues(brandsArray); - } else { - setBrandValues([]); - } - - if (router.query.category) { - let categoriesArray: string[] = []; - - if (Array.isArray(router.query.category)) { - categoriesArray = router.query.category; - } else if (typeof router.query.category === 'string') { - categoriesArray = router.query.category.split(',').map((category) => category.trim()); - } - setCategoryValues(categoriesArray); - } else { - setCategoryValues([]); - } - }, [router.query.brand, router.query.category]); - - interface Brand { - brand: string; - qty: number; - } - - interface Category { - name: string; - qty: number; - } - - useEffect(() => { - const loadPromo = async () => { - setLoading(true); - const brandsData: Brand[] = []; - const categoriesData: Category[] = []; - - const pageNumber = Array.isArray(page) ? parseInt(page[0], 10) : parseInt(page, 10); - setCurrentPage(pageNumber) - - try { - const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`,0,100); - setPromoItems(items); - - if (items.length === 0) { - setPromoData([]) - setLoading(false); - return; - } - - const brandArray = Array.isArray(brand) ? brand : brand.split(','); - const categoryArray = Array.isArray(category) ? category : category.split(','); - - const promoDataPromises = items.map(async (item) => { - - try { - let brandQuery = ''; - if (brand) { - brandQuery = brandArray.map(b => `manufacture_name_s:${b}`).join(' OR '); - brandQuery = `(${brandQuery})`; - } - - let categoryQuery = ''; - if (category) { - categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR '); - categoryQuery = `(${categoryQuery})`; - } - - let priceQuery = ''; - if (priceFrom && priceTo) { - priceQuery = `price_f:[${priceFrom} TO ${priceTo}]`; - } else if (priceFrom) { - priceQuery = `price_f:[${priceFrom} TO *]`; - } else if (priceTo) { - priceQuery = `price_f:[* TO ${priceTo}]`; - } - - let combinedQuery = ''; - let combinedQueryPrice = `${priceQuery}`; - if (brand && category && priceFrom || priceTo) { - combinedQuery = `${brandQuery} AND ${categoryQuery} `; - } else if (brand && category) { - combinedQuery = `${brandQuery} AND ${categoryQuery}`; - } else if (brand && priceFrom || priceTo) { - combinedQuery = `${brandQuery}`; - } else if (category && priceFrom || priceTo) { - combinedQuery = `${categoryQuery}`; - } else if (brand) { - combinedQuery = brandQuery; - } else if (category) { - combinedQuery = categoryQuery; - } - - if (combinedQuery && priceFrom || priceTo) { - const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`); - const product = response.response.docs[0]; - const product_id = product.id; - const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`,0,100); - return response2; - }else if(combinedQuery){ - const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`); - const product = response.response.docs[0]; - const product_id = product.id; - const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `,0,100); - return response2; - } else { - const response = await fetchPromoItemsSolr(`id:${item.id}`,0,100); - return response; - } - } catch (fetchError) { - return []; - } - }); - - const promoDataArray = await Promise.all(promoDataPromises); - const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); - setPromoData(mergedPromoData); - - const dataBrandCategoryPromises = promoDataArray.map(async (promoData) => { - if (promoData) { - const dataBrandCategory = promoData.map(async (item) => { - let response; - if(category){ - const categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR '); - response = await fetchVariantSolr(`id:${item.products[0].product_id} AND (${categoryQuery})`); - }else{ - response = await fetchVariantSolr(`id:${item.products[0].product_id}`) - } - - - if (response.response?.docs?.length > 0) { - const product = response.response.docs[0]; - const manufactureNameS = product.manufacture_name; - if (Array.isArray(manufactureNameS)) { - for (let i = 0; i < manufactureNameS.length; i += 2) { - const brand = manufactureNameS[i]; - const qty = 1; - const existingBrandIndex = brandsData.findIndex(b => b.brand === brand); - if (existingBrandIndex !== -1) { - brandsData[existingBrandIndex].qty += qty; - } else { - brandsData.push({ brand, qty }); - } - } - } - - const categoryNameS = product.category_name; - if (Array.isArray(categoryNameS)) { - for (let i = 0; i < categoryNameS.length; i += 2) { - const name = categoryNameS[i]; - const qty = 1; - const existingCategoryIndex = categoriesData.findIndex(c => c.name === name); - if (existingCategoryIndex !== -1) { - categoriesData[existingCategoryIndex].qty += qty; - } else { - categoriesData.push({ name, qty }); - } - } - } - } - }); - - return Promise.all(dataBrandCategory); - } - }); - - await Promise.all(dataBrandCategoryPromises); - setBrands(brandsData); - setCategories(categoriesData); - setLoading(false); - - } catch (loadError) { - // console.error("Error loading promo items:", loadError) - setLoading(false); - } - } - - if (slug) { - loadPromo() - } - },[slug, brand, category, priceFrom, priceTo, currentPage]); - - - function capitalizeFirstLetter(string) { - string = string.replace(/_/g, ' '); - return string.replace(/(^\w|\s\w)/g, function(match) { - return match.toUpperCase(); - }); - } - - const handleDeleteFilter = async (source, value) => { - let params = { - q: router.query.q, - orderBy: '', - brand: brandValues.join(','), - category: categoryValues.join(','), - priceFrom: priceFrom || '', - priceTo: priceTo || '', - }; - - let brands = brandValues; - let catagories = categoryValues; - switch (source) { - case 'brands': - brands = brandValues.filter((item) => item !== value); - params.brand = brands.join(','); - await setBrandValues(brands); - break; - case 'category': - catagories = categoryValues.filter((item) => item !== value); - params.category = catagories.join(','); - await setCategoryValues(catagories); - break; - case 'price': - params.priceFrom = ''; - params.priceTo = ''; - break; - case 'delete': - params = { - q: router.query.q, - orderBy: '', - brand: '', - category: '', - priceFrom: '', - priceTo: '', - }; - break; - } - - handleSubmitFilter(params); - }; - const handleSubmitFilter = (params) => { - params = _.pickBy(params, _.identity); - params = toQuery(params); - router.push(`${slug}?${params}`); - }; - - const visiblePromotions = promoData?.slice( (currentPage-1) * itemsPerPage, currentPage * 12) - - const toQuery = (obj) => { - const str = Object.keys(obj) - .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) - .join('&') - return str - } - - const whatPromo = capitalizeFirstLetter(slug) - const queryWithoutSlug = _.omit(router.query, ['slug']) - const queryString = toQuery(queryWithoutSlug) - - return ( - <BasicLayout> - <Seo - title={`Promo ${Array.isArray(slug) ? slug[0] : slug} Terkini`} - description='B2B Marketplace MRO & Industri dengan Layanan Pembayaran Tempo, Faktur Pajak, Online Quotation, Garansi Resmi & Harga Kompetitif' - /> - <Promocrumb brandName={whatPromo} /> - <MobileView> - <div className='p-4 pt-0'> - <h1 className='mb-2 font-semibold text-h-sm'>Promo {whatPromo}</h1> - - <FilterChoicesComponent - brandValues={brandValues} - categoryValues={categoryValues} - priceFrom={priceFrom} - priceTo={priceTo} - handleDeleteFilter={handleDeleteFilter} - /> - {promoItems.length >= 1 && ( - <div className='flex items-center gap-x-2 mb-5 justify-between'> - <div> - <button - className='btn-light py-2 px-5 h-[40px]' - onClick={popup.activate} - > - Filter - </button> - </div> - </div> - )} - - {loading ? ( - <div className='container flex justify-center my-4'> - <LogoSpinner width={48} height={48} /> - </div> - ) : promoData && promoItems.length >= 1 ? ( - <> - <div className='grid grid-cols-1 gap-x-1 gap-y-1'> - {visiblePromotions?.map((promotion) => ( - <div key={promotion.id} className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4 "> - <ProductPromoCard promotion={promotion}/> - </div> - ))} - </div> - </> - ) : ( - <div className="text-center my-8"> - <p>Belum ada promo pada kategori ini</p> - </div> - )} - - <Pagination - pageCount={Math.ceil((promoData?.length ?? 0) / itemsPerPage)} - currentPage={currentPage} - url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`} - className='mt-6 mb-2' - /> - <ProductFilter - active={popup.active} - close={popup.deactivate} - brands={brands || []} - categories={categories || []} - prefixUrl={router.asPath.includes('?') ? `${router.asPath}` : `${router.asPath}?`} - defaultBrand={null} - /> - </div> - - </MobileView> - <DesktopView> - <div className='container mx-auto flex mb-3 flex-col'> - <div className='w-full pl-6'> - <h1 className='text-2xl mb-2 font-semibold'>Promo {whatPromo}</h1> - <div className=' w-full h-full flex flex-row items-center '> - - <div className='detail-filter w-1/2 flex justify-start items-center mt-4'> - - <FilterChoicesComponent - brandValues={brandValues} - categoryValues={categoryValues} - priceFrom={priceFrom} - priceTo={priceTo} - handleDeleteFilter={handleDeleteFilter} - /> - </div> - <div className='Filter w-1/2 flex flex-col'> - - <ProductFilterDesktop - brands={brands || []} - categories={categories || []} - prefixUrl={'/shop/promo'} - // defaultBrand={null} - /> - </div> - </div> - {loading ? ( - <div className='container flex justify-center my-4'> - <LogoSpinner width={48} height={48} /> - </div> - ) : promoData && promoItems.length >= 1 ? ( - <> - <div className='grid grid-cols-1 gap-x-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'> - {visiblePromotions?.map((promotion) => ( - <div key={promotion.id} className="min-w-[400px] max-w-[400px] mb-[20px] sm:min-w-[350px] md:min-w-[380px] lg:min-w-[400px] xl:min-w-[400px] "> - <ProductPromoCard promotion={promotion}/> - </div> - ))} - </div> - </> - ) : ( - <div className="text-center my-8"> - <p>Belum ada promo pada kategori ini</p> - </div> - )} - <div className='flex justify-between items-center mt-6 mb-2'> - <div className='pt-2 pb-6 flex items-center gap-x-3'> - <NextImage - src='/images/logo-question.png' - alt='Logo Question Indoteknik' - width={60} - height={60} - /> - <div className='text-gray_r-12/90'> - <span> - Barang yang anda cari tidak ada?{' '} - <a - href={ - router.query?.q - ? whatsappUrl('productSearch', { - name: router.query.q, - }) - : whatsappUrl() - } - className='text-danger-500' - > - Hubungi Kami - </a> - </span> - </div> - </div> - - - - <Pagination - pageCount={Math.ceil((promoData?.length ?? 0) / itemsPerPage)} - currentPage={currentPage} - url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`} - className='mt-6 mb-2' - /> - </div> - - </div> - </div> - </DesktopView> - </BasicLayout> - ) - } - -const FilterChoicesComponent = ({ - brandValues, - categoryValues, - priceFrom, - priceTo, - handleDeleteFilter, - }) => ( - <div className='flex items-center mb-4'> - <HStack spacing={2} className='flex-wrap'> - {brandValues?.map((value, index) => ( - <Tag - size='lg' - key={index} - borderRadius='lg' - variant='outline' - colorScheme='gray' - > - <TagLabel>{value}</TagLabel> - <TagCloseButton onClick={() => handleDeleteFilter('brands', value)} /> - </Tag> - ))} - - {categoryValues?.map((value, index) => ( - <Tag - size='lg' - key={index} - borderRadius='lg' - variant='outline' - colorScheme='gray' - > - <TagLabel>{value}</TagLabel> - <TagCloseButton - onClick={() => handleDeleteFilter('category', value)} - /> - </Tag> - ))} - {priceFrom && priceTo && ( - <Tag size='lg' borderRadius='lg' variant='outline' colorScheme='gray'> - <TagLabel> - {formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)} - </TagLabel> - <TagCloseButton - onClick={() => handleDeleteFilter('price', priceFrom)} - /> - </Tag> - )} - {brandValues?.length > 0 || - categoryValues?.length > 0 || - priceFrom || - priceTo ? ( - <span> - <button - className='btn-transparent py-2 px-5 h-[40px] text-red-700' - onClick={() => handleDeleteFilter('delete')} - > - Hapus Semua - </button> - </span> - ) : ( - '' - )} - </HStack> - </div> -); diff --git a/src/pages/sitemap/brands.xml.js b/src/pages/sitemap/brands.xml.js index c85c40e9..65a84e97 100644 --- a/src/pages/sitemap/brands.xml.js +++ b/src/pages/sitemap/brands.xml.js @@ -15,8 +15,8 @@ export async function getServerSideProps({ res }) { const url = sitemap.ele('url') url.ele('loc', createSlug(baseUrl, brand.name, brand.id)) url.ele('lastmod', date.toISOString().slice(0, 10)) - url.ele('changefreq', 'weekly') - url.ele('priority', '0.6') + url.ele('changefreq', 'daily') + url.ele('priority', '1.0') }) res.setHeader('Content-Type', 'text/xml') diff --git a/src/pages/sitemap/products/[page].js b/src/pages/sitemap/products/[page].js index 2f9c3198..e39755d6 100644 --- a/src/pages/sitemap/products/[page].js +++ b/src/pages/sitemap/products/[page].js @@ -19,7 +19,7 @@ export async function getServerSideProps({ query, res }) { const url = sitemap.ele('url') url.ele('loc', createSlug(baseUrl, product.name, product.id)) url.ele('lastmod', date.toISOString().slice(0, 10)) - url.ele('changefreq', 'weekly') + url.ele('changefreq', 'daily') url.ele('priority', '0.8') }) diff --git a/src/pages/tracking-order.jsx b/src/pages/tracking-order.jsx new file mode 100644 index 00000000..002acd42 --- /dev/null +++ b/src/pages/tracking-order.jsx @@ -0,0 +1,27 @@ +import Seo from '@/core/components/Seo' +import dynamic from 'next/dynamic' +import SimpleFooter from '@/core/components/elements/Footer/SimpleFooter'; +import BasicLayout from '@/core/components/layouts/BasicLayout'; +import DesktopView from '@/core/components/views/DesktopView'; +import MobileView from '@/core/components/views/MobileView'; +const PageTrackingOrder = dynamic(() => import('@/lib/tracking-order/component/TrackingOrder')) + +export default function TrackingOrder() { + return ( + <> + <Seo title='Tracking Order - Indoteknik.com' /> + + <DesktopView> + <BasicLayout> + <PageTrackingOrder/> + </BasicLayout> + </DesktopView> + + <MobileView> + <BasicLayout> + <PageTrackingOrder/> + </BasicLayout> + </MobileView> + </> + ); +} |
