import React, { CSSProperties, useEffect, useState, useCallback } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; interface CountdownProps { targetDate?: string; heading?: string; style?: CSSProperties; digitColor?: string; labelColor?: string; bgColor?: string; } interface TimeLeft { days: number; hours: number; minutes: number; seconds: number; } function getDefaultTargetDate(): string { const d = new Date(); d.setDate(d.getDate() + 30); return d.toISOString().split('T')[0]; } function calcTimeLeft(target: string): TimeLeft { const diff = new Date(target).getTime() - Date.now(); if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0 }; return { days: Math.floor(diff / (1000 * 60 * 60 * 24)), hours: Math.floor((diff / (1000 * 60 * 60)) % 24), minutes: Math.floor((diff / (1000 * 60)) % 60), seconds: Math.floor((diff / 1000) % 60), }; } const DEFAULT_TARGET = getDefaultTargetDate(); export const Countdown: UserComponent = ({ targetDate = DEFAULT_TARGET, heading = 'Coming Soon', style = {}, digitColor = '#ffffff', labelColor = 'rgba(255,255,255,0.7)', bgColor = '#18181b', }) => { const { connectors: { connect, drag }, selected, } = useNode((node) => ({ selected: node.events.selected, })); const [timeLeft, setTimeLeft] = useState(() => calcTimeLeft(targetDate)); useEffect(() => { setTimeLeft(calcTimeLeft(targetDate)); const interval = setInterval(() => { setTimeLeft(calcTimeLeft(targetDate)); }, 1000); return () => clearInterval(interval); }, [targetDate]); const pad = (n: number) => String(n).padStart(2, '0'); const boxStyle: CSSProperties = { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px', minWidth: '80px', }; const digitStyle: CSSProperties = { fontSize: '48px', fontWeight: '700', color: digitColor, lineHeight: '1', fontFamily: 'Inter, sans-serif', }; const unitLabelStyle: CSSProperties = { fontSize: '12px', color: labelColor, textTransform: 'uppercase', letterSpacing: '0.1em', fontFamily: 'Inter, sans-serif', }; const units: Array<{ label: string; value: number }> = [ { label: 'Days', value: timeLeft.days }, { label: 'Hours', value: timeLeft.hours }, { label: 'Minutes', value: timeLeft.minutes }, { label: 'Seconds', value: timeLeft.seconds }, ]; return (
{ if (ref) connect(drag(ref)); }} style={{ padding: '60px 20px', textAlign: 'center', backgroundColor: bgColor, outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} > {heading && (

{heading}

)}
{units.map((u) => (
{pad(u.value)} {u.label}
))}
); }; /* ---------- Settings panel ---------- */ const CountdownSettings: React.FC = () => { const { actions: { setProp }, props } = useNode((node) => ({ props: node.data.props as CountdownProps, })); const labelStyle: CSSProperties = { fontSize: 11, color: '#a1a1aa', display: 'block', marginBottom: 6 }; const inputStyle: CSSProperties = { width: '100%', padding: '4px 8px', background: '#27272a', color: '#e4e4e7', border: '1px solid #3f3f46', borderRadius: 4, fontSize: 12, }; const colorPresets = ['#ffffff', '#f8fafc', '#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899']; const bgPresets = ['#18181b', '#0f172a', '#1e293b', '#1e1b4b', '#042f2e', '#27272a', '#ffffff', '#f8fafc']; return (
{/* Target date */}
setProp((p: CountdownProps) => { p.targetDate = e.target.value; })} style={inputStyle} />
{/* Heading */}
setProp((p: CountdownProps) => { p.heading = e.target.value; })} placeholder="Coming Soon" style={inputStyle} />
{/* Digit color */}
{colorPresets.map((c) => (
{/* Label color */}
{colorPresets.map((c) => (
{/* Background color */}
{bgPresets.map((c) => (
); }; /* ---------- Craft config ---------- */ Countdown.craft = { displayName: 'Countdown', props: { targetDate: DEFAULT_TARGET, heading: 'Coming Soon', style: {}, digitColor: '#ffffff', labelColor: 'rgba(255,255,255,0.7)', bgColor: '#18181b', }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true, }, related: { settings: CountdownSettings, }, }; /* ---------- HTML export ---------- */ (Countdown as any).toHtml = (props: CountdownProps, _childrenHtml: string) => { const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { targetDate = DEFAULT_TARGET, heading = 'Coming Soon', style = {}, digitColor = '#ffffff', labelColor = 'rgba(255,255,255,0.7)', bgColor = '#18181b', } = props; const sectionStyle = cssPropsToString({ padding: '60px 20px', textAlign: 'center', backgroundColor: bgColor, ...style, }); const headingHtml = heading ? `

${esc(heading)}

` : ''; const boxStyle = 'display:flex;flex-direction:column;align-items:center;gap:4px;min-width:80px'; const dStyle = `font-size:48px;font-weight:700;color:${digitColor};line-height:1;font-family:Inter,sans-serif`; const lStyle = `font-size:12px;color:${labelColor};text-transform:uppercase;letter-spacing:0.1em;font-family:Inter,sans-serif`; // Generate a unique ID for this countdown instance const uid = 'cd_' + Math.random().toString(36).slice(2, 8); return { html: ` ${headingHtml}
00Days
00Hours
00Minutes
00Seconds
`, }; };