Skip to content

Commit

Permalink
feat(18228): add pricing page to atlas app (#770)
Browse files Browse the repository at this point in the history
* feat(18228): add pricing page

---------

Co-authored-by: Natallia Harshunova <[email protected]>
  • Loading branch information
Natallia-Harshunova and nattallius authored Jun 27, 2024
1 parent 60b43bb commit bbfd01b
Show file tree
Hide file tree
Showing 19 changed files with 558 additions and 13 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@
"@deck.gl/mesh-layers": "^8.9.33",
"@github/mini-throttle": "^2.1.1",
"@homer0/deferred": "^3.0.5",
"@konturio/default-icons": "^2.3.2",
"@konturio/default-icons": "^2.4.0",
"@konturio/default-theme": "~3.2.5",
"@konturio/floating": "^1.1.2",
"@konturio/ui-kit": "^5.2.0",
"@konturio/ui-kit": "^5.3.0",
"@loaders.gl/core": "^3.4.14",
"@loaders.gl/loader-utils": "^3.4.14",
"@loaders.gl/worker-utils": "^3.4.14",
Expand Down
20 changes: 10 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions src/core/api/subscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { apiClient } from '~core/apiClientInstance';
import { configRepo } from '~core/config';

export type CurrentSubscription = {
id: string;
billingPlanId: string;
billingSubscriptionId: string;
};

export async function getCurrentUserSubscription() {
return await apiClient.get<CurrentSubscription | null>(
'/users/current_user/billing_subscription',
{ appId: configRepo.get().id },
true,
);
}
1 change: 1 addition & 0 deletions src/core/auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const AppFeature = {
REFERENCE_AREA: 'reference_area',
LLM_ANALYTICS: 'llm_analytics',
MAP: 'map',
SUBSCRIPTION: 'subscription',
} as const;

export type AppFeatureType = (typeof AppFeature)[keyof typeof AppFeature];
Expand Down
6 changes: 6 additions & 0 deletions src/core/localization/translations/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,12 @@
"connect": "Could not connect to authentication service"
}
},
"subscription": {
"title": "Plans & Pricing",
"price_summary": "* Billed as {{pricePerYear}} once yearly",
"unauthorized_button": "Sign in to subscribe",
"current_plan_button": "Current plan"
},
"reports": {
"title": "Disaster Ninja Reports",
"no_data": "No data for this report",
Expand Down
9 changes: 9 additions & 0 deletions src/core/router/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
Prefs24,
User24,
Reports16,
Diamond24,
} from '@konturio/default-icons';
import { i18n } from '~core/localization';
import { AppFeature } from '~core/auth/types';
import { configRepo } from '~core/config';
import { PagesDocument } from '~core/pages';
import { goTo } from './goTo';
import type { AppRouterConfig } from './types';
const { PricingPage } = lazily(() => import('~views/Pricing/Pricing'));
const { MapPage } = lazily(() => import('~views/Map/Map'));
const { ReportsPage } = lazily(() => import('~views/Reports/Reports'));
const { ReportPage } = lazily(() => import('~views/Report/Report'));
Expand Down Expand Up @@ -65,6 +67,13 @@ export const routerConfig: AppRouterConfig = {
view: <ProfilePage />,
requiredFeature: AppFeature.APP_LOGIN,
},
{
slug: 'pricing',
title: i18n.t('subscription.title'),
icon: <Diamond24 />,
view: <PricingPage />,
requiredFeature: AppFeature.SUBSCRIPTION,
},
{
slug: 'about',
title: i18n.t('modes.about'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.paymentPlanButton {
background-color: var(--strong-color);

--subscribe-btn-hover: #269b00;

&:global(.premium) {
--subscribe-btn-hover: var(--accent-strong-up);
}

&:hover {
background-color: var(--subscribe-btn-hover);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button } from '@konturio/ui-kit';
import React, { memo } from 'react';
import clsx from 'clsx';
import { i18n } from '~core/localization';
import s from './PaymentPlanButton.module.css';
import type { CurrentSubscription } from '~core/api/subscription';
import type { PaymentPlan } from '~features/subscriptions/types';

export type PaymentPlanButtonProps = {
plan: PaymentPlan;
isUserAuthorized: boolean;
onUnauthorizedUserClick: () => void;
currentSubscription: CurrentSubscription | null;
style: string;
};

const PaymentPlanButton = memo(function PaymentPlanButton({
plan,
isUserAuthorized,
onUnauthorizedUserClick,
currentSubscription,
style,
}: PaymentPlanButtonProps) {
if (!isUserAuthorized) {
return (
<Button
className={clsx(s.paymentPlanButton, style)}
onClick={onUnauthorizedUserClick}
>
{i18n.t('subscription.unauthorized_button')}
</Button>
);
}
if (plan.id === currentSubscription?.id) {
return <Button disabled>{i18n.t('subscription.current_plan_button')}</Button>;
}
return <Button className={s.paymentPlanButton}>Subscribe</Button>;
});

export default PaymentPlanButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
.planCard {
border: 1px solid var(--faint-weak-up, #d2d5d8);
border-radius: var(--double-unit, 16px);
padding: 48px 40px 24px;
height: 578px;
max-width: 400px;
min-width: 220px;
display: flex;
position: relative;
flex-direction: column;

--strong-color: var(--success-strong);
--plan-type-bg-color: #def5df;

&:global(.premium) {
--strong-color: var(--accent-strong);
--plan-type-bg-color: #d8effc;

background-color: var(--accent-weak);
border-color: var(--strong-color);
}
}

.planIcon {
margin-right: var(--unit);
}

.planName {
position: absolute;
padding: var(--unit) var(--double-unit);
border-radius: 18px;
right: 40px;
top: -18px;
background-color: var(--plan-type-bg-color);
color: var(--strong-color);
display: flex;
align-items: center;
}

.initialPrice {
color: var(--faint-strong, #9ea5ab);
text-decoration: line-through;
margin-bottom: var(--double-unit);
}

.price {
margin-bottom: var(--double-unit);
}

.perMonth {
font-size: 14px;
color: #888;
margin-left: 5px;
}

.planDescription {
color: var(--faint-strong);
margin-bottom: var(--double-unit);
}

.buttonWrapper {
margin-bottom: 24px;
}

.cancelButton {
position: relative;
bottom: -11px;
}

.highlights {
list-style: none;
padding: 0;
margin: 0;

& li {
margin: 5px 0;
}
}

.highlight {
display: flex;
align-items: center;
}

.highlightIcon {
margin-right: var(--double-unit);
color: var(--strong-color);
}

.footerWrapper {
display: flex;
justify-content: flex-end;
margin-top: auto;
color: var(--faint-strong);

& span {
color: var(--faint-strong-down);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { memo, useMemo } from 'react';
import { Heading, Text } from '@konturio/ui-kit';
import { Finish24 } from '@konturio/default-icons';
import clsx from 'clsx';
import { Price } from '~features/subscriptions/components/Price/Price';
import PaymentPlanButton from '~features/subscriptions/components/PaymentPlanButton/PaymentPlanButton';
import PaymentPlanCardFooter from '~features/subscriptions/components/PaymentPlanCardFooter/PaymentPlanCardFooter';
import s from './PaymentPlanCard.module.css';
import { PLANS_STYLE_CONFIG } from './contants';
import type { PaymentPlan } from '~features/subscriptions/types';
import type { CurrentSubscription } from '~core/api/subscription';

export type PaymentPlanCardProps = {
plan: PaymentPlan;
currentBillingCycleId: string;
currentSubscription: CurrentSubscription | null;
isUserAuthorized: boolean;
onUnauthorizedUserClick: () => void;
};

const PaymentPlanCard = memo(function PaymentPlanCard({
plan,
currentBillingCycleId,
currentSubscription,
isUserAuthorized,
onUnauthorizedUserClick,
}: PaymentPlanCardProps) {
const styleConfig = PLANS_STYLE_CONFIG[plan.style];

const billingOption = useMemo(
() => plan.billingCycles.find((option) => option.id === currentBillingCycleId),
[plan.billingCycles, currentBillingCycleId],
);

return (
<div className={clsx(s.planCard, styleConfig.className)}>
<div className={s.planName}>
{styleConfig.icon()}
<Heading type="heading-04" margins={false}>
{plan.name}
</Heading>
</div>
{billingOption?.initialPricePerMonth && (
<div className={s.initialPrice}>${billingOption.initialPricePerMonth}</div>
)}
{billingOption?.pricePerMonth && (
<Price className={s.price} amount={billingOption.pricePerMonth} />
)}
<Text className={s.planDescription} type="short-m">
{plan.description}
</Text>
<div className={s.buttonWrapper}>
<PaymentPlanButton
plan={plan}
isUserAuthorized={isUserAuthorized}
onUnauthorizedUserClick={onUnauthorizedUserClick}
currentSubscription={currentSubscription}
style={styleConfig.className}
/>
</div>
<ul className={s.highlights}>
{plan.highlights.map((highlight, index) => (
<li key={index} className={s.highlight}>
<Finish24 className={s.highlightIcon} />
<span>{highlight}</span>
</li>
))}
</ul>

<div className={s.footerWrapper}>
<PaymentPlanCardFooter
plan={plan}
isUserAuthorized={isUserAuthorized}
currentSubscription={currentSubscription}
billingOption={billingOption}
/>
</div>
</div>
);
});

export default PaymentPlanCard;
14 changes: 14 additions & 0 deletions src/features/subscriptions/components/PaymentPlanCard/contants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { FavAdded16 } from '@konturio/default-icons';
import s from './PaymentPlanCard.module.css';

export const PLANS_STYLE_CONFIG = {
basic: {
className: '', // Uses default styles
icon: () => null,
},
premium: {
className: 'premium',
icon: () => <FavAdded16 className={s.planIcon} />,
},
};
Loading

0 comments on commit bbfd01b

Please sign in to comment.