feat(nagbar): add invite CTA to Fluxer HQ (#68)

This commit is contained in:
Hampus
2026-01-07 16:06:43 +01:00
committed by GitHub
parent 288477a5b3
commit a61b5802f3
40 changed files with 10863 additions and 10579 deletions

View File

@@ -28,9 +28,17 @@ interface NagbarButtonProps {
isMobile: boolean;
className?: string;
disabled?: boolean;
submitting?: boolean;
}
export const NagbarButton = ({children, onClick, isMobile, className, disabled = false}: NagbarButtonProps) => {
export const NagbarButton = ({
children,
onClick,
isMobile,
className,
disabled = false,
submitting,
}: NagbarButtonProps) => {
return (
<Button
variant="inverted-outline"
@@ -40,6 +48,7 @@ export const NagbarButton = ({children, onClick, isMobile, className, disabled =
className={clsx(styles.button, className)}
onClick={onClick}
disabled={disabled}
submitting={submitting}
>
{children}
</Button>

View File

@@ -25,6 +25,7 @@ import {DesktopDownloadNagbar} from './nagbars/DesktopDownloadNagbar';
import {DesktopNotificationNagbar} from './nagbars/DesktopNotificationNagbar';
import {EmailVerificationNagbar} from './nagbars/EmailVerificationNagbar';
import {GiftInventoryNagbar} from './nagbars/GiftInventoryNagbar';
import {GuildMembershipCtaNagbar} from './nagbars/GuildMembershipCtaNagbar';
import {MobileDownloadNagbar} from './nagbars/MobileDownloadNagbar';
import {PendingBulkDeletionNagbar} from './nagbars/PendingBulkDeletionNagbar';
import {PremiumExpiredNagbar} from './nagbars/PremiumExpiredNagbar';
@@ -66,6 +67,8 @@ export const NagbarContainer: React.FC<NagbarContainerProps> = observer(({nagbar
return <DesktopDownloadNagbar key={nagbar.type} isMobile={mobileLayout.enabled} />;
case NagbarType.MOBILE_DOWNLOAD:
return <MobileDownloadNagbar key={nagbar.type} isMobile={mobileLayout.enabled} />;
case NagbarType.GUILD_MEMBERSHIP_CTA:
return <GuildMembershipCtaNagbar key={nagbar.type} isMobile={mobileLayout.enabled} />;
default:
return null;
}

View File

@@ -162,6 +162,13 @@ export const useNagbarConditions = (): NagbarConditions => {
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
@@ -185,6 +192,7 @@ export const useNagbarConditions = (): NagbarConditions => {
canShowDesktopDownload,
canShowMobileDownload,
hasPendingBulkMessageDeletion,
canShowGuildMembershipCta,
};
};
@@ -208,23 +216,28 @@ export const useActiveNagbars = (conditions: NagbarConditions): Array<NagbarStat
visible: conditions.userIsUnclaimed,
},
{
type: NagbarType.EMAIL_VERIFICATION,
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: 3,
priority: 4,
visible: conditions.canShowPremiumGracePeriod,
},
{
type: NagbarType.PREMIUM_EXPIRED,
priority: 4,
priority: 5,
visible: conditions.canShowPremiumExpired,
},
{
type: NagbarType.PREMIUM_ONBOARDING,
priority: 5,
priority: 6,
visible: conditions.canShowPremiumOnboarding,
},
{

View File

@@ -0,0 +1,103 @@
/*
* 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 {Trans} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import React from 'react';
import * as ModalActionCreators from '~/actions/ModalActionCreators';
import {modal} from '~/actions/ModalActionCreators';
import {Nagbar} from '~/components/layout/Nagbar';
import {NagbarButton} from '~/components/layout/NagbarButton';
import {NagbarContent} from '~/components/layout/NagbarContent';
import {InviteAcceptModal} from '~/components/modals/InviteAcceptModal';
import AuthenticationStore from '~/stores/AuthenticationStore';
import GuildMemberStore from '~/stores/GuildMemberStore';
import InviteStore from '~/stores/InviteStore';
import NagbarStore from '~/stores/NagbarStore';
import {isGuildInvite} from '~/types/InviteTypes';
const FLUXER_HQ_INVITE_CODE = 'fluxer-hq';
export const GuildMembershipCtaNagbar = observer(({isMobile}: {isMobile: boolean}) => {
const currentUserId = AuthenticationStore.currentUserId;
const inviteState = InviteStore.invites.get(FLUXER_HQ_INVITE_CODE);
const invite = inviteState?.data ?? null;
const [isSubmitting, setIsSubmitting] = React.useState(false);
if (!currentUserId) {
return null;
}
if (invite && isGuildInvite(invite)) {
const guildId = invite.guild.id;
const isMember = Boolean(GuildMemberStore.getMember(guildId, currentUserId));
if (isMember) {
return null;
}
}
const handleJoinGuild = async () => {
if (isSubmitting) return;
setIsSubmitting(true);
try {
const module = await import('~/actions/InviteActionCreators');
await module.fetchWithCoalescing(FLUXER_HQ_INVITE_CODE);
const loadedInvite = InviteStore.invites.get(FLUXER_HQ_INVITE_CODE)?.data ?? null;
if (loadedInvite && isGuildInvite(loadedInvite)) {
const guildId = loadedInvite.guild.id;
const isMember = Boolean(GuildMemberStore.getMember(guildId, currentUserId));
if (isMember) {
NagbarStore.guildMembershipCtaDismissed = true;
return;
}
}
} catch {
} finally {
setIsSubmitting(false);
ModalActionCreators.push(modal(() => <InviteAcceptModal code={FLUXER_HQ_INVITE_CODE} />));
}
};
const handleDismiss = () => {
NagbarStore.guildMembershipCtaDismissed = true;
};
return (
<Nagbar
isMobile={isMobile}
backgroundColor="var(--brand-primary)"
textColor="var(--text-on-brand-primary)"
onDismiss={handleDismiss}
dismissible={true}
>
<NagbarContent
isMobile={isMobile}
message={<Trans>Join Fluxer HQ to chat with the team and stay up to date on the latest!</Trans>}
actions={
<NagbarButton isMobile={isMobile} onClick={handleJoinGuild} submitting={isSubmitting} disabled={isSubmitting}>
<Trans>Join Fluxer HQ</Trans>
</NagbarButton>
}
/>
</Nagbar>
);
});

View File

@@ -28,6 +28,7 @@ export const NagbarType = {
BULK_DELETE_PENDING: 'bulk-delete-pending',
DESKTOP_DOWNLOAD: 'desktop-download',
MOBILE_DOWNLOAD: 'mobile-download',
GUILD_MEMBERSHIP_CTA: 'guild-membership-cta',
} as const;
export type NagbarType = (typeof NagbarType)[keyof typeof NagbarType];
@@ -53,6 +54,7 @@ export interface NagbarConditions {
canShowDesktopDownload: boolean;
canShowMobileDownload: boolean;
hasPendingBulkMessageDeletion: boolean;
canShowGuildMembershipCta: boolean;
}
export const UPDATE_DISMISS_KEY = 'fluxer_update_dismissed_until';