Skip to content

Commit f6ea8a6

Browse files
committed
Add ability to remove reminders
1 parent 70b6181 commit f6ea8a6

File tree

6 files changed

+130
-39
lines changed

6 files changed

+130
-39
lines changed

next.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
module.exports = {
33
reactStrictMode: true,
44
images: {
5-
domains: ["cdn.discordapp.com", "i.imgur.com"]
5+
domains: ["cdn.discordapp.com", "discord.com", "i.imgur.com"]
66
}
77
}

pages/api/reminders/delete.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import fetch from "node-fetch"
2+
import { serialize } from "cookie"
3+
import { config } from "../../../utils/config"
4+
import { sign } from "jsonwebtoken"
5+
import { DiscordUser, Reminder } from "../../../utils/types"
6+
import { NextApiRequest, NextApiResponse } from "next"
7+
import { parseUser } from "../../../utils/parse-user"
8+
9+
10+
export default async function api(req: NextApiRequest, res: NextApiResponse) {
11+
if (req.method !== "POST") return res.redirect("/")
12+
const user = parseUser(req.headers.cookie)
13+
const reminder: Reminder = req.body?.r
14+
15+
if (!user || !reminder) {
16+
return {
17+
redirect: {
18+
destination: "/api/oauth",
19+
permanent: false,
20+
},
21+
}
22+
}
23+
24+
console.log(`[${new Date().toISOString()}] Deleting reminder ${user.id} / ${reminder.id}`)
25+
26+
const fetched = await fetch(`${config.discordUri}/reminders/${user.id}/delete`, {
27+
headers: { Authorization: `${config.discordSecret}`, "Content-Type": "application/json" },
28+
body: JSON.stringify(reminder),
29+
method: "POST"
30+
})
31+
32+
res.status(fetched.status).send(await fetched.text())
33+
}

pages/reminders.tsx

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,93 @@
11
import { GetServerSideProps } from "next"
2-
import { DiscordUser } from "../utils/types"
2+
import { DiscordUser, Reminder } from "../utils/types"
33
import { parseUser } from "../utils/parse-user"
44
import Main from "../components/Main"
55
import Head from "next/head"
6+
import { config } from "../utils/config"
7+
import { Component, useState } from "react"
8+
import Image from "next/image"
69

710
interface Props {
8-
user: DiscordUser;
11+
user: DiscordUser
12+
reminders: Reminder[]
913
}
1014

11-
export default function Reminders(props: Props) {
12-
return (
13-
<Main>
14-
<Head>
15-
<title>Reminders | Hu Tao</title>
16-
<meta name="twitter:card" content="summary" />
17-
<meta property="og:title" content="Reminders | Hu Tao" />
18-
<meta property="og:description" content="Change, add and remove your Discord reminders" />
19-
</Head>
20-
<h1 className="text-5xl font-bold">
21-
Reminders
22-
</h1>
23-
<div className="text-xs">ID: {props.user.id}</div>
24-
<div>Not yet implemented</div>
25-
</Main>
26-
)
15+
export default class Reminders extends Component<Props, { reminders: Reminder[] }> {
16+
constructor(props: Props) {
17+
super(props)
18+
this.state = {
19+
reminders: this.props.reminders
20+
}
21+
}
22+
23+
render() {
24+
const { user } = this.props
25+
const { reminders } = this.state
26+
27+
return (
28+
<Main>
29+
<Head>
30+
<title>Reminders | Hu Tao</title>
31+
<meta name="twitter:card" content="summary" />
32+
<meta property="og:title" content="Reminders | Hu Tao" />
33+
<meta property="og:description" content="Change, add and remove your Discord reminders" />
34+
</Head>
35+
<h1 className="text-5xl font-bold">
36+
Reminders
37+
</h1>
38+
<div className="text-base">Logged in as: <DiscordAvatar user={user} /> {user.username}<span className="text-xs">#{user.discriminator}</span></div>
39+
<div className="text-xl mt-3">{reminders.length} reminder{reminders.length == 1 ? "" : "s"}</div>
40+
{reminders.map(r => <ReminderCard r={r} key={r.id} onDelete={() => this.setState({
41+
reminders: reminders.filter(re => r.id != re.id)
42+
})} />)}
43+
</Main>
44+
)
45+
}
46+
}
47+
48+
function ReminderCard({ r, onDelete }: { r: Reminder, onDelete: () => void }) {
49+
const deleteReminder = async () => {
50+
const res = await fetch("/api/reminders/delete", {
51+
body: JSON.stringify({ r }),
52+
headers: { "Content-Type": "application/json" },
53+
method: "POST"
54+
})
55+
56+
if ((res.status >= 200 && res.status < 300) || res.status == 404)
57+
onDelete()
58+
}
59+
return <div key={r.id} className="border rounded-md p-2 mt-2 text-slate-700 dark:text-slate-300">
60+
<div className="text-xl text-slate-900 dark:text-slate-100">#{r.id}: <span>{r.subject}</span></div>
61+
Will trigger on <span className="text-slate-900 dark:text-slate-100">{new Date(r.timestamp).toLocaleString(undefined, { day: "numeric", year: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })}</span>
62+
<div className="bg-red-500 text-slate-50 w-16 text-center rounded-lg mt-2 cursor-pointer" onClick={deleteReminder}>Delete</div>
63+
</div>
64+
}
65+
66+
function DiscordAvatar({ user }: { user: DiscordUser }) {
67+
return <Image
68+
src={user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp?size=16` : "https://discord.com/assets/1f0bfc0865d324c2587920a7d80c609b.png"}
69+
alt="Discord avatar"
70+
width={16}
71+
height={16}
72+
className="rounded-xl p-0 m-0"
73+
/>
2774
}
2875

2976
export const getServerSideProps: GetServerSideProps<Props> = async function (ctx) {
30-
const user = parseUser(ctx)
31-
32-
if (!user) {
33-
return {
34-
redirect: {
35-
destination: "/api/oauth",
36-
permanent: false,
37-
},
38-
}
39-
}
40-
console.log(user)
77+
const user = parseUser(ctx.req.headers.cookie)
4178

42-
return { props: { user } }
79+
if (!user) {
80+
return {
81+
redirect: {
82+
destination: "/api/oauth",
83+
permanent: false,
84+
},
85+
}
4386
}
87+
88+
const reminders: Reminder[] = await fetch(`${config.discordUri}/reminders/${user.id}/get`, {
89+
headers: { Authorization: `${config.discordSecret}` },
90+
}).then((res) => res.json())
91+
92+
return { props: { user, reminders } }
93+
}

utils/config.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ export const config = {
2626
clientId: validateEnv("CLIENT_ID"),
2727
clientSecret: validateEnv("CLIENT_SECRET"),
2828
appUri: validateEnv("APP_URI", "http://localhost:3000", true),
29-
jwtSecret: validateEnv(
30-
"JWT_SECRET",
31-
"this is a development value that should be changed in production!!!!!",
32-
true
33-
),
29+
discordUri: validateEnv("DISCORD_BOT_URI", "http://localhost:3000", true),
30+
discordSecret: validateEnv("DISCORD_BOT_SECRET", "CHANGE THIS", true),
31+
jwtSecret: validateEnv("JWT_SECRET", "CHANGE THIS", true),
3432
} as const

utils/parse-user.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { verify } from "jsonwebtoken"
44
import { config } from "./config"
55
import type { DiscordUser } from "./types"
66

7-
export function parseUser(ctx: GetServerSidePropsContext): DiscordUser | null {
8-
if (!ctx.req.headers.cookie) {
7+
export function parseUser(cookie?: string): DiscordUser | null {
8+
if (!cookie) {
99
return null
1010
}
1111

12-
const token = parse(ctx.req.headers.cookie)[config.cookieName]
12+
const token = parse(cookie)[config.cookieName]
1313

1414
if (!token) {
1515
return null

utils/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ export interface DiscordUser {
1010
premium_type: number
1111
}
1212

13+
// Reminders
14+
export interface Reminder {
15+
id: number
16+
subject: string
17+
user: string
18+
timestamp: number
19+
duration: number
20+
}
21+
22+
1323
// Guides
1424
export interface Guide {
1525
name: string

0 commit comments

Comments
 (0)