Skip to content

Commit

Permalink
1/14 전달사항 1차 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
ipcgrdn committed Jan 14, 2025
1 parent f807b79 commit d6b57cb
Show file tree
Hide file tree
Showing 18 changed files with 366 additions and 171 deletions.
15 changes: 10 additions & 5 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const nextConfig = {
output: "standalone",
images: {
formats: ["image/avif", "image/webp"],
minimumCacheTTL: 60,
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60 * 60 * 24 * 30,
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 64, 96, 128, 256],
remotePatterns: [
{
protocol: "https",
Expand All @@ -27,17 +27,22 @@ const nextConfig = {
// 정적 이미지 최적화
webpack(config) {
config.module.rules.push({
test: /\.(webp)$/i,
test: /\.(webp|avif)$/i,
use: [
{
loader: 'image-webpack-loader',
options: {
webp: {
quality: 75,
quality: 50,
lossless: false,
progressive: true,
optimizationLevel: 3,
},
avif: {
quality: 50,
lossless: false,
speed: 5,
},
},
},
],
Expand Down
1 change: 0 additions & 1 deletion src/app/albums/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ async function getAlbum(id: string) {
"Content-Type": "application/json",
},
credentials: 'include',
cache: 'no-store',
});

if (!response.ok) {
Expand Down
52 changes: 26 additions & 26 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cache } from 'react';
import "./globals.css";
import type { Metadata } from "next";
import { AuthProvider } from "@/contexts/auth/AuthContext";
Expand All @@ -6,25 +7,13 @@ import { Header } from "@/components/layout/Header";
import { BackgroundImage } from "@/components/layout/BackgroundImage";
import { getAuthCookie } from "@/lib/server-auth";
import { ThemeProvider } from "@/contexts/theme/ThemeContext";
import { UserProvider } from "@/contexts/auth/UserContext";

export const metadata: Metadata = {
title: "SOFO",
description: "SOund FOrest",
icons: {
icon: "/images/logo.svg",
},
};

export const viewport = {
width: 'device-width',
initialScale: 1,
};

async function getUser() {
// getUser를 layout 레벨에서 캐싱
const getUser = cache(async () => {
const accessToken = getAuthCookie();

if (!accessToken) {
console.log('No access token found');
return null;
}

Expand All @@ -33,42 +22,53 @@ async function getUser() {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
Cookie: `access_token=${accessToken}`,
},
credentials: "include",
cache: 'no-store',
});

if (!response.ok) return null;
const userData = await response.json();
return userData;
return response.json();
} catch (error) {
console.error('Failed to fetch user:', error);
return null;
}
}
});

export const metadata: Metadata = {
title: "SOFO",
description: "SOund FOrest",
icons: {
icon: "/images/logo.svg",
},
};

export const viewport = {
width: 'device-width',
initialScale: 1,
};

export default async function RootLayout({
children,
}: Readonly<{
}: {
children: React.ReactNode;
}>) {
}) {
// 캐시된 getUser 함수 사용
const user = await getUser();

return (
<html lang="ko" suppressHydrationWarning>
<body>
<ThemeProvider>
<AuthProvider initialUser={user}>
<div className="relative min-h-screen w-full overflow-hidden">
<AuthProvider>
<UserProvider initialUser={user}>
<BackgroundImage />
<div className="relative">
<div className="relative min-h-screen w-full overflow-hidden">
<div className="fixed inset-0 backdrop-blur-[2px] bg-white/[0.01] dark:bg-transparent" />
<Sidebar />
<Header />
<main className="relative pt-24">{children}</main>
</div>
</div>
</UserProvider>
</AuthProvider>
</ThemeProvider>
</body>
Expand Down
6 changes: 4 additions & 2 deletions src/components/albums/AlbumInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { User } from "lucide-react";
import { AlbumActions } from "./AlbumActions";
import { useAuth } from "@/contexts/auth/AuthContext";
import { useUser } from "@/contexts/auth/UserContext";
import { useState } from "react";
import { EditAlbumModal } from "./EditAlbumModal";

Expand All @@ -25,9 +26,10 @@ interface AlbumInfoProps {
}

export function AlbumInfo({ album, artist }: AlbumInfoProps) {
const { user } = useAuth();
const { isAuthenticated } = useAuth();
const { user } = useUser();
const [showEditModal, setShowEditModal] = useState(false);
const isOwner = user?.uuid === artist.uuid;
const isOwner = isAuthenticated && user?.uuid === artist.uuid;

return (
<div className="relative">
Expand Down
67 changes: 49 additions & 18 deletions src/components/home/AlbumSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,42 @@ import Link from "next/link";
import { useEffect, useState } from "react";
import { formatDate } from "@/lib/format";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { RefreshCcw } from "lucide-react";
import { useToast } from "@/hooks/use-toast";

export function AlbumSection() {
const [albums, setAlbums] = useState<Album[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { toast } = useToast();

useEffect(() => {
const fetchAlbums = async () => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/albums?page=0&pageSize=6`,
{
credentials: 'include',
}
);
if (!response.ok) throw new Error('앨범 목록을 불러오는데 실패했습니다.');
const data = await response.json();
setAlbums(data);
} catch (error) {
console.error('Failed to fetch albums:', error);
} finally {
setIsLoading(false);
}
};
const fetchAlbums = async () => {
try {
setError(null);
setIsLoading(true);
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/albums?page=0&pageSize=6`,
{
credentials: 'include',
}
);
if (!response.ok) throw new Error('앨범 목록을 불러오는데 실패했습니다.');
const data = await response.json();
setAlbums(data);
} catch (error) {
console.error('Failed to fetch albums:', error);
setError(error instanceof Error ? error.message : "앨범 목록을 불러오는데 실패했습니다.");
toast({
variant: "destructive",
description: error instanceof Error ? error.message : "앨범 목록을 불러오는데 실패했습니다.",
});
} finally {
setIsLoading(false);
}
};

useEffect(() => {
fetchAlbums();
}, []);

Expand All @@ -51,6 +63,25 @@ export function AlbumSection() {
);
}

if (error) {
return (
<section className="p-6">
<h2 className="text-xl font-bold mb-4">최신 앨범</h2>
<div className="flex flex-col items-center justify-center py-12 px-4">
<p className="text-muted-foreground text-center mb-4">{error}</p>
<Button
variant="outline"
onClick={fetchAlbums}
className="gap-2"
>
<RefreshCcw className="w-4 h-4" />
다시 시도
</Button>
</div>
</section>
);
}

return (
<section className="p-6">
<h2 className="text-xl font-bold mb-4">최신 앨범</h2>
Expand Down
56 changes: 39 additions & 17 deletions src/components/layout/BackgroundImage.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
"use client";

import Image from "next/image";
import { useTheme } from "@/contexts/theme/ThemeContext";
import { useState } from "react";
import { cn } from "@/lib/utils";

export function BackgroundImage() {
const { theme } = useTheme();
const [isLoading, setIsLoading] = useState(true);

const handleImageLoad = () => {
setIsLoading(false);
};

return (
<div className="fixed inset-0 w-full h-full">
<div className="absolute inset-0 bg-background dark:hidden">
<div
className={cn(
"absolute inset-0 transition-opacity duration-1000",
isLoading ? "opacity-0" : "opacity-100"
)}
>
<Image
src="/images/background-color.webp"
alt="Background"
src={theme === 'dark'
? "/images/background-color-dark.webp"
: "/images/background-color.webp"
}
alt=""
fill
priority
quality={40}
sizes="100vw"
quality={60}
className="object-cover"
/>
</div>
<div className="absolute inset-0 bg-background hidden dark:block">
<Image
src="/images/background-color-dark.webp"
alt="Background Dark"
fill
priority
sizes="100vw"
quality={60}
className="object-cover"
className={cn(
"object-cover",
"transition-transform duration-[2s]",
"scale-[1.02]",
isLoading ? "blur-xl" : "blur-0"
)}
onLoad={handleImageLoad}
/>
</div>

<div
className={cn(
"absolute inset-0 bg-gradient-to-b from-background to-background/80",
"transition-opacity duration-1000",
isLoading ? "opacity-100" : "opacity-0"
)}
/>
</div>
);
}
10 changes: 6 additions & 4 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import Image from "next/image";
import { useAuth } from "@/contexts/auth/AuthContext";
import { useUser } from "@/contexts/auth/UserContext";
import {
Tooltip,
TooltipContent,
Expand All @@ -18,7 +19,8 @@ import { LoginModal } from "@/components/auth/LoginModal";

export function Sidebar() {
const pathname = usePathname();
const { user, isAuthenticated } = useAuth();
const { isAuthenticated } = useAuth();
const { user } = useUser();
const [showLoginModal, setShowLoginModal] = useState(false);

const sidebarItems = [
Expand All @@ -31,17 +33,17 @@ export function Sidebar() {
fill
priority
sizes="16px"
quality={90}
quality={50}
className="object-contain"
/>
</div>
),
label: "홈",
href: "/"
},
{ icon: Search, label: "검색", href: "/" },
{ icon: Search, label: "검색", href: "" },
{ icon: Upload, label: "업로드", href: "/upload" },
{ icon: Bell, label: "알림", href: "/" },
{ icon: Bell, label: "알림", href: "" },
{
icon: ({ className }: { className?: string }) =>
isAuthenticated ? (
Expand Down
Loading

0 comments on commit d6b57cb

Please sign in to comment.