← back to IWasCute

src/components/licensor/CopyrightInfo.tsx

535 lines

'use client'

import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { ChevronDown, ChevronLeft, ChevronRight, Info } from 'lucide-react'
import clsx from 'clsx'
import type { PhotoFile } from './PhotoUpload'

export interface CopyrightData {
  photographer: string
  relationship: string
  year: string
  hasPermission: string
  estateRights: boolean
  applyToAll: boolean
}

interface CopyrightInfoProps {
  photos: PhotoFile[]
  copyrightInfo: CopyrightData
  onCopyrightChange: (info: CopyrightData) => void
  onNext: () => void
  onBack: () => void
}

const RELATIONSHIP_OPTIONS = [
  { value: '', label: 'Select who took this photo' },
  { value: 'parent', label: 'My parent' },
  { value: 'family', label: 'Other family member' },
  { value: 'professional', label: 'Professional photographer' },
  { value: 'unknown', label: "I don't know" },
]

const PERMISSION_OPTIONS = [
  { value: '', label: 'Select an option' },
  { value: 'yes', label: 'Yes, I have permission' },
  { value: 'no', label: 'No, I do not have explicit permission' },
  { value: 'deceased', label: 'The photographer is deceased' },
]

const CURRENT_YEAR = new Date().getFullYear()

function SelectField({
  label,
  value,
  options,
  onChange,
  required,
  id,
}: {
  label: string
  value: string
  options: { value: string; label: string }[]
  onChange: (v: string) => void
  required?: boolean
  id: string
}) {
  return (
    <div className="flex flex-col gap-1.5">
      <label
        htmlFor={id}
        className="text-sm font-semibold tracking-wide"
        style={{ color: 'var(--color-brown)' }}
      >
        {label}
        {required && (
          <span className="ml-1" style={{ color: 'var(--color-peach-dark)' }} aria-hidden>
            *
          </span>
        )}
      </label>
      <div className="relative">
        <select
          id={id}
          value={value}
          onChange={(e) => onChange(e.target.value)}
          required={required}
          className={clsx(
            'w-full appearance-none px-4 py-3 pr-10 rounded-2xl text-sm',
            'border outline-none transition-all duration-200',
            'focus:ring-2'
          )}
          style={{
            background: 'rgba(255,255,255,0.8)',
            borderColor: 'var(--color-cream-dark)',
            color: value ? 'var(--color-brown)' : 'var(--color-warm-gray)',
          }}
        >
          {options.map((opt) => (
            <option key={opt.value} value={opt.value} disabled={opt.value === ''}>
              {opt.label}
            </option>
          ))}
        </select>
        <ChevronDown
          size={16}
          className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none"
          style={{ color: 'var(--color-warm-gray)' }}
        />
      </div>
    </div>
  )
}

function TextField({
  label,
  value,
  onChange,
  placeholder,
  type = 'text',
  min,
  max,
  id,
  optional,
}: {
  label: string
  value: string
  onChange: (v: string) => void
  placeholder?: string
  type?: string
  min?: number
  max?: number
  id: string
  optional?: boolean
}) {
  return (
    <div className="flex flex-col gap-1.5">
      <label
        htmlFor={id}
        className="text-sm font-semibold tracking-wide"
        style={{ color: 'var(--color-brown)' }}
      >
        {label}
        {optional && (
          <span className="ml-1.5 text-xs font-normal" style={{ color: 'var(--color-warm-gray)' }}>
            (optional)
          </span>
        )}
      </label>
      <input
        id={id}
        type={type}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        min={min}
        max={max}
        className={clsx(
          'w-full px-4 py-3 rounded-2xl text-sm',
          'border outline-none transition-all duration-200',
          'focus:ring-2'
        )}
        style={{
          background: 'rgba(255,255,255,0.8)',
          borderColor: 'var(--color-cream-dark)',
          color: 'var(--color-brown)',
        }}
      />
    </div>
  )
}

function CopyrightForm({
  data,
  onChange,
  photoIndex,
  totalPhotos,
}: {
  data: CopyrightData
  onChange: (patch: Partial<CopyrightData>) => void
  photoIndex?: number
  totalPhotos?: number
}) {
  const idSuffix = photoIndex !== undefined ? `-${photoIndex}` : '-all'

  return (
    <div className="flex flex-col gap-5">
      {photoIndex !== undefined && totalPhotos !== undefined && totalPhotos > 1 && (
        <p
          className="text-sm font-medium"
          style={{ color: 'var(--color-warm-gray)' }}
        >
          Photo {photoIndex + 1} of {totalPhotos}
        </p>
      )}

      <SelectField
        id={`relationship${idSuffix}`}
        label="Who took this photo?"
        value={data.relationship}
        options={RELATIONSHIP_OPTIONS}
        onChange={(v) => onChange({ relationship: v })}
        required
      />

      <AnimatePresence>
        {data.relationship !== 'unknown' && (
          <motion.div
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: 'auto' }}
            exit={{ opacity: 0, height: 0 }}
            transition={{ duration: 0.2 }}
            className="overflow-hidden"
          >
            <TextField
              id={`photographer${idSuffix}`}
              label="Photographer name"
              value={data.photographer}
              onChange={(v) => onChange({ photographer: v })}
              placeholder="e.g. Mom, Grandpa Joe, Jane Smith Photography"
              optional
            />
          </motion.div>
        )}
      </AnimatePresence>

      <TextField
        id={`year${idSuffix}`}
        label="Approximate year photo was taken"
        value={data.year}
        onChange={(v) => onChange({ year: v })}
        type="number"
        placeholder={`e.g. ${CURRENT_YEAR - 25}`}
        min={1900}
        max={CURRENT_YEAR}
      />

      <SelectField
        id={`permission${idSuffix}`}
        label="Do you have permission from the photographer?"
        value={data.hasPermission}
        options={PERMISSION_OPTIONS}
        onChange={(v) => onChange({ hasPermission: v })}
        required
      />

      <AnimatePresence>
        {data.hasPermission === 'deceased' && (
          <motion.div
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: 'auto' }}
            exit={{ opacity: 0, height: 0 }}
            transition={{ duration: 0.2 }}
            className="overflow-hidden"
          >
            <label
              className="flex items-start gap-3 cursor-pointer group"
              htmlFor={`estate${idSuffix}`}
            >
              <div className="relative mt-0.5">
                <input
                  id={`estate${idSuffix}`}
                  type="checkbox"
                  checked={data.estateRights}
                  onChange={(e) => onChange({ estateRights: e.target.checked })}
                  className="sr-only"
                />
                <div
                  className={clsx(
                    'w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all',
                    data.estateRights ? 'border-transparent' : ''
                  )}
                  style={{
                    background: data.estateRights ? 'var(--color-sage)' : 'white',
                    borderColor: data.estateRights ? 'var(--color-sage)' : 'var(--color-cream-dark)',
                  }}
                  aria-hidden
                >
                  {data.estateRights && (
                    <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
                      <path
                        d="M2 5.5l2.5 2.5L9 3"
                        stroke="white"
                        strokeWidth="1.6"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                  )}
                </div>
              </div>
              <div>
                <p
                  className="text-sm font-semibold"
                  style={{ color: 'var(--color-brown)' }}
                >
                  I have evidence of estate rights
                </p>
                <p className="text-xs mt-0.5" style={{ color: 'var(--color-warm-gray)' }}>
                  e.g. will, inheritance document, or written permission from the estate executor
                </p>
              </div>
            </label>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  )
}

export default function CopyrightInfo({
  photos,
  copyrightInfo,
  onCopyrightChange,
  onNext,
  onBack,
}: CopyrightInfoProps) {
  const [activePhotoIndex, setActivePhotoIndex] = useState(0)
  const multiPhoto = photos.length > 1

  // Per-photo overrides (index → CopyrightData)
  const [perPhoto, setPerPhoto] = useState<Record<number, Partial<CopyrightData>>>({})

  const handleBatchChange = (patch: Partial<CopyrightData>) => {
    onCopyrightChange({ ...copyrightInfo, ...patch })
  }

  const handlePerPhotoChange = (index: number, patch: Partial<CopyrightData>) => {
    setPerPhoto((prev) => ({
      ...prev,
      [index]: { ...(prev[index] ?? {}), ...patch },
    }))
  }

  const getPhotoData = (index: number): CopyrightData => ({
    ...copyrightInfo,
    ...(perPhoto[index] ?? {}),
  })

  const canProceed =
    copyrightInfo.relationship !== '' && copyrightInfo.hasPermission !== ''

  return (
    <div className="flex flex-col gap-6">
      {/* Header */}
      <div>
        <h2
          className="text-2xl md:text-3xl font-bold tracking-tight"
          style={{ color: 'var(--color-brown)' }}
        >
          Copyright Information
        </h2>
        <p className="mt-2 text-base" style={{ color: 'var(--color-warm-gray)' }}>
          Help us understand who owns the copyright to your photos.
        </p>
      </div>

      {/* Info banner */}
      <div
        className="flex items-start gap-3 p-4 rounded-2xl"
        style={{ background: 'rgba(181, 213, 197, 0.25)', border: '1px solid var(--color-sage)' }}
        role="note"
      >
        <Info size={16} className="mt-0.5 shrink-0" style={{ color: 'var(--color-sage-dark)' }} />
        <p className="text-sm leading-relaxed" style={{ color: 'var(--color-warm-gray)' }}>
          In most cases, the person who pressed the shutter button owns the copyright — not the
          person in the photo. We need this information to ensure your uploads are compliant.
        </p>
      </div>

      {/* Apply-to-all toggle */}
      {multiPhoto && (
        <label
          className="flex items-center gap-3 cursor-pointer"
          htmlFor="apply-all"
        >
          <div className="relative">
            <input
              id="apply-all"
              type="checkbox"
              checked={copyrightInfo.applyToAll}
              onChange={(e) => handleBatchChange({ applyToAll: e.target.checked })}
              className="sr-only"
            />
            <div
              className="w-10 h-6 rounded-full transition-colors relative"
              style={{
                background: copyrightInfo.applyToAll
                  ? 'var(--color-peach)'
                  : 'var(--color-cream-dark)',
              }}
              aria-hidden
            >
              <div
                className="absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-all duration-200"
                style={{
                  left: copyrightInfo.applyToAll ? '1.25rem' : '0.25rem',
                }}
              />
            </div>
          </div>
          <div>
            <p
              className="text-sm font-semibold"
              style={{ color: 'var(--color-brown)' }}
            >
              Apply same info to all {photos.length} photos
            </p>
            <p className="text-xs" style={{ color: 'var(--color-warm-gray)' }}>
              Toggle off to fill copyright info per photo
            </p>
          </div>
        </label>
      )}

      {/* Form */}
      <div
        className="p-6 rounded-3xl"
        style={{ background: 'rgba(255,255,255,0.6)', border: '1px solid var(--color-cream-dark)' }}
      >
        {!multiPhoto || copyrightInfo.applyToAll ? (
          <CopyrightForm
            data={copyrightInfo}
            onChange={handleBatchChange}
            totalPhotos={photos.length}
          />
        ) : (
          <div className="flex flex-col gap-6">
            {/* Per-photo nav */}
            <div className="flex items-center justify-between">
              <button
                onClick={() => setActivePhotoIndex((i) => Math.max(0, i - 1))}
                disabled={activePhotoIndex === 0}
                className="flex items-center gap-1 text-sm font-medium px-3 py-2 rounded-xl transition-all disabled:opacity-30"
                style={{ color: 'var(--color-warm-gray)' }}
                aria-label="Previous photo"
              >
                <ChevronLeft size={16} />
                Previous
              </button>

              {/* Thumbnail strip */}
              <div className="flex gap-2">
                {photos.map((photo, i) => (
                  <button
                    key={photo.id}
                    onClick={() => setActivePhotoIndex(i)}
                    className={clsx(
                      'w-10 h-10 rounded-xl overflow-hidden border-2 transition-all',
                      i === activePhotoIndex
                        ? 'scale-110'
                        : 'opacity-60 hover:opacity-80'
                    )}
                    style={{
                      borderColor:
                        i === activePhotoIndex
                          ? 'var(--color-peach)'
                          : 'transparent',
                    }}
                    aria-label={`Select photo ${i + 1}`}
                    aria-pressed={i === activePhotoIndex}
                  >
                    {/* eslint-disable-next-line @next/next/no-img-element */}
                    <img
                      src={photo.preview}
                      alt={`Photo ${i + 1}`}
                      className="w-full h-full object-cover"
                    />
                  </button>
                ))}
              </div>

              <button
                onClick={() =>
                  setActivePhotoIndex((i) => Math.min(photos.length - 1, i + 1))
                }
                disabled={activePhotoIndex === photos.length - 1}
                className="flex items-center gap-1 text-sm font-medium px-3 py-2 rounded-xl transition-all disabled:opacity-30"
                style={{ color: 'var(--color-warm-gray)' }}
                aria-label="Next photo"
              >
                Next
                <ChevronRight size={16} />
              </button>
            </div>

            {/* Active photo form */}
            <AnimatePresence mode="wait">
              <motion.div
                key={activePhotoIndex}
                initial={{ opacity: 0, x: 20 }}
                animate={{ opacity: 1, x: 0 }}
                exit={{ opacity: 0, x: -20 }}
                transition={{ duration: 0.2 }}
              >
                <CopyrightForm
                  data={getPhotoData(activePhotoIndex)}
                  onChange={(patch) => handlePerPhotoChange(activePhotoIndex, patch)}
                  photoIndex={activePhotoIndex}
                  totalPhotos={photos.length}
                />
              </motion.div>
            </AnimatePresence>
          </div>
        )}
      </div>

      {/* Navigation */}
      <div className="flex items-center justify-between pt-2">
        <button
          onClick={onBack}
          className="flex items-center gap-2 px-6 py-3 rounded-3xl text-sm font-semibold tracking-wide transition-all hover:scale-105"
          style={{
            background: 'var(--color-cream-dark)',
            color: 'var(--color-warm-gray)',
          }}
        >
          <ChevronLeft size={16} />
          Back
        </button>

        <motion.button
          whileHover={canProceed ? { scale: 1.03 } : {}}
          whileTap={canProceed ? { scale: 0.97 } : {}}
          onClick={onNext}
          disabled={!canProceed}
          className={clsx(
            'flex items-center gap-2 px-8 py-3.5 rounded-3xl text-sm font-semibold tracking-wide',
            'transition-all duration-200',
            !canProceed && 'opacity-40 cursor-not-allowed'
          )}
          style={{
            background: canProceed ? 'var(--color-peach)' : 'var(--color-cream-dark)',
            color: 'var(--color-brown)',
          }}
          aria-disabled={!canProceed}
        >
          Continue to Likeness Release
          <ChevronRight size={16} />
        </motion.button>
      </div>
    </div>
  )
}