Skip to content

Commit

Permalink
feat: appeal and redesign components
Browse files Browse the repository at this point in the history
  • Loading branch information
iCrawl committed Oct 2, 2023
1 parent 55d0c5e commit 8c1925d
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 52 deletions.
1 change: 1 addition & 0 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"license": "AGPL-3.0",
"private": true,
"dependencies": {
"@lukeed/ms": "^2.0.1",
"next": "^13.5.3",
"next-themes": "^0.2.1",
"react": "^18.2.0",
Expand Down
88 changes: 88 additions & 0 deletions apps/website/src/app/appeal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import process from "node:process";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { CaseCard } from "~/components/CaseCard";
import { UserDisplay } from "~/components/UserDisplay";

export default async function Page() {
const cookieStore = cookies();

const token = cookieStore.get("discord_token");
if (!token) {
return (
<a
href={`https://discord.com/api/oauth2/authorize?client_id=${
process.env.DISCORD_CLIENT_ID
}&redirect_uri=${encodeURIComponent(
process.env.DISCORD_REDIRECT_URI!,
)}&response_type=code&scope=identify%20guilds.members.read%20guilds.join%20guilds`}
>
Login with Discord
</a>
);
}

const userData = await fetch("https://discord.com/api/v10/users/@me", {
headers: {
Authorization: `Bearer ${token.value}`,
},
next: { revalidate: 86_400 },
});

if (userData.status !== 200) {
return redirect("/api/discord/logout");
}

const oauth2User = await userData.json();

const banData = await fetch(`https://bot.yuudachi.dev/api/users/${oauth2User.id}`, {
headers: {
Authorization: `Bearer ${process.env.JWT_TOKEN}`,
},
});

const { user, banned, case: case_ } = await banData.json();

if (!banned) {
return <div className="mx-auto max-w-5xl gap-2 p-8 font-medium">You are not banned.</div>;
}

return (
<div className="flex flex-col gap-8">
<h1 className="mb-4 pt-12 text-center text-4xl font-extrabold leading-none tracking-tight md:mb-8 md:text-5xl">
Appeal your <span className="underline-offset-3 decoration-blurple underline decoration-8">ban</span>
</h1>
<div className="mx-auto flex w-full max-w-3xl flex-col gap-8 px-4 pb-8 md:max-w-4xl md:flex-row md:gap-8">
<div className="flex flex-col gap-8">
<div className="dark:from-dark-600 dark:from-82% from-82% from-light-600 top-0 flex w-full flex-col place-content-start gap-4 bg-gradient-to-b md:w-auto">
<UserDisplay user={user} />
</div>

<div className="flex flex-col gap-4">
<h2 className="text-lg font-semibold">Case #{case_.case_id}</h2>
<CaseCard case_={case_} />
</div>
</div>

<div className="flex w-full grow flex-col gap-4">
<div className="flex grow grow flex-col gap-4">
<label className="flex grow flex-col gap-4 text-lg font-semibold">
Why do you deserve a second chance?
<textarea
cols={12}
className="dark:bg-dark-800 bg-light-400 dark:border-dark-100 focus-visible:ring-blurple dark:focus-visible:ring-offset-dark-800 h-full min-h-[150px] w-full rounded-lg border shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
/>
</label>

<button
type="button"
className="dark:border-dark-100 bg-blurple hover:bg-blurple-300 focus-visible:ring-blurple dark:focus-visible:ring-offset-dark-800 focus-visible:ring-offset text-light-100 transform-gpu place-self-end rounded-lg border px-4 py-2 font-medium outline-none focus-visible:ring-2 active:translate-y-px"
>
Send
</button>
</div>
</div>
</div>
</div>
);
}
48 changes: 24 additions & 24 deletions apps/website/src/app/cases/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import process from "node:process";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { Fragment } from "react";
import { CaseCard } from "~/components/CaseCard";
import { UserDisplay } from "~/components/UserDisplay";

export default async function Page({ params }: { params: { id: string } }) {
Expand All @@ -21,19 +23,6 @@ export default async function Page({ params }: { params: { id: string } }) {
);
}

// const userData = await fetch("https://discord.com/api/v10/users/@me", {
// headers: {
// Authorization: `Bearer ${token.value}`,
// },
// next: { revalidate: 86_400 },
// });

// if (userData.status !== 200) {
// return redirect("/api/discord/logout");
// }

// const user = await userData.json();

const memberData = await fetch(`https://discord.com/api/v10/users/@me/guilds/222078108977594368/member`, {
headers: {
Authorization: `Bearer ${token.value}`,
Expand All @@ -60,22 +49,33 @@ export default async function Page({ params }: { params: { id: string } }) {
const { user, cases } = await caseData.json();

if (!user) {
return <div className="mx-auto max-w-5xl gap-2 p-8">No user found.</div>;
return <div className="mx-auto max-w-5xl gap-2 p-8 font-medium">No user found.</div>;
}

return (
<div className="mx-auto flex max-w-5xl flex-col gap-2 px-4 pb-8 md:flex-row md:gap-8">
<div className="dark:from-dark-800 from-82% dark:md:bg-dark-800 sticky top-0 w-full place-self-center bg-gradient-to-b from-white py-8 md:w-auto md:place-self-start md:bg-white">
<UserDisplay user={user} />
</div>
<div className="flex flex-col gap-8">
<h1 className="mb-4 pt-12 text-center text-4xl font-extrabold leading-none tracking-tight md:mb-8 md:text-5xl">
Review <span className="underline-offset-3 decoration-blurple underline decoration-8">cases</span>
</h1>
<div className="mx-auto flex w-full max-w-3xl flex-col gap-8 px-4 pb-8 md:max-w-4xl md:flex-row md:gap-8">
<div className="dark:from-dark-600 from-light-600 sticky top-0 flex w-full flex-col place-content-start gap-4 bg-gradient-to-b from-85% dark:from-85% md:w-auto">
<UserDisplay className="sticky top-0 py-4" user={user} />
</div>

<div className="flex w-full flex-col gap-4 md:pt-8">
{cases.map((case_: any) => (
<div key={case_.case_id} className="bg-light-600 dark:bg-dark-400 rounded-lg p-4">
<div>Case: {case_.case_id}</div>
<div>Reason: {case_.reason}</div>
<div className="flex w-full flex-col gap-4">
<div className="flex flex-col gap-4">
{cases.length ? (
cases.map((case_: any) => (
<Fragment key={case_.case_id}>
<h2 className="text-lg font-semibold">Case #{case_.case_id}</h2>
<CaseCard case_={case_} />
</Fragment>
))
) : (
<h2 className="pt-4 text-center text-lg font-semibold">No cases</h2>
)}
</div>
))}
</div>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: PropsWithChildren) {
return (
<html lang="en" suppressHydrationWarning>
<body className="dark:bg-dark-800 bg-white">
<body className="bg-light-600 dark:bg-dark-600 dark:text-light-600">
<Providers>{children}</Providers>
</body>
</html>
Expand Down
6 changes: 1 addition & 5 deletions apps/website/src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,5 @@ import { ThemeProvider } from "next-themes";
import type { PropsWithChildren } from "react";

export function Providers({ children }: PropsWithChildren) {
return (
<ThemeProvider attribute="class" enableSystem>
{children}
</ThemeProvider>
);
return <ThemeProvider attribute="class">{children}</ThemeProvider>;
}
7 changes: 7 additions & 0 deletions apps/website/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { PropsWithChildren } from "react";

export function Card({ children }: PropsWithChildren) {
return (
<div className="bg-light-400 dark:bg-dark-800 dark:border-dark-100 rounded-lg border shadow-sm">{children}</div>
);
}
90 changes: 90 additions & 0 deletions apps/website/src/components/CaseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { format } from "@lukeed/ms";
import { Card } from "./Card";

enum CaseAction {
Role,
Unrole,
Warn,
Kick,
Softban,
Ban,
Unban,
Timeout,
TimeoutEnd,
}

function caseActionLabel(key: CaseAction) {
switch (key) {
case CaseAction.Role:
return "Role";
case CaseAction.Unrole:
return "Unrole";
case CaseAction.Warn:
return "Warn";
case CaseAction.Kick:
return "Kick";
case CaseAction.Softban:
return "Softban";
case CaseAction.Ban:
return "Ban";
case CaseAction.Unban:
return "Unban";
case CaseAction.Timeout:
return "Timeout";
case CaseAction.TimeoutEnd:
return "TimeoutEnd";
default:
return "Unknown";
}
}

function generateCaseColor(key: CaseAction) {
switch (key) {
case CaseAction.Role:
case CaseAction.Warn:
case CaseAction.Timeout:
return 16_767_836;
case CaseAction.Kick:
case CaseAction.Softban:
return 16_225_364;
case CaseAction.Ban:
return 16_735_324;
case CaseAction.Unban:
return 6_094_749;
default:
return 3_092_790;
}
}

export function CaseCard({ case_ }: { readonly case_: any }) {
const createdAt = new Date(case_.created_at);

return (
<Card>
<div className="flex">
<div
className="rounded-l-lg border-4"
style={{ borderColor: `#${generateCaseColor(case_.action).toString(16)}` }}
/>
<div className="flex flex-col p-4">
<div>
<span className="font-semibold">Action:</span> {caseActionLabel(case_.action)}
</div>
<div role="separator" className="from-light-900 dark:from-dark-100 my-2 h-[1px] bg-gradient-to-r" />
<div>
<span className="font-semibold">Moderator:</span> {case_.mod_tag}
</div>
<div role="separator" className="from-light-900 dark:from-dark-100 my-2 h-[1px] bg-gradient-to-r" />
<div>
<span className="font-semibold">Reason:</span> {case_.reason}
</div>
<div role="separator" className="from-light-900 dark:from-dark-100 my-2 h-[1px] bg-gradient-to-r" />
<div>
<span className="font-semibold">Date:</span> {new Intl.DateTimeFormat("en-GB").format(createdAt)} (
{format(Date.now() - createdAt.getTime(), true)} ago)
</div>
</div>
</div>
</Card>
);
}
53 changes: 32 additions & 21 deletions apps/website/src/components/UserDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export function UserDisplay({
user,
className,
}: {
readonly className?: string;
readonly user: {
accent_color?: number | null;
avatar?: string | null;
Expand All @@ -13,48 +15,57 @@ export function UserDisplay({
const isBannerAnimated = user.banner?.startsWith("a_");

return (
<div className="flex w-[340px] max-w-[340px] flex-col">
<div className={`flex w-[340px] max-w-[340px] flex-col ${className ?? ""}`}>
<div className="relative">
<div className="h-[120px] max-h-[120px]">
{user.banner ? (
<picture>
<img
alt="Banner"
className="h-full w-full rounded-lg object-cover"
className="h-full w-full rounded-lg object-cover shadow-md"
src={`https://cdn.discordapp.com/banners/${user.id}/${user.banner}${
isBannerAnimated ? ".gif" : ".png"
}?size=480`}
/>
</picture>
) : user.accent_color ? (
<div className="h-full w-full rounded-lg" style={{ background: `#${user.accent_color.toString(16)}` }} />
<div
className="h-full w-full rounded-lg shadow-md"
style={{ background: `#${user.accent_color.toString(16)}` }}
/>
) : (
<div className="flex h-full w-full place-content-center items-center rounded-lg bg-black">
<span>Actually poor.</span>
<div className="dark:bg-dark-900 bg-dark-600 flex h-full w-full place-content-center items-center rounded-lg shadow-md">
<span className="text-light-600">Actually poor.</span>
</div>
)}
</div>

{user.avatar ? (
<picture>
<img
alt="Avatar"
className="border-3 absolute left-[22px] top-[72px] h-[80px] w-[80px] rounded-full"
src={`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}${isAvatarAnimated ? ".gif" : ".png"}`}
/>
</picture>
<div className="absolute left-[22px] top-[76px] rounded-full border-4">
<picture>
<img
alt="Avatar"
className="h-[80px] w-[80px] rounded-full shadow-md"
src={`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}${
isAvatarAnimated ? ".gif" : ".png"
}`}
/>
</picture>
</div>
) : (
<picture>
<img
alt="Avatar"
className="border-3 absolute left-[22px] top-[72px] h-[80px] w-[80px] rounded-full"
src={`https://cdn.discordapp.com/embed/avatars/${(BigInt(user.id) >> 22n) % 6n}.png`}
/>
</picture>
<div className="absolute left-[22px] top-[76px] rounded-full border-4">
<picture>
<img
alt="Avatar"
className="h-[80px] w-[80px] rounded-full shadow-md"
src={`https://cdn.discordapp.com/embed/avatars/${(BigInt(user.id) >> 22n) % 6n}.png`}
/>
</picture>
</div>
)}
</div>
<div className="flex place-content-end p-2">
<span className="">{user.username}</span>
<div className="flex place-content-end truncate py-3 pl-28 pr-[22px]">
<span className="truncate font-medium">{user.username}</span>
</div>
</div>
);
Expand Down
Loading

1 comment on commit 8c1925d

@vercel
Copy link

@vercel vercel bot commented on 8c1925d Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

yuudachi – ./apps/website

yuudachi-git-main-discordjs.vercel.app
yuudachi.vercel.app
yuudachi-discordjs.vercel.app
yuudachi.dev
www.yuudachi.dev

Please sign in to comment.