98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
import { useState } from 'react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { X } from '../../lib/icons'
|
|
import { normaliseLetterboxdListUrl } from '../../api/letterboxd'
|
|
import { useLetterboxdLists } from '../../stores/letterboxd-lists-store'
|
|
|
|
interface Props {
|
|
open: boolean
|
|
onClose: () => void
|
|
}
|
|
|
|
export default function LetterboxdAddModal({ open, onClose }: Props) {
|
|
const add = useLetterboxdLists(s => s.add)
|
|
const [url, setUrl] = useState('')
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
function save() {
|
|
const norm = normaliseLetterboxdListUrl(url)
|
|
if (!norm) {
|
|
setError('That doesn\'t look like a Letterboxd list URL.')
|
|
return
|
|
}
|
|
add(norm)
|
|
setUrl('')
|
|
setError(null)
|
|
onClose()
|
|
}
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{open && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.18 }}
|
|
onClick={onClose}
|
|
className="fixed inset-0 z-[80] grid place-items-center bg-black/65 backdrop-blur-sm p-6"
|
|
>
|
|
<motion.div
|
|
initial={{ y: 20, scale: 0.96 }}
|
|
animate={{ y: 0, scale: 1 }}
|
|
exit={{ y: 20, scale: 0.96 }}
|
|
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
|
onClick={e => e.stopPropagation()}
|
|
className="relative w-full max-w-md rounded-2xl bg-surface ring-1 ring-border-strong shadow-[0_30px_80px_-20px_rgba(0,0,0,0.85)] p-5"
|
|
>
|
|
<button
|
|
onClick={onClose}
|
|
aria-label="Close"
|
|
className="absolute top-3 right-3 w-8 h-8 grid place-items-center rounded-full text-text-3 hover:text-text-1 hover:bg-elevated transition focus-ring"
|
|
>
|
|
<X size={14} stroke={2} />
|
|
</button>
|
|
<h2 className="text-[16px] font-semibold tracking-tight text-text-1 mb-1">
|
|
Add a Letterboxd list
|
|
</h2>
|
|
<p className="text-[12px] text-text-3 mb-4 leading-relaxed">
|
|
Paste the URL of any public Letterboxd list. We'll cross-check it against
|
|
your library so missing entries are obvious.
|
|
</p>
|
|
<input
|
|
type="url"
|
|
value={url}
|
|
onChange={e => {
|
|
setUrl(e.target.value)
|
|
if (error) setError(null)
|
|
}}
|
|
onKeyDown={e => { if (e.key === 'Enter') save() }}
|
|
placeholder="https://letterboxd.com/USER/list/SLUG/"
|
|
autoFocus
|
|
className="w-full h-10 px-3 rounded-md bg-elevated/60 ring-1 ring-border focus:ring-accent/50 outline-none text-[13px] tracking-tight"
|
|
/>
|
|
{error && (
|
|
<p className="text-[11.5px] text-red-300 mt-2">{error}</p>
|
|
)}
|
|
<div className="flex items-center justify-end gap-2 mt-5">
|
|
<button
|
|
onClick={onClose}
|
|
className="h-10 px-4 rounded-full text-[12.5px] text-text-2 hover:text-text-1 hover:bg-elevated transition focus-ring"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={save}
|
|
disabled={!url.trim()}
|
|
className="h-10 px-5 rounded-full bg-accent text-void text-[12.5px] font-semibold tracking-tight transition disabled:opacity-40 disabled:cursor-not-allowed hover:bg-accent-hover focus-ring"
|
|
>
|
|
Add list
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
)
|
|
}
|