Skip to content

Commit e23f965

Browse files
Merge pull request #2002 from appwrite/feat-plan-update
Plan update
2 parents 4b94159 + 6ab6ee8 commit e23f965

File tree

9 files changed

+277
-32
lines changed

9 files changed

+277
-32
lines changed

src/lib/components/billing/gradientBanner.svelte

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<script lang="ts">
22
import { base } from '$app/paths';
3-
import { isTabletViewport } from '$lib/stores/viewport';
4-
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
53
import { Button } from '$lib/elements/forms';
64
import { Icon } from '@appwrite.io/pink-svelte';
75
import { IconX } from '@appwrite.io/pink-icons-svelte';
6+
import { isTabletViewport } from '$lib/stores/viewport';
7+
import PinkBackground from '$lib/images/pink-background.svg';
8+
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
9+
10+
export let variant: 'gradient' | 'image' = 'gradient';
811
912
let container: HTMLElement;
1013
const dispatch = createEventDispatcher();
@@ -19,8 +22,7 @@
1922
const alertHeight = container?.getBoundingClientRect()?.height || 0;
2023
const { header, sidebar, content } = queryLayoutElements();
2124
const headerHeight = header?.getBoundingClientRect().height || 0;
22-
const offset = alertHeight + (!isTabletViewport && header ? headerHeight : 0);
23-
25+
const offset = alertHeight + (!$isTabletViewport && header ? headerHeight : 0);
2426
if (header) header.style.top = `${alertHeight}px`;
2527
if (sidebar) {
2628
sidebar.style.top = `${offset}px`;
@@ -35,23 +37,32 @@
3537

3638
<svelte:window on:resize={setNavigationHeight} />
3739

38-
<div bind:this={container} class="top-banner alert is-action is-action-and-top-sticky">
39-
<div class="top-banner-bg">
40-
<div class="top-banner-bg-1">
41-
<img
42-
src={`${base}/images/top-banner/bg-pink-desktop.svg`}
43-
width="1283"
44-
height="1278"
45-
alt="" />
40+
<div
41+
bind:this={container}
42+
class:darker={variant === 'image'}
43+
class="top-banner alert is-action is-action-and-top-sticky">
44+
{#if variant === 'gradient'}
45+
<div class="top-banner-bg">
46+
<div class="top-banner-bg-1">
47+
<img
48+
src={`${base}/images/top-banner/bg-pink-desktop.svg`}
49+
width="1283"
50+
height="1278"
51+
alt="" />
52+
</div>
53+
<div class="top-banner-bg-2">
54+
<img
55+
src={`${base}/images/top-banner/bg-mint-desktop.svg`}
56+
width="1051"
57+
height="1271"
58+
alt="" />
59+
</div>
4660
</div>
47-
<div class="top-banner-bg-2">
48-
<img
49-
src={`${base}/images/top-banner/bg-mint-desktop.svg`}
50-
width="1051"
51-
height="1271"
52-
alt="" />
61+
{:else}
62+
<div class="centered-image-only">
63+
<img src={PinkBackground} width="1283" height="1278" alt="" />
5364
</div>
54-
</div>
65+
{/if}
5566

5667
<div class="top-banner-content u-color-text-primary">
5768
<slot />
@@ -62,17 +73,39 @@
6273
</Button>
6374
</div>
6475

65-
<style>
76+
<style lang="scss">
6677
.alert {
6778
top: 0;
6879
width: 100%;
6980
z-index: 100;
7081
position: fixed;
82+
padding: 0.8rem;
83+
84+
&.darker {
85+
background: var(--bgcolor-neutral-default);
86+
}
87+
}
88+
89+
.centered-image-only {
90+
top: 0;
91+
width: 100%;
92+
height: 100%;
93+
padding-left: 25vw;
94+
position: absolute;
95+
border-bottom: 1px solid var(--border-neutral);
96+
97+
@media (max-width: 768px) {
98+
& img {
99+
max-width: 100vw;
100+
}
101+
}
71102
}
72103
73-
@media (max-width: 768px) {
74-
:global(.top-banner-button.position) {
104+
:global(.top-banner-button.position) {
105+
z-index: 1;
106+
@media (max-width: 768px) {
75107
position: absolute;
108+
padding-block-start: 1rem;
76109
padding-block-end: 3.7625rem;
77110
}
78111
}

src/lib/components/billing/planSelection.svelte

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script lang="ts">
2-
import { BillingPlan } from '$lib/constants';
2+
import { BASE_BILLING_PLANS, BillingPlan } from '$lib/constants';
33
import { formatCurrency } from '$lib/helpers/numbers';
44
import { plansInfo, type Tier, tierFree, tierPro, tierScale } from '$lib/stores/billing';
5-
import { organization } from '$lib/stores/organization';
5+
import { currentPlan, organization } from '$lib/stores/organization';
66
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
77
import { LabelCard } from '..';
88
@@ -14,6 +14,8 @@
1414
$: freePlan = $plansInfo.get(BillingPlan.FREE);
1515
$: proPlan = $plansInfo.get(BillingPlan.PRO);
1616
$: scalePlan = $plansInfo.get(BillingPlan.SCALE);
17+
18+
$: isBasePlan = BASE_BILLING_PLANS.includes($currentPlan?.$id);
1719
</script>
1820

1921
<Layout.Stack>
@@ -72,4 +74,25 @@
7274
{formatCurrency(scalePlan?.price ?? 0)} per month + usage
7375
</Typography.Text>
7476
</LabelCard>
77+
{#if $currentPlan && !isBasePlan}
78+
<LabelCard
79+
name="plan"
80+
bind:group={billingPlan}
81+
value={$currentPlan.$id}
82+
title={$currentPlan.name}>
83+
<svelte:fragment slot="action">
84+
{#if $organization?.billingPlan === $currentPlan.$id && !isNewOrg}
85+
<Badge variant="secondary" size="xs" content="Current plan" />
86+
{/if}
87+
</svelte:fragment>
88+
<Typography.Caption variant="400">
89+
{$currentPlan.desc}
90+
</Typography.Caption>
91+
<Typography.Text>
92+
{@const isZeroPrice = ($currentPlan?.price ?? 0) <= 0}
93+
{@const price = formatCurrency($currentPlan?.price ?? 0)}
94+
{isZeroPrice ? price : `${price} per month + usage`}
95+
</Typography.Text>
96+
</LabelCard>
97+
{/if}
7598
</Layout.Stack>

src/lib/components/breadcrumbs.svelte

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import { isCloud } from '$lib/system';
1313
import { goto } from '$app/navigation';
1414
import { base } from '$app/paths';
15-
import { newOrgModal } from '$lib/stores/organization';
15+
import { currentPlan, newOrgModal } from '$lib/stores/organization';
1616
import { Click, trackEvent } from '$lib/actions/analytics';
1717
import { page } from '$app/stores';
1818
@@ -182,6 +182,14 @@
182182
projectsBottomSheetOpen = false;
183183
}
184184
}
185+
186+
$: correctPlanName =
187+
// the plan names are hardcoded in some cases and are not available locally,
188+
// so we rely on the plan's source of truth - `$currentPlan`
189+
$currentPlan &&
190+
$currentPlan?.name.toLocaleLowerCase() !== selectedOrg?.tierName.toLocaleLowerCase()
191+
? $currentPlan.name
192+
: selectedOrg?.tierName; // fallback
185193
</script>
186194

187195
<svelte:window on:resize={onResize} />
@@ -195,9 +203,9 @@
195203
aria-label="Open organizations tab">
196204
<span class="orgName">{selectedOrg?.name ?? 'Organization'}</span>
197205
<span class="not-mobile"
198-
>{#if selectedOrg?.tierName}<Badge
206+
>{#if correctPlanName}<Badge
199207
variant="secondary"
200-
content={selectedOrg?.tierName} />{/if}</span>
208+
content={correctPlanName} />{/if}</span>
201209
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
202210
</button>
203211
{:else}
@@ -211,7 +219,7 @@
211219
<span class="orgName" class:noProjects={!selectedProject}
212220
>{selectedOrg?.name ?? 'Organization'}</span>
213221
<span class="not-mobile"
214-
><Badge variant="secondary" content={selectedOrg?.tierName ?? ''} /></span>
222+
><Badge variant="secondary" content={correctPlanName ?? ''} /></span>
215223
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
216224
</button>
217225
{/if}

src/lib/components/navbar.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@
229229
style:padding-inline-end="8px"
230230
style:padding-block="4px">
231231
<Typography.Text variant="m-500">
232-
{$user.email}
232+
{$user?.email}
233233
</Typography.Text>
234234
</div>
235235
<ActionMenu.Item.Anchor

src/lib/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ export enum BillingPlan {
530530
ENTERPRISE = 'ent-1'
531531
}
532532

533+
export const BASE_BILLING_PLANS: string[] = [BillingPlan.FREE, BillingPlan.PRO, BillingPlan.SCALE];
534+
533535
export const feedbackDowngradeOptions = [
534536
{
535537
value: 'availableFeatures',

src/lib/images/pink-background.svg

Lines changed: 76 additions & 0 deletions
Loading

src/lib/stores/billing.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequi
1111
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
1212
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
1313
import { cachedStore } from '$lib/helpers/cache';
14-
import { sizeToBytes, type Size } from '$lib/helpers/sizeConvertion';
14+
import { type Size, sizeToBytes } from '$lib/helpers/sizeConvertion';
1515
import type {
1616
AddressesList,
1717
Aggregation,
@@ -28,12 +28,18 @@ import { Query } from '@appwrite.io/console';
2828
import { derived, get, writable } from 'svelte/store';
2929
import { headerAlert } from './headerAlert';
3030
import { addNotification, notifications } from './notifications';
31-
import { organization, type Organization, type OrganizationError } from './organization';
31+
import {
32+
currentPlan,
33+
organization,
34+
type Organization,
35+
type OrganizationError
36+
} from './organization';
3237
import { canSeeBilling } from './roles';
3338
import { sdk } from './sdk';
3439
import { user } from './user';
3540
import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budgetLimitAlert.svelte';
3641
import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte';
42+
import EnterpriseTrial from '$routes/(console)/organization-[organization]/enterpriseTrial.svelte';
3743

3844
export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1';
3945

@@ -268,6 +274,32 @@ export function isServiceLimited(serviceId: PlanServices, plan: Tier, total: num
268274
return isLimited && total >= limit && !hasUsageFees;
269275
}
270276

277+
export function checkForEnterpriseTrial(org: Organization) {
278+
if (!org || !org.billingNextInvoiceDate) return;
279+
if (calculateEnterpriseTrial(org) > 0) {
280+
headerAlert.add({
281+
id: 'teamEnterpriseTrial',
282+
component: EnterpriseTrial,
283+
show: true,
284+
importance: 11
285+
});
286+
}
287+
}
288+
289+
export function calculateEnterpriseTrial(org: Organization) {
290+
const endDate = new Date(org.billingNextInvoiceDate);
291+
const startDate = new Date(org.billingCurrentInvoiceDate);
292+
const today = new Date();
293+
294+
let diffCycle = endDate.getTime() - startDate.getTime();
295+
diffCycle = Math.ceil(diffCycle / (1000 * 60 * 60 * 24));
296+
if (diffCycle === 14) {
297+
const remaining = endDate.getTime() - today.getTime();
298+
return Math.ceil(remaining / (1000 * 60 * 60 * 24));
299+
}
300+
return 0;
301+
}
302+
271303
export function calculateTrialDay(org: Organization) {
272304
if (org?.billingPlan === BillingPlan.FREE) return false;
273305
const endDate = new Date(org?.billingStartDate);
@@ -323,7 +355,7 @@ export async function checkForUsageLimit(org: Organization) {
323355
];
324356

325357
const members = org.total;
326-
const plan = get(plansInfo)?.get(org.billingPlan);
358+
const plan = get(currentPlan);
327359
const membersOverflow =
328360
members > plan.addons.seats.limit ? members - (plan.addons.seats.limit || members) : 0;
329361

src/routes/(console)/+layout.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Create from './createOrganization.svelte';
1414
import {
1515
calculateTrialDay,
16+
checkForEnterpriseTrial,
1617
checkForMandate,
1718
checkForMarkedForDeletion,
1819
checkForMissingPaymentMethod,
@@ -304,6 +305,7 @@
304305
if (currentOrganizationId === org.$id) return;
305306
if (isCloud) {
306307
currentOrganizationId = org.$id;
308+
checkForEnterpriseTrial(org);
307309
await checkForUsageLimit(org);
308310
checkForMarkedForDeletion(org);
309311
await checkForNewDevUpgradePro(org);

0 commit comments

Comments
 (0)