Skip to content

Commit

Permalink
Features to bots (#38)
Browse files Browse the repository at this point in the history
* Able to add edit permissions to users for bots & redesign bot manage page

* Rename check changes script file name to checkChanges

* Add local data storing for bot data in NewBot component

* Remove zod package from client

* Seperate webhook related codes in manage bot page

* Make responsive bot manage page

* Make it possible to add GitHub repository to bots (for open-source bots)

* Refactor GitHub repository handling in bot manage page

* Update GitHubCacheSchema to only include necessary fields from GitHub API

* Update GitHubCacheSchema to only include necessary fields from GitHub API
  • Loading branch information
chimpdev committed Jun 29, 2024
1 parent 043481b commit 427f1b9
Show file tree
Hide file tree
Showing 33 changed files with 1,706 additions and 777 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { MdChevronLeft } from 'react-icons/md';
import { useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';
import { RiEyeFill, RiEyeOffFill } from 'react-icons/ri';
import { FaEyeSlash, FaEye } from 'react-icons/fa';
import cn from '@/lib/cn';
import { IoMdCheckmarkCircle } from 'react-icons/io';
import createBot from '@/lib/request/bots/createBot';
Expand All @@ -14,14 +13,10 @@ import Lottie from 'react-lottie';
import confetti from '@/lib/lotties/confetti.json';
import { TbLoader } from 'react-icons/tb';
import Markdown from '@/app/components/Markdown';
import ServerIcon from '@/app/(servers)/servers/components/ServerIcon';
import Link from 'next/link';
import Tooltip from '@/app/components/Tooltip';
import CopyButton from '@/app/components/CopyButton';
import useAccountStore from '@/stores/account';
import { useLocalStorage } from 'react-use';

export default function NewBot() {
const data = useAccountStore(state => state.data);
const setCurrentlyAddingBot = useAccountStore(state => state.setCurrentlyAddingBot);

const descriptionRef = useRef(null);
Expand All @@ -34,10 +29,46 @@ export default function NewBot() {
const [botDescription, setBotDescription] = useState('');
const [botInviteUrl, setBotInviteUrl] = useState('');
const [botCategories, setBotCategories] = useState([]);
const [botSupportServerId, setBotSupportServerId] = useState('');
const [botWebhookUrl, setBotWebhookUrl] = useState('');
const [botWebhookToken, setBotWebhookToken] = useState('');
const [webhookTokenBlurred, setWebhookTokenBlurred] = useState(true);

const [localData, setLocalData] = useLocalStorage('bot-stored-data', {
botId: '',
botShortDescription: '',
botDescription: '',
botInviteUrl: '',
botCategories: []
});

useEffect(() => {

if (localData) {
if (localData.botId === '' && localData.botShortDescription === '' && localData.botDescription === '' && localData.botInviteUrl === '' && localData.botCategories.length === 0) return;

setBotId(localData.botId);
setBotShortDescription(localData.botShortDescription);
setBotDescription(localData.botDescription);
descriptionRef.current.innerText = localData.botDescription;
setBotInviteUrl(localData.botInviteUrl);
setBotCategories(localData.botCategories);

toast.info('Previously submitted application restored.');
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (botId === '' && botShortDescription === '' && botDescription === '' && botInviteUrl === '' && botCategories.length === 0) return;

setLocalData({
botId,
botShortDescription,
botDescription,
botInviteUrl,
botCategories
});

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [botId, botShortDescription, botDescription, botInviteUrl, botCategories]);

useEffect(() => {
if (markdownPreviewing === false) descriptionRef.current.innerText = botDescription;
Expand All @@ -48,8 +79,6 @@ export default function NewBot() {
const router = useRouter();

function addBot() {
if (!botWebhookUrl && botWebhookToken) return toast.error('If you set Webhook Token, you must set Webhook URL too.');

setLoading(true);

const botData = {
Expand All @@ -59,11 +88,6 @@ export default function NewBot() {
categories: botCategories
};

if (botSupportServerId) botData.support_server_id = botSupportServerId;
if (botWebhookUrl || botWebhookToken) botData.webhook = {};
if (botWebhookUrl) botData.webhook.url = botWebhookUrl;
if (botWebhookToken) botData.webhook.token = botWebhookToken;

toast.promise(createBot(botId, botData), {
loading: `Adding ${botId}..`,
success: () => {
Expand All @@ -76,7 +100,6 @@ export default function NewBot() {
setBotShortDescription('');
setBotDescription('');
setBotCategories([]);
setBotSupportServerId('');
}, 3000);
setRenderConfetti(true);

Expand All @@ -103,7 +126,6 @@ export default function NewBot() {
setBotShortDescription('');
setBotDescription('');
setBotCategories([]);
setBotSupportServerId('');
setCurrentlyAddingBot(false);
}}>
<MdChevronLeft size={24}/>
Expand Down Expand Up @@ -241,102 +263,6 @@ export default function NewBot() {
))}
</div>

<h2 className="flex items-center mt-8 text-lg font-semibold gap-x-2">
Support Server <span className="text-xs font-normal select-none text-tertiary">(optional)</span>
</h2>

<p className="text-sm sm:text-base text-tertiary">
You can select a server that users can join to get support for your bot. This is optional.<br/>
You can only select servers that you listed on discord.place.
</p>

{data.servers.filter(server => server.is_created).length <= 0 ? (
<p className="mt-4 text-sm text-tertiary">
You don{'\''}t have any servers listed on discord.place.
</p>
) : (
<div className="grid grid-cols-1 gap-4 mt-4 mobile:grid-cols-2 sm:grid-cols-4 lg:grid-cols-5">
{data.servers.filter(server => server.is_created).map(server => (
<button
className="flex flex-col bg-secondary hover:bg-quaternary p-2 rounded-xl w-full h-[180px] items-center cursor-pointer overflow-clip relative"
key={server.id}
onClick={() => setBotSupportServerId(oldServerId => oldServerId === server.id ? '' : server.id)}
>
<div className="relative">
<ServerIcon width={128} height={128} icon_url={server.icon_url} name={server.name}/>
<div className={cn(
'absolute w-full h-full text-3xl text-primary transition-opacity rounded-lg flex items-center justify-center bg-secondary/60 z-[0] top-0 left-0',
botSupportServerId !== server.id && 'opacity-0'
)}>
<IoMdCheckmarkCircle/>
</div>
</div>

<h1 className="w-full max-w-full mt-2 text-base font-medium text-center truncate">{server.name}</h1>
</button>
))}
</div>
)}

<h2 className="mt-8 text-lg font-semibold">
Webhook <span className="text-xs font-normal select-none text-tertiary">(optional)</span>
</h2>

<p className="text-sm text-tertiary">
You can use webhooks to get notified when someone votes for your bot. Documentation can be found <Link href={config.docsUrl} target="_blank" rel="noopener noreferrer" className="text-primary">here</Link>.
</p>

<h3 className="mt-4 text-sm font-medium text-secondary">
Webhook URL
</h3>

<input
className="block w-full p-2 mt-2 text-sm border-2 border-transparent rounded-lg outline-none bg-secondary text-placeholder focus-visible:text-primary focus-visible:border-purple-500"
value={botWebhookUrl}
onChange={event => setBotWebhookUrl(event.target.value)}
/>

<h3 className="mt-4 text-sm font-medium text-secondary">
Webhook Token
</h3>

<div className='relative flex items-center justify-center mt-2'>
<input
className='block w-full p-2 pr-16 text-sm border-2 rounded-lg outline-none border-primary bg-secondary text-placeholder focus-visible:text-primary focus-visible:border-purple-500'
value={botWebhookToken}
onChange={event => setBotWebhookToken(event.target.value)}
type={webhookTokenBlurred ? 'password' : 'text'}
/>

<div className='absolute right-0 flex items-center gap-x-1'>
<Tooltip content={webhookTokenBlurred ? 'Click to show Webhook Token' : 'Click to hide Webhook Token'}>
<div className='flex items-center text-sm text-secondary hover:text-tertiary'>
<FaEye
className={cn(
'cursor-pointer transition-all',
!webhookTokenBlurred && 'opacity-0 scale-0'
)}
onClick={() => setWebhookTokenBlurred(old => !old)}
/>

<FaEyeSlash
className={cn(
'cursor-pointer transition-all absolute',
webhookTokenBlurred && 'opacity-0 scale-0'
)}
onClick={() => setWebhookTokenBlurred(old => !old)}
/>
</div>
</Tooltip>

<CopyButton
successText='Copied Webhook Token!'
copyText={botWebhookToken}
className='justify-end'
/>
</div>
</div>

<h2 className="mt-8 text-lg font-semibold">
Content Policy
</h2>
Expand Down Expand Up @@ -374,7 +300,6 @@ export default function NewBot() {
setBotShortDescription('');
setBotDescription('');
setBotCategories([]);
setBotSupportServerId('');
setCurrentlyAddingBot(false);
}}
disabled={loading}
Expand Down
58 changes: 55 additions & 3 deletions client/app/(bots)/bots/[id]/components/sections/About.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { motion } from 'framer-motion';
import { TbSquareRoundedChevronUp } from 'react-icons/tb';
import { FaCompass } from 'react-icons/fa';
import { FaCompass, FaGithub } from 'react-icons/fa';
import { TiStarFullOutline } from 'react-icons/ti';
import Markdown from '@/app/components/Markdown';
import { RiSlashCommands2, RiUserAddLine } from 'react-icons/ri';
import Image from 'next/image';
import Link from 'next/link';
import cn from '@/lib/cn';
import { PiGitForkBold } from 'react-icons/pi';

const formatter = new Intl.NumberFormat('en-US', {
notation: 'compact',
Expand Down Expand Up @@ -70,6 +73,52 @@ export default function About({ bot }) {
}
];

if (bot.github_repository?.data) keys.push({
key: 'GitHub Repository',
label: 'GitHub Repository',
icon: <FaGithub />,
component: <>
<p className='text-tertiary'>
This bot is open-source and available on GitHub.
</p>

<Link
className='cursor-pointer flex flex-col gap-y-3 max-w-[90%] w-full h-max mt-6 bg-tertiary hover:bg-quaternary rounded-lg border-2 border-primary p-4'
href={bot.github_repository.data.html_url}
target='_blank'
rel='noopener noreferrer'
>
<span className='px-2 py-0.5 text-xs font-medium dark:bg-white/20 border dark:border-white/40 select-none bg-blasck/20 border-black/40 rounded-full text-primary w-max'>
{bot.github_repository.data.language}
</span>

<div className='flex items-center text-secondary'>
{bot.github_repository.data.owner.login}/

<span className='font-semibold text-primary'>
{bot.github_repository.data.name}
</span>
</div>

<p className='-mt-2 text-xs whitespace-pre-wrap text-tertiary line-clamp-3'>
{bot.github_repository.data.description}
</p>

<div className='flex items-center gap-x-2 text-tertiary'>
<div className='font-medium text-tertiary text-ms flex gap-x-1.5 items-center'>
<TiStarFullOutline />
{formatter.format(bot.github_repository.data.stargazers_count)}
</div>

<div className='font-medium text-tertiary text-sm flex gap-x-1.5 items-center'>
<PiGitForkBold />
{formatter.format(bot.github_repository.data.forks_count)}
</div>
</div>
</Link>
</>
});

return (
<div className='w-full lg:w-[70%] flex flex-col'>
<motion.h2
Expand Down Expand Up @@ -108,7 +157,10 @@ export default function About({ bot }) {
{keys.map(({ key, label, icon, value, component }, index) => (
<motion.div
key={key}
className='flex items-center h-max gap-x-4'
className={cn(
'flex items-start h-max gap-x-4',
index === keys.length - 1 && index % 2 === 0 && 'sm:col-span-2'
)}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, type: 'spring', stiffness: 100, damping: 10, delay: 0.20 + (.05 * index) }}
Expand All @@ -117,7 +169,7 @@ export default function About({ bot }) {
{icon}
</div>

<div className='flex flex-col'>
<div className='flex flex-col w-full'>
<h3 className='font-semibold'>
{label}
</h3>
Expand Down
Loading

0 comments on commit 427f1b9

Please sign in to comment.