> ## Documentation Index
> Fetch the complete documentation index at: https://academy.pathfindr.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Masterclasses

> Join our upcoming live masterclasses to learn and grow with the community.

export const UpcomingWebinars = ({endpoint = '', variant = 'tiles', max = 6, timezone, ctaLabel = 'Register', exclude = [88585832856]}) => {
  const [webinars, setWebinars] = useState([]);
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState(null);
  const clockIcon = <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style={{
    marginRight: 6
  }} aria-hidden="true">
      <path d="M12 2a10 10 0 1 0 .001 20.001A10 10 0 0 0 12 2zm.5 4h-1v6l5 3 .5-.866L12.5 12.5V6z" />
    </svg>;
  const cameraIcon = <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style={{
    marginRight: 6
  }} aria-hidden="true">
      <path d="M9 4l-1.2 2H5a3 3 0 0 0-3 3v7a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3h-2.8L15 4H9zm3 12a4 4 0 1 1-.001-8.001A4 4 0 0 1 12 16z" />
    </svg>;
  const globeIcon = <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M12 2C6.476 2 2 6.476 2 12s4.476 10 10 10 10-4.476 10-10S17.524 2 12 2zm6.93 9h-2.18c-.158-2.042-.742-3.921-1.68-5.359A8.031 8.031 0 0 1 18.93 11zM12 20c-1.612 0-3.527-2.467-3.95-7h7.9c-.423 4.533-2.338 7-3.95 7zm-3.95-9c.423-4.533 2.338-7 3.95-7s3.527 2.467 3.95 7h-7.9zM8.93 5.641C7.992 7.079 7.408 8.958 7.25 11H5.07a8.031 8.031 0 0 1 3.86-5.359zM5.07 13h2.18c.158 2.042.742 3.921 1.68 5.359A8.031 8.031 0 0 1 5.07 13zm12.86 0c-.158 2.042-.742 3.921-1.68 5.359A8.031 8.031 0 0 0 18.93 13z" />
    </svg>;
  const TRACKING_META = {
    public: {
      label: 'Public',
      icon: globeIcon,
      tone: '#059669'
    },
    copilot: {
      label: 'Copilot',
      iconType: 'image',
      iconSrc: '/images/copilot365-icon.svg',
      tone: '#6366f1',
      iconBg: '#eef2ff'
    },
    chatgpt: {
      label: 'ChatGPT',
      iconType: 'image',
      iconSrc: '/images/openai.svg',
      tone: '#0ea5e9',
      iconBg: '#e0f2fe'
    },
    gemini: {
      label: 'Gemini',
      iconType: 'image',
      iconSrc: '/images/gemini-icon.svg',
      tone: '#f97316',
      iconBg: '#fff7ed'
    }
  };
  const normalizeTracking = raw => {
    if (!raw) return {};
    if (typeof raw === 'object' && !Array.isArray(raw)) {
      return Object.entries(raw).reduce((acc, [key, val]) => {
        const normalizedKey = key.toString().trim().toLowerCase().replace(/[^a-z0-9]+/g, '');
        acc[normalizedKey] = Boolean(val);
        return acc;
      }, {});
    }
    if (Array.isArray(raw)) {
      return raw.reduce((acc, item = {}) => {
        const key = (item.field || item.field_name || item.name || '').toString().trim();
        if (!key) return acc;
        const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]+/g, '');
        const val = (item.value ?? '').toString().trim().toLowerCase();
        acc[normalizedKey] = ['true', 'yes', 'y', '1'].includes(val);
        return acc;
      }, {});
    }
    return {};
  };
  const renderBadgeIcon = (meta, context) => {
    if (meta.iconType === 'image' && meta.iconSrc) {
      const circleStyle = {
        display: 'inline-flex',
        alignItems: 'center',
        justifyContent: 'center',
        width: 20,
        height: 20,
        borderRadius: '50%',
        background: meta.iconBg || '#f1f5f9',
        border: context === 'overlay' ? `1px solid ${meta.tone || '#cbd5f5'}55` : 'none'
      };
      return <span style={circleStyle} aria-hidden="true">
          <img src={meta.iconSrc} alt="" style={{
        width: 12,
        height: 12,
        objectFit: 'contain'
      }} />
        </span>;
    }
    if (meta.icon) {
      return <span style={{
        display: 'inline-flex',
        alignItems: 'center'
      }} aria-hidden="true">
          {meta.icon}
        </span>;
    }
    return null;
  };
  const buildTrackingDisplay = trackingRaw => {
    const tracking = normalizeTracking(trackingRaw);
    const badges = Object.entries(TRACKING_META).filter(([key]) => tracking[key]).map(([key, meta]) => ({
      key,
      ...meta
    }));
    const internalOnly = tracking.public === false && badges.length === 0;
    const publicBadge = badges.find(badge => badge.key === 'public') || null;
    const otherBadges = badges.filter(badge => badge.key !== 'public');
    return {
      badges,
      internalOnly,
      publicBadge,
      otherBadges
    };
  };
  const makeBadgeElements = ({items = [], internalOnly} = {}, context = 'default') => {
    if (internalOnly) {
      const style = {
        display: 'inline-flex',
        alignItems: 'center',
        gap: 4,
        fontSize: 11,
        fontWeight: 600,
        borderRadius: 999,
        padding: '3px 8px',
        color: '#7c3aed',
        background: 'rgba(124,58,237,0.12)'
      };
      if (context === 'overlay') {
        style.background = 'rgba(255,255,255,0.88)';
        style.border = '1px solid rgba(124,58,237,0.25)';
      }
      return [<span key="internal" style={style}>
          Internal Session
        </span>];
    }
    return items.map(meta => {
      const {key, label, tone} = meta;
      const style = {
        display: 'inline-flex',
        alignItems: 'center',
        gap: 4,
        fontSize: 11,
        fontWeight: 600,
        borderRadius: 999,
        padding: '3px 8px',
        color: tone,
        background: `${tone}1A`
      };
      if (context === 'overlay') {
        style.background = 'rgba(255,255,255,0.88)';
        style.border = `1px solid ${tone}33`;
      }
      const iconEl = renderBadgeIcon(meta, context);
      return <span key={key} style={style}>
          {iconEl}
          {label}
        </span>;
    });
  };
  const formatStart = (startTime, tz) => {
    if (!startTime) return 'TBA';
    try {
      const date = new Date(startTime);
      return date.toLocaleString(undefined, {
        weekday: 'short',
        month: 'short',
        day: 'numeric',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        timeZoneName: tz ? 'short' : undefined,
        timeZone: tz || undefined
      });
    } catch {
      return new Date(startTime).toLocaleString();
    }
  };
  const formatDuration = minutes => {
    if (typeof minutes !== 'number' || Number.isNaN(minutes)) return null;
    if (minutes < 60) return `${minutes} min session`;
    const hrs = Math.floor(minutes / 60);
    const mins = minutes % 60;
    const hourLabel = `${hrs} ${hrs === 1 ? 'hour' : 'hours'}`;
    return mins ? `${hourLabel} ${mins} min session` : `${hourLabel} session`;
  };
  const shouldExclude = webinar => {
    const webinarId = webinar.id?.toString();
    if (exclude.some(id => id?.toString() === webinarId)) {
      return true;
    }
    const tracking = normalizeTracking(webinar.tracking || webinar.tracking_fields || []);
    return tracking.public === false || tracking.hidden || tracking.exclude;
  };
  useEffect(() => {
    let cancelled = false;
    if (!endpoint) {
      setErr('No endpoint configured for UpcomingWebinars.');
      setWebinars([]);
      setLoading(false);
      return () => {
        cancelled = true;
      };
    }
    const fetchWebinars = async () => {
      setLoading(true);
      setErr(null);
      try {
        const res = await fetch(endpoint, {
          method: 'GET'
        });
        if (!res.ok) {
          const errorText = await res.text();
          throw new Error(`Request failed ${res.status}: ${errorText}`);
        }
        const data = await res.json();
        const allWebinars = Array.isArray(data.webinars) ? data.webinars : [];
        const filteredWebinars = allWebinars.filter(w => !shouldExclude(w));
        if (!cancelled) setWebinars(filteredWebinars);
      } catch (e) {
        if (!cancelled) {
          setErr(`Unable to load: ${e.message || e}`);
          setWebinars([]);
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    fetchWebinars();
    return () => {
      cancelled = true;
    };
  }, [endpoint]);
  const getSoonLabel = start => {
    if (!start) return null;
    const startMs = new Date(start).getTime();
    if (Number.isNaN(startMs)) return null;
    const now = Date.now();
    const diff = startMs - now;
    if (diff <= 0 || diff > 72 * 60 * 60 * 1000) return null;
    const minuteMs = 60 * 1000;
    const hourMs = 60 * minuteMs;
    const dayMs = 24 * hourMs;
    if (diff >= dayMs) {
      const days = Math.max(1, Math.ceil(diff / dayMs));
      return `In ${days} ${days === 1 ? 'day' : 'days'}`;
    }
    if (diff >= 2 * hourMs) {
      const hours = Math.max(2, Math.floor(diff / hourMs));
      return `In ${hours} ${hours === 1 ? 'hour' : 'hours'}`;
    }
    if (diff >= hourMs) {
      let hours = Math.floor(diff / hourMs);
      let minutes = Math.round((diff - hours * hourMs) / minuteMs);
      if (minutes >= 60) {
        hours += 1;
        minutes = 0;
      }
      if (hours >= 2) {
        return `In ${hours} hours`;
      }
      if (minutes === 0) {
        return `In ${hours} ${hours === 1 ? 'hour' : 'hours'}`;
      }
      const hourLabel = hours === 1 ? 'hour' : 'hours';
      const minuteLabel = minutes === 1 ? 'minute' : 'minutes';
      return `In ${hours} ${hourLabel} and ${minutes} ${minuteLabel}`;
    }
    const minutes = Math.max(1, Math.round(diff / minuteMs));
    return `In ${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`;
  };
  const abstractBackdrop = id => {
    const seed = typeof id === 'number' ? id : String(id || '').split('').reduce((acc, c) => acc + c.charCodeAt(0), 0);
    const palettes = [{
      backgroundColor: '#0f172a',
      backgroundImage: `
          radial-gradient(at 20% 20%, rgba(96,165,250,0.85) 0, rgba(15,23,42,0) 55%),
          radial-gradient(at 80% 15%, rgba(244,114,182,0.8) 0, rgba(15,23,42,0) 60%),
          radial-gradient(at 50% 80%, rgba(45,212,191,0.75) 0, rgba(15,23,42,0) 55%)`
    }, {
      backgroundColor: '#312e81',
      backgroundImage: `
          radial-gradient(at 25% 25%, rgba(196,181,253,0.85) 0, rgba(49,46,129,0) 55%),
          radial-gradient(at 80% 20%, rgba(129,140,248,0.75) 0, rgba(49,46,129,0) 60%),
          radial-gradient(at 40% 75%, rgba(244,114,182,0.7) 0, rgba(49,46,129,0) 55%)`
    }, {
      backgroundColor: '#022c22',
      backgroundImage: `
          radial-gradient(at 20% 30%, rgba(16,185,129,0.9) 0, rgba(2,44,34,0) 55%),
          radial-gradient(at 75% 20%, rgba(34,211,238,0.75) 0, rgba(2,44,34,0) 55%),
          radial-gradient(at 50% 80%, rgba(250,204,21,0.65) 0, rgba(2,44,34,0) 60%)`
    }, {
      backgroundColor: '#3f1f5b',
      backgroundImage: `
          radial-gradient(at 25% 20%, rgba(244,114,182,0.85) 0, rgba(63,31,91,0) 55%),
          radial-gradient(at 75% 25%, rgba(129,140,248,0.75) 0, rgba(63,31,91,0) 55%),
          radial-gradient(at 50% 80%, rgba(253,224,71,0.65) 0, rgba(63,31,91,0) 60%)`
    }, {
      backgroundColor: '#111827',
      backgroundImage: `
          radial-gradient(at 30% 20%, rgba(14,165,233,0.85) 0, rgba(17,24,39,0) 55%),
          radial-gradient(at 70% 25%, rgba(16,185,129,0.75) 0, rgba(17,24,39,0) 55%),
          radial-gradient(at 50% 80%, rgba(249,115,22,0.7) 0, rgba(17,24,39,0) 55%)`
    }];
    const palette = palettes[Math.abs(seed) % palettes.length];
    return {
      ...palette,
      backgroundRepeat: 'no-repeat',
      backgroundSize: '140% 140%',
      backgroundPosition: 'center'
    };
  };
  const items = webinars.slice(0, max);
  if (loading) {
    return <div aria-busy="true" style={{
      display: 'grid',
      gap: 16,
      gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))'
    }}>
        {Array.from({
      length: Math.min(3, max)
    }).map((_, i) => <div key={i} style={{
      background: '#fff',
      borderRadius: 12,
      overflow: 'hidden',
      border: '1px solid #e5e7eb'
    }}>
            <div style={{
      height: 140,
      background: 'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 37%, #f3f4f6 63%)',
      backgroundSize: '400% 100%',
      animation: 'shine 1.4s ease infinite'
    }} />
            <div style={{
      padding: 16
    }}>
              <div style={{
      width: 120,
      height: 12,
      background: '#e5e7eb',
      borderRadius: 6,
      margin: '10px 0'
    }} />
              <div style={{
      width: 200,
      height: 12,
      background: '#e5e7eb',
      borderRadius: 6,
      margin: '10px 0'
    }} />
              <div style={{
      width: 160,
      height: 12,
      background: '#e5e7eb',
      borderRadius: 6,
      margin: '10px 0'
    }} />
            </div>
          </div>)}
      </div>;
  }
  if (err) return <div role="alert" style={{
    color: 'red',
    fontWeight: 'bold'
  }}>{err}</div>;
  if (!items.length) return <p>No upcoming webinars scheduled.</p>;
  if (variant === 'list') {
    return <div className="upcoming-webinars" style={{
      maxWidth: 720
    }}>
        <h2>Upcoming Live Masterclasses</h2>
        <div role="list" style={{
      display: 'grid',
      gap: 16
    }}>
          {items.map(w => {
      const display = buildTrackingDisplay(w.tracking || w.tracking_fields || []);
      const badgeElements = makeBadgeElements({
        items: display.badges,
        internalOnly: display.internalOnly
      });
      return <article key={w.id} role="listitem" style={{
        padding: 20,
        border: '1px solid #e5e7eb',
        borderRadius: 12,
        background: '#fff',
        boxShadow: '0 6px 18px rgba(15, 23, 42, 0.06)',
        display: 'grid',
        gap: 12
      }}>
                <div style={{
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'flex-start',
        gap: 12
      }}>
                  <h3 style={{
        margin: 0,
        fontSize: 20,
        fontWeight: 600,
        lineHeight: 1.3
      }}>
                    {w.topic || 'Untitled Webinar'}
                  </h3>
                  {w.public_link && <a href={w.public_link} target="_blank" rel="noopener noreferrer" style={{
        display: 'inline-block',
        padding: '8px 14px',
        fontSize: 14,
        fontWeight: 700,
        color: '#fff',
        background: '#2563eb',
        borderRadius: 999,
        textDecoration: 'none'
      }}>
                      {ctaLabel}
                    </a>}
                </div>
                {badgeElements.length > 0 && <div style={{
        display: 'flex',
        flexWrap: 'wrap',
        gap: 8
      }}>
                    {badgeElements}
                  </div>}
                <div style={{
        color: '#475569',
        fontSize: 14
      }}>
                  {formatStart(w.start_time, timezone || w.timezone)}
                </div>
                {formatDuration(w.duration) && <div style={{
        color: '#64748b',
        fontSize: 14
      }}>{formatDuration(w.duration)}</div>}
                {!w.public_link && <div style={{
        color: '#64748b',
        fontSize: 13,
        fontStyle: 'italic'
      }}>
                    Registration link not available
                  </div>}
              </article>;
    })}
        </div>
      </div>;
  }
  return <div role="list" style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
    gap: 24,
    alignItems: 'stretch',
    width: '100%'
  }}>
      {items.map(w => {
    const display = buildTrackingDisplay(w.tracking || w.tracking_fields || []);
    const publicBadgeOverlay = display.publicBadge ? makeBadgeElements({
      items: [display.publicBadge]
    }, 'overlay') : [];
    const otherOverlayBadges = makeBadgeElements({
      items: display.otherBadges,
      internalOnly: display.internalOnly
    }, 'overlay');
    const soonLabel = getSoonLabel(w.start_time);
    return <article key={w.id} role="listitem" style={{
      backgroundColor: '#fff',
      borderRadius: 12,
      overflow: 'hidden',
      boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
      display: 'flex',
      flexDirection: 'column',
      minHeight: 320
    }}>
            <div style={{
      height: 120,
      position: 'relative',
      backgroundColor: '#eef2f7',
      ...w.image_url ? {
        backgroundImage: `url(${w.image_url})`,
        backgroundSize: 'cover',
        backgroundPosition: 'center'
      } : abstractBackdrop(w.id)
    }} aria-hidden="true">
              {soonLabel && <span style={{
      position: 'absolute',
      top: 12,
      left: 12,
      backgroundColor: '#1fa0e6',
      color: '#fff',
      padding: '4px 8px',
      borderRadius: 8,
      fontSize: 12,
      fontWeight: 700,
      whiteSpace: 'nowrap'
    }}>
                  {soonLabel}
                </span>}
              {publicBadgeOverlay.length > 0 && <div style={{
      position: 'absolute',
      top: 12,
      right: 12,
      display: 'flex',
      flexWrap: 'wrap',
      gap: 6
    }}>
                  {publicBadgeOverlay}
                </div>}
              {otherOverlayBadges.length > 0 && <div style={{
      position: 'absolute',
      bottom: 12,
      left: 12,
      display: 'flex',
      flexWrap: 'wrap',
      gap: 6
    }}>
                  {otherOverlayBadges}
                </div>}
            </div>

            <div style={{
      padding: 14,
      display: 'grid',
      gap: 6,
      gridAutoRows: 'min-content',
      flexGrow: 1
    }}>
              <div style={{
      color: '#0ea5e9',
      fontSize: 11,
      fontWeight: 700,
      letterSpacing: 0.2
    }}>
                {w.format || 'Livestream'}
              </div>
              <h3 style={{
      fontSize: 17,
      fontWeight: 700,
      margin: 0,
      lineHeight: 1.35
    }}>
                {w.topic || 'Untitled Webinar'}
              </h3>
              {w.description && <p style={{
      fontSize: 13,
      color: '#4b5563',
      margin: 0,
      lineHeight: 1.5
    }}>{w.description}</p>}

              <div style={{
      display: 'flex',
      alignItems: 'center',
      fontSize: 13,
      color: '#475569'
    }}>
                {clockIcon}
                <span>{formatStart(w.start_time, timezone || w.timezone)}</span>
              </div>
              <div style={{
      display: 'flex',
      alignItems: 'center',
      fontSize: 13,
      color: '#475569'
    }}>
                {cameraIcon}
                <span>{w.location || 'Online'}</span>
              </div>
              <div style={{
      marginTop: 'auto',
      display: 'flex',
      alignItems: 'center',
      gap: 12,
      paddingBottom: 0
    }}>
                {formatDuration(w.duration) ? <div style={{
      fontSize: 12.5,
      color: '#64748b'
    }}>{formatDuration(w.duration)}</div> : null}
                {w.public_link ? <a href={w.public_link} target="_blank" rel="noopener noreferrer" style={{
      display: 'inline-flex',
      alignItems: 'center',
      gap: 6,
      padding: '5px 10px',
      fontSize: 10,
      fontWeight: 600,
      color: '#fff',
      background: 'linear-gradient(135deg, #2563eb 0%, #1e40af 100%)',
      borderRadius: 10,
      textDecoration: 'none',
      marginLeft: 'auto',
      position: 'relative',
      overflow: 'hidden',
      transition: 'background 0.18s ease, transform 0.18s ease'
    }}>
                    <span style={{
      position: 'relative',
      zIndex: 1
    }}>{ctaLabel}</span>
                    <svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{
      position: 'relative',
      zIndex: 1
    }}>
                      <path d="M5 12h14" />
                      <path d="M13 6l6 6-6 6" />
                    </svg>
                    <span aria-hidden="true" style={{
      position: 'absolute',
      inset: 0,
      background: 'linear-gradient(120deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.35) 100%)',
      opacity: 0.7
    }} />
                  </a> : <div style={{
      color: '#64748b',
      fontSize: 13,
      fontStyle: 'italic',
      marginLeft: 'auto'
    }}>
                    Registration link not available
                  </div>}
              </div>
            </div>
          </article>;
  })}
    </div>;
};

<UpcomingWebinars endpoint="https://zoom-webinars-backend-pathfindrais-projects.vercel.app/api/zoom-webinars" />
