Example - Countdown timer

Display a countdown timer to a date/time, optionally linking to a destination.

This example renders a simple countdown timer to a configured end time.

Code

import * as React from 'react';
import { Text, getTextStyle, getDestinationHandler } from '@tapcart/mobile-components';

function pad2(value) {
  return String(value).padStart(2, '0');
}

function getTimeLeft(endDate) {
  const diff = +new Date(endDate) - +new Date();

  if (Number.isNaN(diff) || 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),
  };
}

export default function CountdownTimerExample({ blockConfig, useActions }) {
  const { openScreen, openProduct, openCollection } = useActions();

  const { endTime, destination, title } = blockConfig;

  const [timeLeft, setTimeLeft] = React.useState(() => getTimeLeft(endTime?.value));

  React.useEffect(() => {
    const timer = setInterval(() => {
      setTimeLeft(getTimeLeft(endTime?.value));
    }, 1000);

    return () => clearInterval(timer);
  }, [endTime?.value]);

  const openDestination = getDestinationHandler(destination?.type);

  const handlePress = (e) => {
    e?.stopPropagation?.();
    if (!destination || destination.type === 'none') return;
    openDestination(destination.location, { openScreen, openProduct, openCollection });
  };

  const titleStyle = title?.enabled ? getTextStyle(title) : {};

  return (
    <div role="button" tabIndex={0} onClick={handlePress} style={{ padding: '16px' }}>
      {title?.enabled && (
        <Text type="h2" style={{ ...titleStyle, marginBottom: '12px' }}>
          {title.text}
        </Text>
      )}

      <div style={{ display: 'flex', gap: '12px' }}>
        <div>
          <Text type="h1">{pad2(timeLeft.days)}</Text>
          <Text type="body">days</Text>
        </div>
        <div>
          <Text type="h1">{pad2(timeLeft.hours)}</Text>
          <Text type="body">hours</Text>
        </div>
        <div>
          <Text type="h1">{pad2(timeLeft.minutes)}</Text>
          <Text type="body">minutes</Text>
        </div>
        <div>
          <Text type="h1">{pad2(timeLeft.seconds)}</Text>
          <Text type="body">seconds</Text>
        </div>
      </div>
    </div>
  );
}

Manifest

[
  {
    "id": "endTime",
    "label": "End Time",
    "type": "date-time",
    "defaultValue": { "value": "2030-01-01T00:00:00.000Z" }
  },
  {
    "id": "destination",
    "label": "Destination (optional)",
    "type": "destination",
    "defaultValue": { "type": "none", "location": null }
  },
  {
    "id": "title",
    "label": "Title",
    "type": "section",
    "defaultValue": true,
    "manifestOptions": [
      { "id": "enabled", "label": "Show title", "type": "toggle", "defaultValue": true },
      { "id": "text", "label": "Text", "type": "text", "defaultValue": "Sale ends soon" },
      { "id": "font", "label": "Font", "type": "font-select", "defaultValue": { "family": "unset", "weight": "unset" } },
      { "id": "size", "label": "Size", "type": "range", "defaultValue": 18, "min": 8, "max": 36, "unit": "px", "step": 1 },
      { "id": "color", "label": "Color", "type": "color-select", "defaultValue": { "type": "brand-kit", "value": "textColors-primaryColor" } }
    ]
  }
]

Notes

  • If you don’t want the entire block to be clickable, remove onClick={handlePress} and attach the handler to a button.

Back to Examples