All files / Bandstand/src/components CalendarLinks.tsx

50% Statements 62/124
33.33% Branches 2/6
33.33% Functions 2/6
50% Lines 62/124

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161    1x 1x 1x                   1x     1x 2x 2x 2x     2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x   2x 2x 2x 2x   2x 2x 2x 2x 2x 2x 2x 2x   2x 2x   2x           1x 2x 2x 2x 2x   2x               2x             2x   2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                                                                                                                     2x   2x  
'use client'
 
import {useState} from 'react'
import {googleCalendarUrl} from '@/lib/ics'
import Dropdown from '@/components/Dropdown'
 
type R = {
  id: string
  date: string
  time: string | null
  place: string | null
  note: string | null
}
 
const SITE = typeof window === 'undefined' ? '' : window.location.origin
 
/** "Add to calendar" for one rehearsal: an .ics file or a Google link. */
export function AddToCalendar({
  r,
  band,
}: {
  r: R
  band: {name: string; timezone: string}
}) {
  const google = googleCalendarUrl({
    uid: r.id,
    date: r.date,
    time: r.time,
    title: `${band.name} rehearsal`,
    tz: band.timezone,
    location: r.place,
    description: [r.note, `Charts and setlists: ${SITE}/setlists`]
      .filter(Boolean)
      .join('\n'),
  })
  return (
    <Dropdown
      trigger="Add to calendar ▾"
      align="left"
      triggerClassName="flex min-h-9 items-center text-sm font-semibold text-sky"
      panelClassName="w-60"
    >
      <a
        href={`/api/rehearsals/${r.id}/ics`}
        className="block rounded-lg px-3 py-2.5 no-underline hover:bg-line"
      >
        Apple Calendar / Outlook
        <span className="block text-xs text-faint">Downloads an .ics file</span>
      </a>
      <a
        href={google}
        target="_blank"
        rel="noreferrer"
        className="block rounded-lg px-3 py-2.5 no-underline hover:bg-line"
      >
        Google Calendar ↗
      </a>
    </Dropdown>
  )
}
 
/**
 * A personal feed of every rehearsal and gig. Calendar apps check it on
 * their own, so new and changed ones appear without anyone adding them.
 */
export function SubscribeCalendar() {
  const [url, setUrl] = useState<string | null>(null)
  const [open, setOpen] = useState(false)
  const [copied, setCopied] = useState(false)
  const [busy, setBusy] = useState(false)
 
  async function show() {
    setOpen(!open)
    if (url || open) return
    const r = await fetch('/api/account/calendar')
    const {url: existing} = await r.json()
    if (existing) setUrl(existing)
    else await make()
  }
  async function make() {
    setBusy(true)
    const r = await fetch('/api/account/calendar', {method: 'POST'})
    setUrl((await r.json()).url)
    setCopied(false)
    setBusy(false)
  }
  const webcal = url?.replace(/^https?:/, 'webcal:')
 
  return (
    <div className="mt-3 border-t border-line pt-3">
      <button
        type="button"
        onClick={show}
        aria-expanded={open}
        className="min-h-9 text-sm font-semibold text-sky"
      >
        Subscribe to rehearsals and gigs {open ? '▴' : '▾'}
      </button>
      {open && (
        <div className="mt-2 space-y-3 text-sm">
          <p className="text-muted">
            Rehearsals and gigs, in every band you’re in, then show up in your
            calendar by themselves. This link is yours: don’t share it.
          </p>
          {url ? (
            <>
              <div className="flex flex-wrap gap-2">
                <a
                  href={webcal}
                  className="inline-flex min-h-11 items-center rounded-lg bg-accent px-4 font-bold text-on-accent no-underline"
                >
                  Apple Calendar / Outlook
                </a>
                <a
                  href={`https://calendar.google.com/calendar/r?cid=${encodeURIComponent(webcal ?? '')}`}
                  target="_blank"
                  rel="noreferrer"
                  className="inline-flex min-h-11 items-center rounded-lg border border-line-2 px-4 no-underline"
                >
                  Google Calendar ↗
                </a>
              </div>
              <div className="flex items-center gap-2">
                <input
                  readOnly
                  value={url}
                  aria-label="Calendar link"
                  className="min-h-10 min-w-0 flex-1 rounded-lg border border-line-2 bg-ink px-2 font-mono text-xs text-muted"
                  onFocus={(e) => e.target.select()}
                />
                <button
                  type="button"
                  onClick={async () => {
                    try {
                      await navigator.clipboard.writeText(url)
                      setCopied(true)
                    } catch {}
                  }}
                  className="min-h-10 rounded-lg border border-line-2 px-3"
                >
                  {copied ? 'Copied ✓' : 'Copy'}
                </button>
              </div>
              <button
                type="button"
                onClick={make}
                disabled={busy}
                className="min-h-9 text-xs text-faint underline"
              >
                Reset link (stops the old one working)
              </button>
            </>
          ) : (
            <p className="text-faint">Making your link…</p>
          )}
        </div>
      )}
    </div>
  )
}