Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 19 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ jobs:
- name: Build
env:
TURSO_CONNECTION_URL: ${{ secrets.TURSO_CONNECTION_URL }}
UPSTASH_URL: ${{ secrets.UPSTASH_URL }}
UPSTASH_TOKEN: ${{ secrets.UPSTASH_TOKEN }}
run: bun run build

quality:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest

- name: Run Biome
run: biome ci .
8 changes: 4 additions & 4 deletions app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Link from "next/link";
import { Metadata } from "next";
import { GitPullRequestArrowIcon } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Navbar } from "@/components/navbar";
import type { Metadata } from "next";
import Link from "next/link";
import { Footer } from "@/components/footer";
import { Navbar } from "@/components/navbar";
import { Alert, AlertDescription } from "@/components/ui/alert";

export const metadata: Metadata = {
title: "Umedu — About",
Expand Down
11 changes: 6 additions & 5 deletions app/api/posts/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as z from "zod";
import type { NextRequest } from "next/server";
import { desc, lt, and, or, eq } from "drizzle-orm";
import { and, desc, eq, lt, or } from "drizzle-orm";
import { revalidateTag, unstable_cache } from "next/cache";
import type { NextRequest } from "next/server";
import * as z from "zod";

import { db } from "@/db";
import { getSession } from "@/lib/auth";
import { postTable, tagsToPostsTable } from "@/db/schema";
import { aesEncrypt } from "@/lib/aes";
import { getSession } from "@/lib/auth";
import { safeDecrypt } from "@/lib/utils";
import { tagsToPostsTable, postTable } from "@/db/schema";

export async function GET(request: NextRequest) {
try {
Expand All @@ -22,6 +22,7 @@ export async function GET(request: NextRequest) {

const posts = await unstable_cache(
async () => {
// biome-ignore lint/suspicious/noImplicitAnyLet: temp
let cursorCondition;

if (cursor) {
Expand Down
8 changes: 4 additions & 4 deletions app/api/tags/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { db } from "@/db";
import { tagTable } from "@/db/schema";
import { getSession } from "@/lib/auth";
import { eq } from "drizzle-orm";
import { unstable_cache } from "next/cache";
import { NextRequest } from "next/server";
import type { NextRequest } from "next/server";
import z from "zod/v4";
import { db } from "@/db";
import { tagTable } from "@/db/schema";
import { getSession } from "@/lib/auth";

export async function GET() {
try {
Expand Down
16 changes: 7 additions & 9 deletions app/auth/google/callback/route.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { z } from "zod/v4";
import type { OAuth2Tokens } from "arctic";
import { decodeIdToken } from "arctic";
import { eq } from "drizzle-orm";
import { cookies } from "next/headers";
import { decodeIdToken } from "arctic";
import type { OAuth2Tokens } from "arctic";

import { z } from "zod/v4";
import { db } from "@/db";
import { forumTable } from "@/db/schema";
import { google } from "@/lib/oauth";
import {
generateSessionToken,
createSession,
generateSessionToken,
setSessionTokenCookie,
} from "@/lib/session";

import { db } from "@/db";
import { google } from "@/lib/oauth";
import { forumTable } from "@/db/schema";

const claimsSchema = z.object({
sub: z.string(),
email: z.email(),
Expand Down
4 changes: 2 additions & 2 deletions app/auth/google/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generateState, generateCodeVerifier } from "arctic";
import { google } from "@/lib/oauth";
import { generateCodeVerifier, generateState } from "arctic";
import { cookies } from "next/headers";
import { google } from "@/lib/oauth";

export async function GET(): Promise<Response> {
const state = generateState();
Expand Down
8 changes: 6 additions & 2 deletions app/forum/components/logout-button.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
"use client";

import { useFormStatus } from "react-dom";
import { Loader2Icon, LogOutIcon } from "lucide-react";
import { useFormStatus } from "react-dom";

export function LogoutButton() {
const { pending } = useFormStatus();

return (
<button disabled={pending} className="grid place-items-center">
<button
type="button"
disabled={pending}
className="grid place-items-center"
>
{pending ? (
<Loader2Icon className="size-5 animate-spin text-muted-foreground" />
) : (
Expand Down
2 changes: 1 addition & 1 deletion app/forum/components/post-card-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";

export function PostCardSkeleton() {
return (
Expand Down
12 changes: 5 additions & 7 deletions app/forum/components/post-card.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import remarkGfm from "remark-gfm";
import Markdown from "react-markdown";
import { CalendarIcon } from "lucide-react";

import { Post, Tag } from "@/db/schema";
import { formatDate, truncateContent } from "@/lib/utils";
import { Card, CardContent, CardTitle, CardFooter } from "@/components/ui/card";
import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardFooter, CardTitle } from "@/components/ui/card";
import type { Post, Tag } from "@/db/schema";
import { formatDate, truncateContent } from "@/lib/utils";

type Props = {
post: Post & { tags: Tag[] };
Expand All @@ -31,7 +30,6 @@ export function PostCard({ post }: Props) {
</Badge>
))}
</div>

</CardContent>

<div className="space-y-6">
Expand Down
10 changes: 5 additions & 5 deletions app/forum/error.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
"use client";

import { useQueryErrorResetBoundary } from "@tanstack/react-query";
import { AlertTriangleIcon, RefreshCwIcon } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { AlertTriangleIcon, RefreshCwIcon } from "lucide-react";
import { useQueryErrorResetBoundary } from "@tanstack/react-query";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";

export default function Error({
export default function ErrorPage({
error,
reset,
}: {
Expand Down
8 changes: 4 additions & 4 deletions app/forum/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Link from "next/link";
import { Metadata } from "next";
import { redirect } from "next/navigation";
import {
HomeIcon,
InfoIcon,
MessageCirclePlusIcon,
SquareCodeIcon,
} from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";

import { logout } from "@/actions/auth";
import { getSession } from "@/lib/auth";
import { Button } from "@/components/ui/button";
import { getSession } from "@/lib/auth";
import { ForumNavbar } from "./components/forum-navbar";
import { LogoutButton } from "./components/logout-button";

Expand Down
6 changes: 3 additions & 3 deletions app/forum/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { PostCardSkeleton } from "./components/post-card-skeleton";
export default function ForumLoading() {
return (
<div className="w-full mx-auto space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<PostCardSkeleton key={i} />
))}
<PostCardSkeleton />
<PostCardSkeleton />
<PostCardSkeleton />
</div>
);
}
23 changes: 10 additions & 13 deletions app/forum/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
/* eslint-disable react-hooks/exhaustive-deps */
"use client";

import { useEffect } from "react";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
import { useThrottledCallback } from "@tanstack/react-pacer/throttler";
import { useInfiniteQuery } from "@tanstack/react-query";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
import { AlertCircleIcon, MessageCircleDashedIcon } from "lucide-react";
import { useThrottledCallback } from "@tanstack/react-pacer/throttler";

import { useEffect } from "react";
import { HoverPrefetchLink } from "@/components/hover-prefetch-link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import type { Post, Tag } from "@/db/schema";
import { PostCard } from "./components/post-card";
import { PostCardSkeleton } from "./components/post-card-skeleton";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Post, Tag } from "@/db/schema";
import { HoverPrefetchLink } from "@/components/hover-prefetch-link";

type PostsResponse = {
posts: (Post & { tags: Tag[] })[];
Expand Down Expand Up @@ -76,11 +74,10 @@ export default function FeedPage() {
}
}, [
hasNextPage,
fetchNextPage,
allPosts.length,
isFetchingNextPage,
handleNextPage,
virtualizer.getVirtualItems(),
virtualizer,
]);

const items = virtualizer.getVirtualItems();
Expand All @@ -101,9 +98,9 @@ export default function FeedPage() {
if (isLoading) {
return (
<div className="w-full mx-auto space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<PostCardSkeleton key={i} />
))}
<PostCardSkeleton />
<PostCardSkeleton />
<PostCardSkeleton />
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion app/forum/submit/components/display-tags.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button } from "@/components/ui/button";
import { useQuery } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { getTagsQuery } from "@/lib/queries";

type Props = {
Expand Down
6 changes: 3 additions & 3 deletions app/forum/submit/components/tags-selection.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"use client";

import { toast } from "sonner";
import { useState } from "react";
import { TagIcon } from "lucide-react";
import { useMediaQuery } from "@/hooks/use-media-query";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -21,6 +20,7 @@ import {
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { useMediaQuery } from "@/hooks/use-media-query";
import { DisplayTags } from "./display-tags";

type Props = {
Expand Down
14 changes: 6 additions & 8 deletions app/forum/submit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
/* eslint-disable react/no-children-prop */
"use client";

import { z } from "zod/v4";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { SendHorizonalIcon, XIcon } from "lucide-react";
import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";

import { useAppForm } from "@/hooks/form";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { z } from "zod/v4";
import { Badge } from "@/components/ui/badge";
import { Label } from "@/components/ui/label";
import { TagsSelection } from "./components/tags-selection";
import { useAppForm } from "@/hooks/form";
import { getTagsQuery } from "@/lib/queries";
import { TagsSelection } from "./components/tags-selection";

const messageSchema = z.object({
title: z
Expand Down
4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Providers from "./providers";
import type { Metadata } from "next";
import { ThemeProvider } from "next-themes";
import { Geist, Geist_Mono } from "next/font/google";
import { ThemeProvider } from "next-themes";
import Providers from "./providers";
import "./globals.css";

import { Toaster } from "@/components/ui/sonner";
Expand Down
4 changes: 2 additions & 2 deletions app/login/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Footer } from "@/components/footer";
import { Navbar } from "@/components/navbar";
import { Skeleton } from "@/components/ui/skeleton";
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Footer } from "@/components/footer";
import { Skeleton } from "@/components/ui/skeleton";

export default function LoginLoading() {
return (
Expand Down
19 changes: 9 additions & 10 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import Link from "next/link";
import { Metadata } from "next";
import { redirect } from "next/navigation";
import {
AlertTriangleIcon,
InfoIcon,
LogInIcon,
SchoolIcon,
ShieldIcon,
InfoIcon,
AlertTriangleIcon,
} from "lucide-react";

import { getSession } from "@/lib/auth";
import type { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
import { Footer } from "@/components/footer";
import { Navbar } from "@/components/navbar";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
Card,
Expand All @@ -19,10 +20,8 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Navbar } from "@/components/navbar";
import { Separator } from "@/components/ui/separator";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Footer } from "@/components/footer";
import { getSession } from "@/lib/auth";

export const metadata: Metadata = {
title: "Umedu — Login",
Expand Down
Loading