Merge pull request #1149 from appwrite/feat-startups-campaign

feat: 'review' template for campaigns
This commit is contained in:
Arman
2024-06-28 14:00:26 +02:00
committed by GitHub
8 changed files with 149 additions and 49 deletions
@@ -42,9 +42,7 @@
{/if}
</div>
{#if couponData.credits >= 100}
<p class="inline-tag" use:tooltip={{ content: formatCurrency(couponData.credits) }}>
Credits applied
</p>
<p class="inline-tag">Credits applied</p>
{:else}
<span class="u-color-text-success">-{formatCurrency(couponData.credits)}</span>
{/if}
@@ -32,9 +32,10 @@
</script>
<section
class="card u-margin-block-start-32 u-flex u-flex-vertical u-gap-8"
class="card u-flex u-flex-vertical u-gap-8"
style:--p-card-padding="1.5rem"
style:--p-card-border-radius="var(--border-radius-small)">
<slot />
<span class="u-flex u-main-space-between">
<p class="text">{currentPlan.name} plan</p>
<p class="text">{formatCurrency(currentPlan.price)}</p>
+1 -1
View File
@@ -108,7 +108,7 @@
<div
class:input-text-wrapper={!$$slots.default}
class:u-flex={$$slots.default}
class:u-gap-16={$$slots.default}
class:u-gap-8={$$slots.default}
class:u-cross-center={$$slots.default}>
<input
{id}
+71 -14
View File
@@ -1,12 +1,12 @@
<script lang="ts">
import { Heading } from '$lib/components';
import { Avatar, AvatarInitials, Card, Heading } from '$lib/components';
import AppwriteLogoDark from '$lib/images/appwrite-logo-dark.svg';
import AppwriteLogoLight from '$lib/images/appwrite-logo-light.svg';
import LoginDark from '$lib/images/login/login-dark-mode.png';
import LoginLight from '$lib/images/login/login-light-mode.png';
import type { Coupon } from '$lib/sdk/billing';
import { app } from '$lib/stores/app';
import { campaigns } from '$lib/stores/campaigns';
import { campaigns, type CampaignData } from '$lib/stores/campaigns';
import { user } from '$lib/stores/user';
export const imgLight = LoginLight;
@@ -15,8 +15,13 @@
export let campaign: string = null;
export let coupon: Coupon = null;
$: variation = coupon?.campaign ?? campaign ? 'card' : 'default';
$: selectedCampaign = campaigns.get(coupon?.campaign ?? campaign);
$: variation = (coupon?.campaign ?? campaign ? selectedCampaign?.template : 'default') as
| 'default'
| CampaignData['template'];
let currentReviewNumber = 0;
$: currentReview = selectedCampaign?.data?.reviews?.[currentReviewNumber];
function generateTitle() {
if (coupon?.credits) {
@@ -37,17 +42,17 @@
<main class="grid-1-1 is-full-page" id="main">
<section
class={`u-flex u-flex-vertical ${variation === 'card' ? 'u-cross-center u-main-center u-height-100-percent u-width-full-line side-bg' : 'side-default'}`}
style:--url={variation === 'card'
class={`u-flex u-flex-vertical ${variation !== 'default' ? 'u-cross-center u-main-center u-height-100-percent u-width-full-line side-bg' : 'side-default'}`}
style:--url={variation !== 'default'
? ''
: `url(${$app.themeInUse === 'dark' ? imgDark : imgLight})`}>
{#if variation === 'card'}
{#if variation !== 'default'}
<div class="side-bg-container" />
{/if}
<div
class="logo u-flex u-gap-16"
class:is-not-mobile={variation === 'default'}
class:logo-variation={variation === 'card'}>
class:logo-variation={variation !== 'default'}>
<a href={user ? '/console' : '/'}>
{#if $app.themeInUse === 'dark'}
<img
@@ -72,15 +77,15 @@
<p>Build like a team of hundreds<span class="underscore">_</span></p>
</div>
{:else if variation === 'card'}
<div
<section
class="u-flex u-flex-vertical u-main-center u-cross-center u-height-100-percent u-width-full-line">
<img
src={`/images/campaigns/${coupon?.campaign ?? campaign}/${$app.themeInUse}.png`}
class="u-block u-image-object-fit-cover side-bg-img"
alt="promo" />
<div class="u-text-center">
<div class="is-only-mobile u-width-full-line auth-container">
<div class="u-text-center auth-container">
<div class="is-only-mobile u-width-full-line">
<Heading
size="5"
tag="h3"
@@ -90,7 +95,7 @@
{generateTitle()}
</Heading>
</div>
<div class="is-not-mobile u-width-full-line auth-container">
<div class="is-not-mobile u-width-full-line">
<Heading
size="4"
tag="h3"
@@ -100,11 +105,60 @@
{generateTitle()}
</Heading>
</div>
<p class="u-margin-block-start-16 auth-container">
<p class="u-margin-block-start-16">
{generateDesc()}
</p>
</div>
</div>
</section>
{:else if variation === 'review'}
<section
class="u-flex u-flex-vertical u-main-center u-cross-center u-height-100-percent u-width-full-line review-container">
<div class="u-text-center">
<div class="is-only-mobile u-width-full-line">
<Heading
size="5"
tag="h3"
class="u-margin-block-start-48"
trimmed={false}
style="font-weight:normal">
{generateTitle()}
</Heading>
</div>
<div class="is-not-mobile u-width-full-line">
<Heading
size="3"
tag="h3"
class="u-margin-block-start-32"
trimmed={false}
style="font-weight:normal">
{generateTitle()}
</Heading>
</div>
</div>
<p class="body-text-1 u-margin-block-start-16 u-text-center">{generateDesc()}</p>
<div class="review-container u-margin-block-start-64">
<Card style="--p-card-padding: 1.25rem">
<p class="body-text-1">
{currentReview.review}
</p>
<div class="u-margin-block-start-16 u-flex u-gap-16">
{#if currentReview?.img}
<Avatar
src={`/images/campaigns/${coupon?.campaign ?? campaign}/reviewers/${currentReview.img}`}
name={currentReview.name}
size={40} />
{:else}
<AvatarInitials size={40} name={currentReview.name} />
{/if}
<div>
<p class="body-text-2 u-bold">{currentReview.name}</p>
<p class="body-text-2">{currentReview.desc}</p>
</div>
</div>
</Card>
</div>
</section>
{/if}
</section>
<section class="grid-1-1-col-2 u-flex u-flex-vertical u-cross-center u-main-center">
@@ -116,7 +170,7 @@
</h1>
<h1
class="heading-level-5 u-margin-block-start-auto is-only-mobile"
class:u-padding-block-start-24={variation === 'card'}>
class:u-padding-block-start-24={variation !== 'default'}>
<slot name="title" />
</h1>
<div class="u-margin-block-start-24">
@@ -303,6 +357,9 @@
.auth-container {
max-inline-size: 27.5rem;
}
.review-container {
max-inline-size: 30rem;
}
.logo-variation {
padding-block-start: 2rem;
+1 -3
View File
@@ -4,8 +4,6 @@
</div>
<div class="wizard-secondary-content-sep"></div>
<div class="wizard-secondary-content-2">
<div class="wizard-secondary-content-sticky">
<slot name="aside" />
</div>
<slot name="aside" />
</div>
</div>
+30 -1
View File
@@ -1,40 +1,69 @@
//campaign welcome and startup
type CampaignData = {
export type CampaignData = {
title: string;
description: string;
template: 'card' | 'review';
data?: Record<string, unknown>;
onlyNewOrgs?: boolean;
};
export const campaigns: Map<string, CampaignData> = new Map();
campaigns
.set('welcome', {
template: 'card',
title: "You've received $VALUE in credits",
description:
'Get $VALUE in credits when you upgrade or create an organization with a Pro plan.'
})
.set('startup', {
template: 'card',
title: 'Welcome to the Startups program!',
description:
"We're excited to have you on board. Add the coupon code to your Appwrite Pro account to join."
})
.set('RenderATL2024', {
template: 'card',
title: 'Claim your $100 RenderATL credits',
description:
'Get $100 in Cloud credits when you upgrade or create an organization with a Pro plan.'
})
.set('dealsfordevs', {
template: 'card',
title: 'Claim your $50 Deals For Devs credits',
description:
'Get $50 in Cloud credits when you upgrade or create an organization with a Pro plan.'
})
.set('WelcomeManual', {
template: 'card',
title: 'Here is your $VALUE welcome credit!',
description:
'Upgrade to Appwrite Pro and add the credits to enjoy the full capabilities of Cloud.'
})
.set('InactiveAccounts', {
template: 'card',
title: 'Get everything out of Cloud. Claim your $VALUE credits.',
description:
'Get $VALUE in Cloud credits when you upgrade or create an organization with a Pro plan.'
})
.set('StartupsProgram', {
template: 'review',
title: 'Welcome to the Startups program',
description:
"We're excited to have you on board. Add your credit code to your Appwrite Pro account to join.",
onlyNewOrgs: true,
data: {
cta: 'Get everything out of Cloud with Pro',
claimed: 'Your credits will be valid for 12 months.',
unclaimed: 'Apply your code to join the Startups program.',
reviews: [
{
name: 'David Foster',
img: '1.jpeg',
desc: 'Managing director',
review: 'We really loved working with Appwrite for launching our bootstrapped "Open Mind" App. It was saving us a lot of money in comparison to Firebase since the amount of users grew quite fast and we needed a quick switch.'
}
]
}
});
+43 -26
View File
@@ -67,10 +67,11 @@
let name: string;
let coupon: string;
let couponData = data?.couponData;
let campaign = campaigns.get(data?.couponData?.campaign ?? data?.campaign);
onMount(async () => {
await loadPaymentMethods();
if (!$organizationList?.total) {
if (!$organizationList?.total || campaign?.onlyNewOrgs) {
selectedOrgId = newOrgId;
}
});
@@ -183,7 +184,6 @@
$: isButtonDisabled =
checkButtonDisabled(selectedOrgId, name, paymentMethodId) ||
couponData?.status !== 'active';
$: campaign = campaigns.get(data?.couponData?.campaign ?? data?.campaign);
function checkButtonDisabled(id: string | null, name: string | null, method: string | null) {
if (id === newOrgId) {
@@ -207,7 +207,7 @@
<WizardSecondaryContent>
<Form bind:this={formComponent} onSubmit={handleSubmit} bind:isSubmitting>
<FormList>
{#if $organizationList?.total}
{#if $organizationList?.total && !campaign?.onlyNewOrgs}
<InputSelect
bind:value={selectedOrgId}
label="Select organization"
@@ -254,24 +254,26 @@
</FormList>
</Form>
<svelte:fragment slot="aside">
<div
class="box card-container u-position-relative"
style:--box-border-radius="var(--border-radius-small)">
<div class="card-bg"></div>
<div class="u-flex u-flex-vertical u-gap-24 u-cross-center u-position-relative">
<img
src={`/images/campaigns/${data?.couponData?.campaign ?? data?.campaign}/${$app.themeInUse}.png`}
class="u-block u-image-object-fit-cover card-img"
alt="promo" />
<p class="text">
{#if couponData?.credits}
{campaign.title.replace('VALUE', couponData.credits.toString())}
{:else}
{campaign.title}
{/if}
</p>
{#if campaign?.template === 'card'}
<div
class="box card-container u-position-relative"
style:--box-border-radius="var(--border-radius-small)">
<div class="card-bg"></div>
<div class="u-flex u-flex-vertical u-gap-24 u-cross-center u-position-relative">
<img
src={`/images/campaigns/${data?.couponData?.campaign ?? data?.campaign}/${$app.themeInUse}.png`}
class="u-block u-image-object-fit-cover card-img"
alt="promo" />
<p class="text">
{#if couponData?.credits}
{campaign.title.replace('VALUE', couponData.credits.toString())}
{:else}
{campaign.title}
{/if}
</p>
</div>
</div>
</div>
{/if}
{#if selectedOrg?.billingPlan === BillingPlan.PRO}
<section
class="card u-margin-block-start-24"
@@ -288,12 +290,27 @@
{/if}
</section>
{:else if selectedOrgId}
<EstimatedTotalBox
fixedCoupon={!!data?.couponData?.code}
billingPlan={BillingPlan.PRO}
{collaborators}
bind:couponData
bind:billingBudget />
<div class:u-margin-block-start={campaign?.template === 'card'}>
<EstimatedTotalBox
fixedCoupon={!!data?.couponData?.code}
billingPlan={BillingPlan.PRO}
{collaborators}
bind:couponData
bind:billingBudget>
{#if campaign?.template === 'review' && (campaign?.data?.cta || campaign?.data?.claimed || campaign?.data?.unclaimed)}
<div class="u-margin-block-end-24">
<p class="body-text-1 u-bold">{campaign?.data?.cta}</p>
<p class="text u-margin-block-start-8">
{#if couponData?.code && couponData?.status === 'active' && campaign?.data?.claimed}
{campaign?.data?.claimed}
{:else if campaign?.data?.unclaimed}
{campaign?.data?.unclaimed}
{/if}
</p>
</div>
{/if}
</EstimatedTotalBox>
</div>
{/if}
</svelte:fragment>
</WizardSecondaryContent>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB