Cleanup
This commit is contained in:
@@ -12,7 +12,6 @@ export const metadata: Metadata = {
|
|||||||
title: "Ember | E-Commerce Management Platform",
|
title: "Ember | E-Commerce Management Platform",
|
||||||
description: "Transform your e-commerce experience with Ember, a comprehensive platform that integrates secure cryptocurrency payments, efficient order management, and advanced analytics to help you optimize performance, boost sales, and expand your customer base",
|
description: "Transform your e-commerce experience with Ember, a comprehensive platform that integrates secure cryptocurrency payments, efficient order management, and advanced analytics to help you optimize performance, boost sales, and expand your customer base",
|
||||||
keywords: "e-commerce, marketplace, cryptocurrency payments, order management, vendor platform, online business",
|
keywords: "e-commerce, marketplace, cryptocurrency payments, order management, vendor platform, online business",
|
||||||
metadataBase: new URL("https://embermarket.app"),
|
|
||||||
authors: [{ name: "Ember Team" }],
|
authors: [{ name: "Ember Team" }],
|
||||||
creator: "Ember ",
|
creator: "Ember ",
|
||||||
publisher: "Ember",
|
publisher: "Ember",
|
||||||
@@ -38,7 +37,6 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Ember | E-Commerce Management Platform",
|
title: "Ember | E-Commerce Management Platform",
|
||||||
description: "Transform your e-commerce experience with Ember, a comprehensive platform that integrates secure cryptocurrency payments, efficient order management, and advanced analytics to help you optimize performance, boost sales, and expand your customer base",
|
description: "Transform your e-commerce experience with Ember, a comprehensive platform that integrates secure cryptocurrency payments, efficient order management, and advanced analytics to help you optimize performance, boost sales, and expand your customer base",
|
||||||
url: "https://embermarket.app",
|
|
||||||
siteName: "Ember",
|
siteName: "Ember",
|
||||||
locale: "en_GB",
|
locale: "en_GB",
|
||||||
type: "website",
|
type: "website",
|
||||||
@@ -51,12 +49,6 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title: "Ember Market | E-Commerce Management Platform",
|
|
||||||
description: "Streamlined e-commerce management with secure cryptocurrency payments",
|
|
||||||
images: ["/twitter-image.jpg"],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
|
|||||||
@@ -6,16 +6,13 @@ import { Button } from "@/components/ui/button";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { AnimatedStatsSection } from "@/components/animated-stats-section";
|
import { AnimatedStatsSection } from "@/components/animated-stats-section";
|
||||||
|
|
||||||
// Force the page to be dynamically rendered
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
// Constants
|
|
||||||
const PY_20 = 20;
|
const PY_20 = 20;
|
||||||
const PY_32 = 32;
|
const PY_32 = 32;
|
||||||
const PX_6 = 6;
|
const PX_6 = 6;
|
||||||
const PX_10 = 10;
|
const PX_10 = 10;
|
||||||
|
|
||||||
// Format number with commas
|
|
||||||
function formatNumberValue(num: number): string {
|
function formatNumberValue(num: number): string {
|
||||||
return new Intl.NumberFormat().format(Math.round(num));
|
return new Intl.NumberFormat().format(Math.round(num));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Suspense, useEffect } from 'react';
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
|
|
||||||
// Dynamically import the 3D stats component
|
|
||||||
const StatsSection = dynamic(() => import('./StatsSection'), {
|
|
||||||
ssr: false,
|
|
||||||
loading: () => <div className="h-40 w-full bg-gray-900 animate-pulse rounded-lg"></div>
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function ClientStatsWrapper({ stats }: { stats: any }) {
|
|
||||||
// Debug the stats to see what's being passed
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('Stats data received in client component:', stats);
|
|
||||||
}, [stats]);
|
|
||||||
|
|
||||||
// Apply defaults if stats is undefined
|
|
||||||
const safeStats = stats || {
|
|
||||||
totalProducts: 0,
|
|
||||||
totalVendors: 0,
|
|
||||||
totalOrders: 0,
|
|
||||||
totalCustomers: 0,
|
|
||||||
gmv: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<div className="h-40 w-full bg-gray-900 animate-pulse rounded-lg"></div>}>
|
|
||||||
<StatsSection stats={safeStats} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useRef } from 'react';
|
|
||||||
import { Canvas, useFrame } from '@react-three/fiber';
|
|
||||||
import { OrbitControls, PerspectiveCamera, useTexture } from '@react-three/drei';
|
|
||||||
import * as THREE from 'three';
|
|
||||||
|
|
||||||
function RotatingCube() {
|
|
||||||
const meshRef = useRef<THREE.Mesh>(null);
|
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
useFrame((state, delta) => {
|
|
||||||
if (meshRef.current) {
|
|
||||||
meshRef.current.rotation.x += delta * 0.2;
|
|
||||||
meshRef.current.rotation.y += delta * 0.3;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mesh ref={meshRef} position={[0, 0, 0]} castShadow receiveShadow>
|
|
||||||
<boxGeometry args={[2, 2, 2]} />
|
|
||||||
<meshStandardMaterial
|
|
||||||
color="#D53F8C"
|
|
||||||
metalness={0.6}
|
|
||||||
roughness={0.2}
|
|
||||||
emissive="#300020"
|
|
||||||
emissiveIntensity={0.3}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FloatingGlobe() {
|
|
||||||
const meshRef = useRef<THREE.Mesh>(null);
|
|
||||||
|
|
||||||
useFrame((state, delta) => {
|
|
||||||
if (meshRef.current) {
|
|
||||||
meshRef.current.rotation.y += delta * 0.1;
|
|
||||||
meshRef.current.position.y = Math.sin(state.clock.elapsedTime * 0.5) * 0.2;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mesh ref={meshRef} position={[0, 0, 0]} castShadow>
|
|
||||||
<sphereGeometry args={[1.5, 64, 64]} />
|
|
||||||
<meshStandardMaterial
|
|
||||||
color="#111111"
|
|
||||||
metalness={0.8}
|
|
||||||
roughness={0.1}
|
|
||||||
emissive="#D53F8C"
|
|
||||||
emissiveIntensity={0.2}
|
|
||||||
wireframe={true}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HeroModel() {
|
|
||||||
return (
|
|
||||||
<div className="w-full h-[300px] md:h-[400px]">
|
|
||||||
<Canvas shadows dpr={[1, 2]}>
|
|
||||||
<ambientLight intensity={0.5} />
|
|
||||||
<directionalLight
|
|
||||||
position={[10, 10, 5]}
|
|
||||||
intensity={1}
|
|
||||||
castShadow
|
|
||||||
shadow-mapSize-width={1024}
|
|
||||||
shadow-mapSize-height={1024}
|
|
||||||
/>
|
|
||||||
<pointLight position={[-10, -10, -10]} color="#D53F8C" intensity={1} />
|
|
||||||
|
|
||||||
<FloatingGlobe />
|
|
||||||
|
|
||||||
<PerspectiveCamera makeDefault position={[0, 0, 6]} fov={40} />
|
|
||||||
<OrbitControls
|
|
||||||
enableZoom={false}
|
|
||||||
enablePan={false}
|
|
||||||
rotateSpeed={0.5}
|
|
||||||
autoRotate
|
|
||||||
autoRotateSpeed={0.5}
|
|
||||||
/>
|
|
||||||
</Canvas>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { Suspense } from 'react';
|
|
||||||
import { ArrowRight } from 'lucide-react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
|
|
||||||
// Dynamically import the 3D component to avoid SSR issues
|
|
||||||
const HeroModel = dynamic(() => import('@/components/3d/HeroModel'), {
|
|
||||||
ssr: false,
|
|
||||||
loading: () => <div className="w-full h-[300px] md:h-[400px] flex items-center justify-center">
|
|
||||||
<div className="animate-pulse text-pink-500">Loading 3D model...</div>
|
|
||||||
</div>
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function HeroSection() {
|
|
||||||
return (
|
|
||||||
<section className="flex-1 py-20 md:py-32 px-6 md:px-10 flex flex-col items-center text-center space-y-10 bg-black">
|
|
||||||
<div className="space-y-4 max-w-3xl">
|
|
||||||
<h1 className="text-4xl md:text-6xl font-bold tracking-tighter">
|
|
||||||
Streamlined E-commerce <span className="text-[#D53F8C]">Management</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-xl md:text-2xl text-gray-400 max-w-2xl mx-auto">
|
|
||||||
Ember provides everything you need to manage your e-commerce business efficiently in one place, with secure cryptocurrency payments.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 3D Model */}
|
|
||||||
<div className="w-full max-w-xl my-8">
|
|
||||||
<Suspense fallback={<div className="h-[300px] w-full bg-gray-900 animate-pulse rounded-lg"></div>}>
|
|
||||||
<HeroModel />
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
|
||||||
<Link href="/dashboard">
|
|
||||||
<Button size="lg" className="gap-2 bg-[#D53F8C] hover:bg-[#B83280] text-white border-0">
|
|
||||||
Go to Dashboard
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/auth/register">
|
|
||||||
<Button size="lg" variant="outline" className="border-gray-800 text-white hover:bg-gray-900">
|
|
||||||
Create Account
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useRef } from 'react';
|
|
||||||
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion';
|
|
||||||
|
|
||||||
interface StatCardProps {
|
|
||||||
title: string;
|
|
||||||
value: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StatCard({ title, value, className = "" }: StatCardProps) {
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
|
||||||
|
|
||||||
const x = useMotionValue(0);
|
|
||||||
const y = useMotionValue(0);
|
|
||||||
|
|
||||||
// Spring animations for smoother movement
|
|
||||||
const springX = useSpring(x, { stiffness: 150, damping: 20 });
|
|
||||||
const springY = useSpring(y, { stiffness: 150, damping: 20 });
|
|
||||||
|
|
||||||
// Transform mouse movement to rotation values
|
|
||||||
const rotateXOutput = useTransform(springY, [-0.5, 0.5], ["7deg", "-7deg"]);
|
|
||||||
const rotateYOutput = useTransform(springX, [-0.5, 0.5], ["-7deg", "7deg"]);
|
|
||||||
|
|
||||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
if (!cardRef.current) return;
|
|
||||||
|
|
||||||
const rect = cardRef.current.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Calculate mouse position relative to card (0-1)
|
|
||||||
const mouseX = (e.clientX - rect.left) / rect.width;
|
|
||||||
const mouseY = (e.clientY - rect.top) / rect.height;
|
|
||||||
|
|
||||||
// Convert to -0.5 to 0.5 range
|
|
||||||
x.set(mouseX - 0.5);
|
|
||||||
y.set(mouseY - 0.5);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
setIsHovered(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
// Reset to neutral position when mouse leaves
|
|
||||||
x.set(0);
|
|
||||||
y.set(0);
|
|
||||||
setIsHovered(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
ref={cardRef}
|
|
||||||
className={`relative overflow-hidden rounded-xl p-6 sm:p-8 bg-gradient-to-br from-gray-800/90 to-gray-900/90 border border-gray-700/50 shadow-lg flex flex-col justify-center group ${className}`}
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
style={{
|
|
||||||
rotateX: rotateXOutput,
|
|
||||||
rotateY: rotateYOutput,
|
|
||||||
transformStyle: "preserve-3d",
|
|
||||||
}}
|
|
||||||
whileHover={{ scale: 1.03 }}
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
>
|
|
||||||
{/* Ambient light reflection */}
|
|
||||||
<div
|
|
||||||
className={`absolute inset-0 w-full h-full bg-gradient-to-tr from-pink-500/10 to-purple-500/5 transition-opacity duration-300 ${isHovered ? 'opacity-100' : 'opacity-0'}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Main content with 3D effect */}
|
|
||||||
<div style={{ transform: "translateZ(20px)" }} className="relative z-10">
|
|
||||||
<p className="text-gray-400 text-xs sm:text-sm mb-1 font-medium">{title}</p>
|
|
||||||
<p className="text-2xl sm:text-3xl md:text-4xl font-bold text-white">{value}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Shiny shimmer effect */}
|
|
||||||
<div className="absolute inset-0 rounded-xl overflow-hidden">
|
|
||||||
<div
|
|
||||||
className={`absolute inset-0 w-[200%] bg-gradient-to-r from-transparent via-pink-500/20 to-transparent -translate-x-[100%] animate-shimmer transition-opacity duration-300 ${isHovered ? 'opacity-100' : 'opacity-0'}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import StatCard from './StatCard';
|
|
||||||
|
|
||||||
interface StatsProps {
|
|
||||||
stats: {
|
|
||||||
totalProducts?: number;
|
|
||||||
totalVendors?: number;
|
|
||||||
totalOrders?: number;
|
|
||||||
totalCustomers?: number;
|
|
||||||
gmv?: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatNumberValue(num: number = 0): string {
|
|
||||||
return new Intl.NumberFormat().format(Math.round(num));
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatCurrencyValue(amount: number = 0): string {
|
|
||||||
return new Intl.NumberFormat('en-GB', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'GBP',
|
|
||||||
maximumFractionDigits: 0
|
|
||||||
}).format(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StatsSection({ stats }: StatsProps) {
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('StatsSection rendering with data:', stats);
|
|
||||||
}, [stats]);
|
|
||||||
|
|
||||||
const totalProducts = stats?.totalProducts ?? 243;
|
|
||||||
const totalVendors = stats?.totalVendors ?? 15;
|
|
||||||
const totalOrders = stats?.totalOrders ?? 1289;
|
|
||||||
const totalCustomers = stats?.totalCustomers ?? 756;
|
|
||||||
const gmv = stats?.gmv ?? 38450;
|
|
||||||
|
|
||||||
// Container animation variants
|
|
||||||
const containerVariants = {
|
|
||||||
hidden: { opacity: 0 },
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
|
||||||
staggerChildren: 0.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Item animation variants
|
|
||||||
const itemVariants = {
|
|
||||||
hidden: { y: 20, opacity: 0 },
|
|
||||||
visible: {
|
|
||||||
y: 0,
|
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 260,
|
|
||||||
damping: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="perspective-1000">
|
|
||||||
<motion.div
|
|
||||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5 sm:gap-6"
|
|
||||||
variants={containerVariants}
|
|
||||||
initial="hidden"
|
|
||||||
animate="visible"
|
|
||||||
style={{ perspective: "1000px" }}
|
|
||||||
>
|
|
||||||
<motion.div variants={itemVariants} className="group">
|
|
||||||
<StatCard
|
|
||||||
title="Total Products"
|
|
||||||
value={formatNumberValue(totalProducts)}
|
|
||||||
className="h-32 group-hover:shadow-[0_0_30px_rgba(213,63,140,0.3)] transition-all duration-300"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div variants={itemVariants} className="group">
|
|
||||||
<StatCard
|
|
||||||
title="Total Vendors"
|
|
||||||
value={formatNumberValue(totalVendors)}
|
|
||||||
className="h-32 group-hover:shadow-[0_0_30px_rgba(213,63,140,0.3)] transition-all duration-300"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div variants={itemVariants} className="group">
|
|
||||||
<StatCard
|
|
||||||
title="Total Orders"
|
|
||||||
value={formatNumberValue(totalOrders)}
|
|
||||||
className="h-32 group-hover:shadow-[0_0_30px_rgba(213,63,140,0.3)] transition-all duration-300"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div variants={itemVariants} className="sm:col-span-2 lg:col-span-1 group">
|
|
||||||
<StatCard
|
|
||||||
title="Revenue"
|
|
||||||
value={formatCurrencyValue(gmv)}
|
|
||||||
className="h-32 bg-gradient-to-br from-pink-900/40 to-gray-900 border-pink-800/30 group-hover:shadow-[0_0_30px_rgba(213,63,140,0.4)] transition-all duration-300"
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -553,7 +553,7 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
Chat with Customer {chat.buyerId.slice(-4)}
|
Chat with Customer {chat.buyerId}
|
||||||
</h3>
|
</h3>
|
||||||
{chat.telegramUsername && (
|
{chat.telegramUsername && (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
interface TextTyperProps {
|
|
||||||
text: string;
|
|
||||||
typingSpeed?: number;
|
|
||||||
delay?: number;
|
|
||||||
className?: string;
|
|
||||||
cursor?: boolean;
|
|
||||||
onComplete?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TextTyper({
|
|
||||||
text,
|
|
||||||
typingSpeed = 50,
|
|
||||||
delay = 0,
|
|
||||||
className = "",
|
|
||||||
cursor = true,
|
|
||||||
onComplete
|
|
||||||
}: TextTyperProps) {
|
|
||||||
// Hide component if text is empty
|
|
||||||
if (!text) return null;
|
|
||||||
|
|
||||||
const [displayedText, setDisplayedText] = useState("");
|
|
||||||
const [cursorVisible, setCursorVisible] = useState(true);
|
|
||||||
const [isComplete, setIsComplete] = useState(false);
|
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
const cursorIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Clear any previous text
|
|
||||||
setDisplayedText("");
|
|
||||||
setIsComplete(false);
|
|
||||||
|
|
||||||
// Reset typing when text changes
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start typing after delay
|
|
||||||
const startTypingTimeout = setTimeout(() => {
|
|
||||||
let currentIndex = 0;
|
|
||||||
|
|
||||||
const typeNextChar = () => {
|
|
||||||
if (currentIndex < text.length) {
|
|
||||||
setDisplayedText(text.substring(0, currentIndex + 1));
|
|
||||||
currentIndex++;
|
|
||||||
timeoutRef.current = setTimeout(typeNextChar, typingSpeed);
|
|
||||||
} else {
|
|
||||||
setIsComplete(true);
|
|
||||||
if (onComplete) {
|
|
||||||
onComplete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typeNextChar();
|
|
||||||
}, delay);
|
|
||||||
|
|
||||||
// Store the start timeout
|
|
||||||
timeoutRef.current = startTypingTimeout;
|
|
||||||
|
|
||||||
// Cursor blinking effect
|
|
||||||
if (cursor) {
|
|
||||||
cursorIntervalRef.current = setInterval(() => {
|
|
||||||
setCursorVisible(prev => !prev);
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
}
|
|
||||||
if (cursorIntervalRef.current) {
|
|
||||||
clearInterval(cursorIntervalRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [text, typingSpeed, delay, cursor, onComplete]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={className}>
|
|
||||||
{displayedText}
|
|
||||||
{cursor && !isComplete && cursorVisible && <span className="text-[#D53F8C]">|</span>}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "my-v0-project",
|
"name": "my-v0-project",
|
||||||
"version": "1.1.8",
|
"version": "2.0.0",
|
||||||
"gitCommit": "1.1.8",
|
"gitCommit": "2.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predev": "node scripts/get-git-hash.js",
|
"predev": "node scripts/get-git-hash.js",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "907831d",
|
"commitHash": "e338a5d",
|
||||||
"buildTime": "2025-04-10T00:00:11.756Z"
|
"buildTime": "2025-05-17T16:44:15.028Z"
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -17,16 +17,13 @@ const DEPRECATED_PATTERNS = [
|
|||||||
"from '@/lib/shippingHelper'",
|
"from '@/lib/shippingHelper'",
|
||||||
"from '@/lib/storeHelper'",
|
"from '@/lib/storeHelper'",
|
||||||
"from '@/lib/stats-service'",
|
"from '@/lib/stats-service'",
|
||||||
// Add other deprecated import patterns here
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Run grep to find deprecated imports
|
|
||||||
function findDeprecatedImports() {
|
function findDeprecatedImports() {
|
||||||
console.log('Searching for deprecated imports...\n');
|
console.log('Searching for deprecated imports...\n');
|
||||||
|
|
||||||
DEPRECATED_PATTERNS.forEach(pattern => {
|
DEPRECATED_PATTERNS.forEach(pattern => {
|
||||||
try {
|
try {
|
||||||
// Using grep to find the pattern in all TypeScript/JavaScript files
|
|
||||||
const result = execSync(`grep -r "${pattern}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" .`, { encoding: 'utf8' });
|
const result = execSync(`grep -r "${pattern}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" .`, { encoding: 'utf8' });
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -36,7 +33,6 @@ function findDeprecatedImports() {
|
|||||||
suggestReplacement(pattern);
|
suggestReplacement(pattern);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// grep returns non-zero exit code if no matches found
|
|
||||||
if (error.status !== 1) {
|
if (error.status !== 1) {
|
||||||
console.error(`Error searching for ${pattern}:`, error.message);
|
console.error(`Error searching for ${pattern}:`, error.message);
|
||||||
}
|
}
|
||||||
@@ -45,14 +41,12 @@ function findDeprecatedImports() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function suggestReplacement(pattern) {
|
function suggestReplacement(pattern) {
|
||||||
// Extract the import path
|
|
||||||
const match = pattern.match(/from '([^']+)'/);
|
const match = pattern.match(/from '([^']+)'/);
|
||||||
if (!match) return;
|
if (!match) return;
|
||||||
|
|
||||||
const oldPath = match[1];
|
const oldPath = match[1];
|
||||||
let replacement = '';
|
let replacement = '';
|
||||||
|
|
||||||
// Generate replacement suggestions based on deprecated path
|
|
||||||
switch (oldPath) {
|
switch (oldPath) {
|
||||||
case '@/lib/client-utils':
|
case '@/lib/client-utils':
|
||||||
case '@/lib/client-service':
|
case '@/lib/client-service':
|
||||||
@@ -84,6 +78,3 @@ function suggestReplacement(pattern) {
|
|||||||
|
|
||||||
// Run the search
|
// Run the search
|
||||||
findDeprecatedImports();
|
findDeprecatedImports();
|
||||||
|
|
||||||
// Uncomment the following line to run the remove-old-files.js script
|
|
||||||
// node scripts/remove-old-files.js
|
|
||||||
Reference in New Issue
Block a user