pricing page and page speed test updated

This commit is contained in:
Alaguraj0361 2025-09-27 13:36:36 +05:30
parent eecffff110
commit cad40a36d5
9 changed files with 829 additions and 2 deletions

View File

@ -0,0 +1,39 @@
import ComponentsPricingTableToggle from '@/components/pricing-table/components-pricing-table-toggle';
import IconArrowLeft from '@/components/icon/icon-arrow-left';
import PanelCodeHighlight from '@/components/panel-code-highlight';
import { Metadata } from 'next';
import Link from 'next/link';
import React from 'react';
export const metadata: Metadata = {
title: 'Pricing Table',
};
const PricingTable = () => {
return (
<div>
<ul className="flex space-x-2 rtl:space-x-reverse">
<li>
<Link href="/" className="text-primary hover:underline">
Dashboard
</Link>
</li>
<li className="before:content-['/'] ltr:before:mr-2 rtl:before:ml-2">
<span>Pricing Table</span>
</li>
</ul>
<div className="space-y-8 pt-5">
{/* Basic */}
{/* Toggle */}
<ComponentsPricingTableToggle />
{/* Animated */}
</div>
</div>
);
};
export default PricingTable;

View File

@ -0,0 +1,268 @@
'use client';
import { useState } from 'react';
import axios from 'axios';
import {
Treemap,
Tooltip as ReTooltip,
ResponsiveContainer
} from 'recharts';
export default function Home() {
const [url, setUrl] = useState('');
const [loading, setLoading] = useState(false);
const [reports, setReports] = useState<any>(null);
const [error, setError] = useState('');
const [activeTab, setActiveTab] = useState<'mobile' | 'desktop'>('mobile');
// --- Helper to parse estimated savings like "1.2 s" or "1200 ms" ---
function parseSavings(value: string): number {
if (!value) return 0;
const val = parseFloat(value);
if (value.toLowerCase().includes('ms')) return val;
if (value.toLowerCase().includes('s')) return val * 1000;
return val;
}
const handleAudit = async () => {
if (!url) return setError('Enter a URL');
setLoading(true);
setError('');
setReports(null);
try {
const { data } = await axios.post('https://api.crawlerx.co/api/lighthouse/audit', { url });
// normalize the results
const normalizedResults: any = {};
['mobile', 'desktop'].forEach((tab) => {
const tabData = data.results?.[tab] || {};
normalizedResults[tab] = {
report: {
scores: tabData.scores || {},
metrics: tabData.metrics || {},
opportunities: Array.isArray(tabData.opportunities) ? tabData.opportunities : [],
diagnostics: tabData.diagnostics || {},
screenshot: tabData.screenshot || '',
},
};
});
setReports(normalizedResults);
setActiveTab('mobile');
} catch (err: any) {
setError(err.response?.data?.message || err.message || 'Error');
} finally {
setLoading(false);
}
};
const renderScoreCircle = (score: number) => {
const color =
score >= 90 ? 'text-green-500 border-green-500' :
score >= 50 ? 'text-yellow-500 border-yellow-500' :
'text-red-500 border-red-500';
return (
<div className={`w-20 h-20 flex items-center justify-center rounded-full border-4 ${color}`}>
<span className="text-lg font-bold">{score}</span>
</div>
);
};
const renderMetrics = (metrics: any = {}) => (
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 mb-6">
{Object.entries(metrics).map(([key, value]) => (
<div key={key} className="bg-white p-4 rounded-lg shadow text-center">
<p className="text-sm font-medium text-gray-600">
{key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}
</p>
<p className="mt-2 font-semibold text-gray-800">{value ?? '-'}</p>
</div>
))}
</div>
);
const renderOpportunities = (opportunities: any[] = []) => (
<div className="bg-white rounded-lg shadow p-4 mb-6">
<h3 className="text-lg font-semibold mb-4">Opportunities</h3>
{opportunities.length === 0 && <p className="text-gray-500">No suggestions available.</p>}
{opportunities.map((op, idx) => (
<div key={idx} className="mb-3 border-b last:border-b-0 pb-2">
<p className="font-medium">{op.title ?? '-'}</p>
<p className="text-gray-500 text-sm">{op.description ?? '-'}</p>
{op.estimatedSavings && (
<p className="text-xs text-blue-600">Estimated Savings: {op.estimatedSavings}</p>
)}
</div>
))}
</div>
);
const renderTreeMap = (opportunities: any[] = []) => {
if (opportunities.length === 0) return null;
const data = opportunities.map(op => ({
name: op.title || 'Untitled',
size: parseSavings(op.estimatedSavings)
}));
return (
<div className="bg-white rounded-lg shadow p-4 mb-6">
<h3 className="text-lg font-semibold mb-4">Opportunities Tree Map</h3>
<div style={{ width: '100%', height: 400 }}>
<ResponsiveContainer>
<Treemap
data={data}
dataKey="size"
nameKey="name"
stroke="#fff"
fill="#3182ce"
>
<ReTooltip
content={({ payload }) => {
if (!payload || payload.length === 0) return null;
const item = payload[0].payload;
return (
<div className="bg-white text-gray-800 p-2 rounded shadow">
<p className="font-semibold">{item.name}</p>
<p>{item.size} ms estimated savings</p>
</div>
);
}}
/>
</Treemap>
</ResponsiveContainer>
</div>
</div>
);
};
const renderDiagnostics = (diagnostics: any = {}) => {
const keys = Object.keys(diagnostics);
return (
<div className="bg-white rounded-lg shadow p-4 mb-6">
<h3 className="text-lg font-semibold mb-4">Diagnostics</h3>
{keys.length === 0 && <p className="text-gray-500">No diagnostics available.</p>}
<div className="divide-y divide-gray-200">
{keys.map((key) => (
<div key={key} className="py-3 flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-700">
{key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}
</p>
{key === 'http2' && (
<p className="text-xs text-gray-500">
All resources should be served via HTTP/2 for better performance.
</p>
)}
</div>
<span
className={`px-2 py-1 text-xs font-bold rounded ${
diagnostics[key] ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-600'
}`}
>
{diagnostics[key] ? 'Pass' : 'Fail'}
</span>
</div>
))}
</div>
</div>
);
};
const currentReport = reports?.[activeTab]?.report || {
scores: {},
metrics: {},
opportunities: [],
diagnostics: {},
screenshot: '',
};
return (
<div className="min-h-screen bg-gray-50 flex flex-col items-center p-4 sm:p-8">
<h1 className="text-2xl sm:text-3xl font-bold mb-6 text-gray-800 text-center">
Lighthouse PageSpeed Audit
</h1>
{/* Input */}
<div className="flex flex-col sm:flex-row gap-4 w-full max-w-xl mb-4">
<input
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="Enter URL"
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleAudit}
disabled={loading}
className={`px-4 py-2 rounded-lg font-semibold text-white shadow-md transition ${
loading ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'
}`}
>
{loading ? 'Auditing...' : 'Run Audit'}
</button>
</div>
{error && <p className="text-red-500 mt-2 text-center">{error}</p>}
{reports && (
<div className="w-full max-w-6xl mt-8">
{/* Tabs */}
<div className="flex gap-4 mb-4">
<button
className={`px-4 py-2 rounded-t-lg font-semibold ${activeTab === 'mobile' ? 'bg-blue-600 text-white' : 'bg-gray-200'}`}
onClick={() => setActiveTab('mobile')}
>
Mobile
</button>
<button
className={`px-4 py-2 rounded-t-lg font-semibold ${activeTab === 'desktop' ? 'bg-green-600 text-white' : 'bg-gray-200'}`}
onClick={() => setActiveTab('desktop')}
>
Desktop
</button>
</div>
{/* Top Scores */}
<div className="flex flex-wrap gap-6 justify-center mb-6">
{['performance', 'accessibility', 'bestPractices', 'seo', 'pwa'].map((key) => {
const score = currentReport.scores?.[key];
if (score === undefined || score === null) return null;
return (
<div key={key} className="text-center">
<p className="text-sm font-medium">{key.charAt(0).toUpperCase() + key.slice(1)}</p>
{renderScoreCircle(score)}
</div>
);
})}
</div>
{/* Metrics */}
{renderMetrics(currentReport.metrics)}
{/* Opportunities */}
{renderOpportunities(currentReport.opportunities)}
{/* Tree Map */}
{renderTreeMap(currentReport.opportunities)}
{/* Diagnostics */}
{renderDiagnostics(currentReport.diagnostics)}
{/* Screenshot */}
{currentReport.screenshot && (
<div className="flex justify-center mb-6">
<img
src={currentReport.screenshot}
alt={`${activeTab} screenshot`}
className="border shadow rounded max-w-full h-auto"
/>
</div>
)}
</div>
)}
</div>
);
}

View File

@ -13,7 +13,7 @@ export default function ForgotPasswordForm() {
setLoading(true);
setMessage("");
try {
const res = await axios.post("http://localhost:3020/api/auth/forgot-password", { email });
const res = await axios.post("https://api.crawlerx.co/api/auth/forgot-password", { email });
setMessage("✅ Weve emailed you a reset code / link. Enter it below.");
} catch (err: any) {
console.error(err);

View File

@ -20,7 +20,7 @@ export default function ResetPasswordForm() {
setLoading(true);
setMessage("");
try {
await axios.post("http://localhost:3020/api/auth/reset-password", {
await axios.post("https://api.crawlerx.co/api/auth/reset-password", {
email,
token, // ✅ use token from URL
newPassword,

21
components/highlight.tsx Normal file
View File

@ -0,0 +1,21 @@
import 'highlight.js/styles/monokai-sublime.css';
import hightlight from 'highlight.js';
import { PropsWithChildren, useEffect, useRef } from 'react';
const CodeHighlight = ({ children }: PropsWithChildren) => {
const highlightElement = useRef<any>(null);
useEffect(() => {
if (highlightElement?.current) {
hightlight.highlightElement(highlightElement.current.querySelector('pre'));
}
}, []);
return (
<div ref={highlightElement} className="highlight-el">
{children}
</div>
);
};
export default CodeHighlight;

View File

@ -0,0 +1,37 @@
'use client';
import CodeHighlight from './highlight';
import IconCode from '@/components/icon/icon-code';
import React, { useState, ReactNode } from 'react';
interface PanelCodeHighlightProps {
children: ReactNode;
title?: string;
codeHighlight?: string;
id?: string;
className?: string;
}
const PanelCodeHighlight = ({ children, title, codeHighlight, id, className = '' }: PanelCodeHighlightProps) => {
const [toggleCode, setToggleCode] = useState(false);
return (
<div className={`panel ${className}`} id={id}>
<div className="mb-5 flex items-center justify-between">
<h5 className="text-lg font-semibold dark:text-white-light">{title}</h5>
<button type="button" className="font-semibold hover:text-gray-400 dark:text-gray-400 dark:hover:text-gray-600" onClick={() => setToggleCode(!toggleCode)}>
<span className="flex items-center">
<IconCode className="me-2" />
Code
</span>
</button>
</div>
{children}
{toggleCode && (
<CodeHighlight>
<pre className="language-xml">{codeHighlight}</pre>
</CodeHighlight>
)}
</div>
);
};
export default PanelCodeHighlight;

View File

@ -0,0 +1,168 @@
'use client';
import React, { useState } from 'react';
import { loadStripe, Stripe } from '@stripe/stripe-js';
import axios from 'axios';
interface Plan {
name: string;
desc: string;
monthly: number;
yearly: number;
features: string[];
btnStyle: string;
popular?: boolean;
planId: string;
}
// ⚡ Lazy load Stripe to avoid undefined.match errors
let stripePromise: Promise<Stripe | null> | null = null;
const getStripe = () => {
if (!stripePromise) {
const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
if (!key) throw new Error('Stripe publishable key is not defined!');
stripePromise = loadStripe(key);
}
return stripePromise;
};
const ComponentsPricingTableToggle: React.FC = () => {
const [yearlyPrice, setYearlyPrice] = useState(false);
const plans: Plan[] = [
{
name: 'Starter SEO Crawl',
desc: 'Perfect for small websites. Crawl up to 5,000 pages per month.',
monthly: 19,
yearly: 19 * 12 * 0.8,
features: ['5,000 Pages/Month', 'Basic Crawl Reports', 'Email Support'],
btnStyle: 'btn-dark',
planId: 'starter-seo-crawl',
},
{
name: 'Pro SEO Crawl',
desc: 'Best for medium websites. Crawl up to 50,000 pages per month.',
monthly: 49,
yearly: 49 * 12 * 0.8,
features: ['50,000 Pages/Month', 'Advanced Crawl Reports', 'Priority Support'],
btnStyle: 'btn-primary',
popular: true,
planId: 'pro-seo-crawl',
},
{
name: 'Enterprise SEO Crawl',
desc: 'For large-scale websites. Unlimited crawling and dedicated support.',
monthly: 199,
yearly: 199 * 12 * 0.8,
features: ['Unlimited Pages', 'Custom Integrations', 'Dedicated Account Manager'],
btnStyle: 'btn-dark',
planId: 'enterprise-seo-crawl',
},
];
// 🔹 Stripe Checkout handler
const handleBuyNow = async (plan: Plan) => {
try {
const stripe = await getStripe();
if (!stripe) return;
// 🔹 Call backend API to create Checkout Session
const { data } = await axios.post(
'https://api.crawlerx.co/api/payment/create-intent',
{
planId: plan.planId,
price: yearlyPrice ? plan.yearly : plan.monthly,
billing: yearlyPrice ? 'yearly' : 'monthly',
}
);
if (!data.sessionId) {
console.error('No sessionId returned from backend!');
return;
}
// 🔹 Redirect to Stripe Checkout
const { error } = await stripe.redirectToCheckout({ sessionId: data.sessionId });
if (error) console.error('Stripe redirect error:', error.message);
} catch (err: any) {
console.error('Error creating checkout session:', err.response?.data || err.message);
}
};
return (
<div className="mb-5 panel">
<div className="mx-auto max-w-[320px] dark:text-white-dark md:max-w-[1140px]">
{/* Toggle */}
<div className="mt-5 flex justify-center space-x-4 text-center text-base font-semibold md:mt-10">
<span className={`${!yearlyPrice ? 'text-primary' : 'text-white-dark'}`}>Monthly</span>
<label className="relative h-6 w-12">
<input
type="checkbox"
className="custom_switch peer absolute top-0 z-10 h-full w-full cursor-pointer opacity-0"
onChange={() => setYearlyPrice(!yearlyPrice)}
/>
<span className="outline_checkbox bg-icon block h-full rounded-full border-2 border-[#ebedf2]
before:absolute before:bottom-1 before:h-4 before:w-4 before:rounded-full before:bg-[#ebedf2]
before:transition-all before:duration-300 peer-checked:border-primary peer-checked:before:bg-primary
ltr:before:left-1 ltr:peer-checked:before:left-7">
</span>
</label>
<span className={`relative ${yearlyPrice ? 'text-primary' : ' text-white-dark'} `}>
Yearly
<span className="badge absolute my-auto hidden whitespace-nowrap rounded-full bg-success ltr:left-full ltr:ml-2">
20% Off
</span>
</span>
</div>
{/* Plans */}
<div className="mt-5 space-y-4 text-white-dark md:mt-16 md:flex md:space-y-0">
{plans.map((plan, idx) => (
<div
key={idx}
className="relative rounded-md border border-white-light p-4 transition-all duration-300
hover:shadow-[0_0_15px_1px_rgba(113,106,202,0.20)] dark:border-[#1b2e4b] lg:p-9"
>
{plan.popular && (
<div className="absolute inset-x-0 top-0 flex h-10 items-center justify-center rounded-t-md bg-primary text-base text-white md:-top-[30px]">
Most Popular
</div>
)}
<h3 className="mb-5 text-xl font-semibold text-black dark:text-white-light">{plan.name}</h3>
<p>{plan.desc}</p>
<div className="my-7 p-2.5 text-center text-lg">
<strong
className={`text-xl ${
plan.popular ? 'text-primary lg:text-4xl'
: 'text-[#3b3f5c] dark:text-white-light lg:text-3xl'
}`}
>
${yearlyPrice ? plan.yearly.toFixed(0) : plan.monthly}
</strong>{' '}
/ {yearlyPrice ? 'yearly' : 'monthly'}
</div>
<div className="mb-6">
<strong className="mb-3 inline-block text-[15px] text-black dark:text-white-light">
{plan.name} Features
</strong>
<ul className="space-y-3">
{plan.features.map((f, i) => (
<li key={i}>{f}</li>
))}
</ul>
</div>
<button
type="button"
className={`btn ${plan.btnStyle} w-full`}
onClick={() => handleBuyNow(plan)}
>
Buy Now
</button>
</div>
))}
</div>
</div>
</div>
);
};
export default ComponentsPricingTableToggle;

291
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@emotion/react": "^11.10.6",
"@headlessui/react": "^2.1.2",
"@reduxjs/toolkit": "^2.2.7",
"@stripe/stripe-js": "^7.9.0",
"@tippyjs/react": "^4.2.6",
"@types/node": "^22.4.0",
"@types/react": "18.3.10",
@ -19,6 +20,7 @@
"eslint": "8.57.0",
"eslint-config-next": "14.2.13",
"fast-xml-parser": "^5.2.5",
"highlight.js": "^11.11.1",
"i18next": "^23.13.0",
"next": "14.2.13",
"ni18n": "^1.0.5",
@ -29,6 +31,7 @@
"react-perfect-scrollbar": "^1.5.8",
"react-popper": "^2.3.0",
"react-redux": "^9.1.2",
"recharts": "^3.2.1",
"typescript": "^5.3.3",
"universal-cookie": "^7.2.0",
"yup": "^1.4.0"
@ -929,6 +932,15 @@
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz",
"integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA=="
},
"node_modules/@stripe/stripe-js": {
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz",
"integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==",
"license": "MIT",
"engines": {
"node": ">=12.16"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@ -1011,6 +1023,69 @@
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -2153,6 +2228,127 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -2222,6 +2418,12 @@
}
}
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/deep-equal": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
@ -2542,6 +2744,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-toolkit": {
"version": "1.39.10",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
"integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==",
"license": "MIT",
"workspaces": [
"docs",
"benchmarks"
]
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@ -3124,6 +3336,12 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -3613,6 +3831,15 @@
"node": ">= 0.4"
}
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -3732,6 +3959,15 @@
"node": ">= 0.4"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@ -5372,6 +5608,33 @@
"node": ">=8.10.0"
}
},
"node_modules/recharts": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz",
"integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==",
"license": "MIT",
"dependencies": {
"@reduxjs/toolkit": "1.x.x || 2.x.x",
"clsx": "^2.1.1",
"decimal.js-light": "^2.5.1",
"es-toolkit": "^1.39.3",
"eventemitter3": "^5.0.1",
"immer": "^10.1.1",
"react-redux": "8.x.x || 9.x.x",
"reselect": "5.1.1",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.2.2",
"victory-vendor": "^37.0.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
@ -6059,6 +6322,12 @@
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tippy.js": {
"version": "6.3.7",
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
@ -6312,6 +6581,28 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/victory-vendor": {
"version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
"license": "MIT AND ISC",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",

View File

@ -12,6 +12,7 @@
"@emotion/react": "^11.10.6",
"@headlessui/react": "^2.1.2",
"@reduxjs/toolkit": "^2.2.7",
"@stripe/stripe-js": "^7.9.0",
"@tippyjs/react": "^4.2.6",
"@types/node": "^22.4.0",
"@types/react": "18.3.10",
@ -20,6 +21,7 @@
"eslint": "8.57.0",
"eslint-config-next": "14.2.13",
"fast-xml-parser": "^5.2.5",
"highlight.js": "^11.11.1",
"i18next": "^23.13.0",
"next": "14.2.13",
"ni18n": "^1.0.5",
@ -30,6 +32,7 @@
"react-perfect-scrollbar": "^1.5.8",
"react-popper": "^2.3.0",
"react-redux": "^9.1.2",
"recharts": "^3.2.1",
"typescript": "^5.3.3",
"universal-cookie": "^7.2.0",
"yup": "^1.4.0"