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

add guild page #1558

Draft
wants to merge 122 commits into
base: v3-main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
3a8874c
feat: add guild page routes
dominik-stumpf Nov 22, 2024
bbf915d
chore: center join guild button
dominik-stumpf Nov 22, 2024
e6df6f7
feat: add requirement ui
dominik-stumpf Nov 23, 2024
dd69324
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 26, 2024
ca7ba25
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 26, 2024
4393710
fix: remove the `[guild]` directory
BrickheadJohnny Nov 26, 2024
008047f
feat: GuildImage component
BrickheadJohnny Nov 26, 2024
a78e0ce
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 26, 2024
90a6378
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 26, 2024
5eb7e94
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 27, 2024
70da86d
cleanup: remove playground and rename the components folder
BrickheadJohnny Nov 27, 2024
87c34e2
feat: role group navigation menu
BrickheadJohnny Nov 27, 2024
8ef76fa
feat: simple create page form
BrickheadJohnny Nov 27, 2024
23b5da5
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 27, 2024
c1c78f5
refactor: extract the `GuildTabs` component and `getGuild` function
BrickheadJohnny Nov 27, 2024
29a0ed4
fix(CreateGuildButton): increase toast duration
BrickheadJohnny Nov 27, 2024
e1db931
feat: revalidate role groups after creation
BrickheadJohnny Nov 27, 2024
81ee35d
cleanup(CreateRoleGroup): remove console.log
BrickheadJohnny Nov 27, 2024
2908f05
feat: cache role groups
BrickheadJohnny Nov 27, 2024
6d396ee
Merge branch 'v3-main' into add-guild-page
dominik-stumpf Nov 27, 2024
e0444fa
Merge branch 'v3-main' into add-guild-page
dominik-stumpf Nov 27, 2024
e68b835
feat: align to new backend responses
dominik-stumpf Nov 27, 2024
5e30e14
chore: get guild using fetcher util
dominik-stumpf Nov 27, 2024
255680c
Merge branch 'v3-main' into add-guild-page
dominik-stumpf Nov 27, 2024
e058d5c
chore: remove console.log
dominik-stumpf Nov 28, 2024
d768d05
fix: dont put comma between url segments
dominik-stumpf Nov 28, 2024
89080a8
feat: create sort for roleGroup tabs
dominik-stumpf Nov 28, 2024
3849ec5
feat: display rewards
dominik-stumpf Nov 28, 2024
f37aa2e
chore: make role card horizontal on small
dominik-stumpf Nov 28, 2024
e1daf6b
feat: add join guild logic
dominik-stumpf Nov 28, 2024
71bd24b
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Nov 29, 2024
c2a9f52
fix(RoleCard): use the old design
BrickheadJohnny Nov 29, 2024
799b90a
feat: add logout action
dominik-stumpf Nov 29, 2024
70278f9
chore: merge v3-main, resolve conflicts
dominik-stumpf Nov 29, 2024
6358a52
feat: add guild join and leave without revalidation
dominik-stumpf Dec 2, 2024
811c65e
Merge branch 'v3-main' of github.com:guildxyz/guild.xyz into add-guil…
dominik-stumpf Dec 2, 2024
e6aac61
chore: migrate from role-group to page, rename route params
dominik-stumpf Dec 2, 2024
915509c
chore: update guild types
dominik-stumpf Dec 2, 2024
89415fe
chore: rename roleGroup to page
dominik-stumpf Dec 2, 2024
f7d1e80
chore: delete create role group
dominik-stumpf Dec 2, 2024
7f8800d
feat: RewardCard component (#1574)
BrickheadJohnny Dec 2, 2024
27cbb67
chore: replace search with batch requests
dominik-stumpf Dec 2, 2024
753d745
Merge branch 'add-guild-page' of github.com:guildxyz/guild.xyz into a…
dominik-stumpf Dec 2, 2024
bf249a8
feat: add hydration boundary to guild page
dominik-stumpf Dec 2, 2024
cfc5ed4
refactor: move explorer fetchers to server actions
dominik-stumpf Dec 2, 2024
5da0b6e
feat: add guild fetcher
dominik-stumpf Dec 2, 2024
d315dd6
chore: add auth fetcher
dominik-stumpf Dec 2, 2024
c65bf03
refactor: replace old fetchers
dominik-stumpf Dec 2, 2024
3ed89fc
feat: implement fetcher with response
dominik-stumpf Dec 2, 2024
db0db1a
feat: create different fetch utilities using composition
dominik-stumpf Dec 2, 2024
a4c6cc2
docs: add jsdoc to fetchers
dominik-stumpf Dec 2, 2024
730d90f
refactor: replace all fetcher
dominik-stumpf Dec 2, 2024
dc1beca
feat: add more control over logging
dominik-stumpf Dec 2, 2024
957c2ad
chore: remove useless optional chain
dominik-stumpf Dec 2, 2024
6517ff5
feat: add hydration for user and guild
dominik-stumpf Dec 3, 2024
b215b4e
chore: expose LOGGING env var to client
dominik-stumpf Dec 3, 2024
2cec819
fix: add default for LOGGING env var and make it shared
dominik-stumpf Dec 3, 2024
119452b
fix: use data on SignInDialog
dominik-stumpf Dec 3, 2024
7d8c2ef
feat: add join and leave using tanstack
dominik-stumpf Dec 3, 2024
2a051dc
fix: close nav menu after navigating
dominik-stumpf Dec 3, 2024
cb51eae
fix: address unique index error
dominik-stumpf Dec 3, 2024
a00c64c
chore: move request to client side, mutate cache on success
dominik-stumpf Dec 3, 2024
82ad909
feat: show join progress using toasts and event stream
dominik-stumpf Dec 3, 2024
ae82d83
chore: disable retry on error
dominik-stumpf Dec 3, 2024
e58ea8e
chore: disable retry on error
dominik-stumpf Dec 3, 2024
c2fa6c6
refactor: remove unused code
dominik-stumpf Dec 3, 2024
6053a91
refactor: move auth into main fetcher, renamings
dominik-stumpf Dec 3, 2024
4cbbfde
refactor: move token into associated guilds
dominik-stumpf Dec 3, 2024
a832ed5
feat: add form data option for fetcher
dominik-stumpf Dec 3, 2024
038ffc8
refactor: move and rename fetchers, options
dominik-stumpf Dec 4, 2024
34a9177
chore: remove token on 401, remove empty file
dominik-stumpf Dec 4, 2024
b8e12cd
chore: update types package
dominik-stumpf Dec 4, 2024
debff3c
fix: use QueryClient wrapper on provider, invalidate on join
dominik-stumpf Dec 4, 2024
ed7e5e0
fix: move query client retrieval inside component
dominik-stumpf Dec 4, 2024
cbd681f
cleanup: remove empty Button component
BrickheadJohnny Dec 4, 2024
5c7d310
fix: validate join flow using event stream
dominik-stumpf Dec 4, 2024
21d1af9
Merge branch 'add-guild-page' of github.com:guildxyz/guild.xyz into a…
dominik-stumpf Dec 4, 2024
d9c46d2
refactor: use tanstack query on explorer
dominik-stumpf Dec 4, 2024
6e5da40
Merge branch 'v3-main' of github.com:guildxyz/guild.xyz into add-guil…
dominik-stumpf Dec 4, 2024
46f1c28
Merge branch 'v3-main' of github.com:guildxyz/guild.xyz into add-guil…
dominik-stumpf Dec 4, 2024
4529d6c
feat: requirements (#1572)
BrickheadJohnny Dec 4, 2024
71c34ce
Merge branch 'v3-main' of github.com:guildxyz/guild.xyz into add-guil…
dominik-stumpf Dec 4, 2024
b7e43c0
Merge branch 'add-guild-page' of github.com:guildxyz/guild.xyz into a…
dominik-stumpf Dec 4, 2024
8347a7e
refactor: use rq in guild tabs
dominik-stumpf Dec 4, 2024
bb23575
feat: add tq to role and reward
dominik-stumpf Dec 4, 2024
6f6d33a
refactor: fetch guild on demand
dominik-stumpf Dec 4, 2024
755af6d
fix: await `prefetchInfiniteQuery` call
BrickheadJohnny Dec 5, 2024
99798ef
fix(JoinButton): simplify toast and fix the join flow
BrickheadJohnny Dec 5, 2024
3d4459e
feat(JoinButton): add icon to success toast
BrickheadJohnny Dec 5, 2024
2e1b646
fix(JoinButton): don't fallback to a sign in button
BrickheadJohnny Dec 5, 2024
0075ebb
fix: more discreet leave button
BrickheadJohnny Dec 5, 2024
70945c5
Merge branch 'add-guild-page' of github.com:guildxyz/guild.xyz into a…
dominik-stumpf Dec 6, 2024
01cf3c3
fix: add type package and update deprecated types
dominik-stumpf Dec 6, 2024
cff8e91
Merge branch 'v3-main' into add-guild-page
dominik-stumpf Dec 6, 2024
9975906
feat: add prefetch to all entities
dominik-stumpf Dec 6, 2024
a38d33d
Merge branch 'v3-main' of github.com:guildxyz/guild.xyz into add-guil…
dominik-stumpf Dec 9, 2024
b5215ec
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Dec 9, 2024
0d6a0df
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Dec 11, 2024
8f10e63
feat: rewards (#1579)
BrickheadJohnny Dec 12, 2024
a525286
feat: basic access indicators
BrickheadJohnny Dec 12, 2024
6932741
feat: store join modal open state in an atom
BrickheadJohnny Dec 12, 2024
c9d2564
fix: fine-tune colors in light mode
BrickheadJohnny Dec 12, 2024
77b5ddd
fix: fine-tune primary subtle button color
BrickheadJohnny Dec 12, 2024
3945561
feat(JoinGuild): better toasts
BrickheadJohnny Dec 12, 2024
e764e1f
fix: move confetti provider to the root layout
BrickheadJohnny Dec 12, 2024
5624e27
fix(JoinGuild): close join modal on success
BrickheadJohnny Dec 12, 2024
6a059d3
fix(DiscordRoleRewardDataSchema): add name prop
BrickheadJohnny Dec 12, 2024
27cbb2f
feat: IconButton component
BrickheadJohnny Jan 2, 2025
ba05480
fix: always import types from "react"
BrickheadJohnny Jan 2, 2025
f21d9d3
fix(leaderboard): use the user ID for server-side requests too
BrickheadJohnny Jan 2, 2025
05926ac
fix(AuthBoundary): wrap return values in fragments
BrickheadJohnny Jan 2, 2025
2a1dbd6
feat: useUser hook
BrickheadJohnny Jan 2, 2025
436545f
feat: useSuspensePages hook
BrickheadJohnny Jan 2, 2025
6fee663
feat: usePageUrlName hook
BrickheadJohnny Jan 2, 2025
9d8c422
feat: useSuspenseRoles hook
BrickheadJohnny Jan 2, 2025
5b909c3
fix: refactor leaderboard-related hooks
BrickheadJohnny Jan 2, 2025
6de6563
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Jan 2, 2025
322f890
cleanup: remove unnecessary entityOptions wrapper
BrickheadJohnny Jan 3, 2025
6cb636a
fix: add userId param to fetchAssociatedGuilds
BrickheadJohnny Jan 3, 2025
23f42c2
fix: fallback to undefined when prefetching the current user
BrickheadJohnny Jan 3, 2025
532afa0
Merge branch 'v3-main' into add-guild-page
BrickheadJohnny Jan 3, 2025
cbd1981
fix: guild image size and name clamp
BrickheadJohnny Jan 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@guildxyz/types": "^3.0.8",
"@hookform/resolvers": "^3.9.1",
"@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-avatar": "^1.1.1",
Expand Down Expand Up @@ -84,6 +85,7 @@
"postcss": "^8",
"storybook": "^8.4.4",
"tailwindcss": "^3.4.1",
"type-fest": "^4.29.1",
dominik-stumpf marked this conversation as resolved.
Show resolved Hide resolved
"typescript": "^5"
},
"overrides": {
Expand Down
52 changes: 16 additions & 36 deletions src/actions/auth.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
"use server";

import { GUILD_AUTH_COOKIE_NAME } from "@/config/constants";
import { env } from "@/lib/env";
import { fetchGuildApi } from "@/lib/fetchGuildApi";
import { authSchema, tokenSchema } from "@/lib/schemas/user";
import { jwtDecode } from "jwt-decode";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { z } from "zod";

const authSchema = z.object({
message: z.string(),
token: z.string(),
userId: z.string().uuid(),
});

const tokenSchema = z.object({
userId: z.string().uuid(),
exp: z.number().positive().int(),
iat: z.number().positive().int(),
});

export const signIn = async ({
message,
Expand All @@ -30,30 +18,17 @@ export const signIn = async ({

const requestInit = {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message,
signature,
}),
} satisfies RequestInit;

const signInRes = await fetch(
`${env.NEXT_PUBLIC_API}/auth/siwe/login`,
requestInit,
);

let json: unknown;
if (signInRes.status === 401) {
const registerRes = await fetch(
`${env.NEXT_PUBLIC_API}/auth/siwe/register`,
requestInit,
);
json = await registerRes.json();
} else {
json = await signInRes.json();
const signInRes = await fetchGuildApi("auth/siwe/login", requestInit);
let json = signInRes.data;
if (signInRes.response.status === 401) {
const registerRes = await fetchGuildApi("auth/siwe/register", requestInit);
json = registerRes.data;
}
const authData = authSchema.parse(json);
const { exp } = tokenSchema.parse(jwtDecode(authData.token));
Expand All @@ -70,8 +45,13 @@ export const signOut = async (redirectTo?: string) => {
redirect(redirectTo ?? "/explorer");
};

export const getAuthCookie = async () => {
const cookieStore = await cookies();
const authCookie = cookieStore.get(GUILD_AUTH_COOKIE_NAME);
return authCookie && tokenSchema.parse(jwtDecode(authCookie.value));
export const tryGetToken = async () => {
const token = (await cookies()).get(GUILD_AUTH_COOKIE_NAME)?.value;
if (!token) throw new Error("Failed to retrieve token");
return token;
};

export const tryGetParsedToken = async () => {
dominik-stumpf marked this conversation as resolved.
Show resolved Hide resolved
const token = await tryGetToken();
return tokenSchema.parse(jwtDecode(token));
};
14 changes: 0 additions & 14 deletions src/actions/me.ts

This file was deleted.

104 changes: 104 additions & 0 deletions src/app/(dashboard)/[guildUrlName]/[pageUrlName]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { ScrollArea } from "@/components/ui/ScrollArea";
import { fetchGuildApiData } from "@/lib/fetchGuildApi";
import type { DynamicRoute } from "@/lib/types";
import type { Schemas } from "@guildxyz/types";
import { Lock } from "@phosphor-icons/react/dist/ssr";

const GuildPage = async ({
params,
}: DynamicRoute<{ pageUrlName: string; guildUrlName: string }>) => {
const { pageUrlName, guildUrlName } = await params;
const guild = await fetchGuildApiData<Schemas["GuildFull"]>(
`guild/urlName/${guildUrlName}`,
);
const pages = await fetchGuildApiData<Schemas["PageFull"][]>("page/batch", {
method: "POST",
body: JSON.stringify({ ids: guild.pages?.map((p) => p.pageId!) ?? [] }),

Check warning on line 18 in src/app/(dashboard)/[guildUrlName]/[pageUrlName]/page.tsx

View workflow job for this annotation

GitHub Actions / quality-assurance

lint/style/noNonNullAssertion

Forbidden non-null assertion.
});
const page = pages.find((p) => p.urlName === pageUrlName)!;

Check warning on line 20 in src/app/(dashboard)/[guildUrlName]/[pageUrlName]/page.tsx

View workflow job for this annotation

GitHub Actions / quality-assurance

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const roles = await fetchGuildApiData<Schemas["RoleFull"][]>("role/batch", {
method: "POST",
body: JSON.stringify({
ids: page.roles?.map((r) => r.roleId!) ?? [],

Check warning on line 24 in src/app/(dashboard)/[guildUrlName]/[pageUrlName]/page.tsx

View workflow job for this annotation

GitHub Actions / quality-assurance

lint/style/noNonNullAssertion

Forbidden non-null assertion.
}),
});

return (
<div className="my-4 space-y-4">
{roles.map((role) => (
<RoleCard role={role} key={role.id} />
))}
</div>
);
};

const RoleCard = async ({ role }: { role: Schemas["RoleFull"] }) => {
const rewards = await fetchGuildApiData<Schemas["RewardFull"][]>(
"reward/batch",
{
method: "POST",
body: JSON.stringify({
ids: role.rewards?.map((r) => r.rewardId!) ?? [],

Check warning on line 43 in src/app/(dashboard)/[guildUrlName]/[pageUrlName]/page.tsx

View workflow job for this annotation

GitHub Actions / quality-assurance

lint/style/noNonNullAssertion

Forbidden non-null assertion.
}),
headers: {
"Content-Type": "application/json",
},
},
);

return (
<Card className="flex flex-col md:flex-row" key={role.id}>
<div className="border-r p-6 md:w-1/2">
<div className="flex items-center gap-3">
{role.imageUrl && (
<img
className="size-14 rounded-full border"
src={role.imageUrl} // TODO: fallback image
alt="role avatar"
/>
)}
<h3 className="font-bold text-xl tracking-tight">{role.name}</h3>
</div>
<p className="mt-4 text-foreground-dimmed leading-relaxed">
{role.description}
</p>
{!!rewards.length && (
<ScrollArea className="mt-8 h-64 rounded-lg border pr-3">
<div className="flex flex-col gap-4">
{rewards.map((reward) => (
<Reward reward={reward} key={reward.id} />
))}
</div>
</ScrollArea>
)}
</div>
<div className="bg-card-secondary p-6 md:w-1/2">
<div className="flex items-center justify-between">
<span className="font-bold text-foreground-secondary text-xs">
REQUIREMENTS
</span>
<Button size="sm">
<Lock />
Join Guild to collect rewards
</Button>
</div>
</div>
</Card>
);
};

const Reward = ({ reward }: { reward: Schemas["RewardFull"] }) => {
return (
<div className="border-b p-4 last:border-b-0">
<div className="mb-2 font-medium">{reward.name}</div>
<div className="text-foreground-dimmed text-sm">{reward.description}</div>
<pre className="mt-3 text-foreground-secondary text-xs">
<code>{JSON.stringify(reward.permissions, null, 2)}</code>
</pre>
</div>
);
};

export default GuildPage;
72 changes: 72 additions & 0 deletions src/app/(dashboard)/[guildUrlName]/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use server";

import { tryGetParsedToken } from "@/actions/auth";
import { fetchGuildApiAuthData, fetchGuildApiData } from "@/lib/fetchGuildApi";
import type { ErrorLike, WithIdLike } from "@/lib/types";
import type { Schemas } from "@guildxyz/types";
import { revalidateTag } from "next/cache";
import z from "zod";

const resolveIdLikeRequest = (idLike: string) => {
const isId = z.string().uuid().safeParse(idLike).success;
return `${isId ? "id" : "urlName"}/${idLike}`;
};

export const joinGuild = async ({ guildId }: { guildId: string }) => {

Check notice on line 15 in src/app/(dashboard)/[guildUrlName]/actions.ts

View workflow job for this annotation

GitHub Actions / quality-assurance

lint/correctness/noUnusedVariables

This variable is unused.
// the response type might not be suitable for this fetcher
revalidateTag("user");
//return fetchGuildApiAuthData(`guild/${guildId}/join`, {
// method: "POST",
//});
};

export const leaveGuild = async ({ guildId }: { guildId: string }) => {
revalidateTag("user");
dominik-stumpf marked this conversation as resolved.
Show resolved Hide resolved
return fetchGuildApiAuthData(`guild/${guildId}/leave`, {
method: "POST",
});
};

export const getGuild = async ({ idLike }: WithIdLike) => {
return fetchGuildApiData<Schemas["GuildFull"]>(
`guild/${resolveIdLikeRequest(idLike)}`,
);
};

export const getEntity = async <Data = object, Error = ErrorLike>({
idLike,
entity,
responseInit,
auth = false,
}: {
entity: string;
idLike: string;
auth?: boolean;
responseInit?: Parameters<typeof fetch>[1];
}) => {
const pathname = `${entity}/${resolveIdLikeRequest(idLike)}`;
return auth
? fetchGuildApiAuthData<Data, Error>(pathname, responseInit)
: fetchGuildApiData<Data, Error>(pathname, responseInit);
};

export const getUser = async () => {
const { userId } = await tryGetParsedToken();
return getEntity<Schemas["UserFull"]>({
entity: "user",
idLike: userId,
auth: true,
responseInit: { next: { tags: ["user"] } },
});
};

export const getPages = async ({ guildId }: { guildId: string }) => {
const guild = await getGuild({ idLike: guildId });
return fetchGuildApiData<Schemas["PageFull"][]>("page/batch", {
method: "POST",
body: JSON.stringify({ ids: guild.pages?.map((p) => p.pageId!) ?? [] }),

Check warning on line 67 in src/app/(dashboard)/[guildUrlName]/actions.ts

View workflow job for this annotation

GitHub Actions / quality-assurance

lint/style/noNonNullAssertion

Forbidden non-null assertion.
headers: {
"Content-Type": "application/json",
},
});
};
59 changes: 59 additions & 0 deletions src/app/(dashboard)/[guildUrlName]/components/GuildTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Card } from "@/components/ui/Card";
import { ScrollArea, ScrollBar } from "@/components/ui/ScrollArea";
import { Skeleton } from "@/components/ui/Skeleton";
import { cn } from "@/lib/cssUtils";
import type { Schemas } from "@guildxyz/types";
import { getPages } from "../actions";
import { PageNavLink } from "./RoleGroupNavLink";

const roleGroupOrder = ["Home", "Admin"].reverse();

export const GuildTabs = async ({ guild }: { guild: Schemas["GuildFull"] }) => {
const pages = await getPages({ guildId: guild.id });

return (
<ScrollArea
className="-ml-8 w-[calc(100%+theme(space.8))]"
style={{
maskImage:
"linear-gradient(to right, transparent 0%, black 32px, black calc(100% - 32px), transparent 100%)",
}}
>
<div className="my-4 flex gap-3 px-8">
{pages
.sort((a, b) => {
const [aIndex, bIndex] = [a, b].map((val) =>
roleGroupOrder.findIndex((pred) => pred === val.name),
);
return bIndex - aIndex;
})
.map((rg) => (
<PageNavLink
key={rg.id}
href={[guild.urlName, rg.urlName]
.filter(Boolean)
.map((s) => `/${s}`)
.join("")}
>
{rg.name}
</PageNavLink>
))}
</div>
<ScrollBar orientation="horizontal" className="hidden" />
</ScrollArea>
);
};

const SKELETON_SIZES = ["w-20", "w-36", "w-28"];
export const GuildTabsSkeleton = () => (
<div className="my-4 flex gap-3">
{[...Array(3)].map((_, i) => (
<Card
key={`${SKELETON_SIZES[i]}${i}`}
className={cn("flex h-11 items-center px-4", SKELETON_SIZES[i])}
>
<Skeleton className="h-4 w-full" />
</Card>
))}
</div>
);
Loading
Loading