From 842becc4bed5b9c1f3e73bf1059435b012e1b133 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 12:54:01 +0200 Subject: [PATCH 1/3] [RAB-4] Create Layout, create analytics page --- apps/next-app/src/constants/ROUTES.ts | 1 + apps/next-app/src/middleware.ts | 19 ++++++++++-- apps/next-app/src/pages/_app.tsx | 21 +++++++++----- apps/next-app/src/pages/analytics/index.tsx | 9 ++++++ .../src/shared/components/Layout/index.ts | 1 + .../components/Layout/layout.component.tsx | 29 +++++++++++++++++++ 6 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 apps/next-app/src/pages/analytics/index.tsx create mode 100644 apps/next-app/src/shared/components/Layout/index.ts create mode 100644 apps/next-app/src/shared/components/Layout/layout.component.tsx diff --git a/apps/next-app/src/constants/ROUTES.ts b/apps/next-app/src/constants/ROUTES.ts index d0ecf49..be36b61 100644 --- a/apps/next-app/src/constants/ROUTES.ts +++ b/apps/next-app/src/constants/ROUTES.ts @@ -7,4 +7,5 @@ export enum ROUTES { SUBSCRIPTION = '/subscription', SUBSCRIPTION_SUCCESS = '/subscription/success', SUBSCRIPTION_CANCEL = '/subscription/cancel', + ANALYTICS = '/analytics', } diff --git a/apps/next-app/src/middleware.ts b/apps/next-app/src/middleware.ts index 2cdff00..f7027c7 100644 --- a/apps/next-app/src/middleware.ts +++ b/apps/next-app/src/middleware.ts @@ -1,5 +1,8 @@ import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'; +import { ROUTES } from 'constants/ROUTES'; import { NextRequest, NextResponse } from 'next/server'; +import { GET_PROFILE } from 'shared/queries/index.graphql'; +import { getApolloServerClient } from 'shared/services/apollo'; export async function middleware(req: NextRequest) { const res = NextResponse.next(); @@ -10,7 +13,19 @@ export async function middleware(req: NextRequest) { } = await supabase.auth.getSession(); if (session) { - // Authentication successful, forward request to protected route. + if (req.nextUrl.pathname.startsWith(ROUTES.ANALYTICS)) { + const client = getApolloServerClient(session.access_token); + + const { data } = await client.query({ + query: GET_PROFILE, + variables: { profileId: session.user.id }, + }); + if (!data.profilesCollection?.edges[0].node.subscription) { + const redirectUrl = req.nextUrl.clone(); + redirectUrl.pathname = ROUTES.SUBSCRIPTION; + return NextResponse.redirect(redirectUrl); + } + } return res; } @@ -21,5 +36,5 @@ export async function middleware(req: NextRequest) { } export const config = { - matcher: ['/dashboard', '/profile', '/subscription'], + matcher: ['/dashboard', '/profile', '/subscription', '/analytics'], }; diff --git a/apps/next-app/src/pages/_app.tsx b/apps/next-app/src/pages/_app.tsx index 9ccffa3..f422921 100644 --- a/apps/next-app/src/pages/_app.tsx +++ b/apps/next-app/src/pages/_app.tsx @@ -3,17 +3,22 @@ import { Nunito } from 'next/font/google'; import type { AppProps } from 'next/app'; import { AppProviders } from 'providers/AppProviders'; import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'; -import { useState } from 'react'; +import { ReactElement, ReactNode, useState } from 'react'; import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'; +import { NextPage } from 'next'; +import { Layout } from 'shared/components/Layout'; const nunito = Nunito({ subsets: ['latin'], weight: ['300', '500', '700'] }); -export default function App({ - Component, - pageProps, -}: AppProps<{ +export type NextPageWithLayout

= NextPage & { + getLayout?: (page: ReactElement) => ReactNode; +}; + +type CustomAppProps = AppProps & { initialSession: Session; -}>) { +}; + +export default function App({ Component, pageProps }: CustomAppProps) { const [supabaseClient] = useState(() => createBrowserSupabaseClient()); return ( @@ -23,7 +28,9 @@ export default function App({ >

- + + ) +
diff --git a/apps/next-app/src/pages/analytics/index.tsx b/apps/next-app/src/pages/analytics/index.tsx new file mode 100644 index 0000000..9988c32 --- /dev/null +++ b/apps/next-app/src/pages/analytics/index.tsx @@ -0,0 +1,9 @@ +const Analytics = () => { + return ( +
+

Hello Analytics!

+
+ ); +}; + +export default Analytics; diff --git a/apps/next-app/src/shared/components/Layout/index.ts b/apps/next-app/src/shared/components/Layout/index.ts new file mode 100644 index 0000000..53176c3 --- /dev/null +++ b/apps/next-app/src/shared/components/Layout/index.ts @@ -0,0 +1 @@ +export { Layout } from './layout.component'; diff --git a/apps/next-app/src/shared/components/Layout/layout.component.tsx b/apps/next-app/src/shared/components/Layout/layout.component.tsx new file mode 100644 index 0000000..afca3e8 --- /dev/null +++ b/apps/next-app/src/shared/components/Layout/layout.component.tsx @@ -0,0 +1,29 @@ +import { ROUTES } from 'constants/ROUTES'; +import Link from 'next/link'; +import { ReactNode } from 'react'; + +interface LayoutProps { + children: ReactNode; +} + +export const Layout = ({ children }: LayoutProps) => { + return ( +
+
+ + Profile + + + Dashboard + + + Subscription + + + Analytics + +
+ {children} +
+ ); +}; From 62774e309136cbb306619a5807971ccb6f21904f Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 13:14:42 +0200 Subject: [PATCH 2/3] [RAB-4] Create transaction history --- apps/next-app/package.json | 1 + apps/next-app/src/pages/profile/index.tsx | 38 ++++++++++++++++++- .../Crossmark/crossmark.component.tsx | 12 +++++- pnpm-lock.yaml | 8 ++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/apps/next-app/package.json b/apps/next-app/package.json index a34886c..f8a6d56 100644 --- a/apps/next-app/package.json +++ b/apps/next-app/package.json @@ -27,6 +27,7 @@ "@types/react-dom": "18.0.11", "autoprefixer": "10.4.14", "axios": "^1.3.6", + "date-fns": "^2.29.3", "eslint": "8.38.0", "eslint-config-next": "13.3.0", "graphql": "^16.6.0", diff --git a/apps/next-app/src/pages/profile/index.tsx b/apps/next-app/src/pages/profile/index.tsx index 8faa536..b44bfcb 100644 --- a/apps/next-app/src/pages/profile/index.tsx +++ b/apps/next-app/src/pages/profile/index.tsx @@ -5,12 +5,18 @@ import { GetServerSidePropsContext } from 'next'; import { AvatarChanger } from 'shared/components/profile/AvatarChanger'; import { EditProfileForm } from 'shared/components/profile/EditProfileForm'; import { getApolloServerClient } from 'shared/services/apollo'; +import { EDGE_FUNCTION_NAMES } from 'constants/EDGE_FUNCTION_NAMES'; +import { Checkmark } from 'shared/components/Checkmark'; +import { Crossmark } from 'shared/components/Crossmark'; +import { getSubscriptionPrice } from 'utils/getSubscriptionPrice'; +import { format, fromUnixTime } from 'date-fns'; interface ProfileProps { profile: GetProfileQuery['profilesCollection']; + charges: any; } -const Profile = ({ profile }: ProfileProps) => { +const Profile = ({ profile, charges }: ProfileProps) => { const userProfile = profile?.edges[0]; return ( @@ -19,6 +25,30 @@ const Profile = ({ profile }: ProfileProps) => { +
+

Transaction history

+
+ {charges.charges.data.map( + ({ id, created, description, amount, currency, status }: any) => ( +
+
+ {description}{' '} + + ({format(fromUnixTime(created), 'Pp')}) + +
+
+ {getSubscriptionPrice(amount)} {currency.toUpperCase()} +
+ {status === 'succeeded' ? : } +
+ ) + )} +
+
); }; @@ -34,5 +64,9 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { query: GET_PROFILE, variables: { profileId: session.data.session?.user.id }, }); - return { props: { profile: data.profilesCollection } }; + + const { data: chargesData } = await supabase.functions.invoke( + EDGE_FUNCTION_NAMES.GET_STRIPE_CHARGES + ); + return { props: { profile: data.profilesCollection, charges: chargesData } }; }; diff --git a/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx b/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx index de07503..72699a3 100644 --- a/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx +++ b/apps/next-app/src/shared/components/Crossmark/crossmark.component.tsx @@ -1,7 +1,15 @@ import CrossmarkSVG from 'assets/cross-mark.svg'; -export const Crossmark = () => { +interface CrossmarkProps { + isError?: boolean; +} + +export const Crossmark = ({ isError }: CrossmarkProps) => { return ( - + ); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adb1782..6278c3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: axios: specifier: ^1.3.6 version: 1.3.6 + date-fns: + specifier: ^2.29.3 + version: 2.29.3 eslint: specifier: 8.38.0 version: 8.38.0 @@ -4043,6 +4046,11 @@ packages: resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} dev: true + /date-fns@2.29.3: + resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + engines: {node: '>=0.11'} + dev: false + /debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} dev: true From 2c0481d20610c5cc2ca4c67b4bce84724a107018 Mon Sep 17 00:00:00 2001 From: Marek Miklaszewski Date: Wed, 26 Apr 2023 13:21:09 +0200 Subject: [PATCH 3/3] [RAB-4] Separate transactions into TransactionHistory --- apps/next-app/package.json | 1 - apps/next-app/src/pages/profile/index.tsx | 30 +------------- .../subscriptions/TransactionHistory/index.ts | 1 + .../transactionHistory.component.tsx | 39 +++++++++++++++++++ pnpm-lock.yaml | 33 +++------------- 5 files changed, 48 insertions(+), 56 deletions(-) create mode 100644 apps/next-app/src/shared/components/subscriptions/TransactionHistory/index.ts create mode 100644 apps/next-app/src/shared/components/subscriptions/TransactionHistory/transactionHistory.component.tsx diff --git a/apps/next-app/package.json b/apps/next-app/package.json index f8a6d56..d19dad7 100644 --- a/apps/next-app/package.json +++ b/apps/next-app/package.json @@ -26,7 +26,6 @@ "@types/react": "18.0.37", "@types/react-dom": "18.0.11", "autoprefixer": "10.4.14", - "axios": "^1.3.6", "date-fns": "^2.29.3", "eslint": "8.38.0", "eslint-config-next": "13.3.0", diff --git a/apps/next-app/src/pages/profile/index.tsx b/apps/next-app/src/pages/profile/index.tsx index b44bfcb..944b8c7 100644 --- a/apps/next-app/src/pages/profile/index.tsx +++ b/apps/next-app/src/pages/profile/index.tsx @@ -6,10 +6,7 @@ import { AvatarChanger } from 'shared/components/profile/AvatarChanger'; import { EditProfileForm } from 'shared/components/profile/EditProfileForm'; import { getApolloServerClient } from 'shared/services/apollo'; import { EDGE_FUNCTION_NAMES } from 'constants/EDGE_FUNCTION_NAMES'; -import { Checkmark } from 'shared/components/Checkmark'; -import { Crossmark } from 'shared/components/Crossmark'; -import { getSubscriptionPrice } from 'utils/getSubscriptionPrice'; -import { format, fromUnixTime } from 'date-fns'; +import { TransactionHistory } from 'shared/components/subscriptions/TransactionHistory'; interface ProfileProps { profile: GetProfileQuery['profilesCollection']; @@ -25,30 +22,7 @@ const Profile = ({ profile, charges }: ProfileProps) => { -
-

Transaction history

-
- {charges.charges.data.map( - ({ id, created, description, amount, currency, status }: any) => ( -
-
- {description}{' '} - - ({format(fromUnixTime(created), 'Pp')}) - -
-
- {getSubscriptionPrice(amount)} {currency.toUpperCase()} -
- {status === 'succeeded' ? : } -
- ) - )} -
-
+ ); }; diff --git a/apps/next-app/src/shared/components/subscriptions/TransactionHistory/index.ts b/apps/next-app/src/shared/components/subscriptions/TransactionHistory/index.ts new file mode 100644 index 0000000..a0b66fb --- /dev/null +++ b/apps/next-app/src/shared/components/subscriptions/TransactionHistory/index.ts @@ -0,0 +1 @@ +export { TransactionHistory } from './transactionHistory.component'; diff --git a/apps/next-app/src/shared/components/subscriptions/TransactionHistory/transactionHistory.component.tsx b/apps/next-app/src/shared/components/subscriptions/TransactionHistory/transactionHistory.component.tsx new file mode 100644 index 0000000..1cf9400 --- /dev/null +++ b/apps/next-app/src/shared/components/subscriptions/TransactionHistory/transactionHistory.component.tsx @@ -0,0 +1,39 @@ +import { format, fromUnixTime } from 'date-fns'; +import { Checkmark } from 'shared/components/Checkmark'; +import { Crossmark } from 'shared/components/Crossmark'; +import { getSubscriptionPrice } from 'utils/getSubscriptionPrice'; + +interface TransactionHistoryProps { + transactions: any; +} + +export const TransactionHistory = ({ + transactions, +}: TransactionHistoryProps) => { + return ( +
+

Transaction history

+
+ {transactions.charges.data.map( + ({ id, created, description, amount, currency, status }: any) => ( +
+
+ {description}{' '} + + ({format(fromUnixTime(created), 'Pp')}) + +
+
+ {getSubscriptionPrice(amount)} {currency.toUpperCase()} +
+ {status === 'succeeded' ? : } +
+ ) + )} +
+
+ ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6278c3a..17dd3f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,9 +42,6 @@ importers: autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.22) - axios: - specifier: ^1.3.6 - version: 1.3.6 date-fns: specifier: ^2.29.3 version: 2.29.3 @@ -3332,6 +3329,7 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true /auto-bind@4.0.0: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} @@ -3363,16 +3361,6 @@ packages: engines: {node: '>=4'} dev: false - /axios@1.3.6: - resolution: {integrity: sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==} - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: false - /axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} dependencies: @@ -3860,6 +3848,7 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: true /commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} @@ -4151,6 +4140,7 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dev: true /dependency-graph@0.11.0: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} @@ -4888,16 +4878,6 @@ packages: /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - /follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: false - /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -4910,6 +4890,7 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true /formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} @@ -6450,12 +6431,14 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + dev: true /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + dev: true /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -7129,10 +7112,6 @@ packages: react-is: 16.13.1 dev: false - /proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: false - /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true