feat: move content to shopify

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe 2024-06-05 14:48:31 +07:00
parent a356148ed3
commit 882d1db67c
No known key found for this signature in database
GPG Key ID: CFD53CE570D42DF5
20 changed files with 179 additions and 118 deletions

View File

@ -1,3 +1,4 @@
import FAQ from 'components/faq';
import Hero from 'components/hero';
import About from 'components/home-page/about';
import WhyChoose from 'components/home-page/why-choose';
@ -24,9 +25,14 @@ export default async function HomePage() {
<Suspense>
<Hero />
</Suspense>
<div className="mx-auto flex min-h-96 max-w-7xl flex-col space-y-14 px-6 py-16 sm:space-y-28 lg:px-8">
<div className="flex min-h-96 flex-col">
<About />
<WhyChoose />
<Suspense>
<WhyChoose />
</Suspense>
<Suspense>
<FAQ />
</Suspense>
</div>
<Suspense>
<Footer />

View File

@ -15,7 +15,7 @@ const DisplayTabs = ({ items }: DisplayTabsProps) => {
onClick={() => setSelectedIndex(index)}
key={item}
className={clsx(
'min-w-[130px] cursor-pointer rounded py-1 text-center text-sm font-medium',
'w-fit cursor-pointer rounded px-6 py-1 text-center text-sm font-medium',
{
'bg-white text-primary': index === selectedIndex,
'bg-transparent text-gray-600': index !== selectedIndex

61
components/faq.tsx Normal file
View File

@ -0,0 +1,61 @@
import { PhoneIcon } from '@heroicons/react/24/outline';
import { getMetaobject } from 'lib/shopify';
import kebabCase from 'lodash.kebabcase';
import Image from 'next/image';
import { Suspense } from 'react';
import AccordionBlock from './page/accordion-block';
import Tag from './tag';
const { SITE_NAME } = process.env;
const FAQ = async () => {
const faqs = await getMetaobject({
handle: { handle: `${kebabCase(SITE_NAME)}-faqs`, type: 'accordion' }
});
if (!faqs) return null;
return (
<div className="bg-gray-100 px-6 py-16">
<div className="mx-auto grid max-w-7xl grid-cols-1 lg:grid-cols-2 lg:gap-20">
<div className="col-span-1 flex flex-col gap-3">
<Tag text="FAQ" />
<h3 className="text-3xl font-semibold lg:text-4xl">Frequently Asked Questions</h3>
<Suspense>
<AccordionBlock block={faqs} />
</Suspense>
</div>
<div className="relative col-span-1 hidden lg:block">
<div className="absolute right-0 h-[500px] w-4/5">
<Image
src="/faq-background.png"
alt="FAQs background"
fill
className="w-full object-cover object-center"
/>
</div>
<div className="absolute left-0 top-0 flex min-h-[300px] min-w-[400px] translate-y-1/4 flex-col gap-3 bg-dark px-12 py-14">
<Tag text="Let's Talk" invert />
<p className="max-w-[250px] text-lg font-medium text-white">
Need Any Help? Get Free Consultation
</p>
<div className="flex flex-row items-center gap-4 text-white">
<div className="flex h-16 w-16 items-center justify-center rounded-full border border-blue-500 bg-blue-600">
<PhoneIcon className="size-5" />
</div>
<div>
<p>Have Any Questions</p>
<a href={`tel:${8882422605}`}>(888) 242-2605</a>
</div>
</div>
<button className="mt-5 w-fit rounded bg-primary px-4 py-2 text-sm text-white">
Contact Us
</button>
</div>
</div>
</div>
</div>
);
};
export default FAQ;

View File

@ -1,14 +1,17 @@
import { getMetaobjects } from 'lib/shopify';
import { getMetaobject, getMetaobjects } from 'lib/shopify';
import kebabCase from 'lodash.kebabcase';
import Image from 'next/image';
import { Suspense } from 'react';
import HomePageFilters, { HomePageFiltersPlaceholder } from './filters/hompage-filters';
import DynamicHeroIcon from './hero-icon';
import ImageDisplay from './page/image-display';
const { SITE_NAME } = process.env;
const Hero = async () => {
const [offers, heroImage] = await Promise.all([
getMetaobjects('usp_item'),
getMetaobjects('hero')
getMetaobject({ handle: { type: 'hero', handle: `${kebabCase(SITE_NAME)}-hero` } })
]);
return (
@ -39,10 +42,10 @@ const Hero = async () => {
<div className="relative bg-gray-900">
{/* Decorative image and overlay */}
<div aria-hidden="true" className="absolute inset-0 overflow-hidden">
{heroImage.length && heroImage[0]?.file ? (
{heroImage ? (
<Suspense fallback={<div className="h-[626px] w-full" />}>
<ImageDisplay
fileId={heroImage[0].file as string}
fileId={heroImage.file as string}
title="Hero Image"
priority
className="h-full w-full object-cover object-center"

View File

@ -5,7 +5,7 @@ import ButtonLink from './button-link';
const About = () => {
return (
<div className="grid grid-cols-1 items-start gap-x-0 gap-y-10 lg:grid-cols-2 lg:gap-x-10">
<div className="mx-auto grid max-w-7xl grid-cols-1 items-start gap-x-0 gap-y-10 px-6 py-16 lg:grid-cols-2 lg:gap-x-10">
<Image
src="/about.png"
alt="About Us"

View File

@ -1,101 +1,43 @@
import Image from 'next/image';
import ImageDisplay from 'components/page/image-display';
import RichTextDisplay from 'components/page/rich-text-display';
import { getMetaobject, getMetaobjectsByIds } from 'lib/shopify';
import kebabCase from 'lodash.kebabcase';
import { Suspense } from 'react';
import Tag from '../tag';
const { SITE_NAME } = process.env;
const WhyChoose = () => {
const WhyChoose = async () => {
const whyChooseContent = await getMetaobject({
handle: { type: 'why_choose', handle: `${kebabCase(SITE_NAME)}-why-choose` }
});
if (!whyChooseContent || !whyChooseContent.items) return null;
const reasons = await getMetaobjectsByIds(JSON.parse(whyChooseContent.items) as string[]);
return (
<div className="grid grid-cols-1 gap-y-5 lg:grid-cols-5 lg:gap-y-0">
<div className="mx-auto grid max-w-7xl grid-cols-1 gap-y-5 px-6 py-16 lg:grid-cols-5 lg:gap-y-0">
<div className="col-span-1 flex flex-col gap-3">
<Tag text="Benefits" />
<h3 className="text-3xl font-semibold lg:text-4xl">Why choose {SITE_NAME}?</h3>
</div>
<div className="col-span-1 grid grid-cols-subgrid gap-x-6 gap-y-12 lg:col-span-4">
<div className="col-span-1 flex lg:col-span-2">
<Image
src="/icons/team.png"
alt="Team"
width={60}
height={60}
className="h-[60px] w-[60px]"
/>
<div className="mx-3 h-[100px] min-w-0.5 bg-gray-200" />
<div className="ml-2 flex flex-col gap-3">
<h4 className="text-xl font-medium text-blue-800">Advanced Team</h4>
<p className="text-justify leading-6 text-blue-200">
We specialize in procuring high-quality used transmissions and engines as well as
remanufactured engines and transmissions for the majority of truck and car makes and
models. Whatever components you might need to repair or upgrade your vehicle, our
advanced team of customer support specialists, sales leads, and automotive gurus can
help you source the perfect quality engine or transmission for your needs.
</p>
{reasons.map((reason) => (
<div className="col-span-1 flex lg:col-span-2" key={reason.id}>
<Suspense>
<ImageDisplay
fileId={reason.file as string}
title={(reason.title || reason.name) as string}
className="h-[60px] w-[60px]"
/>
</Suspense>
<div className="mx-3 h-[100px] min-w-0.5 bg-gray-200" />
<div className="ml-2 flex flex-col gap-3">
<h4 className="text-xl font-medium text-blue-800">{reason.title}</h4>
<RichTextDisplay contentBlocks={JSON.parse(reason.description || '{}').children} />
</div>
</div>
</div>
<div className="col-span-1 flex lg:col-span-2">
<Image
src="/icons/customer-support.png"
alt="Customer Support"
width={60}
height={60}
className="h-[60px] w-[60px]"
/>
<div className="mx-3 h-[100px] min-w-0.5 bg-gray-200" />
<div className="ml-2 flex flex-col gap-3">
<h4 className="text-xl font-medium text-blue-800">Customer Support Staff</h4>
<p className="text-justify leading-6 text-blue-200">
With more than 20 years of experience providing support to our customers, our
representatives have deep knowledge about all aspects of vehicles. We will quickly
help you locate the exact compatible engine or transmission you need and get it to
your commercial or residential address quickly. Our customer service team undergoes
extensive training so that no matter what your problem is, we can fix it.
</p>
</div>
</div>
<div className="col-span-1 flex lg:col-span-2">
<Image
src="/icons/replacement.png"
alt="Replacement Process"
width={60}
height={60}
className="h-[60px] w-[60px]"
/>
<div className="mx-3 h-[100px] min-w-0.5 bg-gray-200" />
<div className="ml-2 flex flex-col gap-3">
<h4 className="text-xl font-medium text-blue-800">Replacement Process</h4>
<p className="text-justify leading-6 text-blue-200">
Car Part Planet is a partner with the most prominent automotive parts manufacturers
and suppliers. The advanced online catalog weve created to support the customers of
Car Part Planet is what you will use to find your exact part. Simply by using our
Search Tool, you can find the exact engine or transmission swap for your ride. You can
shop our smart catalog 24/7, no matter what time zone youre in, for sourcing anything
from a complete transmission replacement to a completely rebuilt engine, all at
low-cost prices.
</p>
</div>
</div>
<div className="col-span-1 flex lg:col-span-2">
<Image
src="/icons/shipping.png"
alt="Fast Shipping & Exclusive Warranty"
width={60}
height={60}
className="h-[60px] w-[60px]"
/>
<div className="mx-3 h-[100px] min-w-0.5 bg-gray-200" />
<div className="ml-2 flex flex-col gap-3">
<h4 className="text-xl font-medium text-blue-800">
Fast Shipping & Exclusive Warranty
</h4>
<p className="text-justify leading-6 text-blue-200">
We can offer great prices because we operate more efficiently as an online retailer,
so we pass this benefit to you with fast flat-rate shipping straight to your
commercial or residential address when you order with us. You deserve proper coverage
for your purchases from Car Part Planet, which is why we are proud to offer unlimited
mile warranty protection for up to five years, depending on what you purchase. Give us
a call today to discuss the details about our outstanding warranty terms or check out
our designated warranty page for complete information.
</p>
</div>
</div>
))}
</div>
</div>
);

View File

@ -1,19 +1,27 @@
'use client';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/24/outline';
import { ChevronRightIcon } from '@heroicons/react/24/outline';
import { ReactNode } from 'react';
const AccordionBlockItem = ({ title, children }: { title: string; children: ReactNode }) => {
const AccordionBlockItem = ({
title,
children,
defaultOpen
}: {
title: string;
children: ReactNode;
defaultOpen?: boolean;
}) => {
return (
<Disclosure as="div" className="pt-6">
<Disclosure defaultOpen={defaultOpen} as="div" className="pt-6">
<dt>
<DisclosureButton className="group flex w-full items-start justify-between text-left text-gray-900">
<span className="text-lg font-semibold leading-7">{title}</span>
<ChevronDownIcon className="size-5 group-data-[open]:rotate-180" />
<span className="text-lg font-medium text-blue-800">{title}</span>
<ChevronRightIcon className="size-5 group-data-[open]:rotate-90 group-data-[open]:text-primary" />
</DisclosureButton>
</dt>
<DisclosurePanel as="dd" className="mt-2 flex flex-col gap-4 py-4 text-base text-gray-800">
<DisclosurePanel as="dd" className="mt-2 flex flex-col gap-4 py-4">
{children}
</DisclosurePanel>
</Disclosure>

View File

@ -1,17 +1,17 @@
import { getMetaobjectById, getMetaobjectsByIds } from 'lib/shopify';
import { getMetaobject, getMetaobjectsByIds } from 'lib/shopify';
import { Metaobject } from 'lib/shopify/types';
import AccordionBlockItem from './accordion-block-item';
import PageContent from './page-content';
const AccordionItem = async ({ id }: { id: string }) => {
const accordionObject = await getMetaobjectById(id);
const AccordionItem = async ({ id, defaultOpen }: { id: string; defaultOpen?: boolean }) => {
const accordionObject = await getMetaobject({ id });
if (!accordionObject) return null;
const content = await getMetaobjectsByIds(JSON.parse(accordionObject.accordion_content || '[]'));
return (
<AccordionBlockItem title={accordionObject.title || 'Section Title'}>
<AccordionBlockItem title={accordionObject.title || 'Section Title'} defaultOpen={defaultOpen}>
{content.map((block) => (
<PageContent block={block} key={block.id} />
))}
@ -19,7 +19,13 @@ const AccordionItem = async ({ id }: { id: string }) => {
);
};
const AccordionBlock = async ({ block }: { block: Metaobject }) => {
const AccordionBlock = ({
block,
defaultOpenIndex = 0
}: {
block: Metaobject;
defaultOpenIndex?: number;
}) => {
const accordionItemIds = JSON.parse(block.accordion || '[]') as string[];
return (
@ -28,8 +34,8 @@ const AccordionBlock = async ({ block }: { block: Metaobject }) => {
<h3 className="mb-7 text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
)}
<dl className="w-full space-y-6 divide-y divide-gray-900/10">
{accordionItemIds.map((id) => (
<AccordionItem key={id} id={id} />
{accordionItemIds.map((id, index) => (
<AccordionItem key={id} id={id} defaultOpen={defaultOpenIndex === index} />
))}
</dl>
</div>

View File

@ -40,7 +40,7 @@ const RichTextBlock = ({ block }: { block: Content }) => {
}
return (
<p className="text-gray-800">
<p className="text-blue-200">
{block.children.map((child, index) => (
<RichTextBlock key={index} block={child} />
))}

View File

@ -1,6 +1,15 @@
const Tag = ({ text }: { text: string }) => {
import clsx from 'clsx';
const Tag = ({ text, invert }: { text: string; invert?: boolean }) => {
return (
<div className="w-fit border-l-4 border-l-primary bg-gray-100 px-2 py-0.5 text-sm">{text}</div>
<div
className={clsx('w-fit border-l-4 border-l-primary px-2 py-0.5 text-sm', {
'bg-transparent text-white': invert,
'bg-gray-200/50 text-blue-800': !invert
})}
>
{text}
</div>
);
};

View File

@ -562,13 +562,19 @@ export async function getMetaobjectsByIds(ids: string[]) {
return reshapeMetaobjects(res.body.data.nodes);
}
export async function getMetaobjectById(id: string) {
export async function getMetaobject({
id,
handle
}: {
id?: string;
handle?: { handle: string; type: string };
}) {
const res = await shopifyFetch<{
data: { metaobject: ShopifyMetaobject };
variables: { id: string };
variables: { id?: string; handle?: { handle: string; type: string } };
}>({
query: getMetaobjectQuery,
variables: { id }
variables: { id, handle }
});
return res.body.data.metaobject ? reshapeMetaobjects([res.body.data.metaobject])[0] : null;

View File

@ -20,8 +20,8 @@ export const getMetaobjectsQuery = /* GraphQL */ `
`;
export const getMetaobjectQuery = /* GraphQL */ `
query getMetaobject($id: ID!) {
metaobject(id: $id) {
query getMetaobject($id: ID, $handle: MetaobjectHandleInput) {
metaobject(id: $id, handle: $handle) {
id
type
fields {

View File

@ -4,6 +4,8 @@ export const colors = {
secondary: '#EF6C02',
blue: {
800: '#1C1F35',
200: '#666C89'
200: '#666C89',
500: '#2D3A7B',
600: '#111C55'
}
};

View File

@ -28,6 +28,7 @@
"clsx": "^2.1.0",
"geist": "^1.3.0",
"lodash.get": "^4.4.2",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "^4.4.0",
"next": "14.1.4",
"react": "18.2.0",
@ -41,6 +42,7 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.11",
"@types/lodash.get": "^4.4.9",
"@types/lodash.kebabcase": "^4.1.9",
"@types/lodash.startcase": "^4.4.9",
"@types/node": "20.11.30",
"@types/react": "18.2.72",

16
pnpm-lock.yaml generated
View File

@ -23,6 +23,9 @@ dependencies:
lodash.get:
specifier: ^4.4.2
version: 4.4.2
lodash.kebabcase:
specifier: ^4.1.1
version: 4.1.1
lodash.startcase:
specifier: ^4.4.0
version: 4.4.0
@ -58,6 +61,9 @@ devDependencies:
'@types/lodash.get':
specifier: ^4.4.9
version: 4.4.9
'@types/lodash.kebabcase':
specifier: ^4.1.9
version: 4.1.9
'@types/lodash.startcase':
specifier: ^4.4.9
version: 4.4.9
@ -759,6 +765,12 @@ packages:
'@types/lodash': 4.17.1
dev: true
/@types/lodash.kebabcase@4.1.9:
resolution: {integrity: sha512-kPrrmcVOhSsjAVRovN0lRfrbuidfg0wYsrQa5IYuoQO1fpHHGSme66oyiYA/5eQPVl8Z95OA3HG0+d2SvYC85w==}
dependencies:
'@types/lodash': 4.17.1
dev: true
/@types/lodash.startcase@4.4.9:
resolution: {integrity: sha512-C0M4DlN1pnn2vEEhLHkTHxiRZ+3GlTegpoAEHHGXnuJkSOXyJMHGiSc+SLRzBlFZWHsBkixe6FqvEAEU04g14g==}
dependencies:
@ -2640,6 +2652,10 @@ packages:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
dev: true
/lodash.kebabcase@4.1.1:
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
dev: false
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true

BIN
public/faq-background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB