Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.local

This file was deleted.

5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
.next
node_modules
/.next
/node_modules
/.env
54 changes: 35 additions & 19 deletions components/Itinerary/Itinerary.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,52 @@ import {
lines,
} from './Itinerary.module.css';

export default function Itinerary() {
export default function Itinerary({linkToEdit}) {
const { query } = useRouter();
const { t, formatNumber } = useTranslation();
const {
trip: { airports, distance, flightClass, passengers },
} = useTrip();

const WrappedAirportCard = (airport, key) =>
<div className={airportWrapper} key={key}>
<AirportCard airport={airport} />
</div>;

const stats =
<Lines
className={lines}
items={{
[t('distance')]: `${formatNumber(distance, 1)} km`,
[t('passengers')]: passengers,
[t('flightClass')]: t(flightClass),
}}
/>;

return (
<>
<h3 className={headline}>{t('itinerary')}</h3>
{airports.map((airport, index) => (
<Link href={{ pathname: '/', query }} key={index}>
{linkToEdit
?
<>
{airports.map((airport, index) => (
<Link href={{ pathname: '/', query }} key={index}>
<a className={formlink}>
{WrappedAirportCard(airport, index)}
</a>
</Link>
))}
<Link href={{ pathname: '/', query }}>
<a className={formlink}>
<div className={airportWrapper}>
<AirportCard airport={airport} />
</div>
{stats}
</a>
</Link>
))}
<Link href={{ pathname: '/', query }}>
<a className={formlink}>
<Lines
items={{
[t('distance')]: `${formatNumber(distance, 1)} km`,
[t('passengers')]: passengers,
[t('flightClass')]: t(flightClass),
}}
className={lines}
/>
</a>
</Link>
</>
: <>
{airports.map(WrappedAirportCard)}
{stats}
</>
}
</>
);
}
47 changes: 5 additions & 42 deletions components/Payment/Payment.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,31 @@
import { useState } from 'react';

import { useTrip, useTranslation } from '../../hooks';
import { usePayment, useTrip, useTranslation } from '../../hooks';

import { Select } from '../Select';

import {
wrapper,
headline,
amount,
select,
button,
} from './Payment.module.css';

function usePayment() {
const { t, formatNumber } = useTranslation();
const {
trip: { cost },
} = useTrip();
const [recipient, setRecipient] = useState('trees');
const getUrl = () => {
const amount = formatNumber(cost, 2);
const language = t('lang');
switch (recipient) {
case 'trees':
return `https://donorbox.org/trees-for-lure?amount=${amount}&language=${language}`;
case 'greenpeace':
return `https://www.greenpeace.de/spenden?betrag=${amount}`;
default:
throw new Error('unknown recipient');
}
};
return {
options: {
trees: t('donateTrees'),
greenpeace: t('donateToGreenpeace'),
},
value: recipient,
onChange: setRecipient,
href: getUrl(),
};
}

export default function Payment() {
const { t, formatNumber } = useTranslation();
const {
trip: { cost },
} = useTrip();
const { options, value, onChange, href } = usePayment();
const { process } = usePayment();

return (
<div className={wrapper}>
<h3 className={headline}>{t('paymentHeadline')}</h3>
<p className={amount}>{formatNumber(cost, 2)} €</p>
<Select
value={value}
options={options}
onChange={onChange}
invert={true}
hideLabel={true}
className={select}
label={t('chooseOrganization')}
/>
<a href={href} className={button}>
<button className={button} onClick={()=> process(cost)}>
{t('makeDonation')}
</a>
</button>
</div>
);
}
12 changes: 12 additions & 0 deletions components/ThankYou/ThankYou.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useTranslation } from '../../hooks';

export default function ThankYou() {
const { t } = useTranslation();

return (
<div>
<h3>{t('thankYouHeadline')}</h3>
<p>{t('thankYouDescription')}</p>
</div>
);
}
1 change: 1 addition & 0 deletions components/ThankYou/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ThankYou } from './ThankYou';
1 change: 1 addition & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { TripForm } from './TripForm';
export { Itinerary } from './Itinerary';
export { Payment } from './Payment';
export { EmissionData } from './EmissionData';
export { ThankYou } from './ThankYou';
7 changes: 4 additions & 3 deletions data/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
"analysis": "Analyse",
"business": "Business",
"calculateEmissions": "Emissionen berechnen",
"chooseOrganization": "Organisation auswählen",
"cookieConsentButton": "Cookies Akzeptieren",
"ch4": "Methan",
"co2": "Kohlendioxid",
"destination": "Zielflughafen",
"distance": "Entfernung",
"donateToGreenpeace": "Greenpeace unterstützen",
"donateTrees": "Bäume pflanzen",
"economy": "Economy",
"emissions": "Emissionen",
"first": "First",
Expand All @@ -25,10 +22,14 @@
"origin": "Abflughafen",
"passengers": "Passagiere",
"paymentHeadline": "Offsetting-Kosten",
"paymentName": "CO2-Kompensation",
"paymentDescription": "Flugreise mit diesen Stationen: {{ airports }}",
"premium": "Economy Premium",
"removeStopover": "Zwischenstop entfernen",
"roundTrip": "Rückflug",
"stopover": "Zwischenstopp",
"trees": "Baum-Äquivalent",
"thankYouHeadline": "Danke für's Spenden!",
"thankYouDescription": "Die Spendenquittung kommt im nächsten Januar per E-Mail. 💚",
"yes": "Ja"
}
1 change: 1 addition & 0 deletions hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { usePayment } from './usePayment';
export { useSuggestions } from './useSuggestions';
export { useTracking, withTracking } from './useTracking';
export { useTranslation, withTranslation } from './useTranslation';
Expand Down
36 changes: 36 additions & 0 deletions hooks/usePayment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from 'react';

function getPaymentURL(amount) {
const params = new URLSearchParams({ amount: Number(amount) });
return `/api/payment?${params.toString()}`;
}

async function fetchPaymentSession(amount) {
const response = await fetch(getPaymentURL(amount));
return response.ok
? response.json()
: Promise.reject(new Error(response.statusText));
}

async function processPayment(amount) {
try {
const { loadStripe } = await import('@stripe/stripe-js');
const [stripe, { sessionId }] = await Promise.all([
loadStripe(window.stripeKey),
fetchPaymentSession(amount),
]);
stripe.redirectToCheckout({ sessionId });
} catch (error) {
console.error(error);
}
}

export function usePayment() {
const [processing, setProcessing] = useState(false);
const process = (amount) => {
if (processing) return;
setProcessing(true);
processPayment(amount);
};
return { processing, process };
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@mdx-js/loader": "^1.6.16",
"@mdx-js/react": "^1.6.16",
"@next/mdx": "^9.5.2",
"@stripe/stripe-js": "^1.11.0",
"@turf/distance": "^6.0.1",
"browserslist": "^4.14.0",
"cookie": "^0.4.1",
Expand All @@ -42,7 +43,8 @@
"postcss-preset-env": "^6.7.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"snakecase-keys": "^3.2.0"
"snakecase-keys": "^3.2.0",
"stripe": "^8.130.0"
},
"devDependencies": {
"@commitlint/cli": "^9.1.2",
Expand Down
12 changes: 10 additions & 2 deletions pages/_document.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ export default withTranslation(
class FlygoodDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
const stripeApiKey = process.env.STRIPE_PUBLIC_KEY;
const trackingId = process.env.GA_MEASUREMENT_ID;
const mapboxToken = process.env.MAPBOX_TOKEN;
return { ...initialProps, trackingId, mapboxToken };
return { ...initialProps, trackingId, stripeApiKey, mapboxToken };
}
render() {
const { trackingId, mapboxToken, t } = this.props;
const { stripeApiKey, trackingId, mapboxToken, t } = this.props;
return (
<Html>
<Head lang={t('lang')}>
Expand Down Expand Up @@ -49,6 +50,13 @@ export default withTranslation(
<body>
<Main />
<NextScript />
{stripeApiKey ? (
<script
dangerouslySetInnerHTML={{
__html: `window.stripeKey = "${stripeApiKey}";`,
}}
/>
) : null}
{trackingId ? (
<script
dangerouslySetInnerHTML={{
Expand Down
40 changes: 40 additions & 0 deletions pages/api/payment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Stripe from 'stripe';

import { useTranslation } from '../../hooks';

const stripe = new Stripe(process.env.STRIPE_PRIVATE_KEY);

const getSuccessUrl = (referer) => {
const url = new URL(referer);
url.pathname = 'success';
return url.toString();
};

const getDescription = (referer) => {
const { t } = useTranslation();
const url = new URL(referer);
const trip = url.searchParams.get('a') || '';
return t('paymentDescription').replace('{{ airports }}', trip.split(',').join(', '));
};

export default async (req, res) => {
const { t } = useTranslation();
const { amount } = req.query;
const { referer } = req.headers;
const { id: sessionId } = await stripe.checkout.sessions.create({
submit_type: 'donate',
payment_method_types: ['card'],
line_items: [
{
name: t('paymentName'),
description: getDescription(referer),
amount: amount * 100,
currency: 'eur',
quantity: 1,
},
],
success_url: getSuccessUrl(referer),
cancel_url: referer,
});
res.json({ sessionId });
};
23 changes: 23 additions & 0 deletions pages/success.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Info from '../contents/info.md';

import { Section, Itinerary, ThankYou, EmissionData } from '../components';
import { withTrip } from '../hooks';

export default withTrip(function Success() {
return (
<>
<Section>
<Itinerary linkToEdit={false} />
</Section>
<Section type="action">
<ThankYou />
</Section>
<Section type="content">
<EmissionData />
<Info />
</Section>
</>
);
});

export { loadTrip as getServerSideProps } from '../hooks';
2 changes: 1 addition & 1 deletion pages/trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default withTrip(function Trip() {
return (
<>
<Section>
<Itinerary />
<Itinerary linkToEdit={true} />
</Section>
<Section type="action">
<Payment />
Expand Down
14 changes: 13 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# TBD
# FlyGood (TBD)

To Be Documented.

# Local development

1. Install dependencies
```sh
yarn
```
2. Create `/.env` file based on `/.env.template`
3. Run
```sh
yarn develop
```
Loading