Skip to content

Commit

Permalink
Implement billing control
Browse files Browse the repository at this point in the history
  • Loading branch information
BrunoBernardino committed Jan 5, 2022
1 parent 157f925 commit 32164c8
Show file tree
Hide file tree
Showing 15 changed files with 469 additions and 24 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const eslint = {
'react/jsx-curly-newline': 'off',
'react/function-component-definition': 'off',
'react/jsx-no-useless-fragment': 'off',
'no-nested-ternary': 'off',
},
parserOptions: {
ecmaFeatures: {
Expand Down
38 changes: 26 additions & 12 deletions components/Layout/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Link from 'next/link';

import styles from './Footer.module.scss';

const Footer = () => {
const Footer = ({ hasValidSession }: { hasValidSession: boolean }) => {
return (
<footer className={styles.Footer}>
<section className={styles.Footer__faq}>
Expand All @@ -10,20 +12,32 @@ const Footer = () => {
<div className={styles['Footer__faq-item']}>
<h4>What is Budget Zen?</h4>
<p>
Simple and easy budget management.{' '}
Simple and encrypted budget management.{' '}
<a href="https://budgetzen.net">Read more here</a>.
</p>
</div>

<div className={styles['Footer__faq-item']}>
<h4>How can I get a Sync Token?</h4>
<p>
<a href="https://budgetzen.net/get-sync-token">
See instructions here
</a>
.
</p>
</div>
{hasValidSession ? (
<div className={styles['Footer__faq-item']}>
<h4>How can I manage my subscription?</h4>
<p>
<Link href="/billing">
<a>In your billing section</a>
</Link>
.
</p>
</div>
) : (
<div className={styles['Footer__faq-item']}>
<h4>How can I subscribe?</h4>
<p>
<Link href="/pricing">
<a>In the pricing section</a>
</Link>
.
</p>
</div>
)}

<div className={styles['Footer__faq-item']}>
<h4>Where's the code for this web app?</h4>
Expand All @@ -37,7 +51,7 @@ const Footer = () => {
</div>
</section>
<h3 className={styles.Footer__links}>
<a href="https://privacy.onbrn.com">Privacy Policy</a> |{' '}
<a href="https://budgetzen.net/privacy">Privacy Policy</a> |{' '}
<a href="mailto:me@brunobernardino.com">Get Help</a> | by{' '}
<a href="https://brunobernardino.com">Bruno Bernardino</a>
</h3>
Expand Down
2 changes: 1 addition & 1 deletion components/Layout/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

&__logo {
display: block;
margin: 3.5em 1em 0;
margin: 0 1em;

img {
width: 220px;
Expand Down
4 changes: 3 additions & 1 deletion components/Layout/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ interface MainLayoutProps {
title?: string;
description?: string;
keywords?: string;
hasValidSession?: boolean;
}

const MainLayout = ({
children,
title,
description,
keywords,
hasValidSession,
}: MainLayoutProps) => {
const metaTags = [
{ property: 'og:title', content: title },
Expand All @@ -42,7 +44,7 @@ const MainLayout = ({
</Head>
{SEOOverride}
<div className="wrapper">{children}</div>
<Footer />
<Footer hasValidSession={Boolean(hasValidSession)} />
</>
);
};
Expand Down
156 changes: 156 additions & 0 deletions components/Panels/Billing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useCallback } from 'react';
import Link from 'next/link';
import styled from 'styled-components';
import userbase from 'userbase-js';

import { Title, Subtitle, Paragraph } from 'components';
import { Header } from 'components/Layout';
import Button from 'components/Button';

interface BillingProps {
hasValidSession: boolean;
hasValidSubscription: boolean;
isTrialing: boolean;
isSubscriptionCanceled: boolean;
isSubscriptionMonthly: boolean;
}

const Hero = styled.section`
@media only screen and (min-width: 600px) {
display: flex;
align-items: center;
}
`;

const HeroText = styled.section`
margin-right: 1em;
`;

const Billing = ({
hasValidSession,
hasValidSubscription,
isTrialing,
isSubscriptionCanceled,
isSubscriptionMonthly,
}: BillingProps) => {
const handleSubscriptionCancelClick = useCallback(async () => {
await userbase.cancelSubscription();
window.location.reload();
}, []);

const handleSubscriptionResumeClick = useCallback(async () => {
await userbase.resumeSubscription();
window.location.reload();
}, []);

const handlePaymentDetailsUpdateClick = useCallback(async () => {
await userbase.updatePaymentMethod({
successUrl: window.location.href,
cancelUrl: window.location.href,
});
}, []);

return (
<>
<Header />
<Title>Billing</Title>
<Hero>
<HeroText>
<Paragraph>Billing is simple.</Paragraph>
<Paragraph>
Below, you can easily cancel your subscription anytime and email me
to ask for a refund. You can also update your payment details.
</Paragraph>
</HeroText>
</Hero>
{hasValidSession ? (
hasValidSubscription ? (
<>
<Subtitle>Thank you so much for your support!</Subtitle>
<Paragraph>
You're currently paying{' '}
<strong>
{isSubscriptionMonthly ? '€2 / month' : '€18 / year'}
</strong>
.
</Paragraph>
<Button
onClick={handlePaymentDetailsUpdateClick}
type="primary"
width="large"
style={{ margin: '2rem auto 1rem' }}
>
Update payment details
</Button>
{!isSubscriptionCanceled ? (
<>
<Button
onClick={handleSubscriptionCancelClick}
type="delete"
style={{ margin: '5rem auto 1rem' }}
>
Cancel subscription
</Button>
<Paragraph isCentered>
The subscription will be canceled at the end of the current
billing period.
</Paragraph>
</>
) : null}
{isSubscriptionCanceled ? (
<>
<Paragraph style={{ marginTop: '5rem' }}>
Your subscription is currently set to be canceled at the end
of the current billing period.
</Paragraph>
<Button
onClick={handleSubscriptionResumeClick}
type="secondary"
width="large"
style={{ margin: '2rem auto 1rem' }}
>
Resume subscription
</Button>
</>
) : null}
</>
) : isTrialing ? (
<>
<Subtitle>Your are on an active trial!</Subtitle>
<Paragraph>
If you're ready to pay, you probably want to check out the{' '}
<Link href="/pricing">
<a>pricing section</a>
</Link>{' '}
instead.
</Paragraph>
</>
) : (
<>
<Subtitle>Your subscription has expired!</Subtitle>
<Paragraph>
You probably want to check out the{' '}
<Link href="/pricing">
<a>pricing section</a>
</Link>{' '}
instead.
</Paragraph>
</>
)
) : (
<>
<Subtitle>Signup or Login first</Subtitle>
<Paragraph>
Before you can pay, you need to{' '}
<Link href="/">
<a>Signup or Login</a>
</Link>{' '}
first.
</Paragraph>
</>
)}
</>
);
};

export default Billing;
20 changes: 15 additions & 5 deletions components/Panels/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,29 @@ const Login = () => {
return (
<>
<Header />
<Title>Simple Budget Management</Title>
<Title>Simple + Encrypted Budget Management</Title>
<Hero>
<HeroText>
<Paragraph>Budget Zen is a simple budget management app.</Paragraph>
<Paragraph>
Budget Zen is a simple and <strong>encrypted</strong> budget
management app. You can{' '}
<a href="https://budgetzen.net">learn more about it here</a>, as
this is the app.
</Paragraph>
<Paragraph>
Currently it's available on every device via web browser, and you
can browse its source code.
</Paragraph>
<Paragraph>
You have a <strong>30-day free trial</strong> (no credit card
required), and at the end, you can pay <strong>€18 / year</strong>,
or <strong>€2 / month</strong>, no limits.
</Paragraph>
<LoginButton />
<Paragraph>
Note that logging in can take up to 30 seconds. This is because the
encryption logic is intentionally slow, in order to generate a safer
assymetric encryption key.
Note that logging in will take up a few seconds. This is
intentional, in order to generate a safer assymetric encryption key.
After logging in, the app should be blazing fast in any device.
</Paragraph>
</HeroText>
</Hero>
Expand Down
Loading

0 comments on commit 32164c8

Please sign in to comment.