Skip to content

Commit

Permalink
feature(website): update account dashboard (#619)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkue authored Nov 11, 2023
1 parent d2a190e commit 77e0aba
Show file tree
Hide file tree
Showing 35 changed files with 573 additions and 201 deletions.
2 changes: 1 addition & 1 deletion seed/auth_export/accounts.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions seed/firebase-export-metadata.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"version": "12.4.4",
"version": "12.7.0",
"firestore": {
"version": "1.18.1",
"version": "1.18.2",
"path": "firestore_export",
"metadata_file": "firestore_export/firestore_export.overall_export_metadata"
},
"auth": {
"version": "12.4.4",
"version": "12.7.0",
"path": "auth_export"
},
"storage": {
"version": "12.4.4",
"version": "12.7.0",
"path": "storage_export"
}
}
Binary file not shown.
Binary file modified seed/firestore_export/all_namespaces/all_kinds/output-0
Binary file not shown.
Binary file modified seed/firestore_export/firestore_export.overall_export_metadata
Binary file not shown.
40 changes: 36 additions & 4 deletions shared/locales/en/website-me.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
{
"tabs": {
"contact-details": "Contact Info",
"contributions": "Contributions"
"sections": {
"account": {
"title": "My Account",
"personal-info": "Personal Info",
"security": "Security"
},
"contributions": {
"title": "My Contributions",
"payments": "Payments",
"subscriptions": "Subscriptions"
}
},
"contributions": {
"amount": "Amount",
"date": "Date",
"source": "Source",
"total": "Total",
"amount-currency": "{{ amount, currency }}",
"date": "Date"
"sources": {
"benevity": "Benevity",
"cash": "Cash",
"stripe": "Stripe",
"wire-transfer": "Wire Transfer"
}
},
"subscriptions": {
"amount": "Amount",
"date": "Date",
"source": "Source",
"total": "Total",
"amount-currency": "{{ amount, currency }}",
"interval": "Interval",
"interval-1": "Monthly",
"interval-3": "Quarterly",
"interval-12": "Annually",
"status": {
"active": "Active",
"canceled": "Canceled",
"paused": "Paused"
}
},
"login": {
"title": "Sign in to your account",
Expand Down
15 changes: 11 additions & 4 deletions shared/src/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ export function getMonthIDs(date: Date, last_n: number) {
return months;
}

export function toDateTime(timestamp: Timestamp | Date, timezone: string = 'utc') {
return timestamp instanceof Date
? DateTime.fromJSDate(timestamp, { zone: timezone })
: DateTime.fromMillis(timestamp.toMillis(), { zone: timezone });
export function toDateTime(timestamp: Timestamp | Date | number, timezone: string = 'utc') {
if (timestamp instanceof Date) {
timestamp = timestamp as Date;
return DateTime.fromJSDate(timestamp, { zone: timezone });
} else if (Number.isInteger(timestamp)) {
timestamp = timestamp as number;
return DateTime.fromMillis(timestamp as number, { zone: timezone });
} else {
timestamp = timestamp as Timestamp;
return DateTime.fromMillis(timestamp.toMillis(), { zone: timezone });
}
}

export function toDate(dateTime: DateTime) {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const buttonVariants = cva(
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-16 rounded-md px-8',
lg: 'h-16 rounded-md px-8 font-semibold',
icon: 'h-10 w-10',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ export default function Section1Form({ translations }: Section1InputProps) {
/>
<CurrencySelector className="h-16 sm:flex-1" currencies={websiteCurrencies} fontSize="lg" />
</div>
<Button size="lg" type="submit" variant="default">
<Typography size="lg" weight="semibold" color="primary-foreground">
{translations.submit}
</Typography>
<Button size="lg" type="submit" variant="default" className="text-lg">
{translations.submit}
</Button>
</form>
</Form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { DefaultPageProps } from '@/app/[lang]/[region]';
import { CreateSubscriptionData } from '@/app/api/stripe/checkout/new/route';
import { CreateSubscriptionData } from '@/app/api/stripe/checkout/new-payment/route';
import { CheckCircleIcon } from '@heroicons/react/24/outline';
import { zodResolver } from '@hookform/resolvers/zod';
import {
Expand All @@ -19,6 +19,7 @@ import {
import classNames from 'classnames';
import { useRouter } from 'next/navigation';
import { UseFormReturn, useForm } from 'react-hook-form';
import { useUser } from 'reactfire';
import Stripe from 'stripe';
import * as z from 'zod';

Expand Down Expand Up @@ -67,6 +68,7 @@ function RadioGroupFormItem({ active, title, value, form, description }: RadioGr

export default function Page({ params, searchParams }: DefaultPageProps) {
const router = useRouter();
const { data: authUser } = useUser();

const formSchema = z.object({
amount: z.coerce.number(),
Expand All @@ -80,12 +82,15 @@ export default function Page({ params, searchParams }: DefaultPageProps) {
});

const onSubmit = async (values: FormSchema) => {
const authToken = await authUser?.getIdToken(true);
const data: CreateSubscriptionData = {
amount: values.amount * 100, // The amount is in cents, so we need to multiply by 100 to get the correct amount.
intervalCount: Number(values.intervalCount),
successUrl: `${window.location.origin}/${params.lang}/${params.region}/donate/success?stripeCheckoutSessionId={CHECKOUT_SESSION_ID}`,
recurring: true,
firebaseAuthToken: authToken,
};
const response = await fetch('/api/stripe/checkout/new', {
const response = await fetch('/api/stripe/checkout/new-payment', {
method: 'POST',
body: JSON.stringify(data),
});
Expand Down
32 changes: 18 additions & 14 deletions website/src/app/[lang]/[region]/(website)/login/login-form.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use client';

import { DefaultPageProps } from '@/app/[lang]/[region]';
import { DefaultParams } from '@/app/[lang]/[region]';
import { zodResolver } from '@hookform/resolvers/zod';
import { SiGoogle } from '@icons-pack/react-simple-icons';
import { Button, Form, FormControl, FormField, FormItem, FormMessage, Input, Typography } from '@socialincome/ui';
import { FirebaseError } from 'firebase/app';
import { browserSessionPersistence, signInWithEmailAndPassword } from 'firebase/auth';
import { useRouter } from 'next/navigation';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { useAuth } from 'reactfire';
Expand All @@ -26,9 +27,9 @@ type LoginFormProps = {
unknownUser: string;
wrongPassword: string;
};
} & DefaultPageProps;
} & DefaultParams;

export default function LoginForm({ params, translations }: LoginFormProps) {
export default function LoginForm({ lang, region, translations }: LoginFormProps) {
const router = useRouter();
const auth = useAuth();

Expand All @@ -43,17 +44,20 @@ export default function LoginForm({ params, translations }: LoginFormProps) {
defaultValues: { email: '', password: '' },
});

const onSubmit = async (values: FormSchema) => {
await auth.setPersistence(browserSessionPersistence);
await signInWithEmailAndPassword(auth, values.email, values.password)
.then(() => {
router.push(`/${params.lang}/${params.region}/me`);
})
.catch((error: FirebaseError) => {
error.code === 'auth/wrong-password' && toast.error(translations.wrongPassword);
error.code === 'auth/user-not-found' && toast.error(translations.unknownUser);
});
};
const onSubmit = useCallback(
async (values: FormSchema) => {
await auth.setPersistence(browserSessionPersistence);
await signInWithEmailAndPassword(auth, values.email, values.password)
.then(() => {
router.push(`/${lang}/${region}/me`);
})
.catch((error: FirebaseError) => {
error.code === 'auth/wrong-password' && toast.error(translations.wrongPassword);
error.code === 'auth/user-not-found' && toast.error(translations.unknownUser);
});
},
[auth, lang, region, router, translations.wrongPassword, translations.unknownUser],
);

return (
<div className="mx-auto flex max-w-xl flex-col space-y-8">
Expand Down
7 changes: 4 additions & 3 deletions website/src/app/[lang]/[region]/(website)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { DefaultPageProps } from '@/app/[lang]/[region]';
import LoginForm from '@/app/[lang]/[region]/(website)/login/login-form';
import { Translator } from '@socialincome/shared/src/utils/i18n';

export default async function Page(props: DefaultPageProps) {
const translator = await Translator.getInstance({ language: props.params.lang, namespaces: 'website-me' });
export default async function Page({ params }: DefaultPageProps) {
const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-me'] });

return (
<LoginForm
lang={params.lang}
region={params.region}
translations={{
title: translator.t('login.title'),
email: translator.t('login.email'),
Expand All @@ -18,7 +20,6 @@ export default async function Page(props: DefaultPageProps) {
unknownUser: translator.t('login.unknown-user'),
wrongPassword: translator.t('login.wrong-password'),
}}
{...props}
/>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,51 @@
import { DefaultParams } from '@/app/[lang]/[region]';
import { UserContext } from '@/app/[lang]/[region]/(website)/me/user-context-provider';
import { useTranslator } from '@/hooks/useTranslator';
import { orderBy } from '@firebase/firestore';
import { CONTRIBUTION_FIRESTORE_PATH, StatusKey } from '@socialincome/shared/src/types/contribution';
import { USER_FIRESTORE_PATH } from '@socialincome/shared/src/types/user';
import { toDateTime } from '@socialincome/shared/src/utils/date';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from '@socialincome/ui';
import { useQuery } from '@tanstack/react-query';
import { collection, getDocs, query, where } from 'firebase/firestore';
import _ from 'lodash';
import { useContext } from 'react';
import { useFirestore } from 'reactfire';

type ContributionsTableProps = {
translations: {
date: string;
amount: string;
source: string;
};
} & DefaultParams;

export function ContributionsTable({ lang, translations }: ContributionsTableProps) {
const firestore = useFirestore();
const { user } = useContext(UserContext);
const translator = useTranslator(lang, 'website-me');
const { user } = useContext(UserContext);
const { data: contributions } = useQuery({
queryKey: [user, firestore],
queryKey: ['ContributionsTable', user, firestore],
queryFn: async () => {
if (user && firestore) {
return await getDocs(
query(
collection(firestore, USER_FIRESTORE_PATH, user.id, CONTRIBUTION_FIRESTORE_PATH),
where('status', '==', StatusKey.SUCCEEDED),
orderBy('created', 'desc'),
),
);
} else return null;
},
staleTime: 1000 * 60 * 60, // 1 hour
});
console.log(user?.id, firestore, contributions?.size);

return (
<Table>
<TableHeader>
<TableRow>
<TableHead>{translations.date}</TableHead>
<TableHead>{translations.source}</TableHead>
<TableHead className="text-right">{translations.amount}</TableHead>
</TableRow>
</TableHeader>
Expand All @@ -54,6 +58,7 @@ export function ContributionsTable({ lang, translations }: ContributionsTablePro
<TableCell>
<Typography>{toDateTime(contribution.get('created')).toFormat('DD', { locale: lang })}</Typography>
</TableCell>
<TableCell>{translator?.t(`contributions.sources.${contribution.get('source')}`)}</TableCell>
<TableCell className="text-right">
<Typography>
{translator?.t('contributions.amount-currency', {
Expand All @@ -68,6 +73,23 @@ export function ContributionsTable({ lang, translations }: ContributionsTablePro
</TableRow>
);
})}
<TableRow>
<TableCell>
<Typography weight="semibold">{translator?.t('contributions.total')}</Typography>
</TableCell>
<TableCell />
<TableCell>
<Typography className="text-right" weight="semibold">
{translator?.t('contributions.amount-currency', {
context: {
amount: _.sum(contributions?.docs.map((contribution) => contribution.get('amount'))),
currency: contributions?.docs[0].get('currency'),
locale: lang,
},
})}
</Typography>
</TableCell>
</TableRow>
</TableBody>
</Table>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { DefaultPageProps } from '@/app/[lang]/[region]';
import { ContributionsTable } from '@/app/[lang]/[region]/(website)/me/contributions/contributions-table';
import { Translator } from '@socialincome/shared/src/utils/i18n';
import { BillingPortalButton } from './billing-portal-button';

export default async function Page({ params }: DefaultPageProps) {
const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-me'] });

return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="flex max-w-lg flex-col space-y-4">
<ContributionsTable
translations={{ date: translator.t('contributions.date'), amount: translator.t('contributions.amount') }}
{...params}
/>
</div>
<BillingPortalButton />
<div className="grid grid-cols-1 gap-4">
<ContributionsTable
translations={{
date: translator.t('contributions.date'),
amount: translator.t('contributions.amount'),
source: translator.t('contributions.source'),
}}
{...params}
/>
</div>
);
}
Loading

0 comments on commit 77e0aba

Please sign in to comment.