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
24 changes: 14 additions & 10 deletions apps/web/next-sitemap.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ const config = {
changefreq: 'weekly',
priority: 0.7,
exclude: [
'/login',
'/signup',
'/update-password',
'/search',
'/library',
'/library/*',
'/tosing',
'/error',
'/login*',
'/signup*',
'/update-password*',
'/search*',
'/info*',
'/tosing*',
'/error*',
'/popular*',
'/recent*',
'/withdrawal*',
'/api/*',
'/admin/*',
],
robotsTxtOptions: {
policies: [
{
userAgent: '*',
disallow: '/', // 기본은 모든 경로 막기
allow: ['/', '/popular'], // 이 두 경로만 허용
disallow: '/', // 다른 경로 막기
allow: ['/$'], // 루트 경로만 허용
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "1.7.0",
"version": "1.8.0",
"type": "module",
"private": true,
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions apps/web/public/changelog.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,9 @@
"화면 UX를 개선했습니다. 세부적인 디자인을 조정했습니다.",
"로그인 상태에서 전체 검색 시 오류가 나는 현상을 수정했습니다."
]
},
"1.8.0": {
"title": "버전 1.8.0",
"message": ["노래방에 최근 추가된 곡을 확인할 수 있는 페이지를 추가했습니다."]
}
}
3 changes: 1 addition & 2 deletions apps/web/public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# *
User-agent: *
Disallow: /
Allow: /$
Allow: /popular$
Disallow: /

# Host
Host: https://www.singcode.kr
Expand Down
3 changes: 1 addition & 2 deletions apps/web/public/sitemap-0.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://www.singcode.kr</loc><lastmod>2025-05-26T01:25:31.687Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.singcode.kr/popular</loc><lastmod>2025-05-26T01:25:31.688Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.singcode.kr</loc><lastmod>2025-09-14T17:29:49.412Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url>
</urlset>
9 changes: 6 additions & 3 deletions apps/web/src/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import { Button } from '@/components/ui/button';
import { cn } from '@/utils/cn';

const navigation = [
{ name: '검색', href: '/' },
{ name: '최신 곡', href: '/recent' },

{ name: '부를 곡', href: '/tosing' },
{ name: '검색', href: '/' },

{ name: '인기곡', href: '/popular' },
{ name: '라이브러리', href: '/library' },
{ name: '정보', href: '/info' },
];

export default function Footer() {
Expand All @@ -25,7 +28,7 @@ export default function Footer() {
<Button
asChild
key={item.name}
className={cn('w-[90px] flex-auto', isActive && 'bg-accent text-accent-foreground')}
className={cn('flex-1 px-0 text-sm', isActive && 'bg-accent text-accent-foreground')}
variant="ghost"
>
<Link href={item.href}>{item.name}</Link>
Expand Down
53 changes: 53 additions & 0 deletions apps/web/src/app/api/songs/recent-add/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NextResponse } from 'next/server';

import createClient from '@/lib/supabase/server';
import { ApiResponse } from '@/types/apiRoute';
import { Song } from '@/types/song';

interface ResponseRecentAddSong {
songs: Song;
}
export async function GET(
request: Request,
): Promise<NextResponse<ApiResponse<ResponseRecentAddSong[]>>> {
try {
const { searchParams } = new URL(request.url);

const year = Number(searchParams.get('year')) || 0;
const month = Number(searchParams.get('month')) || 0;

const startDate = new Date(year, month, 1);
const endDate = new Date(year, month + 1, 1);

const supabase = await createClient();

// songs 테이블의 startDate, endDate 사이의 데이터를 가져옴
const { data, error: recentError } = await supabase
.from('songs') // songs 테이블에서 검색
.select(`*`)
.gte('created_at', startDate.toISOString())
.lte('created_at', endDate.toISOString())
.order('created_at', { ascending: false })
.limit(100); // 단순히 songs의 created_at으로 정렬

if (recentError) throw recentError;

return NextResponse.json({ success: true, data });
} catch (error) {
if (error instanceof Error && error.cause === 'auth') {
return NextResponse.json(
{
success: false,
error: 'User not authenticated',
},
{ status: 401 },
);
}

console.error('Error in recent API:', error);
return NextResponse.json(
{ success: false, error: 'Failed to get recent songs' },
{ status: 500 },
);
}
}
1 change: 0 additions & 1 deletion apps/web/src/app/api/songs/save/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export async function POST(request: Request): Promise<NextResponse<ApiResponse<v
const userId = await getAuthenticatedUser(supabase);

const { songId, folderName } = await request.json();
const today = new Date();

const { data: folderData, error: folderError } = await supabase
.from('save_folders')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { BarChart2, Folder, Heart } from 'lucide-react';
import { useRouter } from 'next/navigation';

import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import useAuthStore from '@/stores/useAuthStore';

const menuItems = [
{
Expand All @@ -30,22 +29,18 @@ const menuItems = [

export default function LibraryPage() {
const router = useRouter();
const { user } = useAuthStore();
const nickname = user?.nickname ?? '근데 누구셨더라...?';

return (
<div className="bg-background h-full space-y-4">
<div className="mb-6 flex items-center justify-between px-2 py-4 shadow-sm">
<h1 className="text-2xl font-bold">내 라이브러리</h1>
<h1 className="text-2xl font-bold">내 정보</h1>
</div>

{/* <h1 className="text-xl font-bold">반가워요, {nickname}</h1> */}

{menuItems.map(item => (
<Card
key={item.id}
className="hover:bg-accent/50 cursor-pointer transition-all hover:shadow-md"
onClick={() => router.push(`/library/${item.id}`)}
onClick={() => router.push(`/info/${item.id}`)}
>
<CardHeader className="flex flex-row items-center space-y-0 pb-2">
<div className="bg-primary/10 text-primary mr-4 rounded-lg p-2">{item.icon}</div>
Expand Down
11 changes: 5 additions & 6 deletions apps/web/src/app/popular/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import PopularRankingList from './PopularRankingList';

export default function PopularPage() {
const [typeTab, setTypeTab] = useState<CountType>('sing_count');
const [periodTab, setPeriodTab] = useState<PeriodType>('month');
const [periodTab, setPeriodTab] = useState<PeriodType>('all');

const { isLoading, isPending, data } = useTotalStatQuery(typeTab, periodTab);

Expand All @@ -36,15 +36,14 @@ export default function PopularPage() {
<TabsContent value="sing_count" className="space-y-6">
<Tabs value={periodTab} onValueChange={value => setPeriodTab(value as PeriodType)}>
<TabsList className="w-full">
<TabsTrigger value="month" className="flex-1">
월별
<TabsTrigger value="all" className="flex-1">
전체
</TabsTrigger>
<TabsTrigger value="year" className="flex-1">
년별
</TabsTrigger>

<TabsTrigger value="all" className="flex-1">
전체
<TabsTrigger value="month" className="flex-1">
월별
</TabsTrigger>
</TabsList>

Expand Down
34 changes: 34 additions & 0 deletions apps/web/src/app/recent/RecentSongCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Separator } from '@/components/ui/separator';
import { Song } from '@/types/song';

export default function RecentSongCard({ song }: { song: Song }) {
const { title, artist, num_tj, num_ky } = song;

return (
<div className="w-full gap-4 px-3">
{/* 노래 정보 */}
<div className="flex flex-col">
{/* 제목 및 가수 */}
<div className="flex justify-between">
<div className="flex w-[70%] flex-col">
<h3 className="truncate text-base font-medium">{title}</h3>
<p className="text-muted-foreground truncate text-sm">{artist}</p>
</div>

<div className="flex flex-col space-x-4">
<div className="flex items-center">
<span className="text-brand-tj mr-1 w-8 text-xs">TJ</span>
<span className="text-sm font-medium">{num_tj}</span>
</div>
<div className="flex items-center">
<span className="text-brand-ky mr-1 w-8 text-xs">금영</span>
<span className="text-sm font-medium">{num_ky}</span>
</div>
</div>
</div>

<Separator className="my-4" />
</div>
</div>
);
}
76 changes: 76 additions & 0 deletions apps/web/src/app/recent/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client';

import { ArrowLeft, ArrowRight, Construction } from 'lucide-react';
import { useState } from 'react';

import StaticLoading from '@/components/StaticLoading';
import { Button } from '@/components/ui/button';
import { useRecentAddSongQuery } from '@/queries/recentAddSongQuery';

import RecentSongCard from './RecentSongCard';

export default function LibraryPage() {
const [today, setToday] = useState(new Date());
const [prevAction, setPrevAction] = useState<'prev' | 'next' | null>(null);

const { data: recentAddSongs, isLoading: isLoadingRecentAddSongs } = useRecentAddSongQuery(
today.getFullYear(),
today.getMonth(),
);

const handlePrevMonth = () => {
setToday(new Date(today.getFullYear(), today.getMonth() - 1, 1));
setPrevAction('prev');
};

const handleNextMonth = () => {
setToday(new Date(today.getFullYear(), today.getMonth() + 1, 1));
setPrevAction('next');
};

return (
<div className="bg-background h-full space-y-4">
<div className="flex items-center justify-between">
<Button
disabled={prevAction === 'prev' && recentAddSongs?.length === 0}
variant="ghost"
size="icon"
onClick={handlePrevMonth}
className="m-2"
>
<ArrowLeft className="h-5 w-5" />
</Button>

<div className="flex items-center gap-2 px-2 py-4 text-2xl font-bold">
<span>{today.getFullYear()}년</span>
<span>{today.getMonth() + 1}월</span>
<h1>최신곡</h1>
</div>

<Button
disabled={prevAction === 'next' && recentAddSongs?.length === 0}
variant="ghost"
size="icon"
onClick={handleNextMonth}
className="m-2"
>
<ArrowRight className="h-5 w-5" />
</Button>
</div>

{recentAddSongs && recentAddSongs.length > 0 ? (
<div className="flex flex-col">
{recentAddSongs.map(song => (
<RecentSongCard key={song.id} song={song} />
))}
</div>
) : (
<div className="flex h-64 flex-col items-center justify-center gap-4">
<Construction className="text-muted-foreground h-16 w-16" />
<p className="text-muted-foreground text-xl">해당하는 월의 최신곡이 없어요.</p>
</div>
)}
{isLoadingRecentAddSongs && <StaticLoading />}
</div>
);
}
3 changes: 0 additions & 3 deletions apps/web/src/app/search/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,8 @@ export default function SearchPage() {

if (searchResults) {
searchSongs = searchResults.pages.flatMap(page => page.data);
// console.log('searchSongs', searchSongs);
}

// console.log('searchResults', searchResults);
// console.log('pages', searchSongs);
const { searchHistory, addToHistory, removeFromHistory } = useSearchHistory();

// 엔터 키 처리
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/app/tosing/AddListModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import StaticLoading from '@/components/StaticLoading';
// import StaticLoading from '@/components/StaticLoading';
import { Button } from '@/components/ui/button';
import {
Dialog,
Expand All @@ -12,8 +12,8 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import useAddListModal from '@/hooks/useAddSongList';
import { useLikeSongQuery } from '@/queries/likeSongQuery';
import { useRecentSongQuery } from '@/queries/recentSongQuery';
import { useSaveSongFolderQuery } from '@/queries/saveSongFolderQuery';
import { useRecentSingLogQuery } from '@/queries/recentSingLogQuery';
// import { useSaveSongFolderQuery } from '@/queries/saveSongFolderQuery';
import { useSaveSongQuery } from '@/queries/saveSongQuery';

import ModalSongItem from './ModalSongItem';
Expand All @@ -34,7 +34,7 @@ export default function AddListModal({ isOpen, onClose }: AddListModalProps) {
} = useAddListModal();

const { data: likedSongs, isLoading: isLoadingLikedSongs } = useLikeSongQuery();
const { data: recentSongs, isLoading: isLoadingRecentSongs } = useRecentSongQuery();
const { data: recentSongs, isLoading: isLoadingRecentSongs } = useRecentSingLogQuery();

const { data: saveSongFolders, isLoading: isLoadingSongFolders } = useSaveSongQuery();

Expand Down
1 change: 0 additions & 1 deletion apps/web/src/app/tosing/SongCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Check, ChevronsDown, ChevronsUp, GripVertical, Trash } from 'lucide-rea

import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { Song } from '@/types/song';

interface SongCardProps {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/update-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function UpdatePasswordPage() {
// 인증 상태 변경 감지
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(async (event, session) => {
} = supabase.auth.onAuthStateChange(async event => {
// console.log('Auth event:', event); // 디버깅용
// console.log('Session:', session); // 디버깅용

Expand Down
Loading