275 lines
9.3 KiB
TypeScript
275 lines
9.3 KiB
TypeScript
/*
|
|
* Copyright (C) 2026 Fluxer Contributors
|
|
*
|
|
* This file is part of Fluxer.
|
|
*
|
|
* Fluxer is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Fluxer is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import {clearPendingBulkDeletionNagbarDismissal} from '~/actions/NagbarActionCreators';
|
|
import AppStorage from '~/lib/AppStorage';
|
|
import DeveloperOptionsStore from '~/stores/DeveloperOptionsStore';
|
|
import NagbarStore from '~/stores/NagbarStore';
|
|
import UserStore from '~/stores/UserStore';
|
|
import {isDesktop} from '~/utils/NativeUtils';
|
|
import * as NotificationUtils from '~/utils/NotificationUtils';
|
|
import {isStandalonePwa} from '~/utils/PwaUtils';
|
|
import {type AppLayoutState, type NagbarConditions, type NagbarState, NagbarType, UPDATE_DISMISS_KEY} from './types';
|
|
|
|
export const useAppLayoutState = (): AppLayoutState => {
|
|
const [isStandalone, setIsStandalone] = React.useState(isStandalonePwa());
|
|
|
|
React.useEffect(() => {
|
|
const checkStandalone = () => {
|
|
setIsStandalone(isStandalonePwa());
|
|
};
|
|
|
|
checkStandalone();
|
|
|
|
document.documentElement.classList.toggle('is-standalone', isStandalone);
|
|
|
|
return () => {
|
|
document.documentElement.classList.remove('is-standalone');
|
|
};
|
|
}, [isStandalone]);
|
|
|
|
return {isStandalone};
|
|
};
|
|
|
|
export const useNagbarConditions = (): NagbarConditions => {
|
|
const user = UserStore.currentUser;
|
|
const nagbarState = NagbarStore;
|
|
const premiumOverrideType = DeveloperOptionsStore.premiumTypeOverride;
|
|
const premiumWillCancel = user?.premiumWillCancel ?? false;
|
|
const isMockPremium = premiumOverrideType != null && premiumOverrideType > 0;
|
|
const previousPendingBulkDeletionKeyRef = React.useRef<string | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
const handleVersionUpdateAvailable = () => {
|
|
if (nagbarState.forceUpdateAvailable) {
|
|
return;
|
|
}
|
|
|
|
const dismissedUntilStr = AppStorage.getItem(UPDATE_DISMISS_KEY);
|
|
if (dismissedUntilStr) {
|
|
const dismissedUntil = Number.parseInt(dismissedUntilStr, 10);
|
|
if (Date.now() < dismissedUntil) {
|
|
return;
|
|
}
|
|
localStorage.removeItem(UPDATE_DISMISS_KEY);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('version-update-available', handleVersionUpdateAvailable as EventListener);
|
|
|
|
return () => {
|
|
window.removeEventListener('version-update-available', handleVersionUpdateAvailable as EventListener);
|
|
};
|
|
}, [nagbarState.forceUpdateAvailable]);
|
|
|
|
const shouldShowDesktopNotification = (() => {
|
|
if (!NotificationUtils.hasNotification()) return false;
|
|
if (typeof Notification !== 'undefined') {
|
|
if (Notification.permission === 'granted') return false;
|
|
if (Notification.permission === 'denied') return false;
|
|
}
|
|
return true;
|
|
})();
|
|
|
|
const canShowPremiumGracePeriod = (() => {
|
|
if (nagbarState.forceHidePremiumGracePeriod) return false;
|
|
if (nagbarState.forcePremiumGracePeriod) return true;
|
|
if (!user?.premiumUntil || user.premiumType === 2 || premiumWillCancel) return false;
|
|
const now = new Date();
|
|
const expiryDate = new Date(user.premiumUntil);
|
|
const gracePeriodMs = 3 * 24 * 60 * 60 * 1000;
|
|
const graceEndDate = new Date(expiryDate.getTime() + gracePeriodMs);
|
|
const isInGracePeriod = now > expiryDate && now <= graceEndDate;
|
|
return isInGracePeriod;
|
|
})();
|
|
|
|
const canShowPremiumExpired = (() => {
|
|
if (nagbarState.forceHidePremiumExpired) return false;
|
|
if (nagbarState.forcePremiumExpired) return true;
|
|
if (!user?.premiumUntil || user.premiumType === 2 || premiumWillCancel) return false;
|
|
const now = new Date();
|
|
const expiryDate = new Date(user.premiumUntil);
|
|
const gracePeriodMs = 3 * 24 * 60 * 60 * 1000;
|
|
const expiredStateDurationMs = 30 * 24 * 60 * 60 * 1000;
|
|
const graceEndDate = new Date(expiryDate.getTime() + gracePeriodMs);
|
|
const expiredStateEndDate = new Date(expiryDate.getTime() + expiredStateDurationMs);
|
|
const isExpired = now > graceEndDate;
|
|
const showExpiredState = isExpired && now <= expiredStateEndDate;
|
|
return showExpiredState;
|
|
})();
|
|
|
|
const canShowGiftInventory = (() => {
|
|
if (nagbarState.forceHideGiftInventory) return false;
|
|
if (nagbarState.forceGiftInventory) return true;
|
|
return Boolean(user?.hasUnreadGiftInventory && !nagbarState.giftInventoryDismissed);
|
|
})();
|
|
|
|
const canShowPremiumOnboarding = (() => {
|
|
if (nagbarState.forceHidePremiumOnboarding) return false;
|
|
if (nagbarState.forcePremiumOnboarding) return true;
|
|
if (isMockPremium) return false;
|
|
return Boolean(
|
|
user?.isPremium() && !user?.hasDismissedPremiumOnboarding && !nagbarState.premiumOnboardingDismissed,
|
|
);
|
|
})();
|
|
|
|
const isNativeDesktop = isDesktop();
|
|
const isMobileDevice = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
const isDesktopBrowser = !isNativeDesktop && !isMobileDevice;
|
|
|
|
const canShowDesktopDownload = (() => {
|
|
if (nagbarState.forceHideDesktopDownload) return false;
|
|
if (nagbarState.forceDesktopDownload) return true;
|
|
return isDesktopBrowser && !nagbarState.desktopDownloadDismissed;
|
|
})();
|
|
|
|
const canShowMobileDownload = (() => {
|
|
if (nagbarState.forceHideMobileDownload) return false;
|
|
if (nagbarState.forceMobileDownload) return true;
|
|
return Boolean(!isMobileDevice && user?.usedMobileClient === false && !nagbarState.mobileDownloadDismissed);
|
|
})();
|
|
|
|
const pendingBulkDeletion = user?.getPendingBulkMessageDeletion();
|
|
const pendingBulkDeletionKey = pendingBulkDeletion?.scheduledAt.toISOString() ?? null;
|
|
|
|
React.useEffect(() => {
|
|
const previousKey = previousPendingBulkDeletionKeyRef.current;
|
|
if (previousKey && previousKey !== pendingBulkDeletionKey) {
|
|
clearPendingBulkDeletionNagbarDismissal(previousKey);
|
|
}
|
|
|
|
previousPendingBulkDeletionKeyRef.current = pendingBulkDeletionKey;
|
|
}, [pendingBulkDeletionKey]);
|
|
|
|
const hasPendingBulkMessageDeletion = Boolean(
|
|
pendingBulkDeletion && !nagbarState.hasPendingBulkDeletionDismissed(pendingBulkDeletionKey),
|
|
);
|
|
|
|
const canShowGuildMembershipCta = (() => {
|
|
if (nagbarState.forceHideGuildMembershipCta) return false;
|
|
if (nagbarState.forceGuildMembershipCta) return true;
|
|
if (!user) return false;
|
|
return !nagbarState.guildMembershipCtaDismissed;
|
|
})();
|
|
|
|
return {
|
|
userIsUnclaimed: nagbarState.forceHideUnclaimedAccount
|
|
? false
|
|
: nagbarState.forceUnclaimedAccount
|
|
? true
|
|
: Boolean(user && !user.isClaimed()),
|
|
userNeedsVerification: nagbarState.forceHideEmailVerification
|
|
? false
|
|
: nagbarState.forceEmailVerification
|
|
? true
|
|
: Boolean(user?.isClaimed() && !user.verified),
|
|
canShowDesktopNotification: nagbarState.forceHideDesktopNotification
|
|
? false
|
|
: nagbarState.forceDesktopNotification
|
|
? true
|
|
: shouldShowDesktopNotification && !nagbarState.desktopNotificationDismissed,
|
|
canShowPremiumGracePeriod,
|
|
canShowPremiumExpired,
|
|
canShowPremiumOnboarding,
|
|
canShowGiftInventory,
|
|
canShowDesktopDownload,
|
|
canShowMobileDownload,
|
|
hasPendingBulkMessageDeletion,
|
|
canShowGuildMembershipCta,
|
|
};
|
|
};
|
|
|
|
export const useActiveNagbars = (conditions: NagbarConditions): Array<NagbarState> => {
|
|
return React.useMemo(() => {
|
|
const undismissibleTypes = new Set<NagbarType>([
|
|
NagbarType.UNCLAIMED_ACCOUNT,
|
|
NagbarType.EMAIL_VERIFICATION,
|
|
NagbarType.BULK_DELETE_PENDING,
|
|
]);
|
|
|
|
const nagbars: Array<NagbarState> = [
|
|
{
|
|
type: NagbarType.BULK_DELETE_PENDING,
|
|
priority: 0,
|
|
visible: conditions.hasPendingBulkMessageDeletion,
|
|
},
|
|
{
|
|
type: NagbarType.UNCLAIMED_ACCOUNT,
|
|
priority: 1,
|
|
visible: conditions.userIsUnclaimed,
|
|
},
|
|
{
|
|
type: NagbarType.GUILD_MEMBERSHIP_CTA,
|
|
priority: 2,
|
|
visible: conditions.canShowGuildMembershipCta,
|
|
},
|
|
{
|
|
type: NagbarType.EMAIL_VERIFICATION,
|
|
priority: 3,
|
|
visible: conditions.userNeedsVerification,
|
|
},
|
|
{
|
|
type: NagbarType.PREMIUM_GRACE_PERIOD,
|
|
priority: 4,
|
|
visible: conditions.canShowPremiumGracePeriod,
|
|
},
|
|
{
|
|
type: NagbarType.PREMIUM_EXPIRED,
|
|
priority: 5,
|
|
visible: conditions.canShowPremiumExpired,
|
|
},
|
|
{
|
|
type: NagbarType.PREMIUM_ONBOARDING,
|
|
priority: 6,
|
|
visible: conditions.canShowPremiumOnboarding,
|
|
},
|
|
{
|
|
type: NagbarType.DESKTOP_NOTIFICATION,
|
|
priority: 7,
|
|
visible: conditions.canShowDesktopNotification,
|
|
},
|
|
{
|
|
type: NagbarType.GIFT_INVENTORY,
|
|
priority: 8,
|
|
visible: conditions.canShowGiftInventory,
|
|
},
|
|
{
|
|
type: NagbarType.DESKTOP_DOWNLOAD,
|
|
priority: 9,
|
|
visible: conditions.canShowDesktopDownload,
|
|
},
|
|
{
|
|
type: NagbarType.MOBILE_DOWNLOAD,
|
|
priority: 10,
|
|
visible: conditions.canShowMobileDownload,
|
|
},
|
|
];
|
|
|
|
const visibleNagbars = nagbars.filter((nagbar) => nagbar.visible).sort((a, b) => a.priority - b.priority);
|
|
|
|
const undismissibleNagbars = visibleNagbars.filter((nagbar) => undismissibleTypes.has(nagbar.type));
|
|
const regularNagbars = visibleNagbars.filter((nagbar) => !undismissibleTypes.has(nagbar.type));
|
|
|
|
const selectedRegularNagbars = regularNagbars.length > 0 ? [regularNagbars[0]] : [];
|
|
|
|
return [...undismissibleNagbars, ...selectedRegularNagbars].sort((a, b) => a.priority - b.priority);
|
|
}, [conditions]);
|
|
};
|