1
0
Fork 0
mirror of https://gitbruv.vercel.app/api/git/bruv/gitbruv.git synced 2025-12-20 23:24:09 +01:00
This commit is contained in:
Ahmet Kilinc 2025-12-20 12:07:05 +00:00
parent 4249f028aa
commit 468781e311
20 changed files with 84 additions and 185 deletions

View file

@ -1,14 +1,10 @@
import { GitBranch } from "lucide-react";
import Link from "next/link";
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen flex flex-col items-center justify-center relative overflow-hidden px-4">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-accent/15 via-background to-background" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,var(--tw-gradient-stops))] from-accent/15 via-background to-background" />
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-[500px] h-[500px] bg-accent/5 rounded-full blur-[100px]" />
<div className="absolute bottom-1/4 right-1/4 w-[400px] h-[400px] bg-primary/5 rounded-full blur-[100px]" />

View file

@ -89,9 +89,7 @@ export default function RegisterPage() {
required
className="bg-input/50 h-11"
/>
<p className="text-xs text-muted-foreground">
This will be your unique identifier on gitbruv
</p>
<p className="text-xs text-muted-foreground">This will be your unique identifier on gitbruv</p>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email address</Label>
@ -117,15 +115,9 @@ export default function RegisterPage() {
minLength={8}
className="bg-input/50 h-11"
/>
<p className="text-xs text-muted-foreground">
Must be at least 8 characters
</p>
<p className="text-xs text-muted-foreground">Must be at least 8 characters</p>
</div>
<Button
type="submit"
disabled={loading}
className="w-full h-11"
>
<Button type="submit" disabled={loading} className="w-full h-11">
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
@ -140,10 +132,7 @@ export default function RegisterPage() {
<div className="mt-6 p-4 rounded-xl border border-border text-center">
<p className="text-sm text-muted-foreground">
Already have an account?{" "}
<Link
href="/login"
className="text-accent hover:underline font-medium"
>
<Link href="/login" className="text-accent hover:underline font-medium">
Sign in
</Link>
</p>

View file

@ -6,10 +6,22 @@ import { BranchSelector } from "@/components/branch-selector";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Lock, Globe, GitCommit, ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
import { Lock, Globe, GitCommit, ChevronLeft, ChevronRight } from "lucide-react";
import { formatDistanceToNow } from "date-fns";
async function CommitsList({ username, repoName, branch, page, perPage }: { username: string; repoName: string; branch: string; page: number; perPage: number }) {
async function CommitsList({
username,
repoName,
branch,
page,
perPage,
}: {
username: string;
repoName: string;
branch: string;
page: number;
perPage: number;
}) {
const skip = (page - 1) * perPage;
const { commits, hasMore } = await getRepoCommits(username, repoName, branch, perPage, skip);

View file

@ -22,11 +22,7 @@ type RepoData = {
ownerId: string;
};
export default function RepoSettingsPage({
params,
}: {
params: Promise<{ username: string; repo: string }>;
}) {
export default function RepoSettingsPage({ params }: { params: Promise<{ username: string; repo: string }> }) {
const { username, repo: repoName } = use(params);
const router = useRouter();
const { data: session } = useSession();
@ -117,9 +113,7 @@ export default function RepoSettingsPage({
<CardContent className="p-12 text-center">
<AlertTriangle className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
<h2 className="text-xl font-semibold mb-2">Access Denied</h2>
<p className="text-muted-foreground mb-6">
You don&apos;t have permission to access this page
</p>
<p className="text-muted-foreground mb-6">You don&apos;t have permission to access this page</p>
<Button asChild>
<Link href={`/${username}/${repoName}`}>Back to repository</Link>
</Button>
@ -175,9 +169,7 @@ export default function RepoSettingsPage({
<div className="space-y-2">
<label
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${
formData.visibility === "public"
? "border-accent bg-accent/5"
: "border-border hover:border-muted-foreground/50"
formData.visibility === "public" ? "border-accent bg-accent/5" : "border-border hover:border-muted-foreground/50"
}`}
>
<input
@ -191,17 +183,13 @@ export default function RepoSettingsPage({
<Globe className="h-5 w-5 text-muted-foreground mt-0.5" />
<div>
<p className="font-medium">Public</p>
<p className="text-sm text-muted-foreground">
Anyone can see this repository
</p>
<p className="text-sm text-muted-foreground">Anyone can see this repository</p>
</div>
</label>
<label
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${
formData.visibility === "private"
? "border-accent bg-accent/5"
: "border-border hover:border-muted-foreground/50"
formData.visibility === "private" ? "border-accent bg-accent/5" : "border-border hover:border-muted-foreground/50"
}`}
>
<input
@ -215,9 +203,7 @@ export default function RepoSettingsPage({
<Lock className="h-5 w-5 text-muted-foreground mt-0.5" />
<div>
<p className="font-medium">Private</p>
<p className="text-sm text-muted-foreground">
Only you can see this repository
</p>
<p className="text-sm text-muted-foreground">Only you can see this repository</p>
</div>
</label>
</div>
@ -236,17 +222,13 @@ export default function RepoSettingsPage({
<Card className="border-destructive/50">
<CardHeader>
<CardTitle className="text-destructive">Danger Zone</CardTitle>
<CardDescription>
Irreversible actions that can affect your repository
</CardDescription>
<CardDescription>Irreversible actions that can affect your repository</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between p-4 rounded-lg border border-destructive/30 bg-destructive/5">
<div>
<p className="font-medium">Delete this repository</p>
<p className="text-sm text-muted-foreground">
Once deleted, it cannot be recovered
</p>
<p className="text-sm text-muted-foreground">Once deleted, it cannot be recovered</p>
</div>
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
<DialogTrigger asChild>
@ -260,33 +242,23 @@ export default function RepoSettingsPage({
<DialogTitle>Delete repository</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the{" "}
<strong>{username}/{repo.name}</strong> repository and all of its contents.
<strong>
{username}/{repo.name}
</strong>{" "}
repository and all of its contents.
</DialogDescription>
</DialogHeader>
<div className="space-y-2 py-4">
<Label htmlFor="confirm">
Type <strong>{repo.name}</strong> to confirm
</Label>
<Input
id="confirm"
value={deleteConfirm}
onChange={(e) => setDeleteConfirm(e.target.value)}
placeholder={repo.name}
/>
<Input id="confirm" value={deleteConfirm} onChange={(e) => setDeleteConfirm(e.target.value)} placeholder={repo.name} />
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setDeleteOpen(false)}
disabled={deleting}
>
<Button variant="outline" onClick={() => setDeleteOpen(false)} disabled={deleting}>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleDelete}
disabled={deleteConfirm !== repo.name || deleting}
>
<Button variant="destructive" onClick={handleDelete} disabled={deleteConfirm !== repo.name || deleting}>
{deleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Delete repository
</Button>
@ -299,4 +271,3 @@ export default function RepoSettingsPage({
</div>
);
}

View file

@ -5,7 +5,7 @@ import { getRepository, getRepoFileTree, getRepoBranches } from "@/actions/repos
import { FileTree } from "@/components/file-tree";
import { BranchSelector } from "@/components/branch-selector";
import { Badge } from "@/components/ui/badge";
import { Lock, Globe, ChevronRight, Home, Loader2 } from "lucide-react";
import { Lock, Globe, ChevronRight, Home } from "lucide-react";
async function TreeContent({ username, repoName, branch, dirPath }: { username: string; repoName: string; branch: string; dirPath: string }) {
const fileTree = await getRepoFileTree(username, repoName, branch, dirPath);

View file

@ -7,7 +7,7 @@ import { getUserRepositoriesWithStars, getUserStarredRepos } from "@/actions/rep
import { RepoList } from "@/components/repo-list";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { CalendarDays, GitBranch, MapPin, Link as LinkIcon, Loader2, Star, BookOpen } from "lucide-react";
import { CalendarDays, GitBranch, MapPin, Link as LinkIcon, Star, BookOpen } from "lucide-react";
import { format } from "date-fns";
import Link from "next/link";
import { GithubIcon, XIcon, LinkedInIcon } from "@/components/icons";
@ -61,13 +61,7 @@ function TabSkeleton() {
);
}
export default async function ProfilePage({
params,
searchParams,
}: {
params: Promise<{ username: string }>;
searchParams: Promise<{ tab?: string }>;
}) {
export default async function ProfilePage({ params, searchParams }: { params: Promise<{ username: string }>; searchParams: Promise<{ tab?: string }> }) {
const { username } = await params;
const { tab } = await searchParams;

View file

@ -1,11 +1,7 @@
import { Header } from "@/components/header";
import { QueryProvider } from "@/lib/query-client";
export default function MainLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function MainLayout({ children }: { children: React.ReactNode }) {
return (
<QueryProvider>
<div className="min-h-screen flex flex-col">
@ -15,4 +11,3 @@ export default function MainLayout({
</QueryProvider>
);
}

View file

@ -28,12 +28,9 @@ export default async function HomePage() {
<p className="text-sm text-muted-foreground truncate">@{username}</p>
</div>
</div>
<nav className="mt-4 space-y-1">
<Link
href={`/${username}`}
className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm hover:bg-card transition-colors"
>
<Link href={`/${username}`} className="flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm hover:bg-card transition-colors">
<BookOpen className="h-4 w-4 text-muted-foreground" />
Your repositories
</Link>
@ -57,9 +54,7 @@ export default async function HomePage() {
<GitBranch className="h-8 w-8 text-accent" />
</div>
<h3 className="text-lg font-semibold mb-2">No repositories yet</h3>
<p className="text-muted-foreground mb-6 max-w-sm mx-auto">
Create your first repository to start building something awesome
</p>
<p className="text-muted-foreground mb-6 max-w-sm mx-auto">Create your first repository to start building something awesome</p>
<Button asChild size="lg">
<Link href="/new">
<Plus className="h-4 w-4 mr-2" />
@ -93,19 +88,14 @@ function LandingPage() {
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold tracking-tight mb-6">
Where the world
<br />
<span className="bg-gradient-to-r from-primary via-accent to-primary bg-clip-text text-transparent">
builds software
</span>
<span className="bg-gradient-to-r from-primary via-accent to-primary bg-clip-text text-transparent">builds software</span>
</h1>
<p className="text-lg lg:text-xl text-muted-foreground max-w-2xl mx-auto mb-10">
Host and review code, manage projects, and build software alongside
millions of developers. Your code, your way.
Host and review code, manage projects, and build software alongside millions of developers. Your code, your way.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button size="lg" asChild className="text-base h-12 px-8">
<Link href="/register">
Get started for free
</Link>
<Link href="/register">Get started for free</Link>
</Button>
<Button size="lg" variant="outline" asChild className="text-base h-12 px-8">
<Link href="/login">Sign in</Link>
@ -118,9 +108,7 @@ function LandingPage() {
<div className="container">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold mb-4">Everything you need to ship</h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
Powerful features to help you build, test, and deploy your projects faster
</p>
<p className="text-muted-foreground max-w-2xl mx-auto">Powerful features to help you build, test, and deploy your projects faster</p>
</div>
<div className="grid md:grid-cols-3 gap-6">
<FeatureCard
@ -128,16 +116,8 @@ function LandingPage() {
title="Collaborative coding"
description="Build better software together with powerful code review and collaboration tools."
/>
<FeatureCard
icon={Rocket}
title="Ship faster"
description="Automate your workflow with CI/CD pipelines and deploy with confidence."
/>
<FeatureCard
icon={Users}
title="Open source"
description="Join the world's largest developer community and contribute to projects."
/>
<FeatureCard icon={Rocket} title="Ship faster" description="Automate your workflow with CI/CD pipelines and deploy with confidence." />
<FeatureCard icon={Users} title="Open source" description="Join the world's largest developer community and contribute to projects." />
</div>
</div>
</section>
@ -145,15 +125,7 @@ function LandingPage() {
);
}
function FeatureCard({
icon: Icon,
title,
description,
}: {
icon: React.ElementType;
title: string;
description: string;
}) {
function FeatureCard({ icon: Icon, title, description }: { icon: React.ElementType; title: string; description: string }) {
return (
<div className="group p-6 rounded-xl border border-border bg-card hover:border-accent/50 transition-all duration-300">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-primary/20 to-accent/20 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">

View file

@ -4,7 +4,6 @@ import { ProfileForm } from "@/components/settings/profile-form";
import { AvatarUpload } from "@/components/settings/avatar-upload";
import { SocialLinksForm } from "@/components/settings/social-links-form";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
export default async function SettingsPage() {
const user = await getCurrentUser();
@ -18,9 +17,7 @@ export default async function SettingsPage() {
<Card>
<CardHeader>
<CardTitle>Profile Picture</CardTitle>
<CardDescription>
Upload a picture to personalize your profile
</CardDescription>
<CardDescription>Upload a picture to personalize your profile</CardDescription>
</CardHeader>
<CardContent>
<AvatarUpload currentAvatar={user.avatarUrl} name={user.name} />
@ -30,9 +27,7 @@ export default async function SettingsPage() {
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
<CardDescription>
Update your profile details visible to other users
</CardDescription>
<CardDescription>Update your profile details visible to other users</CardDescription>
</CardHeader>
<CardContent>
<ProfileForm
@ -51,9 +46,7 @@ export default async function SettingsPage() {
<Card>
<CardHeader>
<CardTitle>Social Links</CardTitle>
<CardDescription>
Add links to your social profiles
</CardDescription>
<CardDescription>Add links to your social profiles</CardDescription>
</CardHeader>
<CardContent>
<SocialLinksForm socialLinks={user.socialLinks} />
@ -62,4 +55,3 @@ export default async function SettingsPage() {
</div>
);
}

View file

@ -2,4 +2,3 @@ import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);

View file

@ -1,22 +1,19 @@
import { NextRequest, NextResponse } from "next/server";
import { r2Get } from "@/lib/r2";
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ filename: string }> }
) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ filename: string }> }) {
const { filename } = await params;
const key = `avatars/${filename}`;
const data = await r2Get(key);
if (!data) {
return new NextResponse(null, { status: 404 });
}
const ext = filename.split(".").pop()?.toLowerCase();
let contentType = "image/png";
if (ext === "jpg" || ext === "jpeg") {
contentType = "image/jpeg";
} else if (ext === "gif") {
@ -32,4 +29,3 @@ export async function GET(
},
});
}

View file

@ -5,15 +5,12 @@ import { GitBranch, Home } from "lucide-react";
export default function NotFound() {
return (
<div className="min-h-screen flex flex-col items-center justify-center px-4">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-destructive/10 via-transparent to-transparent" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,var(--tw-gradient-stops))] from-destructive/10 via-transparent to-transparent" />
<div className="relative text-center">
<GitBranch className="h-16 w-16 mx-auto mb-6 text-muted-foreground" />
<h1 className="text-7xl font-bold text-foreground mb-2">404</h1>
<h2 className="text-2xl font-semibold mb-4">Page not found</h2>
<p className="text-muted-foreground mb-8 max-w-md">
The page you&apos;re looking for doesn&apos;t exist or you don&apos;t have
permission to view it.
</p>
<p className="text-muted-foreground mb-8 max-w-md">The page you&apos;re looking for doesn&apos;t exist or you don&apos;t have permission to view it.</p>
<Button asChild>
<Link href="/" className="gap-2">
<Home className="h-4 w-4" />
@ -24,4 +21,3 @@ export default function NotFound() {
</div>
);
}