Skip to content

Commit

Permalink
add state to server selector and redis
Browse files Browse the repository at this point in the history
  • Loading branch information
Badbird5907 committed Jul 4, 2024
1 parent f3f54c6 commit 624157f
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 38 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"@trpc/client": "next",
"@trpc/react-query": "next",
"@trpc/server": "next",
"@upstash/redis": "^1.32.0",
"@vercel/functions": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/app/(authenticated)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Page = async () => {
const session = await getServerAuthSession();
return (
<ContentLayout title="Dashboard">
<h1 className="text-4xl font-bold">Hello, {session?.user.name}</h1>
<h1 className="text-4xl font-bold pb-4">Hello, {session?.user.name}</h1>
<AnimationSection>
<ServerSelector />
</AnimationSection>
Expand Down
11 changes: 6 additions & 5 deletions src/components/dashboard/nav-server-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import { CommandList } from "cmdk";
import { api } from "@/trpc/react";
import { type Guild } from "@/types/guild";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useServerSelector } from "@/hooks/server-selector";

const NavServerSelector = () => {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
const servers = api.guilds.getGuilds.useQuery();
const serverSelector = useServerSelector();
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
Expand All @@ -34,8 +35,8 @@ const NavServerSelector = () => {
aria-expanded={open}
className="w-[200px] justify-between"
>
{value && servers.data
? truncate(servers.data?.[value]?.name ?? value, 20)
{serverSelector.value && servers.data
? truncate(servers.data?.[serverSelector.value]?.name ?? serverSelector.value, 20)
: "Select server..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
Expand All @@ -56,15 +57,15 @@ const NavServerSelector = () => {
key={server.id}
value={server.id}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
serverSelector.setSelectedServer(currentValue === serverSelector.value ? "" : currentValue)
setOpen(false)
}}
className="hover:cursor-pointer truncate"
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === server.id ? "opacity-100" : "opacity-0"
serverSelector.value === server.id ? "opacity-100" : "opacity-0"
)}
/>
{truncate(server.name, 20)}
Expand Down
14 changes: 10 additions & 4 deletions src/components/dashboard/server-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
"use client";

import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { useServerSelector } from "@/hooks/server-selector";
import { api } from "@/trpc/react";
import type { Guild } from "@/types/guild";
import Image from "next/image";

const Server = ({ guild }: { guild: Guild }) => {
const Server = ({ guild, onClick }: { guild: Guild; onClick: (id: string) => void }) => {
const img = `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`;
return (
<Card className="w-full hover:scale-105 transition-all hover:drop-shadow-2xl hover:border-blue-500 hover:cursor-pointer">
<Card
className="w-full hover:scale-105 transition-all hover:drop-shadow-2xl hover:border-blue-500 hover:cursor-pointer"
onClick={() => onClick(guild.id)}
>
<CardHeader className="flex flex-row justify-center w-full text-center p-0 py-4">
<span className="truncate w-full">
{guild.name}
Expand All @@ -31,11 +35,13 @@ const Server = ({ guild }: { guild: Guild }) => {

const ServerSelector = () => {
const servers = api.guilds.getGuilds.useQuery();
console.log(servers.data)
const serverSelector = useServerSelector();
return (
<div className="w-full grid grid-cols-2 md:grid-cols-4 grid-flow-row gap-4 col-span-4">
{servers.data && Object.values(servers.data).map(guild => (
<Server key={guild.id} guild={guild} />
<Server key={guild.id} guild={guild} onClick={(id) => {
serverSelector.setSelectedServer(id);
}} />
))}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/navbar/mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { NavConfig, siteConfig } from "@/lib/site-config";
import { type NavConfig, siteConfig } from "@/lib/site-config";
import { cn } from "@/lib/utils";
import Link, { type LinkProps } from "next/link";
import { useRouter } from "next/navigation";
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Command = React.forwardRef<
))
Command.displayName = CommandPrimitive.displayName

interface CommandDialogProps extends DialogProps {}
type CommandDialogProps = DialogProps

const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
Expand Down
8 changes: 8 additions & 0 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const env = createEnv({
),
DISCORD_CLIENT_ID: z.string(),
DISCORD_CLIENT_SECRET: z.string(),
BACKEND_TOKEN: z.string(),
REDIS_URI: z.string(),
REDIS_TOKEN: z.string(),
DISABLE_CACHING: z.boolean().default(false),
},

/**
Expand All @@ -44,6 +48,10 @@ export const env = createEnv({
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET,
BACKEND_TOKEN: process.env.BACKEND_TOKEN,
REDIS_URI: process.env.REDIS_URI,
REDIS_TOKEN: process.env.REDIS_TOKEN,
DISABLE_CACHING: process.env.DISABLE_CACHING,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},
/**
Expand Down
14 changes: 8 additions & 6 deletions src/hooks/server-selector.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";

const useServerSelector = create(
type ServerSelectorState = {
value: string | null;
setSelectedServer: (server: string | null) => void;
}
export const useServerSelector = create<ServerSelectorState>()(
persist(
(set, get) => ({
selectedServer: null,
setSelectedServer: (server: string) => {
set({ selectedServer: server });
},
(set) => ({
value: null,
setSelectedServer: (server) => set({ value: server }),
}),
{
name: "serverSelector",
Expand Down
50 changes: 50 additions & 0 deletions src/lib/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use server";

import { Redis } from "@upstash/redis";

import { env } from "@/env";

const redis = new Redis({
url: env.REDIS_URI,
token: env.REDIS_TOKEN,
});

export interface getOptions {
force?: boolean;
}
export async function get<T>(
key: string,
options?: getOptions,
): Promise<T | null> {
if (env.DISABLE_CACHING === true && !options?.force) return null;
const value = await redis.get(key);
if (!value) return null;
return value as T;
}

export interface setOptions {
ttl?: number; // in seconds
force?: boolean;
}

export async function set<T>(
key: string,
value: T,
options?: setOptions,
): Promise<void> {
if (env.DISABLE_CACHING === true && !options?.force) return;
await redis.set(
key,
JSON.stringify(value),
options?.ttl ? { ex: options?.ttl } : { ex: 300 },
);
}

export interface delOptions {
force?: boolean;
}

export async function del(key: string, options?: delOptions): Promise<void> {
if (env.DISABLE_CACHING === true && !options?.force) return;
await redis.del(key);
}
13 changes: 10 additions & 3 deletions src/server/api/routers/guilds.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { get, set } from "@/lib/redis";
import {
createTRPCRouter,
protectedProcedure,
} from "@/server/api/trpc";
import type { Guild } from "@/types/guild";
import { TRPCError } from "@trpc/server";
import { waitUntil } from "@vercel/functions";

export const guildsRouter = createTRPCRouter({
getGuilds: protectedProcedure.query(async ({ ctx, input }) => {
// TODO: cache this!
const { accessToken, refreshToken } = ctx.session.user.discord;
console.log(ctx.session);
const { accessToken, id } = ctx.session.user.discord;
type GuildRecord = Record<string, Guild>;
const cached = await get<GuildRecord>(`guilds:${id}`);
if (cached) {
return cached;
}
const resp = await fetch("https://discord.com/api/users/@me/guilds", {
headers: {
Authorization: `Bearer ${accessToken}`,
Expand Down Expand Up @@ -37,10 +43,11 @@ export const guildsRouter = createTRPCRouter({
if (b.owner) return 1;
return a.name.localeCompare(b.name);
});
const map: Record<string, Guild> = {};
const map: GuildRecord = {};
hasPermission.forEach(guild => {
map[guild.id] = guild;
});
waitUntil(set(`guilds:${id}`, map, { ttl: 128 }))
return map;
}),
});
2 changes: 2 additions & 0 deletions src/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module "next-auth" {
discord: {
accessToken: string;
refreshToken: string;
id: string;
}
// ...other properties
// role: UserRole;
Expand Down Expand Up @@ -58,6 +59,7 @@ export const authOptions: NextAuthOptions = {
token.discord = {
accessToken: account.access_token,
refreshToken: account.refresh_token,
id: account.userId
};
}
return token;
Expand Down
39 changes: 22 additions & 17 deletions src/trpc/react.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
import { createTRPCClient, loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import { useState } from "react";
Expand All @@ -11,6 +11,23 @@ import { type AppRouter } from "@/server/api/root";

const createQueryClient = () => new QueryClient();

const links = [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: SuperJSON,
url: getBaseUrl() + "/api/trpc",
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
}),
];

let clientQueryClientSingleton: QueryClient | undefined = undefined;
const getQueryClient = () => {
if (typeof window === "undefined") {
Expand All @@ -22,6 +39,9 @@ const getQueryClient = () => {
};

export const api = createTRPCReact<AppRouter>();
export const apiQueryClient = createTRPCClient<AppRouter>({
links,
})

/**
* Inference helper for inputs.
Expand All @@ -42,22 +62,7 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) {

const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: SuperJSON,
url: getBaseUrl() + "/api/trpc",
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
}),
],
links,
})
);

Expand Down

0 comments on commit 624157f

Please sign in to comment.