Skip to content

Commit 69cb3e1

Browse files
committed
feat: complete settings page
1 parent 972ed87 commit 69cb3e1

9 files changed

Lines changed: 133 additions & 56 deletions

File tree

apps/website/src/app/dashboard/[id]/page.tsx

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
'use client';
22

3-
import { useRouter, useParams } from 'next/navigation';
4-
import { Button } from '@/components/common/Button';
3+
import { BOTS } from '@chatsift/core';
4+
import Link from 'next/link';
5+
import { notFound, useParams } from 'next/navigation';
6+
import { Breadcrumb } from '@/components/common/Breadcrumb';
57
import { GuildIcon } from '@/components/common/GuildIcon';
68
import { Heading } from '@/components/common/Heading';
79
import { Skeleton } from '@/components/common/Skeleton';
8-
import { SvgAMA } from '@/components/icons/SvgAMA';
910
import { client } from '@/data/client';
11+
import { Bots } from '@/utils/bots';
12+
import { cn } from '@/utils/util';
1013

1114
export default function GuildPage() {
1215
const params = useParams<{ id: string }>();
13-
const router = useRouter();
1416
const { data: me, isLoading } = client.auth.useMe();
1517

1618
const guild = me?.guilds.find((g) => g.id === params.id);
@@ -20,54 +22,57 @@ export default function GuildPage() {
2022
}
2123

2224
if (!guild) {
23-
return (
24-
<div className="flex flex-col gap-4">
25-
<p className="text-lg text-secondary dark:text-secondary-dark">Guild not found</p>
26-
</div>
27-
);
25+
return notFound();
2826
}
2927

3028
return (
3129
<div className="space-y-8">
32-
<div className="flex flex-row flex-grow gap-72">
33-
<div className="space-y-2 w-64">
34-
<Button
35-
className="flex items-center gap-2 text-lg text-secondary hover:text-primary dark:text-secondary-dark dark:hover:text-primary-dark"
36-
onPress={() => router.replace('/dashboard')}
37-
>
38-
← Servers
39-
</Button>
40-
41-
<Heading title="Server Settings" />
42-
</div>
43-
</div>
30+
<Breadcrumb
31+
segments={[
32+
{ label: 'Servers', href: '/dashboard' },
33+
{ label: guild.name, highlight: true },
34+
]}
35+
/>
4436

45-
<div className="flex h-36 w-full flex-col gap-3 rounded-lg border-[1px] border-on-secondary bg-[#FFFFFF] p-4 dark:border-on-secondary-dark dark:bg-[#1C1C21]">
46-
<GuildIcon data={guild} hasBots={guild.bots.length > 0} />
47-
<div className="flex flex-col gap-1">
48-
<p className="w-full overflow-hidden overflow-ellipsis whitespace-nowrap text-lg font-medium text-primary dark:text-primary-dark">
37+
{/* TODO: Show member counts maybe */}
38+
<div className="flex w-full flex-col gap-3 rounded-lg border-[1px] border-on-secondary bg-card p-4 dark:border-on-secondary-dark dark:bg-card-dark">
39+
<div className="flex flex-row gap-4">
40+
<GuildIcon data={guild} disableLink hasBots={guild.bots.length > 0} />
41+
<p className="content-center w-full overflow-hidden overflow-ellipsis whitespace-nowrap text-lg font-medium text-primary dark:text-primary-dark">
4942
{guild.name}
5043
</p>
51-
<p className="text-base text-secondary dark:text-secondary-dark">{guild.bots.length} bot(s) active</p>
5244
</div>
45+
46+
<p className="text-base text-secondary dark:text-secondary-dark">{guild.bots.length} bot(s) active</p>
5347
</div>
5448

55-
{/* Bot navigation section */}
5649
<div className="space-y-4">
57-
<Heading subtitle="Configure your bots" title="Bots" />
50+
<Heading subtitle="Configure the bots installed in your server" title="Bots" />
5851
<div className="flex flex-col gap-3">
59-
{guild.bots.includes('AMA') && (
60-
<a
61-
className="flex items-center gap-4 rounded-lg border-[1px] border-on-secondary bg-[#FFFFFF] p-4 hover:bg-on-tertiary dark:border-on-secondary-dark dark:bg-[#1C1C21] dark:hover:bg-on-tertiary-dark"
62-
href={`/dashboard/${guild.id}/ama`}
63-
>
64-
<SvgAMA height={32} width={32} />
65-
<div className="flex flex-col">
66-
<p className="text-lg font-medium text-primary dark:text-primary-dark">AMA</p>
67-
<p className="text-sm text-secondary dark:text-secondary-dark">Configure AMA bot settings</p>
68-
</div>
69-
</a>
70-
)}
52+
{BOTS.map((bot, index) => {
53+
const { Icon } = Bots[bot];
54+
const hasIt = guild.bots.includes(bot);
55+
56+
return (
57+
<Link
58+
className={cn(
59+
'flex items-center gap-4 rounded-lg border-[1px] border-on-secondary bg-card p-4 hover:bg-on-tertiary dark:border-on-secondary-dark dark:bg-card-dark dark:hover:bg-on-tertiary-dark',
60+
!hasIt && 'opacity-50 hover:opacity-75',
61+
)}
62+
href={hasIt ? `/dashboard/${guild.id}/ama` : `/invites/${bot.toLowerCase()}`}
63+
key={index}
64+
prefetch
65+
>
66+
<Icon height={32} width={32} />
67+
<div className="flex flex-col">
68+
<p className="text-lg font-medium text-primary dark:text-primary-dark">{bot}</p>
69+
<p className="text-sm text-secondary dark:text-secondary-dark">
70+
{hasIt ? `Configure ${bot} bot settings` : `Invite ${bot} to your server`}
71+
</p>
72+
</div>
73+
</Link>
74+
);
75+
})}
7176
</div>
7277
</div>
7378
</div>

apps/website/src/app/dashboard/_components/GuildCard.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { MeGuild } from '@chatsift/api';
2+
import Link from 'next/link';
23
import { GuildIcon } from '@/components/common/GuildIcon';
34
import { SvgAMA } from '@/components/icons/SvgAMA';
45
import { cn } from '@/utils/util';
@@ -15,13 +16,17 @@ export default function GuildCard({ data }: GuildCardProps) {
1516
<div
1617
className={cn(
1718
'flex h-36 w-[80vw] flex-col gap-3 rounded-lg border-[1px] border-on-secondary p-4 dark:border-on-secondary-dark md:w-52',
18-
hasBots ? 'bg-[#FFFFFF] dark:bg-[#1C1C21]' : 'group',
19+
hasBots ? 'bg-card dark:bg-card-dark' : 'group',
1920
)}
2021
>
2122
<GuildIcon data={data} hasBots={hasBots} />
2223
<div className="flex flex-col gap-1">
2324
<p className="w-full overflow-hidden overflow-ellipsis whitespace-nowrap text-lg font-medium text-primary group-hover:hidden dark:text-primary-dark">
24-
<a href={url}>{data.name}</a>
25+
{url && (
26+
<Link href={url} prefetch>
27+
{data.name}
28+
</Link>
29+
)}
2530
</p>
2631

2732
{hasBots ? (
@@ -41,9 +46,9 @@ export default function GuildCard({ data }: GuildCardProps) {
4146
<p className="text-lg font-medium text-primary dark:text-primary-dark">Invite a bot:</p>
4247
<ul className="flex flex-row gap-3">
4348
<li>
44-
<a href="/invites/ama">
49+
<Link href="/invites/ama">
4550
<SvgAMA />
46-
</a>
51+
</Link>
4752
</li>
4853
</ul>
4954
</div>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import Link from 'next/link';
2+
import { cn } from '@/utils/util';
3+
4+
export interface BreadcrumbSegment {
5+
readonly highlight?: boolean;
6+
readonly href?: string;
7+
readonly label: string;
8+
}
9+
10+
interface BreadcrumbProps {
11+
readonly segments: BreadcrumbSegment[];
12+
}
13+
14+
export function Breadcrumb({ segments }: BreadcrumbProps) {
15+
return (
16+
<nav className="flex items-center gap-2 text-lg">
17+
{segments.map((segment, index) => {
18+
const isLast = index === segments.length - 1;
19+
20+
return (
21+
<div className="flex items-center gap-2" key={`${segment.label}-${index}`}>
22+
{segment.href ? (
23+
<Link
24+
className={cn(
25+
'hover:text-primary dark:hover:text-primary-dark',
26+
isLast
27+
? 'text-primary dark:text-primary-dark font-medium'
28+
: 'text-secondary dark:text-secondary-dark',
29+
segment.highlight && 'italic',
30+
)}
31+
href={segment.href}
32+
prefetch
33+
>
34+
{segment.label}
35+
</Link>
36+
) : (
37+
<span
38+
className={cn(
39+
isLast
40+
? 'text-primary dark:text-primary-dark font-medium'
41+
: 'text-secondary dark:text-secondary-dark',
42+
segment.highlight && 'italic',
43+
)}
44+
>
45+
{segment.label}
46+
</span>
47+
)}
48+
49+
{!isLast && <span className="text-secondary dark:text-secondary-dark">/</span>}
50+
</div>
51+
);
52+
})}
53+
</nav>
54+
);
55+
}

apps/website/src/components/common/GuildIcon.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { MeGuild } from '@chatsift/api';
22
import Image from 'next/image';
3+
import Link from 'next/link';
4+
import { cn } from '@/utils/util';
35

46
function getGuildAcronym(guildName: string) {
57
return guildName
@@ -10,16 +12,17 @@ function getGuildAcronym(guildName: string) {
1012

1113
export interface GuildIconProps {
1214
readonly data: MeGuild;
15+
readonly disableLink?: boolean;
1316
readonly hasBots: boolean;
1417
}
1518

16-
export function GuildIcon({ data, hasBots }: GuildIconProps) {
19+
export function GuildIcon({ data, hasBots, disableLink }: GuildIconProps) {
1720
const icon = data?.icon ? `https://cdn.discordapp.com/icons/${data.id}/${data.icon}.png` : null;
1821
const url = hasBots ? `/dashboard/${data.id}` : undefined;
1922

2023
return (
2124
<div className="flex flex-row items-center">
22-
<a href={url}>
25+
<Link className={cn((disableLink || !url) && 'pointer-events-none')} href={url ?? '#'}>
2326
{icon ? (
2427
<Image
2528
alt="Guild icon"
@@ -33,7 +36,7 @@ export function GuildIcon({ data, hasBots }: GuildIconProps) {
3336
{getGuildAcronym(data.name)}
3437
</p>
3538
)}
36-
</a>
39+
</Link>
3740
</div>
3841
);
3942
}

apps/website/src/components/footer/Footer.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Link from 'next/link';
12
import { ThemeSwitchButton } from './ThemeSwitchButton';
23
import SvgDiscord from '@/components/icons/SvgDiscord';
34
import { SvgGitHub } from '@/components/icons/SvgGitHub';
@@ -8,12 +9,12 @@ export function Footer() {
89
<span className="whitespace-nowrap text-secondary dark:text-secondary-dark">© ChatSift, 2022 - Present</span>
910
<div className="flex w-full flex-row content-between items-center gap-4">
1011
<div className="flex flex-row items-center gap-4">
11-
<a className="flex" href="/github">
12+
<Link className="flex" href="/github">
1213
<SvgGitHub />
13-
</a>
14-
<a className="flex" href="/support">
14+
</Link>
15+
<Link className="flex" href="/support">
1516
<SvgDiscord />
16-
</a>
17+
</Link>
1718
</div>
1819
<div className="ml-auto flex flex-row items-center gap-2">
1920
<p className="text-lg font-medium text-secondary dark:text-secondary-dark">Theme:</p>
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import Link from 'next/link';
12
import { Button } from '@/components/common/Button';
23
import { URLS } from '@/utils/urls';
34

45
export function LoginButton() {
56
return (
67
<Button type="button">
7-
<a href={URLS.API.LOGIN}>Log in</a>
8+
<Link href={URLS.API.LOGIN}>Log in</Link>
89
</Button>
910
);
1011
}

apps/website/src/utils/bots.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { BotId } from '@chatsift/core';
2+
import { SvgAMA } from '@/components/icons/SvgAMA';
3+
4+
export const Bots = {
5+
AMA: { Icon: SvgAMA },
6+
} as const satisfies Record<BotId, { Icon: React.ComponentType }>;

packages/private/backend-core/src/lib/data/bots.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1+
import type { BotId } from '@chatsift/core';
12
import { createRecipe, DataType } from 'bin-rw';
23

3-
export const BOTS = ['AMA'] as const;
4-
5-
export type BotId = (typeof BOTS)[number];
6-
74
// TODO: Abstract
85
export const GlobalCaches = {
96
/**
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
export const BOTS = ['AMA'] as const;
2+
3+
export type BotId = (typeof BOTS)[number];
4+
15
export const NewAccessTokenHeader = 'X-Update-Access-Token' as const;

0 commit comments

Comments
 (0)