feat: Add CMS context, hooks, and types for managing CMS data
- Implemented CmsContext and CmsProvider for global state management of CMS data. - Created a custom hook `useGetCms` to fetch CMS data using React Query. - Defined TypeScript interfaces for CMS response and its related data structures. - Added error handling for data fetching in both the service and context.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { CmsData } from "@/types/TCms";
|
||||||
import { ApexOptions } from "apexcharts";
|
import { ApexOptions } from "apexcharts";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -15,7 +16,7 @@ interface ChartState {
|
|||||||
options: ApexOptions;
|
options: ApexOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApexChart = () => {
|
const ApexChart = ({ cmsData }: { cmsData?: CmsData }) => {
|
||||||
const [state, setState] = useState<ChartState | null>(null);
|
const [state, setState] = useState<ChartState | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -23,10 +24,7 @@ const ApexChart = () => {
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "Sales",
|
name: "Sales",
|
||||||
data: [
|
data: cmsData?.chart.data.map((item) => item.value) || [10, 10, 10],
|
||||||
182, 192, 230, 198, 243, 234, 244, 210, 274, 220, 225, 289, 265,
|
|
||||||
304,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
@@ -58,22 +56,7 @@ const ApexChart = () => {
|
|||||||
colors: ["#0164FF"],
|
colors: ["#0164FF"],
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
categories: [
|
categories: cmsData?.chart.data.map((item) => item.key),
|
||||||
"1/11/2024",
|
|
||||||
"2/11/2024",
|
|
||||||
"3/11/2024",
|
|
||||||
"5/11/2024",
|
|
||||||
"6/11/2024",
|
|
||||||
"7/11/2024",
|
|
||||||
"8/11/2024",
|
|
||||||
"9/11/2024",
|
|
||||||
"10/11/2024",
|
|
||||||
"11/11/2024",
|
|
||||||
"12/11/2024",
|
|
||||||
"1/11/2025",
|
|
||||||
"2/11/2025",
|
|
||||||
"3/11/2025",
|
|
||||||
],
|
|
||||||
tickAmount: 0,
|
tickAmount: 0,
|
||||||
labels: {
|
labels: {
|
||||||
formatter: function (value: any, timestamp: any, opts: any) {
|
formatter: function (value: any, timestamp: any, opts: any) {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import ApexChart from "@/app/_components/ApexChart";
|
import ApexChart from "@/app/_components/ApexChart";
|
||||||
|
import { cmsService } from "@/hooks/useCmsQueries";
|
||||||
|
import { CmsResponse } from "@/types/TCms";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
@@ -21,7 +23,20 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Home() {
|
interface IProps {
|
||||||
|
params: { id: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Home({ params }: IProps) {
|
||||||
|
const { id } = params;
|
||||||
|
|
||||||
|
let cmsData: CmsResponse | null = null;
|
||||||
|
try {
|
||||||
|
cmsData = await cmsService.getCms(id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching CMS data:", error);
|
||||||
|
}
|
||||||
|
|
||||||
const centerFrames = [
|
const centerFrames = [
|
||||||
{
|
{
|
||||||
src: "/magnifier.svg",
|
src: "/magnifier.svg",
|
||||||
@@ -45,6 +60,7 @@ export default function Home() {
|
|||||||
"We operate on a partnership model. We only succeed when you win. This aligns our goals and makes us a true partner in your growth.",
|
"We operate on a partnership model. We only succeed when you win. This aligns our goals and makes us a true partner in your growth.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="block lg:flex justify-around items-center">
|
<section className="block lg:flex justify-around items-center">
|
||||||
@@ -52,13 +68,13 @@ export default function Home() {
|
|||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
<h2>
|
<h2>
|
||||||
<span>$</span>
|
<span>$</span>
|
||||||
<span>12,345,678</span>
|
<span>{cmsData?.data.chart.missedAmount ?? 1672}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<h2 className="capitalize text-2xl font-bold lg:text-3xl">
|
<h2 className="capitalize text-2xl font-bold lg:text-3xl">
|
||||||
total tender amount you missed today
|
total tender amount you missed today
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<ApexChart />
|
<ApexChart cmsData={cmsData?.data} />
|
||||||
</div>
|
</div>
|
||||||
<div className="m-auto w-xs lg:m-0 lg:w-auto -z-20 relative flex justify-center h-[650px] lg:h-[750px]">
|
<div className="m-auto w-xs lg:m-0 lg:w-auto -z-20 relative flex justify-center h-[650px] lg:h-[750px]">
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export default async function RootLayout({
|
|||||||
|
|
||||||
<FooterForm />
|
<FooterForm />
|
||||||
|
|
||||||
<div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-96 ">
|
<div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-80 ">
|
||||||
<div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2">
|
<div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2">
|
||||||
<div className="flex flex-col gap-10">
|
<div className="flex flex-col gap-10">
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default function Home() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ul className="flex flex-col gap-4 lg:flex-row lg:justify-between">
|
<ul className="flex flex-col gap-4 lg:flex-row lg:justify-between">
|
||||||
<li className="flex flex-col gap-4 lg:w-80">
|
<li className="flex flex-col lg:w-80 gap-4 transition duration-500 hover:shadow-2xl p-5 rounded-2xl hover:transform hover:scale-110">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Image
|
<Image
|
||||||
src={"/clock.svg"}
|
src={"/clock.svg"}
|
||||||
@@ -139,10 +139,10 @@ export default function Home() {
|
|||||||
monitor the market and prepare high-quality bids.
|
monitor the market and prepare high-quality bids.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex flex-col gap-4 lg:w-80">
|
<li className="flex flex-col gap-4 lg:w-80 transition duration-500 hover:shadow-2xl p-5 rounded-2xl hover:transform hover:scale-110">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Image
|
<Image
|
||||||
src={"/clipboard.svg"}
|
src={"/file.svg"}
|
||||||
alt="clipboard icon"
|
alt="clipboard icon"
|
||||||
width={36}
|
width={36}
|
||||||
height={36}
|
height={36}
|
||||||
@@ -154,7 +154,7 @@ export default function Home() {
|
|||||||
corporations with dedicated tender departments.
|
corporations with dedicated tender departments.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex flex-col gap-4 lg:w-80">
|
<li className="flex flex-col gap-4 lg:w-80 transition duration-500 hover:shadow-2xl p-5 rounded-2xl hover:transform hover:scale-110">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Image
|
<Image
|
||||||
src={"/arrow-up.svg"}
|
src={"/arrow-up.svg"}
|
||||||
@@ -176,7 +176,7 @@ export default function Home() {
|
|||||||
The Opplens Advantage
|
The Opplens Advantage
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-col gap-10 lg:flex-row">
|
<div className="flex flex-col gap-10 lg:flex-row">
|
||||||
<article className="flex flex-col gap-7 bg-[#FFF7EB] p-10 rounded-3xl lg:pb-20">
|
<article className="flex flex-col gap-7 bg-[#FFF7EB] p-10 rounded-3xl lg:pb-20 hover:shadow-2xl hover:transform hover:scale-110 hover:border border-[#ffdca7] transition duration-500">
|
||||||
<Image
|
<Image
|
||||||
src={"people.svg"}
|
src={"people.svg"}
|
||||||
width={48}
|
width={48}
|
||||||
@@ -192,7 +192,7 @@ export default function Home() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<article className="flex flex-col gap-7 bg-[#E5EFFF] p-10 rounded-3xl lg:pb-20">
|
<article className="flex flex-col gap-7 bg-[#E5EFFF] p-10 rounded-3xl lg:pb-20 hover:shadow-2xl hover:transform hover:scale-110 hover:border border-(--primary) transition duration-500">
|
||||||
<Image
|
<Image
|
||||||
src={"shield-tick.svg"}
|
src={"shield-tick.svg"}
|
||||||
width={48}
|
width={48}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { CmsResponse } from "@/types/TCms";
|
||||||
|
import React, { createContext, ReactNode, useContext } from "react";
|
||||||
|
|
||||||
|
interface CmsContextType {
|
||||||
|
cmsData: CmsResponse | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CmsContext = createContext<CmsContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
interface CmsProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
cmsData: CmsResponse | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CmsProvider: React.FC<CmsProviderProps> = ({
|
||||||
|
children,
|
||||||
|
cmsData,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<CmsContext.Provider value={{ cmsData }}>{children}</CmsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCms = (): CmsContextType => {
|
||||||
|
const context = useContext(CmsContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useCms must be used within a CmsProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function fetchCmsData(
|
||||||
|
id: string,
|
||||||
|
cmsService: { getCms: (id: string) => Promise<CmsResponse> }
|
||||||
|
): Promise<CmsResponse | null> {
|
||||||
|
try {
|
||||||
|
const cmsData = await cmsService.getCms(id);
|
||||||
|
return cmsData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching CMS data:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import api from "@/service/api";
|
||||||
|
import { CmsResponse } from "@/types/TCms";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
export const cmsService = {
|
||||||
|
getCms: async (id: string): Promise<CmsResponse> => {
|
||||||
|
try {
|
||||||
|
return (await api.get(`cms/${id}`)).data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error fetching CMS data:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const useGetCms = (pageId: string) => {
|
||||||
|
const queryKey = useMemo(() => ["cms", pageId], [pageId]);
|
||||||
|
return useQuery({
|
||||||
|
queryKey,
|
||||||
|
queryFn: () => cmsService.getCms(pageId),
|
||||||
|
});
|
||||||
|
};
|
||||||
Generated
+27
@@ -8,6 +8,7 @@
|
|||||||
"name": "tm-landing",
|
"name": "tm-landing",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.90.7",
|
||||||
"apexcharts": "^5.3.5",
|
"apexcharts": "^5.3.5",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"next": "15.5.5",
|
"next": "15.5.5",
|
||||||
@@ -1015,6 +1016,32 @@
|
|||||||
"tailwindcss": "4.1.14"
|
"tailwindcss": "4.1.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/query-core": {
|
||||||
|
"version": "5.90.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.90.7.tgz",
|
||||||
|
"integrity": "sha512-6PN65csiuTNfBMXqQUxQhCNdtm1rV+9kC9YwWAIKcaxAauq3Wu7p18j3gQY3YIBJU70jT/wzCCZ2uqto/vQgiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/react-query": {
|
||||||
|
"version": "5.90.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.90.7.tgz",
|
||||||
|
"integrity": "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/query-core": "5.90.7"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.21",
|
"version": "20.19.21",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.21.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.21.tgz",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.90.7",
|
||||||
"apexcharts": "^5.3.5",
|
"apexcharts": "^5.3.5",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"next": "15.5.5",
|
"next": "15.5.5",
|
||||||
|
|||||||
+4
-1
@@ -1 +1,4 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
<svg width="36" height="43" viewBox="0 0 36 43" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.1429 21.5H24.8571H11.1429ZM11.1429 30.1667H24.8571H11.1429ZM29.4286 41H6.57143C5.35901 41 4.19625 40.5435 3.33894 39.7308C2.48163 38.9181 2 37.8159 2 36.6667V6.33333C2 5.18406 2.48163 4.08186 3.33894 3.2692C4.19625 2.45655 5.35901 2 6.57143 2H19.3394C19.9456 2.00012 20.5269 2.22848 20.9554 2.63483L33.3303 14.3652C33.759 14.7714 33.9999 15.3224 34 15.897V36.6667C34 37.8159 33.5184 38.9181 32.6611 39.7308C31.8038 40.5435 30.641 41 29.4286 41Z" fill="#24A6B4" fill-opacity="0.2"/>
|
||||||
|
<path d="M11.1429 21.5H24.8571M11.1429 30.1667H24.8571M29.4286 41H6.57143C5.35901 41 4.19625 40.5435 3.33894 39.7308C2.48163 38.9181 2 37.8159 2 36.6667V6.33333C2 5.18406 2.48163 4.08186 3.33894 3.2692C4.19625 2.45655 5.35901 2 6.57143 2H19.3394C19.9456 2.00012 20.5269 2.22848 20.9554 2.63483L33.3303 14.3652C33.759 14.7714 33.9999 15.3224 34 15.897V36.6667C34 37.8159 33.5184 38.9181 32.6611 39.7308C31.8038 40.5435 30.641 41 29.4286 41Z" stroke="#24A6B4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 11 MiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,72 @@
|
|||||||
|
export interface CmsResponse {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
data: CmsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsData {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
hero: CmsHero;
|
||||||
|
chart: CmsChart;
|
||||||
|
features: CmsSectionWithCards;
|
||||||
|
challenges: CmsSectionWithCards;
|
||||||
|
advantages: CmsSectionWithCards;
|
||||||
|
contact: CmsContact;
|
||||||
|
footer: CmsFooter;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsHero {
|
||||||
|
Title: string;
|
||||||
|
Description: string;
|
||||||
|
ButtonText: string;
|
||||||
|
ButtonLink: string;
|
||||||
|
GifFile: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsChart {
|
||||||
|
title: string;
|
||||||
|
missedAmount: string;
|
||||||
|
data: CmsChartDataItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsChartDataItem {
|
||||||
|
key: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsSectionWithCards {
|
||||||
|
Title: string;
|
||||||
|
Description: string;
|
||||||
|
Cards: CmsCard[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsCard {
|
||||||
|
Icon: string;
|
||||||
|
Title: string;
|
||||||
|
Description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsContact {
|
||||||
|
Title: string;
|
||||||
|
Description: string;
|
||||||
|
SubmitButtonText: string;
|
||||||
|
Fields: CmsContactField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsContactField {
|
||||||
|
Name: string;
|
||||||
|
Label: string;
|
||||||
|
Placeholder: string;
|
||||||
|
Required: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsFooter {
|
||||||
|
Email: string;
|
||||||
|
Phone: string;
|
||||||
|
Location: string;
|
||||||
|
Tagline: string;
|
||||||
|
Copyright: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user