Skip to content

Commit 118a988

Browse files
committed
subscription setup frontend and refactor old code
1 parent 5225564 commit 118a988

File tree

11 files changed

+178
-56
lines changed

11 files changed

+178
-56
lines changed

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import Footer from "./components/Footer";
77
import { useDispatch } from "react-redux";
88
import { getUserInfo } from "./redux-store/slices/authSlice";
99
import { AppDispatch } from "./redux-store/store";
10+
import { getSubscriptionInfo } from "./redux-store/slices/subscriptionslice";
1011
function App() {
1112
const dispatch: AppDispatch = useDispatch();
1213
useEffect(() => {
1314
dispatch(getUserInfo());
15+
dispatch(getSubscriptionInfo());
1416
}, [dispatch]);
1517
return (
1618
<div className="min-h-screen w-full ">

src/api/axiosclient.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,5 @@ export const axiosClient=axios.create({
88
'Content-Type': 'application/json',
99
'Access-Control-Allow-Origin': String(import.meta.env.VITE_APP_FRONTEND_BASE_URL),
1010
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
11-
// 'cache-control': 'private',
12-
// 'E-Tag': Cookies.get('auth_token'),
1311
},
1412
});

src/components/ChartView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ export default function App({ type }: { type: string }) {
4545
datasets: [
4646
{
4747
label: type,
48-
data: type === "Score" ? chartdata.scoresval : chartdata.accuracyval,
48+
data: type === "Wpm" ? chartdata.scoresval : chartdata.accuracyval,
4949
borderColor:
50-
type === "Score" ? "rgb(54, 162, 235)" : "rgb(255, 159, 64)",
50+
type === "Wpm" ? "rgb(54, 162, 235)" : "rgb(255, 159, 64)",
5151
backgroundColor:
52-
type === "Score" ? "rgb(54, 162, 235)" : "rgb(255, 159, 64)",
52+
type === "Wpm" ? "rgb(54, 162, 235)" : "rgb(255, 159, 64)",
5353
borderWidth: 1,
5454
},
5555
],

src/components/History.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ResultData } from "@/redux-store/slices/resultslice";
2+
import React from "react";
3+
interface Props {
4+
results: ResultData[] | undefined;
5+
}
6+
const History: React.FC<Props> = ({ results }) => {
7+
if (!results) {
8+
return <div>No Test Reesults</div>;
9+
}
10+
return (
11+
<div className="w-full overflow-x-auto">
12+
<table className="w-full table-auto border-collapse text-left dark:bg-gray-950 dark:text-gray-50">
13+
<thead className="bg-gray-100 dark:bg-gray-800">
14+
<tr>
15+
<th className="px-4 py-3 font-medium">WPM</th>
16+
<th className="px-4 py-3 font-medium">Accuracy</th>
17+
<th className="px-4 py-3 font-medium">Date</th>
18+
</tr>
19+
</thead>
20+
<tbody>
21+
{results?.map((result) => (
22+
<tr
23+
key={result.id}
24+
className="border-b border-gray-200 dark:border-gray-800"
25+
>
26+
<td className="px-4 py-3"> {result.score}</td>
27+
<td className="px-4 py-3">{result.accuracy}</td>
28+
<td className="px-4 py-3">{new Date(result.createdAt).getDate() +"/"+ new Date(result.createdAt).getMonth()+"/"+new Date(result.createdAt).getFullYear()}</td>
29+
</tr>
30+
))}
31+
</tbody>
32+
</table>
33+
</div>
34+
);
35+
};
36+
37+
export default History;

src/components/Pricing.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ export default function Pricing() {
1414
<div>
1515
<h3 className="text-2xl font-bold text-center">Pro</h3>
1616
<div className="mt-4 text-center text-zinc-600 dark:text-zinc-400">
17-
<span className="text-4xl font-bold">$59</span>/ month
17+
<span className="text-4xl font-bold">&#8377; 1000</span>/ month
1818
</div>
1919
<ul className="mt-4 space-y-2">
2020
<li className="flex items-center dark:text-black">
2121
<CheckIcon className="text-white text-2xs bg-green-500 rounded-full mr-2 p-1 " />
22-
100 daily Limit
22+
Unlimited Test
2323
</li>
2424
<li className="flex items-center dark:text-black">
2525
<CheckIcon className="text-white text-xs bg-green-500 rounded-full mr-2 p-1" />
26-
Unlimited Usage
26+
Detailed Report of Wpm
2727
</li>
2828
<li className="flex items-center dark:text-black">
2929
<CheckIcon className="text-white text-xs bg-green-500 rounded-full mr-2 p-1" />
30-
24/7 Support
30+
Graphical Review of Accuracy and Wpm.
3131
</li>
3232
</ul>
3333
</div>

src/hooks/useSubscription.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { AuthState } from "@/redux-store/store";
2+
import { useSelector } from "react-redux"
3+
4+
export const useSubscription = () => {
5+
const { isLoadingSubscription, isProUser, subscriptionData } = useSelector((state: AuthState) => state.subscription);
6+
return { isLoadingSubscription, isProUser, subscriptionData };
7+
}

src/pages/Dashboard/DashBoard.tsx

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import { logoutUser } from "@/redux-store/slices/authSlice";
1616
import { calculateAverage } from "@/lib/helpers/calculateUserAverage";
1717
import ChartView from "@/components/ChartView";
1818
import { ModeToggle } from "@/components/ui/toggle-theme";
19+
import { useSubscription } from "@/hooks/useSubscription";
20+
import History from "@/components/History";
1921

2022
export default function Component() {
2123
const dispatch = useAppDispatch();
24+
const { isProUser } = useSubscription();
2225
const { user } = useAuthStatus();
2326
const { results } = useResultStatus();
2427
const [averagedata, setAverageData] = useState({
@@ -29,18 +32,20 @@ export default function Component() {
2932
if (user?.id) {
3033
dispatch(getResultsInfo(String(user?.id)));
3134
}
35+
}, [dispatch, user?.id]);
36+
useEffect(() => {
3237
if (results) {
3338
const res = calculateAverage(results);
3439
setAverageData(res);
3540
}
36-
}, [results, user?.id, dispatch]);
41+
}, [results]);
3742
return (
3843
<div className="flex h-screen w-full flex-col p-0">
3944
<div className="flex h-full flex-row">
4045
<div className="flex flex-col flex-1">
4146
<header className="h-16 px-4 border-b shrink-0 md:px-6 flex items-center">
4247
<div className="flex items-center justify-end w-full gap-4 md:ml-auto md:gap-2 lg:gap-4">
43-
{!user?.isProUser && (
48+
{!isProUser && (
4449
<Link
4550
className="text-gray-600 hover:text-gray-900 dark:text-gray-200"
4651
to="/pricing"
@@ -86,12 +91,12 @@ export default function Component() {
8691
<Card>
8792
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
8893
<CardTitle className="text-sm font-medium">
89-
Average score
94+
Average wpm
9095
</CardTitle>
9196
</CardHeader>
9297
<CardContent>
9398
<div className="text-2xl font-bold text-green-500">
94-
{averagedata.averageScore}
99+
{averagedata.averageScore.toFixed(1)}
95100
</div>
96101
</CardContent>
97102
</Card>
@@ -108,39 +113,53 @@ export default function Component() {
108113
</CardContent>
109114
</Card>
110115
</div>
111-
<div className="grid gap-4 md:grid-cols-2">
112-
<Card>
113-
<CardHeader className="pb-4">
114-
<CardTitle className="text-lg font-medium">
115-
Score Overview
116-
</CardTitle>
117-
<CardDescription>
118-
Your score's Variations in the typing test
119-
</CardDescription>
120-
</CardHeader>
121-
<CardContent className="">
122-
{/* Chart Component Here */}
123-
<ChartView type="Score" />
124-
</CardContent>
125-
</Card>
126-
<Card>
127-
<CardHeader className="pb-4">
128-
<CardTitle className="text-lg font-medium">
129-
Accuracy Overview
130-
</CardTitle>
131-
<CardDescription>
132-
Your accuracy Variations in the typing test
133-
</CardDescription>
134-
</CardHeader>
135-
<CardContent className="">
136-
{/* Chart Component Here */}
137-
<ChartView type="Accuracy" />
138-
</CardContent>
139-
</Card>
116+
<div>
117+
<h1 className="text-xl font-bold text-black dark:text-white mb-4">
118+
Test History
119+
</h1>
120+
<History results={results} />
140121
</div>
122+
{isProUser && (
123+
<div className="grid gap-4 md:grid-cols-2">
124+
<Card>
125+
<CardHeader className="pb-4">
126+
<CardTitle className="text-lg font-medium">
127+
Wpm Overview
128+
</CardTitle>
129+
<CardDescription>
130+
Your wpm Variations in the typing test
131+
</CardDescription>
132+
</CardHeader>
133+
<CardContent className="">
134+
{/* Chart Component Here */}
135+
<ChartView type="Wpm" />
136+
</CardContent>
137+
</Card>
138+
<Card>
139+
<CardHeader className="pb-4">
140+
<CardTitle className="text-lg font-medium">
141+
Accuracy Overview
142+
</CardTitle>
143+
<CardDescription>
144+
Your accuracy Variations in the typing test
145+
</CardDescription>
146+
</CardHeader>
147+
<CardContent className="">
148+
{/* Chart Component Here */}
149+
<ChartView type="Accuracy" />
150+
</CardContent>
151+
</Card>
152+
</div>
153+
)}
141154
</main>
142155
</div>
143156
</div>
157+
<Button
158+
className="fixed bottom-7 left-10 object-cover w-fit shadow-2xl bg-gradient-to-r from-purple-400 to-fuchsia-600"
159+
variant={"destructive"}
160+
>
161+
{isProUser ? "Manage Subscriptions" : "Go Pro"}
162+
</Button>
144163
</div>
145164
);
146165
}

src/redux-store/slices/authSlice.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { AxiosError } from "axios";
66
import { ErrorResponse } from "@/types/interfaces";
77
export interface User {
88
id: number;
9-
createdAt: string;
9+
createdAt: Date;
1010
email: string;
1111
name: string;
12-
password: string;
1312
isEmailVerified: boolean;
14-
isProUser: boolean;
13+
limit: number;
14+
updatedAt: Date;
1515
}
1616
interface AuthState {
1717
isLoading: boolean;
@@ -110,7 +110,7 @@ const authSlice = createSlice({
110110
toast.error(response.message);
111111
});
112112
builder.addCase(logoutUser.fulfilled, (state, action) => {
113-
state.user=undefined;
113+
state.user = undefined;
114114
const response = action.payload.data;
115115
toast.success(response.message);
116116
})

src/redux-store/slices/resultslice.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const initialState: ResultState = {
2424
};
2525
export const getResultsInfo = createAsyncThunk("results/me", async (id: string, { rejectWithValue }) => {
2626
try {
27-
const data = await axiosClient.get(`result/${id}`);
27+
const data = await axiosClient.get(`/result/${id}`);
2828
return data;
2929
} catch (error) {
3030
const err = error as AxiosError;
@@ -49,7 +49,6 @@ const resultSlice = createSlice({
4949
state.isLoadingResults = false;
5050
const response = action.payload as ErrorResponse;
5151
toast.error(response.message);
52-
console.log(response)
5352
});
5453
},
5554
})
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { axiosClient } from "@/api/axiosclient";
2+
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
3+
import { AxiosError } from "axios";
4+
const DAY_IN_MS = 86_400_400;
5+
export interface SubscriptionData {
6+
id: number;
7+
userID: number;
8+
stripeSubscriptionId: string;
9+
stripeCustomerId: string;
10+
stripePriceId: string;
11+
stripeCurrentPeriodEnd: Date;
12+
}
13+
14+
interface SubscriptionState {
15+
isLoadingSubscription: boolean;
16+
isProUser: boolean
17+
subscriptionData: SubscriptionData | undefined;
18+
}
19+
20+
const initialState: SubscriptionState = {
21+
isLoadingSubscription: false,
22+
isProUser: true,
23+
subscriptionData: undefined,
24+
};
25+
export const getSubscriptionInfo = createAsyncThunk("mySubscription", async (_, { rejectWithValue }) => {
26+
try {
27+
const data = await axiosClient.get(`/mysubscription`);
28+
return data;
29+
} catch (error) {
30+
const err = error as AxiosError;
31+
return rejectWithValue(err.response?.data)
32+
}
33+
})
34+
const resultSlice = createSlice({
35+
name: 'subscription',
36+
initialState,
37+
reducers: {},
38+
extraReducers: (builder) => {
39+
builder.addCase(getSubscriptionInfo.pending, (state) => {
40+
state.isLoadingSubscription = true;
41+
});
42+
builder.addCase(getSubscriptionInfo.fulfilled, (state, action) => {
43+
state.isLoadingSubscription = false;
44+
const response = action.payload.data;
45+
state.subscriptionData = response.data;
46+
if (state.subscriptionData?.stripePriceId && state.subscriptionData?.stripeCurrentPeriodEnd.getTime() + DAY_IN_MS > Date.now()) {
47+
state.isProUser = true;
48+
}
49+
});
50+
builder.addCase(getSubscriptionInfo.rejected, (state) => {
51+
state.isLoadingSubscription = false;
52+
state.isProUser = false;
53+
state.subscriptionData = undefined
54+
});
55+
},
56+
})
57+
58+
export default resultSlice.reducer;

src/redux-store/store.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
import { configureStore } from "@reduxjs/toolkit";
22
import authSlice from "./slices/authSlice";
33
import resultSlice from "./slices/resultslice";
4-
const store=configureStore({
5-
reducer:{
6-
auth:authSlice,
7-
result: resultSlice,
8-
},
9-
middleware: (getDefaultMiddleware) =>
4+
import subscriptionslice from "./slices/subscriptionslice";
5+
const store = configureStore({
6+
reducer: {
7+
auth: authSlice,
8+
result: resultSlice,
9+
subscription: subscriptionslice
10+
},
11+
middleware: (getDefaultMiddleware) =>
1012
getDefaultMiddleware({
1113
serializableCheck: {
1214
// Ignore these action types
13-
ignoredActions: ['auth/me/fulfilled', 'auth/me/pending', 'auth/me/rejected',"results/me/fulfilled","results/me/pending","results/me/rejected","auth/login/fulfilled","auth/register/fulfilled","auth/logout/fulfilled"],
15+
ignoredActions: ['auth/me/fulfilled', 'auth/me/pending', 'auth/me/rejected', "results/me/fulfilled", "results/me/pending", "results/me/rejected", "auth/login/fulfilled", "auth/register/fulfilled", "auth/logout/fulfilled", "mySubscription/pending", "mySubscription/rejected", "mySubscription/fulfilled"],
1416
// Ignore these field paths in all actions
1517
ignoredActionPaths: ['meta.arg', 'payload.timestamp'],
1618
// Ignore these paths in the state
1719
ignoredPaths: ['items.dates'],
1820
},
19-
}),
21+
}),
2022
});
2123

2224
export default store;

0 commit comments

Comments
 (0)