Skip to content

Commit f234909

Browse files
committed
Add create reminders stuff
1 parent 1fbe029 commit f234909

File tree

9 files changed

+248
-19
lines changed

9 files changed

+248
-19
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"react": "17.0.2",
1616
"react-dom": "17.0.2",
1717
"react-markdown": "^7.1.1",
18+
"react-modal": "^3.14.4",
1819
"sharp": "^0.29.3"
1920
},
2021
"devDependencies": {
@@ -23,6 +24,7 @@
2324
"@types/node": "17.0.5",
2425
"@types/node-fetch": "2",
2526
"@types/react": "17.0.38",
27+
"@types/react-modal": "^3.13.1",
2628
"autoprefixer": "^10.4.0",
2729
"eslint": "8.5.0",
2830
"eslint-config-next": "12.0.7",

pages/_app.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { AppProps } from "next/app"
22
import Head from "next/head"
3-
import { Component, useEffect } from "react"
3+
import { useEffect } from "react"
44
import "tailwindcss/tailwind.css"
55
import Footer from "../components/Footer"
66
import NavBar from "../components/NavBar"
77
import * as gtag from "../utils/gtag"
88
import "../public/global.css"
9+
import ReactModal from "react-modal"
910

1011
export default function MyApp({ Component, pageProps, router }: AppProps) {
1112
useEffect(() => {
@@ -17,7 +18,8 @@ export default function MyApp({ Component, pageProps, router }: AppProps) {
1718
router.events.off("routeChangeComplete", handleRouteChange)
1819
}
1920
}, [router.events])
20-
return <div className="dark:bg-slate-700 min-h-screen flex flex-col items-center justify-between text-slate-900 dark:text-slate-100">
21+
22+
return <div className="bg-slate-50 dark:bg-slate-700 min-h-screen flex flex-col items-center justify-between text-slate-900 dark:text-slate-100">
2123
<Head>
2224
<title>{router.pathname.substring(1).replace(/^\w/, w => w.toUpperCase())} | Hu Tao</title>
2325
<link rel="icon" href="/favicon.ico" />

pages/api/reminders/create.ts

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

pages/api/reminders/delete.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1+
import { NextApiRequest, NextApiResponse } from "next"
12
import fetch from "node-fetch"
2-
import { serialize } from "cookie"
33
import { config } from "../../../utils/config"
4-
import { sign } from "jsonwebtoken"
5-
import { DiscordUser, Reminder } from "../../../utils/types"
6-
import { NextApiRequest, NextApiResponse } from "next"
74
import { parseUser } from "../../../utils/parse-user"
5+
import { Reminder } from "../../../utils/types"
86

97

108
export default async function api(req: NextApiRequest, res: NextApiResponse) {

pages/api/sendmessage.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { NextApiRequest, NextApiResponse } from "next"
2+
import fetch from "node-fetch"
3+
import { config } from "../../utils/config"
4+
import { parseUser } from "../../utils/parse-user"
5+
6+
7+
export default async function api(req: NextApiRequest, res: NextApiResponse) {
8+
if (req.method !== "POST") return res.redirect("/")
9+
const user = parseUser(req.headers.cookie)
10+
11+
if (!user) {
12+
return {
13+
redirect: {
14+
destination: "/api/oauth",
15+
permanent: false,
16+
},
17+
}
18+
}
19+
20+
const fetched = await fetch(`${config.discordUri}/testmessage/${user.id}`, {
21+
headers: { Authorization: `${config.discordSecret}`, "Content-Type": "application/json" },
22+
method: "POST"
23+
})
24+
25+
res.status(fetched.status).send(await fetched.text())
26+
}

pages/tools/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export default function Tools({ location }: {location: string}) {
2626
Reminders are send to your Discord DM&apos;s via the Discord bot. Please make sure you share at least one server with the Discord bot
2727
and have DM&apos;s enabled in that server. You can invite the bot to your own Discord server or join the support server via the links on
2828
the <FormattedLink location={location} href="/" >homepage</FormattedLink>.
29-
3029
</div>
3130
</li>
3231
</ul>

pages/tools/reminders.tsx

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,63 @@
11
import { serialize } from "cookie"
22
import { GetServerSideProps } from "next"
33
import Head from "next/head"
4-
import { Component } from "react"
4+
import { useRouter } from "next/router"
5+
import { Component, useState } from "react"
6+
import ReactModal from "react-modal"
57
import Main from "../../components/Main"
68
import { config } from "../../utils/config"
79
import { parseUser } from "../../utils/parse-user"
810
import { DiscordUser, Reminder } from "../../utils/types"
11+
import { send } from "../../utils/utils"
912

1013
interface Props {
1114
user: DiscordUser
1215
reminders: Reminder[]
1316
}
1417

15-
export default class Reminders extends Component<Props, { reminders: Reminder[] }> {
18+
const modalStyle: ReactModal.Styles = {
19+
overlay: {
20+
backgroundColor: "rgba(0, 0, 0, 0.66)"
21+
},
22+
content: {
23+
position: "absolute",
24+
top: "min(10%, 200px)",
25+
bottom: "auto",
26+
27+
width: "min(42rem, calc(100% - 40px))",
28+
left: "max(calc(50% - (42rem/2)), 20px)",
29+
30+
border: "1px solid rgb(64, 79, 100)",
31+
color: "white",
32+
background: "rgb(51 65 85)",
33+
borderRadius: "1rem"
34+
}
35+
}
36+
const erorrModalStyle: ReactModal.Styles = {
37+
overlay: {
38+
backgroundColor: "rgba(10, 0, 0, 0.66)"
39+
},
40+
content: {
41+
position: "absolute",
42+
top: "max(calc(50% - (10rem/2)), 20px)",
43+
maxHeight: "10rem",
44+
45+
width: "min(20rem, calc(100% - 40px))",
46+
left: "max(calc(50% - (20rem/2)), 20px)",
47+
48+
border: "1px solid rgb(156, 79, 100)",
49+
color: "white",
50+
background: "rgb(156 65 85)",
51+
borderRadius: "1rem"
52+
}
53+
}
54+
55+
export default class Reminders extends Component<Props, { reminders: Reminder[], createReminderOpen: boolean }> {
1656
constructor(props: Props) {
1757
super(props)
1858
this.state = {
19-
reminders: this.props.reminders
59+
reminders: this.props.reminders,
60+
createReminderOpen: false
2061
}
2162
}
2263

@@ -37,21 +78,99 @@ export default class Reminders extends Component<Props, { reminders: Reminder[]
3778
</h1>
3879
<div className="text-base">Logged in as: <DiscordAvatar user={user} /> {user.username}<span className="text-xs">#{user.discriminator}</span></div>
3980
<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-
})} />)}
81+
{reminders.map(r => <ReminderCard
82+
r={r}
83+
key={r.id}
84+
onDelete={() => this.setState({
85+
reminders: reminders.filter(re => r.id != re.id)
86+
})}
87+
/>)}
88+
<div className="flex flex-wrap gap-2">
89+
<span className="bg-blue-600 text-slate-50 w-fit px-3 py-1 text-center rounded-lg mt-2 cursor-pointer" onClick={() => fetch("/api/sendmessage", {
90+
headers: { "Content-Type": "application/json" },
91+
method: "POST"
92+
})}>Send test message</span>
93+
<span className="bg-green-600 text-slate-50 w-fit px-3 py-1 text-center rounded-lg mt-2 cursor-pointer" onClick={() => this.setState({ createReminderOpen: true })}>Create reminder</span>
94+
<CreateReminder
95+
isOpen={this.state.createReminderOpen}
96+
requestClose={() => this.setState({ createReminderOpen: false })}
97+
addReminder={(r) => this.setState({ reminders: [...reminders, r] })}
98+
/>
99+
</div>
43100
</Main>
44101
)
45102
}
46103
}
47104

105+
function CreateReminder({ isOpen, requestClose, addReminder }: { isOpen: boolean, requestClose: () => void, addReminder: (r: Reminder) => void }) {
106+
const [name, setName] = useState("")
107+
const [duration, setDuration] = useState("")
108+
109+
const [error, setError] = useState("")
110+
111+
async function createReminder(name: string, duration: string) {
112+
if (name.length > 128) return setError("Name too long")
113+
if (name.length == 0) return setError("No name given")
114+
if (duration.length == 0) return setError("No duration given")
115+
116+
const res = await send("/api/reminders/create", { name, duration })
117+
118+
if ((res.status >= 200 && res.status < 300)) {
119+
requestClose()
120+
addReminder(await res.json())
121+
122+
setName("")
123+
setDuration("")
124+
} else {
125+
return setError("An error occurred... Try again later")
126+
}
127+
}
128+
return <ReactModal style={modalStyle} isOpen={isOpen} onRequestClose={requestClose} ariaHideApp={false}>
129+
<ReactModal style={erorrModalStyle} isOpen={error.length > 0} onRequestClose={() => setError("")} ariaHideApp={false}>
130+
<div className="flex flex-1 justify-center items-center h-full" onKeyDown={() => setError("")}>
131+
<div className="font-semibold text-xl">
132+
{error}
133+
</div>
134+
</div>
135+
</ReactModal>
136+
137+
<h4 className="text-lg font-semibold">Create a reminder</h4>
138+
<div className="flex flex-col">
139+
<form onSubmit={(e) => {
140+
e.preventDefault()
141+
createReminder(name, duration)
142+
}}>
143+
<TextInput value={name} set={setName} placeholder="Enter a name" maxLength={128} label="Name:" />
144+
<TextInput value={duration} set={setDuration} placeholder="Enter a duration (ex. 10 hours 2 resin 5s)" maxLength={128} label="Duration:" />
145+
<button className="bg-green-600 text-slate-50 w-fit px-3 py-1 text-center rounded-lg mt-2 cursor-pointer" formAction="submit">Create reminder</button>
146+
</form>
147+
</div>
148+
<br />
149+
150+
<h4 className="text-lg font-semibold">Presets</h4>
151+
<div className="flex flex-wrap gap-2">
152+
<span className="bg-green-600 text-slate-50 w-fit px-3 py-1 text-center rounded-lg mt-2 cursor-pointer" onClick={() => createReminder("Parametric", "6d22h")}>Parametric (6d22h)</span>
153+
<span className="bg-green-600 text-slate-50 w-fit px-3 py-1 text-center rounded-lg mt-2 cursor-pointer" onClick={() => createReminder("Ores", "72h")}>Ores (72h)</span>
154+
</div>
155+
</ReactModal>
156+
}
157+
158+
function TextInput({ value, set, maxLength, placeholder, label }: { value: string, set: (newValue: string) => unknown, maxLength?: number, placeholder: string, label: string }) {
159+
return <div><label>
160+
{label}
161+
<input
162+
className="bg-slate-800 rounded-lg px-2 ml-2 mt-1"
163+
value={value}
164+
onChange={(e) => set(e.target.value)}
165+
placeholder={placeholder}
166+
maxLength={maxLength}
167+
/>
168+
</label></div>
169+
}
170+
48171
function ReminderCard({ r, onDelete }: { r: Reminder, onDelete: () => void }) {
49172
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-
})
173+
const res = await send("/api/reminders/delete", { r })
55174

56175
if ((res.status >= 200 && res.status < 300) || res.status == 404)
57176
onDelete()
@@ -86,7 +205,8 @@ export const getServerSideProps: GetServerSideProps<Props> = async function (ctx
86205
maxAge: 3600,
87206
sameSite: "lax",
88207
path: "/",
89-
}))
208+
})
209+
)
90210

91211
return {
92212
redirect: {

0 commit comments

Comments
 (0)