Skip to content

Commit

Permalink
feat: add desktop renewal notice on the homepage
Browse files Browse the repository at this point in the history
  • Loading branch information
th0rgall committed Jan 4, 2024
1 parent 955a1d2 commit fcfb172
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 56 deletions.
74 changes: 55 additions & 19 deletions src/lib/components/Nav/Top/TopNav.svelte
Original file line number Diff line number Diff line change
@@ -1,36 +1,72 @@
<script>
import { _ } from 'svelte-i18n';
import { _, locale } from 'svelte-i18n';
import routes from '$lib/routes';
import NavLink from './NavLink.svelte';
import UserDropdown from './UserDropdown.svelte';
import { user } from '$lib/stores/auth';
import WtmgLogo from '../../UI/WTMGLogo.svelte';
import { COMMUNITY_FORUM_URL } from '$lib/constants';
import NewBadge from '../NewBadge.svelte';
import { COMMUNITY_FORUM_URL, MEMBERSHIP_YEARLY_AMOUNTS } from '$lib/constants';
import { anchorText } from '$lib/util/translation-helpers';
import { PlausibleEvent } from '$lib/types/Plausible';
import trackEvent from '$lib/util/track-plausible';
import { hasOpenRenewalInvoice, subscriptionJustEnded } from '$lib/stores/subscription';
$: firstName = $user ? $user.firstName : '';
$: shouldShowRenewalTopBar = $user && $user.stripeSubscription && $subscriptionJustEnded;
$: shouldShowTopBar = !$user?.superfan || shouldShowRenewalTopBar;
</script>
<nav>
{#if !$user?.superfan}
{#if shouldShowTopBar}
<div class="nav-extra">
<span
><strong style="font-weight: 500;">{$_('navigation.membership-notice.prompt')}</strong
>{@html $_('navigation.membership-notice.answer', {
values: {
linkText: anchorText({
href: routes.ABOUT_MEMBERSHIP,
track: [PlausibleEvent.VISIT_ABOUT_MEMBERSHIP, { source: 'top_navbar_announcement' }],
linkText: $_('navigation.membership-notice.link-text'),
style: 'text-decoration: underline; cursor: pointer;',
newtab: false
})
}
})}
</span>
<!-- Inform non-superfans of the membership offering -->
{#if !$user?.superfan}
<span
><strong style="font-weight: 500;">{$_('navigation.membership-notice.prompt')}</strong
>{@html $_('navigation.membership-notice.answer', {
values: {
linkText: anchorText({
href: routes.ABOUT_MEMBERSHIP,
track: [
PlausibleEvent.VISIT_ABOUT_MEMBERSHIP,
{ source: 'top_navbar_announcement' }
],
linkText: $_('navigation.membership-notice.link-text'),
style: 'text-decoration: underline; cursor: pointer;',
newtab: false
})
}
})}
</span>
{:else if shouldShowRenewalTopBar && $user.stripeSubscription}
<!-- Inform renewal amount -->
<span
><strong style="font-weight: 500;">
{$_('navigation.membership-expired-notice.prompt', {
values: {
amount:
($locale !== 'fr' ? '' : '') +
(MEMBERSHIP_YEARLY_AMOUNTS[$user.stripeSubscription.priceId] || 60) +
($locale === 'fr' ? '' : '')
}
})}</strong
>{' '}{@html $_('navigation.membership-expired-notice.answer', {
values: {
linkText: anchorText({
href:
$hasOpenRenewalInvoice && $user.stripeSubscription.renewalInvoiceLink
? $user.stripeSubscription.renewalInvoiceLink
: `${routes.ABOUT_MEMBERSHIP}#pricing`,
// TODO: this isn't accurate, the hosted invoice page visitors aren't about_membership page visitors
track: [PlausibleEvent.VISIT_ABOUT_MEMBERSHIP, { source: 'top_navbar_renewal' }],
linkText: $_('navigation.membership-expired-notice.link-text'),
style: 'text-decoration: underline; cursor: pointer;',
newtab: false
})
}
})}
</span>
{/if}
</div>
{/if}
<div class="main-nav">
Expand Down Expand Up @@ -81,7 +117,7 @@
<!-- !important is necessary because the svelte component-scoped CSS otherwise has higher CSS specificity -->
<!-- -->
<!-- Hide the extra bar vvvv (prettier duplicates this comment if put within the block on every save) -->
{#if $user?.superfan}
{#if !shouldShowTopBar}
<style>
.nav-extra {
display: none !important;
Expand Down
7 changes: 7 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ export const nonMemberMaxZoom = 12;
export const memberMaxZoom = 22;

export const NOTIFICATION_PROMPT_DISMISSED_COOKIE = 'notif_dismissed';

// In EUR
export const MEMBERSHIP_YEARLY_AMOUNTS: { [key: string]: number } = {
[import.meta.env.VITE_STRIPE_PRICE_ID_REDUCED]: 36,
[import.meta.env.VITE_STRIPE_PRICE_ID_NORMAL]: 60,
[import.meta.env.VITE_STRIPE_PRICE_ID_SOLIDARITY]: 120
};
55 changes: 55 additions & 0 deletions src/lib/stores/subscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { derived } from 'svelte/store';
import { user } from './auth';

export const hasValidSubscription = derived(user, ($user) =>
$user?.superfan
? $user.stripeSubscription &&
$user.stripeSubscription.status === 'active' &&
$user.stripeSubscription.latestInvoiceStatus === 'paid'
: false
);

const nowSeconds = () =>
// 1827649800 + 3600 * 24 * 7 + 3600;
new Date().valueOf() / 1000;

// nowSeconds just as a default value
const sevenDayMarkSec = derived(user, ($user) =>
$user?.stripeSubscription?.currentPeriodStart != null
? $user.stripeSubscription.currentPeriodStart + 3600 * 24 * 7
: null
);

// The last cycle of the subscription ended at most 30 days ago, and the latest
// invoice wasn't paid
//
// Note: until after 7 days, the current subscription can be renewed.
// After that, a new one has to be completed.
export const subscriptionJustEnded = derived(
user,
($user) =>
$user?.stripeSubscription &&
$user.stripeSubscription.latestInvoiceStatus !== 'paid' &&
// This is not the initial invoice
$user.stripeSubscription.currentPeriodStart !== $user.stripeSubscription.startDate &&
// It was at most 30 days since the last cycle ended
nowSeconds() < $user.stripeSubscription.currentPeriodStart + 3600 * 24 * 30
);

export const hasOpenRenewalInvoice = derived(
[user, sevenDayMarkSec],
([$user, $sevenDayMarkSec]) =>
$sevenDayMarkSec &&
$user?.superfan &&
$user.stripeSubscription &&
$user.stripeSubscription.latestInvoiceStatus === 'open' &&
$user.stripeSubscription.renewalInvoiceLink &&
// The current second epoch is less than 7 days from the current (= new/unpaid) period start
nowSeconds() < $sevenDayMarkSec
);

export const shouldPromptForNewSubscription = derived(
[subscriptionJustEnded, sevenDayMarkSec],
([$subscriptionJustEnded, $sevenDayMarkSec]) =>
$sevenDayMarkSec && $subscriptionJustEnded && nowSeconds() >= $sevenDayMarkSec
);
5 changes: 5 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
"prompt": "New: ",
"answer": "a WTMG membership for all slow travellers. Find out more {linkText}!",
"link-text": "here"
},
"membership-expired-notice": {
"prompt": "Want to renew your WTMG membership ({amount}/year) now?",
"answer": "Then click {linkText}. Thank you for your support!",
"link-text": "here"
}
},
"index": {
Expand Down
5 changes: 5 additions & 0 deletions src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
"prompt": "Nouveau : ",
"answer": "une adhésion WTMG pour tous les voyageurs lents. Pour en savoir plus, {linkText}",
"link-text": "cliquez ici !"
},
"membership-expired-notice": {
"prompt": "Souhaitez-vous renouveler votre adhésion WTMG ({amount}/an) dès maintenant ?",
"answer": "{linkText}. Merci pour votre soutien !",
"link-text": "Cliquez ici"
}
},
"index": {
Expand Down
5 changes: 5 additions & 0 deletions src/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
"prompt": "Nieuw: ",
"answer": "een WTMG-lidmaatschap voor alle trage reizigers. Lees er {linkText} meer over!",
"link-text": "hier"
},
"membership-expired-notice": {
"prompt": "Wil je je WTMG-lidmaatschap ({amount}/jaar) nu verlengen?",
"answer": "Klik dan {linkText}. Bedankt voor je steun!",
"link-text": "hier"
}
},
"index": {
Expand Down
47 changes: 10 additions & 37 deletions src/routes/account/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
import EmailChangeModal from './EmailChangeModal.svelte';
import { countryNames } from '$lib/stores/countryNames';
import NotificationSection from './NotificationSection.svelte';
import {
hasOpenRenewalInvoice,
hasValidSubscription,
shouldPromptForNewSubscription,
subscriptionJustEnded
} from '$lib/stores/subscription';
let showAccountDeletionModal = false;
let showEmailChangeModal = false;
Expand Down Expand Up @@ -70,39 +76,6 @@
}
};
$: hasValidSubscription =
$user?.superfan &&
$user.stripeSubscription &&
$user.stripeSubscription.status === 'active' &&
$user.stripeSubscription.latestInvoiceStatus === 'paid';
const nowSeconds = () =>
// 1827649800 + 3600 * 24 * 7 + 3600;
new Date().valueOf() / 1000;
// nowSeconds just as a default value
$: sevenDayMarkSec =
($user?.stripeSubscription?.currentPeriodStart || nowSeconds()) + 3600 * 24 * 7;
// Note: until after 7 days, the current subscription can be renewed.
// After that, a new one has to be completed.
$: subscriptionJustEnded =
$user?.stripeSubscription &&
$user.stripeSubscription.latestInvoiceStatus !== 'paid' &&
// This is not the initial invoice
$user.stripeSubscription.currentPeriodStart !== $user.stripeSubscription.startDate &&
// We are 30 days until the last cycle ended
nowSeconds() < $user.stripeSubscription.currentPeriodStart + 3600 * 24 * 30;
$: hasOpenRenewalInvoice =
$user?.superfan &&
$user.stripeSubscription &&
$user.stripeSubscription.latestInvoiceStatus === 'open' &&
$user.stripeSubscription.renewalInvoiceLink &&
// The current second epoch is less than 7 days from the current (= new/unpaid) period start
nowSeconds() < sevenDayMarkSec;
$: promptForNewSubscription = subscriptionJustEnded && nowSeconds() >= sevenDayMarkSec;
const formatDate = (locale: string, date: Date) =>
new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date);
</script>
Expand Down Expand Up @@ -138,7 +111,7 @@
{$countryNames[$user.countryCode]}
</div>
</div>
{#if hasValidSubscription && $user.stripeSubscription}
{#if $hasValidSubscription && $user.stripeSubscription}
<div class="superfan-validity">
<p>
✅ {$_('account.superfan.valid', {
Expand All @@ -151,16 +124,16 @@
})}
</p>
</div>
{:else if subscriptionJustEnded}
{:else if $subscriptionJustEnded}
<div class="superfan-validity invalid">
<p>{@html $_('account.superfan.just-ended')}</p>
<Button
xxsmall
uppercase
on:click={() => {
if (hasOpenRenewalInvoice) {
if ($hasOpenRenewalInvoice) {
window.open($user?.stripeSubscription?.renewalInvoiceLink, '_blank');
} else if (promptForNewSubscription) {
} else if ($shouldPromptForNewSubscription) {
window.location.href = `${routes.ABOUT_MEMBERSHIP}#pricing`;
}
}}>{$_('account.superfan.renew-btn-text')}</Button
Expand Down

0 comments on commit fcfb172

Please sign in to comment.