Skip to content

Commit

Permalink
Dashboard (#320)
Browse files Browse the repository at this point in the history
* Added new model called Activities to capture user activities

* Graphs showing essential metrics

* Added index

* CodeQL fixes

---------

Co-authored-by: Rajat Saxena <[email protected]>
  • Loading branch information
rajat1saxena and Rajat Saxena authored Mar 27, 2024
1 parent 98902f6 commit c1e4ba2
Show file tree
Hide file tree
Showing 173 changed files with 2,192 additions and 327 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
20 changes: 17 additions & 3 deletions apps/web/components/admin/base-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import React, { ReactNode, useEffect } from "react";
import { connect } from "react-redux";
import { useRouter } from "next/router";
import { Rocket, Text, Person, Mail, Settings } from "@courselit/icons";
import {
Rocket,
Text,
Person,
Mail,
Settings,
Home,
Globe,
} from "@courselit/icons";
import {
CREATOR_AREA_PAGE_TITLE,
SIDEBAR_MENU_BLOGS,
Expand All @@ -10,6 +18,7 @@ import {
SIDEBAR_MENU_USERS,
SIDEBAR_MENU_MAILS,
SIDEBAR_MENU_PAGES,
SIDEBAR_MENU_DASHBOARD,
} from "../../ui-config/strings";
import AppLoader from "../app-loader";
import Head from "next/head";
Expand All @@ -25,11 +34,16 @@ import {
AppState,
} from "@courselit/state-management";
import { useSession } from "next-auth/react";
import { Globe } from "@courselit/icons";
const { permissions } = constants;

const getSidebarMenuItems = (profile: Profile, featureFlags: string[]) => {
const items = [];
const items = [
{
label: SIDEBAR_MENU_DASHBOARD,
href: "/dashboard",
icon: <Home />,
},
];

if (
checkPermission(profile.permissions, [
Expand Down
52 changes: 52 additions & 0 deletions apps/web/components/admin/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Address, SiteInfo } from "@courselit/common-models";
import { AppDispatch, AppState } from "@courselit/state-management";
import { DASHBOARD_PAGE_HEADER } from "@ui-config/strings";
import { connect } from "react-redux";
import ToDo from "./to-do";
import Metric from "./metric";

interface IndexProps {
dispatch: AppDispatch;
address: Address;
loading: boolean;
siteinfo: SiteInfo;
}

const Index = ({ loading, address, dispatch, siteinfo }: IndexProps) => {
return (
<div>
<h1 className="text-4xl font-semibold mb-8">
{DASHBOARD_PAGE_HEADER}
</h1>
<div className="mb-8">
<ToDo />
</div>
<div className="grid xs:grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Metric title="Revenue" type="purchased" duration="7d" />
<Metric title="Enrollments" type="enrolled" duration="7d" />
<Metric
title="New accounts"
type="user_created"
duration="7d"
/>
<Metric
title="Subscribers"
type="newsletter_subscribed"
duration="7d"
/>
</div>
</div>
);
};

const mapStateToProps = (state: AppState) => ({
address: state.address,
loading: state.networkAction,
siteinfo: state.siteinfo,
});

const mapDispatchToProps = (dispatch: AppDispatch) => ({
dispatch,
});

export default connect(mapStateToProps, mapDispatchToProps)(Index);
171 changes: 171 additions & 0 deletions apps/web/components/admin/dashboard/metric.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Select,
Skeleton,
} from "@courselit/components-library";
import constants from "@config/constants";
import { useEffect, useState } from "react";
import { FetchBuilder } from "@courselit/utils";
import { AppState } from "@courselit/state-management";
import { Address } from "@courselit/common-models";
import { connect } from "react-redux";
import {
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
const { analyticsDurations, activityTypes } = constants;

type Duration = (typeof analyticsDurations)[number];

interface MetricProps {
title: string;
type: (typeof activityTypes)[number];
duration?: Duration;
description?: string;
address: Address;
}

const Metric = ({
title,
duration = "7d",
type,
description,
address,
}: MetricProps) => {
const [data, setData] = useState<{
count: number;
points: { date: string; count: number }[];
}>();
const [internalDuration, setInternalDuration] = useState(duration);
const [loading, setLoading] = useState(false);

useEffect(() => {
const getData = async () => {
const query = `
query {
activities: getActivities(
type: ${type.toUpperCase()},
duration: _${internalDuration.toUpperCase()}
) {
count,
points {
date,
count
}
}
}
`;

const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/graph`)
.setPayload(query)
.setIsGraphQLEndpoint(true)
.build();

try {
setLoading(true);
const response = await fetch.exec();
if (response.activities) {
const pointsWithDate = response.activities.points.map(
(point: { date: string; count: number }) => {
return {
date: new Date(
+point.date,
).toLocaleDateString(),
count: point.count,
};
},
);

setData({
count: response.activities.count,
points: pointsWithDate,
});
}
} catch (err: any) {
console.log("Error in fetching activities"); // eslint-disable-line
} finally {
setLoading(false);
}
};

getData();
}, [internalDuration]);

Check warning on line 101 in apps/web/components/admin/dashboard/metric.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has missing dependencies: 'address.backend' and 'type'. Either include them or remove the dependency array

return (
<Card>
<CardHeader>
<div className="flex justify-between items-start">
<CardTitle>{title}</CardTitle>
<div className="w-[90px]">
<Select
value={internalDuration}
disabled={loading}
onChange={(e: Duration) => setInternalDuration(e)}
options={[
{ label: "7d", value: "7d" },
{ label: "30d", value: "30d" },
{ label: "90d", value: "90d" },
{ label: "1y", value: "1y" },
{ label: "lifetime", value: "lifetime" },
]}
variant="without-label"
title={"duration"}
/>
</div>
</div>
{description && (
<CardDescription>
Number of users who enrolled in a course
</CardDescription>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
{loading && (
<div>
<Skeleton className="h-8 w-full mb-2" />
<Skeleton className="h-[200px] w-full" />
</div>
)}
{!loading && (
<>
<div className="flex text-sm text-slate-500 mb-4 font-bold justify-between">
<span>Total</span>
<span>{data?.count}</span>
</div>
<ResponsiveContainer width="100%" height={200}>
<LineChart
width={300}
height={200}
data={data?.points}
>
<Line
type="monotone"
dataKey="count"
stroke="#8884d8"
/>
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
</LineChart>
</ResponsiveContainer>
</>
)}
</CardContent>
</Card>
);
};

const mapStateToProps = (state: AppState) => ({
address: state.address,
});

export default connect(mapStateToProps)(Metric);
61 changes: 61 additions & 0 deletions apps/web/components/admin/dashboard/to-do.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { SiteInfo } from "@courselit/common-models";
import { Link } from "@courselit/components-library";
import { AppState } from "@courselit/state-management";
import {
SITE_SETTINGS_SECTION_GENERAL,
SITE_SETTINGS_SECTION_PAYMENT,
} from "@ui-config/strings";
import { connect } from "react-redux";

interface TodoProps {
siteinfo: SiteInfo;
}

const Todo = ({ siteinfo }) => {
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{(!siteinfo.title || (siteinfo.logo && !siteinfo.logo.file)) && (
<div className="flex flex-col border border-red-200 p-2 rounded-lg">
<h2 className="font-semibold mb-1">
Basic details missing
</h2>
<p className="text-sm text-slate-500 mb-4">
Give your school a proper name, description and a logo.
</p>
<div>
<Link
href={`/dashboard/settings?tab=${SITE_SETTINGS_SECTION_GENERAL}`}
>
<span className="underline font-medium text-sm text-slate-700">
Update now
</span>
</Link>
</div>
</div>
)}
{(!siteinfo.currencyISOCode || !siteinfo.paymentMethod) && (
<div className="flex flex-col border border-red-200 p-2 rounded-lg">
<h2 className="font-semibold mb-1">Start earning</h2>
<p className="text-sm text-slate-500 mb-4">
Update your payment details to sell paid products.
</p>
<div>
<Link
href={`/dashboard/settings?tab=${SITE_SETTINGS_SECTION_PAYMENT}`}
>
<span className="underline font-medium text-sm text-slate-700">
Update now
</span>
</Link>
</div>
</div>
)}
</div>
);
};

const mapStateToProps = (state: AppState) => ({
siteinfo: state.siteinfo,
});

export default connect(mapStateToProps)(Todo);
23 changes: 14 additions & 9 deletions apps/web/components/admin/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
BUTTON_SAVE,
SITE_SETTINGS_PAYMENT_METHOD_NONE_LABEL,
SITE_CUSTOMISATIONS_SETTING_CODEINJECTION_BODY,
BTN_EDIT_SITE,
SITE_APIKEYS_SETTING_HEADER,
APIKEY_NEW_BUTTON,
APIKEY_EXISTING_HEADER,
Expand Down Expand Up @@ -70,13 +69,26 @@ interface SettingsProps {
address: Address;
networkAction: boolean;
loading: boolean;
selectedTab:
| typeof SITE_SETTINGS_SECTION_GENERAL
| typeof SITE_SETTINGS_SECTION_PAYMENT
| typeof SITE_CUSTOMISATIONS_SETTING_HEADER
| typeof SITE_APIKEYS_SETTING_HEADER;
}

const Settings = (props: SettingsProps) => {
const [settings, setSettings] = useState<Partial<SiteInfo>>({});
const [newSettings, setNewSettings] = useState<Partial<SiteInfo>>({});
const [apikeyPage, setApikeyPage] = useState(1);
const [apikeys, setApikeys] = useState([]);
const selectedTab = [
SITE_SETTINGS_SECTION_GENERAL,
SITE_SETTINGS_SECTION_PAYMENT,
SITE_CUSTOMISATIONS_SETTING_HEADER,
SITE_APIKEYS_SETTING_HEADER,
].includes(props.selectedTab)
? props.selectedTab
: SITE_SETTINGS_SECTION_GENERAL;

const fetch = new FetchBuilder()
.setUrl(`${props.address.backend}/api/graph`)
Expand Down Expand Up @@ -416,14 +428,6 @@ const Settings = (props: SettingsProps) => {
<h1 className="text-4xl font-semibold mb-4">
{SITE_SETTINGS_PAGE_HEADING}
</h1>
<div>
<Button
href={`/dashboard/page/homepage/edit?redirectTo=/dashboard/settings`}
component="link"
>
{BTN_EDIT_SITE}
</Button>
</div>
</div>
<Tabs
items={[
Expand All @@ -432,6 +436,7 @@ const Settings = (props: SettingsProps) => {
SITE_CUSTOMISATIONS_SETTING_HEADER,
SITE_APIKEYS_SETTING_HEADER,
]}
selected={selectedTab}
>
<Form
onSubmit={handleSettingsSubmit}
Expand Down
Loading

0 comments on commit c1e4ba2

Please sign in to comment.