From 3ac0ebd979be68cc080ca46d798a93dd38b7a3ff Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:08:34 +0300 Subject: [PATCH 1/8] Add check for user in guild after voting and return it in response --- server/src/routes/servers/[id]/vote.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/routes/servers/[id]/vote.js b/server/src/routes/servers/[id]/vote.js index a604fced..4525af47 100644 --- a/server/src/routes/servers/[id]/vote.js +++ b/server/src/routes/servers/[id]/vote.js @@ -27,7 +27,10 @@ module.exports = { if (timeout) return response.sendError(`You can vote again in ${Math.floor((timeout.createdAt.getTime() + 86400000 - Date.now()) / 3600000)} hours, ${Math.floor((timeout.createdAt.getTime() + 86400000 - Date.now()) / 60000) % 60} minutes.`, 400); return incrementVote(id, request.user.id) - .then(() => response.status(204).end()) + .then(async () => { + const userInGuild = guild.members.cache.get(request.user.id) || await guild.members.fetch(request.user.id).catch(() => false); + return response.json({ inGuild: !!userInGuild }); + }) .catch(error => response.sendError(error.message, 400)); } ] From e5427143e540aa7c4e3a630fdd65ce8cf13e30aa Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:11:46 +0300 Subject: [PATCH 2/8] Add set reminder route for servers --- server/src/routes/servers/[id]/reminder.js | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 server/src/routes/servers/[id]/reminder.js diff --git a/server/src/routes/servers/[id]/reminder.js b/server/src/routes/servers/[id]/reminder.js new file mode 100644 index 00000000..1e302f56 --- /dev/null +++ b/server/src/routes/servers/[id]/reminder.js @@ -0,0 +1,50 @@ +const checkAuthentication = require('@/utils/middlewares/checkAuthentication'); +const useRateLimiter = require('@/utils/useRateLimiter'); +const { param, matchedData } = require('express-validator'); +const Server = require('@/schemas/Server'); +const VoteTimeout = require('@/schemas/Server/Vote/Timeout'); +const Reminder = require('@/schemas/Server/Vote/Reminder'); +const checkCaptcha = require('@/utils/middlewares/checkCaptcha'); +const bodyParser = require('body-parser'); + +module.exports = { + post: [ + useRateLimiter({ maxRequests: 5, perMinutes: 1 }), + bodyParser.json(), + checkAuthentication, + checkCaptcha, + param('id'), + async (request, response) => { + const { id } = matchedData(request); + + const guild = client.guilds.cache.get(id); + if (!guild) return response.sendError('Guild not found.', 404); + + const server = await Server.findOne({ id }); + if (!server) return response.sendError('Server not found.', 404); + + const timeout = await VoteTimeout.findOne({ 'user.id': request.user.id, 'guild.id': id }); + if (!timeout) return response.sendError('You can\'t set a reminder for a server you haven\'t voted for.', 400); + + const reminder = await Reminder.findOne({ 'user.id': request.user.id, 'guild.id': id }); + if (reminder) return response.sendError('You already set a reminder for this server.', 400); + + const newReminder = new Reminder({ + user: { + id: request.user.id + }, + guild: { + id + }, + date: Date.now() + 86400000 + }); + + const validationErrors = newReminder.validateSync(); + if (validationErrors) return response.sendError('An unknown error occurred.', 400); + + await newReminder.save(); + + return response.sendStatus(204).end(); + } + ] +}; \ No newline at end of file From e55237d22c38bd7f2e3b37c0d29f3aa0caffa70a Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:31:56 +0300 Subject: [PATCH 3/8] Rename Reminder to VoteReminder, remove date field from newReminder object --- server/src/routes/servers/[id]/reminder.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/server/src/routes/servers/[id]/reminder.js b/server/src/routes/servers/[id]/reminder.js index 1e302f56..05080b01 100644 --- a/server/src/routes/servers/[id]/reminder.js +++ b/server/src/routes/servers/[id]/reminder.js @@ -3,8 +3,7 @@ const useRateLimiter = require('@/utils/useRateLimiter'); const { param, matchedData } = require('express-validator'); const Server = require('@/schemas/Server'); const VoteTimeout = require('@/schemas/Server/Vote/Timeout'); -const Reminder = require('@/schemas/Server/Vote/Reminder'); -const checkCaptcha = require('@/utils/middlewares/checkCaptcha'); +const VoteReminder = require('@/schemas/Server/Vote/Reminder'); const bodyParser = require('body-parser'); module.exports = { @@ -12,7 +11,6 @@ module.exports = { useRateLimiter({ maxRequests: 5, perMinutes: 1 }), bodyParser.json(), checkAuthentication, - checkCaptcha, param('id'), async (request, response) => { const { id } = matchedData(request); @@ -26,17 +24,16 @@ module.exports = { const timeout = await VoteTimeout.findOne({ 'user.id': request.user.id, 'guild.id': id }); if (!timeout) return response.sendError('You can\'t set a reminder for a server you haven\'t voted for.', 400); - const reminder = await Reminder.findOne({ 'user.id': request.user.id, 'guild.id': id }); + const reminder = await VoteReminder.findOne({ 'user.id': request.user.id, 'guild.id': id }); if (reminder) return response.sendError('You already set a reminder for this server.', 400); - const newReminder = new Reminder({ + const newReminder = new VoteReminder({ user: { id: request.user.id }, guild: { id - }, - date: Date.now() + 86400000 + } }); const validationErrors = newReminder.validateSync(); From 81c46aa051a0547c2c10190532c5385de1ef77f3 Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:32:13 +0300 Subject: [PATCH 4/8] Add can_set_reminder field to get server route --- server/src/routes/servers/[id]/index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/routes/servers/[id]/index.js b/server/src/routes/servers/[id]/index.js index 31cf5e80..5424f593 100644 --- a/server/src/routes/servers/[id]/index.js +++ b/server/src/routes/servers/[id]/index.js @@ -7,6 +7,7 @@ const Server = require('@/schemas/Server'); const Premium = require('@/schemas/Premium'); const VoteTimeout = require('@/schemas/Server/Vote/Timeout'); const VoiceActivity = require('@/schemas/Server/VoiceActivity'); +const VoteReminder = require('@/schemas/Server/Vote/Reminder'); const Review = require('@/schemas/Server/Review'); const inviteLinkValidation = require('@/validations/servers/inviteLink'); const updatePanelMessage = require('@/utils/servers/updatePanelMessage'); @@ -57,6 +58,11 @@ module.exports = { ) }; + const voteTimeout = await VoteTimeout.findOne({ 'user.id': request.user?.id, 'guild.id': id }); + const reminder = await VoteReminder.findOne({ 'user.id': request.user?.id, 'guild.id': id }); + const memberInGuild = guild.members.cache.get(request.user?.id) || await guild.members.fetch(request.user?.id).catch(() => false); + const tenMinutesPassedAfterVote = voteTimeout && Date.now() - voteTimeout.createdAt.getTime() > 600000; + return response.json({ ...await server.toPubliclySafe(), name: guild.name, @@ -68,12 +74,13 @@ module.exports = { vanity_url: guild.vanityURLCode ? `https://discord.com/invite/${guild.vanityURLCode}` : null, boost_level: guild.premiumTier, total_boosts: guild.premiumSubscriptionCount, - vote_timeout: request.user ? (await VoteTimeout.findOne({ 'user.id': request.user.id, 'guild.id': id }) || null) : null, + vote_timeout: request.user ? (voteTimeout || null) : null, badges, voiceActivity: voiceActivity ? voiceActivity.data : null, reviews, has_reviewed: request.user ? !!reviews.find(review => review.user.id === request.user.id) : null, - permissions + permissions, + can_set_reminder: !!(request.user && !reminder && voteTimeout && memberInGuild && !tenMinutesPassedAfterVote) }); } ], From 7f3151a122652cc124ca457decccbf06de675436 Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:32:29 +0300 Subject: [PATCH 5/8] Remove date field from new vote reminder object --- server/src/bot/commands/vote.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/bot/commands/vote.js b/server/src/bot/commands/vote.js index d2b0a85e..db5be4d5 100644 --- a/server/src/bot/commands/vote.js +++ b/server/src/bot/commands/vote.js @@ -74,8 +74,7 @@ module.exports = { }, guild: { id: interaction.guild.id - }, - date: Date.now() + 86400000 + } }).save(); return reminderCollected.update({ components: [], content: 'You will be reminded in 24 hours.' }); From 9257a586ebfaad68c6d6207e456a9232398c0e71 Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:32:36 +0300 Subject: [PATCH 6/8] Refactor voteServer to return response data --- client/lib/request/servers/voteServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/request/servers/voteServer.js b/client/lib/request/servers/voteServer.js index ada1e9cc..17e9ae01 100644 --- a/client/lib/request/servers/voteServer.js +++ b/client/lib/request/servers/voteServer.js @@ -7,8 +7,8 @@ export default function voteServer(id, captchaResponse) { const url = `${config.api.url}/servers/${id}/vote`; try { - await axios.post(url, { captchaResponse }, { withCredentials: true }); - resolve(); + const response = await axios.post(url, { captchaResponse }, { withCredentials: true }); + resolve(response.data); } catch (error) { reject(error instanceof axios.AxiosError ? (error.response?.data?.error || error.message) : error.message); } From 23f0d9cb9fd2857bdaa1ead27359b2427790a66a Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:33:55 +0300 Subject: [PATCH 7/8] Add createReminder function --- client/lib/request/servers/createReminder.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 client/lib/request/servers/createReminder.js diff --git a/client/lib/request/servers/createReminder.js b/client/lib/request/servers/createReminder.js new file mode 100644 index 00000000..69d319b4 --- /dev/null +++ b/client/lib/request/servers/createReminder.js @@ -0,0 +1,16 @@ +import config from '@/config'; +import axios from 'axios'; + +export default function createReminder(id) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + const url = `${config.api.url}/servers/${id}/reminder`; + + try { + await axios.post(url, {}, { withCredentials: true }); + resolve(); + } catch (error) { + reject(error instanceof axios.AxiosError ? (error.response?.data?.error || error.message) : error.message); + } + }); +} \ No newline at end of file From 6b85b5e595fab08b0eae2b03c217b3c64b946d4f Mon Sep 17 00:00:00 2001 From: chimpdev Date: Fri, 29 Mar 2024 13:34:08 +0300 Subject: [PATCH 8/8] Add create reminder button and functionality to Actions component --- .../components/sections/RightSide/Actions.jsx | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/client/app/(servers)/servers/[id]/components/sections/RightSide/Actions.jsx b/client/app/(servers)/servers/[id]/components/sections/RightSide/Actions.jsx index c164f87c..4cef863c 100644 --- a/client/app/(servers)/servers/[id]/components/sections/RightSide/Actions.jsx +++ b/client/app/(servers)/servers/[id]/components/sections/RightSide/Actions.jsx @@ -14,15 +14,19 @@ import Script from 'next/script'; import { useEffect, useRef, useState } from 'react'; import { toast } from 'sonner'; import voteServer from '@/lib/request/servers/voteServer'; +import createReminder from '@/lib/request/servers/createReminder'; import Countdown from '@/app/components/Countdown'; import Tooltip from '@/app/components/Tooltip'; +import { FaRegBell, FaBell } from 'react-icons/fa'; export default function Actions({ server }) { const [serverVotes, setServerVotes] = useState(server.votes); const [voteTimeout, setVoteTimeout] = useState(server.vote_timeout); + const [canSetReminder, setCanSetReminder] = useState(server.can_set_reminder); const loggedIn = useAuthStore(state => state.loggedIn); const [showCaptcha, setShowCaptcha] = useState(false); const [loading, setLoading] = useState(false); + const [createReminderLoading, setCreateReminderLoading] = useState(false); const formatter = new Intl.NumberFormat('en-US', { notation: 'compact', @@ -48,10 +52,11 @@ export default function Actions({ server }) { toast.promise(voteServer(server.id, response), { loading: `Voting ${server.name}..`, - success: () => { + success: data => { setLoading(false); setServerVotes(serverVotes + (server.badges.includes('Premium') ? 2 : 1)); setVoteTimeout({ createdAt: new Date().getTime() + 86400000 }); + setCanSetReminder(data.inGuild); return `Successfully voted for ${server.name}!`; }, @@ -68,6 +73,24 @@ export default function Actions({ server }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [showCaptcha]); + function setReminder() { + setCreateReminderLoading(true); + + toast.promise(createReminder(server.id), { + loading: `Creating reminder for ${server.name}..`, + success: () => { + setCreateReminderLoading(false); + setCanSetReminder(false); + + return `I will remind you to vote for ${server.name} in 24 hours! Please keep your DMs open and don't leave the server.`; + }, + error: error => { + setCreateReminderLoading(false); + return error; + } + }); + } + return (
+ {canSetReminder && ( + { + if (!loggedIn) return toast.error('You need to be logged in to set a reminder for a server.'); + + setReminder(); + }} + > +
+ {createReminderLoading && } + Create Reminder +
+ +
+
+ + +
+
+
+ )} +