Skip to content

Commit 73c18f0

Browse files
committed
add: sending ad_message to users via admin dashboard.
1 parent 344acfc commit 73c18f0

File tree

17 files changed

+237
-10
lines changed

17 files changed

+237
-10
lines changed

apps/dashboard/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const Tutor = lazy(() => import("@/pages/Tutor"));
2121
const Tutors = lazy(() => import("@/pages/Tutors"));
2222
const PlanInvites = lazy(() => import("@/pages/PlanInvites"));
2323
const Lesson = lazy(() => import("@/pages/Lesson"));
24+
const AdMessage = lazy(() => import("@/pages/AdMessage"));
2425
const Main = lazy(() => import("@/pages/Main"));
2526

2627
const router = createBrowserRouter([
@@ -67,6 +68,10 @@ const router = createBrowserRouter([
6768
path: Dashboard.Lesson,
6869
element: <Page page={<Lesson />} />,
6970
},
71+
{
72+
path: Dashboard.AdMessage,
73+
element: <Page page={<AdMessage />} />,
74+
},
7075
],
7176
},
7277
]);

apps/dashboard/src/components/Layout/Sidebar.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Tag from "@litespace/assets/Tag";
2121
import Users from "@litespace/assets/Users";
2222
import Video from "@litespace/assets/Video";
2323
import Home from "@litespace/assets/Home";
24+
import Messages from "@litespace/assets/Monitor";
2425

2526
import { router } from "@/lib/route";
2627
import { Icon } from "@/types/common";
@@ -174,6 +175,13 @@ const Sidebar: React.FC = () => {
174175
Icon: People,
175176
};
176177

178+
const adMessage: LinkInfo = {
179+
label: intl("dashboard.ad-message.title"),
180+
route: Dashboard.AdMessage,
181+
isActive: match(Dashboard.AdMessage),
182+
Icon: Messages,
183+
};
184+
177185
if (user?.role === IUser.Role.Studio) return [photoSession];
178186

179187
return [
@@ -187,6 +195,7 @@ const Sidebar: React.FC = () => {
187195
photoSession,
188196
lessons,
189197
tutors,
198+
adMessage,
190199
];
191200
}, [intl, location.pathname, user?.role]);
192201

apps/dashboard/src/hooks/authRoutes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ const routeConfigMap: Record<Dashboard, RouteConfig> = {
8383
[Dashboard.Lesson]: {
8484
whitelist: [superAdmin, regularAdmin],
8585
},
86+
[Dashboard.AdMessage]: {
87+
whitelist: [superAdmin, regularAdmin],
88+
},
8689
[Dashboard.Main]: {
8790
whitelist: [superAdmin, regularAdmin],
8891
},
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import BackLink from "@/components/Common/BackLink";
2+
import { useUsers } from "@litespace/headless/users";
3+
import { useSendAdMessage } from "@litespace/headless/student";
4+
import { IUser } from "@litespace/types";
5+
import { Button } from "@litespace/ui/Button";
6+
import { DateInput } from "@litespace/ui/DateInput";
7+
import { useFormatMessage } from "@litespace/ui/hooks/intl";
8+
import { Typography } from "@litespace/ui/Typography";
9+
import dayjs from "dayjs";
10+
import { useCallback, useState } from "react";
11+
import { useOnError } from "@/hooks/error";
12+
import { useToast } from "@litespace/ui/Toast";
13+
14+
const AdMessage: React.FC = () => {
15+
const intl = useFormatMessage();
16+
const toast = useToast();
17+
18+
const [from, setFrom] = useState(
19+
dayjs().subtract(1, "day").format("YYYY-MM-DD")
20+
);
21+
const [to, setTo] = useState(dayjs().format("YYYY-MM-DD"));
22+
23+
const findStudents = useUsers({
24+
role: IUser.Role.Student,
25+
createdAt: {
26+
gte: from,
27+
lte: to,
28+
},
29+
full: true,
30+
});
31+
32+
const onError = useOnError({
33+
type: "mutation",
34+
handler: (error) => toast.error({ title: intl(error.messageId) }),
35+
});
36+
37+
const sendAdMessageMutation = useSendAdMessage({
38+
onSuccess: () => toast.success({ title: intl("labels.done") }),
39+
onError,
40+
});
41+
42+
const send = useCallback(() => {
43+
sendAdMessageMutation.mutate({
44+
payload: {
45+
createdAt: {
46+
gte: from,
47+
lte: to,
48+
},
49+
},
50+
});
51+
}, [sendAdMessageMutation, from, to]);
52+
53+
return (
54+
<div className="w-full flex flex-col gap-6 max-w-screen-2xl mx-auto p-6">
55+
<BackLink />
56+
57+
<div className="flex flex-row gap-2 max-w-[350px] items-center">
58+
<Typography tag="label">{intl("placeholders.from")}</Typography>
59+
<DateInput value={from} onChange={setFrom} />
60+
</div>
61+
62+
<div className="flex flex-row gap-2 max-w-[350px] items-center">
63+
<Typography tag="label">{intl("placeholders.to")}</Typography>
64+
<DateInput value={to} onChange={setTo} />
65+
</div>
66+
67+
<div className="flex flex-col gap-1 max-h-[300px] overflow-auto">
68+
{findStudents.query.data?.list.map((student) =>
69+
student.phone ? (
70+
<Typography tag="span">
71+
{student.id} - {student.name}
72+
</Typography>
73+
) : null
74+
)}
75+
</div>
76+
77+
<div className="flex flex-row gap-2 max-w-[350px] items-center">
78+
<Button
79+
variant="primary"
80+
size="large"
81+
className="flex-1"
82+
onClick={send}
83+
loading={sendAdMessageMutation.isPending}
84+
disabled={sendAdMessageMutation.isPending}
85+
>
86+
{intl("labels.send")}
87+
</Button>
88+
</div>
89+
</div>
90+
);
91+
};
92+
93+
export default AdMessage;

packages/atlas/src/api/student.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@ export class Student extends Base {
1919
): Promise<IStudent.FindByIdApiResponse> {
2020
return this.get({ route: `api/v1/student/${query.id}` });
2121
}
22+
23+
async sendAdMessage(
24+
payload: IStudent.SendAdMessageApiPayload
25+
): Promise<IStudent.SendAdMessageApiResponse> {
26+
return this.post({ route: `api/v1/student/send-ad-message`, payload });
27+
}
2228
}

packages/headless/src/constants/mutation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ export enum MutationKey {
5555
FawryRefund = "fawry-refund",
5656
PlanCheckout = "plan-checkout",
5757
LessonCheckout = "lesson-checkout",
58+
SendAdMessage = "send-ad-message",
5859
}

packages/headless/src/student.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,26 @@ export function useFindStudentById(id: number): UseQueryResult<IStudent.Self> {
9696
queryKey: [QueryKey.FindStudentById],
9797
});
9898
}
99+
100+
export function useSendAdMessage({
101+
onSuccess,
102+
onError,
103+
}: {
104+
onSuccess?: OnSuccess<IStudent.SendAdMessageApiResponse>;
105+
onError?: OnError;
106+
}) {
107+
const api = useApi();
108+
109+
const send = useCallback(
110+
async ({ payload }: { payload: IStudent.SendAdMessageApiPayload }) =>
111+
api.student.sendAdMessage(payload),
112+
[api.student]
113+
);
114+
115+
return useMutation({
116+
mutationFn: send,
117+
mutationKey: [MutationKey.SendAdMessage],
118+
onSuccess,
119+
onError,
120+
});
121+
}

packages/models/src/users.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
countRows,
33
knex,
4+
withDateFilter,
45
withListFilter,
56
WithOptionalTx,
67
withSkippablePagination,
@@ -151,6 +152,7 @@ export class Users extends Model<
151152
gender,
152153
city,
153154
select,
155+
createdAt,
154156
...pagination
155157
}: WithOptionalTx<IUser.FindModelQuery<T>>): Promise<
156158
Paginated<Pick<IUser.Self, T>>
@@ -162,6 +164,7 @@ export class Users extends Model<
162164
if (verified) base.andWhere(this.column("verified_email"), verified);
163165
if (gender) base.andWhere(this.column("gender"), gender);
164166
if (city) base.andWhere(this.column("city"), city);
167+
withDateFilter(base, this.column("created_at"), createdAt);
165168

166169
const total = await countRows(base.clone(), {
167170
column: this.column("id"),

packages/types/src/messenger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export type Template =
4848
time: string;
4949
url: string;
5050
};
51+
}
52+
| {
53+
name: "ad_message";
54+
parameters: object;
5155
};
5256

5357
export type Message = {

packages/types/src/student.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,11 @@ export type FindApiResponse = Paginated<Self>;
8686
export type FindByIdApiQuery = { id: number };
8787

8888
export type FindByIdApiResponse = Self;
89+
90+
export type SendAdMessageApiPayload = {
91+
createdAt: IFilter.Date;
92+
};
93+
94+
export type SendAdMessageApiResponse = {
95+
count: number;
96+
};

0 commit comments

Comments
 (0)