Skip to content

Commit

Permalink
fix: add ability to send email to all artists as an admin
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon committed Feb 5, 2025
1 parent eee807a commit d75fa43
Show file tree
Hide file tree
Showing 19 changed files with 327 additions and 27 deletions.
3 changes: 3 additions & 0 deletions client/src/components/Admin/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const Admin: React.FC = () => {
<li>
<NavLink to="serverTasks">{t("serverTasks")}</NavLink>
</li>
<li>
<NavLink to="sendEmails">{t("sendEmails")}</NavLink>
</li>
</Tabs>
<div
className={css`
Expand Down
67 changes: 67 additions & 0 deletions client/src/components/Admin/AdminSendEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Button from "components/common/Button";
import TextEditor from "components/common/TextEditor";
import WidthContainer from "components/common/WidthContainer";
import React from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import api from "services/api";
import { useSnackbar } from "state/SnackbarContext";
import { useConfirm } from "utils/useConfirm";

interface FormData {
content: string;
}

const AdminSendEmail = () => {
const { ask } = useConfirm();

const snackbar = useSnackbar();
const methods = useForm<FormData>();

const updateSettings = React.useCallback(
async (data: FormData) => {
try {
const ok = await ask(
"Are you sure you want to send this to these users? With great power comes great responsibility."
);
if (!ok) {
return;
}
const { result } = await api.post<
{ content: string },
{ result: { sentTo: number } }
>("admin/send-email", data);
snackbar(`Sent email to ${result.sentTo} users!`, { type: "success" });
} catch (e) {
console.error(e);
snackbar("Oops something went wrong", { type: "warning" });
}
},
[snackbar]
);

return (
<WidthContainer variant="medium">
<h3>Send Email</h3>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(updateSettings)}>
<Controller
name="content"
render={({ field: { onChange, value } }) => {
return (
<TextEditor
onChange={(val: any) => {
onChange(val);
}}
value={value}
/>
);
}}
/>
<Button type="submit">Send email</Button>
</form>
</FormProvider>
</WidthContainer>
);
};

export default AdminSendEmail;
4 changes: 4 additions & 0 deletions client/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ const Header = () => {
@media (prefers-color-scheme: dark) {
background-color: var(--mi-white) !important;
color: var(--mi-black) !important;
svg {
fill: var(--mi-black) !important;
}
}
@media screen and (max-width: ${bp.medium}px) {
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/Post/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ const Post: React.FC = () => {
<div
className={css`
text-align: center;
display: flex;
justify-content: center;
margin: 1rem;
`}
>
<SupportArtistPopUp artist={post.artist} />
Expand Down
1 change: 1 addition & 0 deletions client/src/components/common/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const ConfirmDialog = () => {
<div>{message}</div>
<div
className={css`
margin-top: 1.5rem;
display: flex;
justify-content: flex-end;
Expand Down
8 changes: 7 additions & 1 deletion client/src/components/common/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ const DropdownMenu: React.FC<{
flex-grow: 1;
svg {
fill: black;
fill: var(--mi-black);
}
@media (prefers-color-scheme: dark) {
svg {
fill: var(--mi-white) !important;
}
}
}
`}
Expand Down
14 changes: 8 additions & 6 deletions client/src/components/common/TextEditor/InsertImageButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import { useForm } from "react-hook-form";
import api from "services/api";
import { useTranslation } from "react-i18next";

const InsertImageButton: React.FC<{ postId: number }> = ({ postId }) => {
const InsertImageButton: React.FC<{ postId?: number }> = ({ postId }) => {
const [isOpen, setIsOpen] = React.useState(false);
const { register, getValues } = useForm<{ images: FileList }>();
const { insertImage } = useCommands();
const { t } = useTranslation("translation", { keyPrefix: "textEditor" });

const onAdd = React.useCallback(async () => {
const images = getValues("images");
const response = await api.uploadFile(`manage/posts/${postId}/images`, [
images[0],
]);
insertImage({ src: response.result.jobId });
if (postId) {
const images = getValues("images");
const response = await api.uploadFile(`manage/posts/${postId}/images`, [
images[0],
]);
insertImage({ src: response.result.jobId });
}
setIsOpen(false);
}, [getValues, postId]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import FormComponent from "../FormComponent";
import Box from "../Box";

const InsertMirloWidgetButton: React.FC<{
postId: number;
artistId: number;
postId?: number;
artistId?: number;
}> = ({ postId, artistId }) => {
const [isOpen, setIsOpen] = React.useState(false);
const [draftAlbum, setDraftAlbum] = React.useState<TrackGroup>();
Expand All @@ -35,7 +35,7 @@ const InsertMirloWidgetButton: React.FC<{
height: variant === "track" ? 137 : 371,
width: 700,
});
if (variant) {
if (variant && postId) {
await api.put(`manage/posts/${postId}/tracks`, {
trackId: trackId,
});
Expand Down Expand Up @@ -79,11 +79,13 @@ const InsertMirloWidgetButton: React.FC<{
}, []);

const loadDraft = React.useCallback(async () => {
const response = await api.get<TrackGroup>(
`manage/artists/${artistId}/drafts`
);
setDraftAlbum(response.result);
}, []);
if (artistId) {
const response = await api.get<TrackGroup>(
`manage/artists/${artistId}/drafts`
);
setDraftAlbum(response.result);
}
}, [artistId]);

React.useEffect(() => {
try {
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/common/TextEditor/TopToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { css } from "@emotion/css";
import InsertMirloWidgetButton from "./InsertMirloWidgetButton";
import InsertImageButton from "./InsertImageButton";

const TopToolbar: React.FC<{ postId: number; artistId: number }> = ({
const TopToolbar: React.FC<{ postId?: number; artistId?: number }> = ({
postId,
artistId,
}) => {
Expand Down
26 changes: 15 additions & 11 deletions client/src/components/common/TextEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,25 @@ import TopToolbar from "./TopToolbar";
import FloatingLinkToolbar from "./FloatingLinkToolbar";
import api from "services/api";

const extensions = (postId: number, reload: () => void) => () => [
const extensions = (postId?: number, reload?: () => void) => () => [
new PlaceholderExtension({ placeholder: "Type something" }),
new TableExtension({}),
new DropCursorExtension({}),
new ImageExtension({
uploadHandler: (
files: { file: File; progress: (progress: number) => void }[]
): DelayedPromiseCreator<ImageAttributes>[] => {
return files.map((f) => async (props: CommandFunctionProps) => {
const response = await api.uploadFile(`manage/posts/${postId}/images`, [
f.file,
]);
reload();
return { src: response.result.jobId };
});
if (postId) {
return files.map((f) => async (props: CommandFunctionProps) => {
const response = await api.uploadFile(
`manage/posts/${postId}/images`,
[f.file]
);
reload?.();
return { src: response.result.jobId };
});
}
return [];
},
}),
new IframeExtension(),
Expand All @@ -47,9 +51,9 @@ const extensions = (postId: number, reload: () => void) => () => [
const TextEditor: React.FC<{
onChange: (val: any) => void;
value: string;
postId: number;
artistId: number;
reloadImages: () => void;
postId?: number;
artistId?: number;
reloadImages?: () => void;
}> = ({ onChange, value, postId, reloadImages, artistId }) => {
const { manager, state, setState } = useRemirror({
extensions: extensions(postId, reloadImages),
Expand Down
9 changes: 9 additions & 0 deletions client/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,15 @@ const routes: RouteObject[] = [
return { Component };
},
},
{
path: "sendEmails",
async lazy() {
const { default: Component } = await import(
"components/Admin/AdminSendEmail"
);
return { Component };
},
},
],
},
{
Expand Down
14 changes: 14 additions & 0 deletions emails/admin-announcement/html.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
doctype html
html
head
block head
meta(charset="utf-8")
meta(name="viewport", content="width=device-width")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="x-apple-disable-message-reformatting")
style
include ../style.css
body.sans-serif
p
div
| !{content}
1 change: 1 addition & 0 deletions emails/admin-announcement/subject.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= `Mirlo: Platform Announcement`
2 changes: 2 additions & 0 deletions emails/admin-announcement/text.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
|
| !{post.content}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "receivePlatformEmails" BOOLEAN NOT NULL DEFAULT true;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ model User {
userArtistTip UserArtistTip[]
merchPurchase MerchPurchase[]
promoCodes String[]
receivePlatformEmails Boolean @default(true)
}

model UserArtistNotificationSetting {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const routes = [
"admin/settings",
"admin/tips",
"admin/users",
"admin/send-email",
"oembed",
"flag",
];
Expand Down
50 changes: 50 additions & 0 deletions src/routers/v1/admin/send-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NextFunction, Request, Response } from "express";
import { userAuthenticated, userHasPermission } from "../../../auth/passport";
import prisma from "@mirlo/prisma";
import { sendMailQueue } from "../../../queues/send-mail-queue";

export default function () {
const operations = {
POST: [userAuthenticated, userHasPermission("admin"), POST],
};

async function POST(req: Request, res: Response, next: NextFunction) {
const { content } = req.body;
try {
const users = await prisma.user.findMany({
where: {
receivePlatformEmails: true,
},
include: {
artists: true,
},
});
const usersWithArtists = users.filter((u) => u.artists.length > 0);

await Promise.all(
usersWithArtists.map(async (user) => {
await sendMailQueue.add("send-mail", {
template: "admin-announcement",
message: {
to: user.email,
},
locals: {
email: user.email,
user,
content,
},
});
})
);
return res.status(200).json({
result: {
sentTo: usersWithArtists.length,
},
});
} catch (e) {
next(e);
}
}

return operations;
}
Loading

0 comments on commit d75fa43

Please sign in to comment.