fix: resolve QA bugs across settings, reminder banner, and modal layout

- Replace all querySelector('.reminder-info') calls with EventBus.emit('showReminder')
  across 14 files — prevents crashes when the element is not mounted
- Move ReminderInfo from inside the sidebar to the top of the right content column
  so it no longer obscures sidebar navigation
- Fix reminder not showing for non-hot-reload settings (Slider hook violation where
  useT() was called inside a requestAnimationFrame callback)
- Skip reminder for LocationSearch since it already emits a hot-reload refresh event
- Suppress reminder for settings with hot-reload (no page refresh needed)
- Fix modal content overflowing right edge: replace width:100%!important on
  .modalTabContent with flex:1 + min-width:0 + overflow-x:hidden
- Fix ReminderInfo refresh button being too small
- Fix sidebar collapse animation using max-width transition instead of width:0
- Fix PhotoInformation showing 0x0 resolution and Unknown Location before image loads
- Fix Wikipedia disambiguation links for quote authors by returning null from
  getAuthorLink (Wikidata links used when available)
- Bail early in useSuggestedPacks when offline mode is enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexsparkes
2026-04-01 10:19:50 +01:00
parent b98478ef68
commit 1ef65fb5d2
21 changed files with 127 additions and 82 deletions

View File

@@ -7,6 +7,7 @@ import SidebarToggle from '../components/SidebarToggle';
import ErrorBoundary from '../../../../features/misc/modals/ErrorBoundary';
import { TAB_TYPES } from '../constants/tabConfig';
import { SearchInput } from 'components/Form/Settings';
import EventBus from 'utils/eventbus';
const Tabs = ({
children,
@@ -129,6 +130,16 @@ const Tabs = ({
}
}, [resetToFirst]);
useEffect(() => {
const handleShowReminder = () => {
localStorage.setItem('showReminder', 'true');
setShowReminder(true);
};
EventBus.on('showReminder', handleShowReminder);
return () => EventBus.off('showReminder', handleShowReminder);
}, []);
const handleHideReminder = () => {
localStorage.setItem('showReminder', 'false');
setShowReminder(false);
@@ -189,21 +200,21 @@ const Tabs = ({
{searchQuery.trim() && filteredChildren.length === 0 && (
<div className="sidebarEmptyState">{t('widgets.weather.not_found')}</div>
)}
{!sidebarCollapsed && (
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
)}
</div>
) : null}
<div className="modalTabContent" ref={contentRef}>
{children.map((tab, index) => {
if (tab.props.label !== currentTab) {
return null;
}
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
<div className="modalTabContent" ref={contentRef}>
{children.map((tab, index) => {
if (tab.props.label !== currentTab) {
return null;
}
return (
<ErrorBoundary key={`error-boundary-${index}`}>{tab.props.children}</ErrorBoundary>
);
})}
return (
<ErrorBoundary key={`error-boundary-${index}`}>{tab.props.children}</ErrorBoundary>
);
})}
</div>
</div>
</div>
);

View File

@@ -9,17 +9,15 @@ const ReminderInfo = ({ isVisible, onHide }) => {
return (
<div className="reminder-info">
<div className="shareHeader">
<span className="title">{t('modals.main.settings.reminder.title')}</span>
<span className="closeModal" onClick={onHide}>
<MdClose />
</span>
</div>
<span className="title">{t('modals.main.settings.reminder.title')}</span>
<span className="subtitle">{t('modals.main.settings.reminder.message')}</span>
<button onClick={() => window.location.reload()}>
<MdRefresh />
{t('modals.main.error_boundary.refresh')}
</button>
<span className="closeModal" onClick={onHide}>
<MdClose />
</span>
</div>
);
};

View File

@@ -324,27 +324,51 @@ h5 {
.reminder-info {
display: flex;
flex-flow: column;
position: absolute;
bottom: 0;
padding: 15px;
gap: 15px;
flex-flow: row;
align-items: center;
gap: 12px;
padding: 10px 16px;
flex-shrink: 0;
@include themed {
background-color: t($modal-secondaryColour);
border-radius: t($borderRadius);
border: 1px solid t($modal-sidebarActive);
border-bottom: 1px solid t($modal-sidebarActive);
}
.title {
font-weight: 600;
white-space: nowrap;
}
.subtitle {
opacity: 0.7;
flex: 1;
}
button {
@include basicIconButton(5px, 5px, modal);
@include basicIconButton(8px 14px, 0.875rem, modal);
display: flex;
justify-content: center;
gap: 15px;
align-items: center;
gap: 8px;
flex-shrink: 0;
cursor: pointer;
svg {
margin: 0 !important;
font-size: 1rem;
}
}
.closeModal {
cursor: pointer;
display: flex;
align-items: center;
flex-shrink: 0;
opacity: 0.6;
&:hover {
opacity: 1;
}
}

View File

@@ -1,7 +1,8 @@
@use 'scss/variables' as *;
.modalTabContent {
width: 100% !important;
flex: 1;
min-width: 0;
/* button {
@include modal-button(standard);
@@ -11,8 +12,8 @@
display: flex;
flex-direction: column;
// height: 100%;
overflow-y: auto;
overflow-x: hidden;
margin-bottom: 0;
padding-bottom: 2rem;

View File

@@ -110,7 +110,11 @@
span {
white-space: nowrap;
transition: opacity 0.25s ease;
max-width: 200px;
overflow: hidden;
transition:
opacity 0.25s ease,
max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
}
@@ -165,8 +169,7 @@
span {
opacity: 0;
width: 0;
overflow: hidden;
max-width: 0;
margin: 0;
}
}

View File

@@ -22,8 +22,9 @@ const Checkbox = memo((props) => {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}

View File

@@ -197,8 +197,9 @@ const Dropdown = memo((props) => {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}

View File

@@ -235,9 +235,6 @@ const LocationSearch = memo((props) => {
closeDropdown();
EventBus.emit('refresh', category);
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
},
[name, category, closeDropdown],
);
@@ -272,8 +269,6 @@ const LocationSearch = memo((props) => {
setSearchQuery('');
EventBus.emit('refresh', category);
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
}
} catch (err) {
console.error('Auto location error:', err);

View File

@@ -43,8 +43,9 @@ const Radio = memo((props) => {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}

View File

@@ -8,6 +8,7 @@ import EventBus from 'utils/eventbus';
import './Slider.scss';
const SliderComponent = memo((props) => {
const t = useT();
const [value, setValue] = useState(localStorage.getItem(props.name) || props.default);
const [hoverValue, setHoverValue] = useState(null);
const [hoverPosition, setHoverPosition] = useState(0);
@@ -32,8 +33,9 @@ const SliderComponent = memo((props) => {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}
@@ -53,7 +55,6 @@ const SliderComponent = memo((props) => {
const startTime = performance.now();
const animate = (currentTime) => {
const t = useT();
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);

View File

@@ -21,8 +21,9 @@ const Switch = memo((props) => {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}

View File

@@ -40,8 +40,9 @@ const Text = memo((props) => {
if (element) {
if (!document.querySelector(element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}

View File

@@ -38,8 +38,9 @@ function Header(props) {
if (props.element) {
if (!document.querySelector(props.element)) {
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
}
}

View File

@@ -154,11 +154,13 @@ function MetadataGrid({
/>
)}
<MetadataItem
icon={<Resolution />}
label={t('widgets.background.resolution')}
value={`${width}x${height}`}
/>
{width !== null && height !== null && (
<MetadataItem
icon={<Resolution />}
label={t('widgets.background.resolution')}
value={`${width}x${height}`}
/>
)}
{colour && (
<MetadataItem
@@ -304,8 +306,8 @@ function ActionButtons({
function PhotoInformation({ info, url, api }) {
const t = useT();
const navigate = useNavigate();
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [width, setWidth] = useState(null);
const [height, setHeight] = useState(null);
const [shareModal, openShareModal] = useState(false);
const [excludeModal, openExcludeModal] = useState(false);
const [favouriteTooltipText, setFavouriteTooltipText] = useState(t('widgets.quote.favourite'));
@@ -325,7 +327,7 @@ function PhotoInformation({ info, url, api }) {
if (typeof info.location === 'string') {
return info.location.split(',').slice(-2).join(', ').trim();
}
return info.location?.name || 'Unknown Location';
return info.location?.name || null;
};
if (info.hidden === true || !info.credit) {
@@ -437,9 +439,11 @@ function PhotoInformation({ info, url, api }) {
{/* PRIMARY SECTION - Always Visible */}
<div className="primary-content">
<div className="photoInformation-text">
<span className="title" title={info.description || ''}>
{getPrimaryText()}
</span>
{getPrimaryText() && (
<span className="title" title={info.description || ''}>
{getPrimaryText()}
</span>
)}
<span className="subtitle attribution" id="credit">
{photo} {credit}
</span>

View File

@@ -31,6 +31,12 @@ export function useSuggestedPacks(category, limit = 4, minToShow = 2) {
}, [timestampKey, cacheExpiryMs]);
const fetchSuggestions = useCallback(async () => {
if (localStorage.getItem('offlineMode') === 'true') {
setSuggestions(null);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);

View File

@@ -39,8 +39,8 @@ const MessageOptions = () => {
setMessages(updatedMessages);
localStorage.setItem('messages', JSON.stringify(updatedMessages));
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
};
const MESSAGE_SECTION = 'modals.main.settings.sections.message';

View File

@@ -9,12 +9,8 @@ import { fetchAuthorFromWikidata } from 'utils/wikidata/wikidataAuthorFetcher';
* Custom hook for loading quote data from various sources
*/
export function useQuoteLoader(updateQuote) {
const getAuthorLink = useCallback((author) => {
return localStorage.getItem('authorLink') === 'false' || author === 'Unknown'
? null
: `https://${variables.languagecode.split('_')[0]}.wikipedia.org/wiki/${author
.split(' ')
.join('_')}`;
const getAuthorLink = useCallback((_author) => {
return null;
}, []);
/**

View File

@@ -72,8 +72,8 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
setCustomQuote(updatedCustomQuote);
localStorage.setItem('customQuote', JSON.stringify(updatedCustomQuote));
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
};
const modifyCustomQuote = (type, index) => {

View File

@@ -1,6 +1,8 @@
import EventBus from 'utils/eventbus';
function showReminder() {
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
}
export const settingsHandler = {

View File

@@ -4,10 +4,9 @@ import variables from 'config/variables';
import { refreshAPIPackCache } from 'features/background/api/photoPackAPI';
import { getHandler } from './handlerRegistry';
// todo: relocate this function
function showReminder() {
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
}
/**

View File

@@ -2,10 +2,9 @@ import EventBus from 'utils/eventbus';
import { clearQueuesOnSettingChange } from 'utils/queueOperations';
import { getHandler } from './handlerRegistry';
// todo: relocate this function
function showReminder() {
document.querySelector('.reminder-info').style.display = 'flex';
localStorage.setItem('showReminder', true);
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
}
export function uninstall(type, name) {