Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Able to create reminder on web #3

Merged
merged 8 commits into from
Mar 29, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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}!`;
},
Expand All @@ -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 (
<div>
<motion.h2
Expand Down Expand Up @@ -99,6 +122,35 @@ export default function Actions({ server }) {
)}
</AnimatePresence>

{canSetReminder && (
<motion.button
className={cn(
'flex items-center justify-between w-full px-3 py-2 text-sm font-semibold text-white bg-black rounded-lg group gap-x-2 hover:bg-black/70 dark:bg-white dark:text-black dark:hover:bg-white/70',
createReminderLoading && 'cursor-default !opacity-70 hover:bg-black dark:hover:bg-white'
)}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, type: 'spring', stiffness: 100, damping: 10 }}
onClick={() => {
if (!loggedIn) return toast.error('You need to be logged in to set a reminder for a server.');

setReminder();
}}
>
<div className='flex gap-x-1.5 items-center'>
{createReminderLoading && <TbLoader className='animate-spin' />}
Create Reminder
</div>

<div className='flex items-center font-bold gap-x-1'>
<div className='relative'>
<FaBell className='absolute transition-transform opacity-0 group-hover:opacity-100 group-hover:scale-[1.2]' />
<FaRegBell className='opacity-100 transition-[transform] group-hover:opacity-0' />
</div>
</div>
</motion.button>
)}

<motion.button
className={cn(
'flex items-center justify-between w-full px-3 py-2 text-sm font-semibold text-white bg-black rounded-lg group gap-x-2 hover:bg-black/70 dark:bg-white dark:text-black dark:hover:bg-white/70',
Expand Down
16 changes: 16 additions & 0 deletions client/lib/request/servers/createReminder.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
}
4 changes: 2 additions & 2 deletions client/lib/request/servers/voteServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 1 addition & 2 deletions server/src/bot/commands/vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.' });
Expand Down
11 changes: 9 additions & 2 deletions server/src/routes/servers/[id]/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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,
Expand All @@ -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)
});
}
],
Expand Down
47 changes: 47 additions & 0 deletions server/src/routes/servers/[id]/reminder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 VoteReminder = require('@/schemas/Server/Vote/Reminder');
const bodyParser = require('body-parser');

module.exports = {
post: [
useRateLimiter({ maxRequests: 5, perMinutes: 1 }),
bodyParser.json(),
checkAuthentication,
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 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 VoteReminder({
user: {
id: request.user.id
},
guild: {
id
}
});

const validationErrors = newReminder.validateSync();
if (validationErrors) return response.sendError('An unknown error occurred.', 400);

await newReminder.save();

return response.sendStatus(204).end();
}
]
};
5 changes: 4 additions & 1 deletion server/src/routes/servers/[id]/vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
]
Expand Down
Loading