Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const translations = {
unstar: 'Remove saved session',
starredOnly: 'Show saved sessions',
donations: 'Donors',
storageErrorNotice: 'Some saved sessions could not be read and were reset.',
storageErrorClear: 'Clear saved sessions',
storageErrorDismiss: 'Dismiss',
donationsTitle: 'Donations',
donationsRegisterPrefix: 'The conference is free to attend; you only need to register on the',
donationsRegisterLink: 'website',
Expand Down Expand Up @@ -60,6 +63,9 @@ const translations = {
unstar: 'Quitar sesión guardada',
starredOnly: 'Mostrar sesiones guardadas',
donations: 'Donors',
storageErrorNotice: 'Algunos datos guardados no se pudieron leer y se han restablecido.',
storageErrorClear: 'Limpiar sesiones guardadas',
storageErrorDismiss: 'Ignorar',
donationsTitle: 'Donaciones',
donationsRegisterPrefix: 'La conferencia es de libre acceso, solo tienes que registrarte en la',
donationsRegisterLink: 'web',
Expand Down Expand Up @@ -182,13 +188,35 @@ function App() {
const [showStarredOnly, setShowStarredOnly] = useState(false);
const [starredIds, setStarredIds] = useState(() => {
const stored = window.localStorage.getItem(starredStorageKey);
return new Set(stored ? JSON.parse(stored) : []);
if (!stored) return new Set();
try {
const parsed = JSON.parse(stored);
return new Set(Array.isArray(parsed) ? parsed : []);
} catch {
return new Set();
}
});
const [storageError, setStorageError] = useState(() => {
const stored = window.localStorage.getItem(starredStorageKey);
if (!stored) return false;
try {
const parsed = JSON.parse(stored);
return !Array.isArray(parsed);
} catch {
return true;
}
});
const [expandedEventId, setExpandedEventId] = useState(null);
const [clickedExpandedEventId, setClickedExpandedEventId] = useState(null);
const [selectedEvent, setSelectedEvent] = useState(null);
const [showDonations, setShowDonations] = useState(false);
const cardRefs = useRef(new Map());

function clearStorage() {
window.localStorage.removeItem(starredStorageKey);
setStarredIds(new Set());
setStorageError(false);
}
const labels = translations[locale];
const activeSchedule = schedule.find((day) => day.date === activeDay) ?? schedule[0];
const normalizedQuery = normalize(query.trim());
Expand Down Expand Up @@ -309,6 +337,19 @@ function App() {

return (
<>
{storageError && (
<div className="storage-error-banner" role="alert">
<p className="storage-error-text">{labels.storageErrorNotice}</p>
<div className="storage-error-actions">
<button className="storage-error-clear" onClick={clearStorage} type="button">
{labels.storageErrorClear}
</button>
<button className="storage-error-dismiss" onClick={() => setStorageError(false)} type="button">
{labels.storageErrorDismiss}
</button>
</div>
</div>
)}
<main className="app-shell">
<section className="hero">
<div className="hero-title">
Expand Down
66 changes: 66 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -749,3 +749,69 @@ h1 {
z-index: 0;
border-radius: inherit;
}

/* Storage error banner */
.storage-error-banner {
position: fixed;
top: 16px;
left: 50%;
z-index: 1000;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
justify-content: space-between;
width: min(560px, calc(100% - 32px));
padding: 12px 16px;
border: 1px solid #c05621;
border-radius: 12px;
background: #fff7ed;
box-shadow: 0 4px 16px rgb(0 0 0 / 12%);
color: #7c2d12;
transform: translateX(-50%);
}

.storage-error-text {
margin: 0;
flex: 1;
min-width: 0;
font-size: 0.875rem;
}

.storage-error-actions {
display: flex;
gap: 8px;
align-items: center;
flex-shrink: 0;
}

.storage-error-clear {
padding: 6px 12px;
border: 1px solid #c05621;
border-radius: 999px;
background: #c05621;
color: #fff;
font-size: 0.8125rem;
font-weight: 700;
cursor: pointer;
white-space: nowrap;
}

.storage-error-clear:hover {
background: #9a3412;
border-color: #9a3412;
}

.storage-error-dismiss {
padding: 6px 10px;
border: none;
background: transparent;
color: #9a3412;
font-size: 0.8125rem;
cursor: pointer;
text-decoration: underline;
}

.storage-error-dismiss:hover {
color: #7c2d12;
}