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
6 changes: 3 additions & 3 deletions .github/workflows/tagging_song.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Tagging Songs

on:
schedule:
- cron: "0 14 * * *" # 한국 시간 23:00 실행 (UTC+9 → UTC 14:00)
workflow_dispatch:
# schedule:
# - cron: "0 14 * * *" # 한국 시간 23:00 실행 (UTC+9 → UTC 14:00)
# workflow_dispatch:
Comment on lines 3 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Workflow has no triggers 🐞 Bug ☼ Reliability

.github/workflows/tagging_song.yml now has an empty on: block (only comments), meaning the
workflow has no configured events and cannot run. This breaks both scheduled tagging and manual
dispatch for the tagging job.
Agent Prompt
## Issue description
`.github/workflows/tagging_song.yml` currently has `on:` with no enabled events (only commented lines), so the workflow cannot be triggered.

## Issue Context
The PR description mentions pausing the tagging cron job. Pausing the schedule is fine, but the workflow file still must define at least one trigger event to remain valid/runnable (e.g., keep `workflow_dispatch` so it can be run manually).

## Fix Focus Areas
- .github/workflows/tagging_song.yml[1-7]

### Suggested change
Re-enable at least one trigger, for example:
```yml
on:
  workflow_dispatch:
  # schedule:
  #   - cron: "0 14 * * *"
```
(or re-enable `schedule` too if the cron should continue).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 3 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Workflow triggers removed 🐞 Bug ☼ Reliability

.github/workflows/tagging_song.yml now has an on: key with no actual events (only comments), so
the workflow has no valid trigger and will be rejected/disabled by GitHub Actions. This stops the
scheduled/manual song-tagging automation entirely.
Agent Prompt
## Issue description
`.github/workflows/tagging_song.yml` has an `on:` section with no actual triggers (only comments). GitHub Actions workflows must declare at least one event trigger; otherwise the workflow is invalid/disabled and will never run.

## Issue Context
Other workflows in `.github/workflows/` define triggers under `on:` (e.g., `schedule`, `workflow_dispatch`). This file should follow the same structure even if the cron needs to be paused.

## Fix Focus Areas
- .github/workflows/tagging_song.yml[1-7]

## Suggested fix
If you want to pause the cron but keep the workflow valid, restore at least `workflow_dispatch:` under `on:` and keep `schedule:` commented out. If you want it completely disabled, keep `workflow_dispatch:` but gate execution (e.g., job-level `if: false`) so the YAML stays valid.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


permissions:
contents: write # push 권한을 위해 필요
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": "2.3.0",
"version": "2.4.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 @@ -108,5 +108,9 @@
"인기곡 페이지의 UI를 개선했습니다. 이제 인기곡 페이지에서 곡을 추천할 수 있습니다.",
"로그인 시 제공되는 코인을 30개로 변경했습니다."
]
},
"2.4.0": {
"title": "버전 2.4.0",
"message": ["네온 나이트 다크 테마를 추가했습니다.", "전체적인 UI를 개선했습니다."]
}
}
2 changes: 1 addition & 1 deletion apps/web/public/sitemap-0.xml
Original file line number Diff line number Diff line change
@@ -1,4 +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>2026-03-30T15:15:37.869Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.singcode.kr</loc><lastmod>2026-04-05T15:20:24.064Z</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url>
</urlset>
2 changes: 1 addition & 1 deletion apps/web/src/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function Footer() {
const navPath = pathname.split('/')[1];

return (
<footer className="bg-background fixed bottom-0 flex h-8 w-full max-w-md justify-between">
<footer className="bg-background fixed bottom-0 flex h-8 w-full max-w-md justify-between border-t">
{navigation.map(item => {
const isActive = '/' + navPath === item.href;
const isAnimating = footerAnimateKey === item.key;
Expand Down
111 changes: 94 additions & 17 deletions apps/web/src/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
'use client';

import { CalendarCheck, MessageCircleQuestion } from 'lucide-react';
import { CalendarCheck, MessageCircleQuestion, Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { type ReactNode, useEffect, useRef, useState } from 'react';

import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { useUserQuery } from '@/queries/userQuery';

import Sidebar from './Sidebar';
import CheckInModal from './components/CheckInModal';

type ExpandedButton = 'theme' | 'checkin' | 'contact' | null;

interface ExpandableButtonProps {
buttonKey: ExpandedButton;
expanded: ExpandedButton;
label: string;
icon: ReactNode;
disabled?: boolean;
onClick: () => void;
}

function ExpandableButton({
buttonKey,
expanded,
label,
icon,
disabled,
onClick,
}: ExpandableButtonProps) {
const isExpanded = expanded === buttonKey;

return (
<Button
variant="outline"
size={isExpanded ? 'default' : 'icon'}
className="dark:hover:bg-primary dark:hover:text-primary-foreground transition-all"
disabled={disabled}
onClick={onClick}
>
{icon}
{isExpanded && label}
</Button>
Comment on lines +37 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Buttons missing glow effect 📎 Requirement gap ≡ Correctness

Updated buttons (e.g., the new ExpandableButton and other outline buttons) add hover color changes
but do not add a glow (box-shadow/filter) in active/hover states. This falls short of the
requirement that buttons (in addition to active tabs) display a subtle neon glow.
Agent Prompt
## Issue description
Buttons updated/introduced in this PR do not apply a neon glow in hover/active/selected states (only background/text changes).

## Issue Context
Compliance requires subtle glow effects on both buttons and active tabs. Glow tokens exist (`--glow-accent`, `--glow-secondary`) but are not applied to buttons.

## Fix Focus Areas
- apps/web/src/Header.tsx[37-46]
- apps/web/src/Sidebar.tsx[142-146]
- apps/web/src/app/search/JpnArtistList.tsx[41-44]
- apps/web/src/globals.css[149-155]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

);
}

export default function Header() {
const [open, setOpen] = useState(false);
const [expanded, setExpanded] = useState<ExpandedButton>(null);
const { theme, setTheme } = useTheme();
const headerRef = useRef<HTMLDivElement>(null);

const router = useRouter();

const { data: user, isLoading, error } = useUserQuery();
const { data: user, isLoading } = useUserQuery();

const lastCheckIn = user?.last_check_in ?? new Date();

Expand All @@ -30,23 +69,58 @@ export default function Header() {
setOpen(false);
};

const handleExpandableClick = (key: ExpandedButton, action: () => void) => {
if (expanded === key) {
action();
setExpanded(null);
} else {
setExpanded(key);
}
};

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (headerRef.current && !headerRef.current.contains(e.target as Node)) {
setExpanded(null);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

return (
<header className="bg-background sticky top-0 z-50 flex h-16 w-full max-w-md items-center justify-between p-4 shadow-sm">
<header className="bg-background sticky top-0 z-50 flex h-16 w-full max-w-md items-center justify-between border-b p-4">
<div
className="font-barcode hover:text-accent cursor-pointer text-5xl transition-colors"
onClick={() => router.push('/')}
>
SINGCODE
</div>
<div className="flex items-center gap-2">
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="justify-start" disabled={isLoading}>
<CalendarCheck className="h-4 w-4" />
출석체크
</Button>
</DialogTrigger>
<div ref={headerRef} className="flex items-center gap-2">
<ExpandableButton
buttonKey="theme"
expanded={expanded}
label={theme === 'dark' ? '라이트 모드' : '다크 모드'}
icon={
<span className="relative h-4 w-4">
<Sun className="absolute h-4 w-4 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-4 w-4 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
</span>
}
onClick={() =>
handleExpandableClick('theme', () => setTheme(theme === 'dark' ? 'light' : 'dark'))
}
/>
Comment on lines +100 to +113
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Header exposes light mode toggle 📎 Requirement gap ≡ Correctness

The updated Header adds a prominent theme toggle that enables switching to Light Mode, so Light
Mode is not removed/minimized as required. This undermines the requirement that the Neon/Night
experience be primarily dark by default with Light Mode not prominently available.
Agent Prompt
## Issue description
The header now provides a direct Light Mode toggle (`setTheme(... 'light' ...)`), which conflicts with the requirement that Light Mode be removed or minimized.

## Issue Context
Dark Mode is set as default in `ThemeProvider`, but the UI still exposes a prominent toggle that enables Light Mode.

## Fix Focus Areas
- apps/web/src/Header.tsx[100-113]
- apps/web/src/app/layout.tsx[134-140]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


<ExpandableButton
buttonKey="checkin"
expanded={expanded}
label="출석체크"
icon={<CalendarCheck className="h-4 w-4" />}
disabled={isLoading}
onClick={() => handleExpandableClick('checkin', () => setOpen(true))}
/>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<CheckInModal
lastCheckIn={lastCheckIn}
Expand All @@ -56,10 +130,13 @@ export default function Header() {
</DialogContent>
</Dialog>

<Button variant="outline" className="justify-start" onClick={() => handleClickContact()}>
<MessageCircleQuestion className="h-4 w-4" />
문의
</Button>
<ExpandableButton
buttonKey="contact"
expanded={expanded}
label="문의"
icon={<MessageCircleQuestion className="h-4 w-4" />}
onClick={() => handleExpandableClick('contact', handleClickContact)}
/>

<Sidebar />
</div>
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ export default function Sidebar() {

{isAuthenticated ? (
<>
<Button variant="outline" className="w-full" onClick={handleLogout}>
<Button
variant="outline"
className="dark:hover:bg-primary dark:hover:text-primary-foreground w-full"
onClick={handleLogout}
>
<LogOut className="mr-2 h-4 w-4" />
로그아웃
</Button>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/info/save/DeleteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default function DeleteModal({
</DialogTitle>
</DialogHeader>
<div className="py-4">
<p className="text-center text-gray-600">
<p className="text-muted-foreground text-center">
{songIdArray.length}개의 노래를 재생목록에서 삭제하시겠습니까?
<br />이 작업은 되돌릴 수 없습니다.
</p>
Expand Down
23 changes: 10 additions & 13 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';
import type { Metadata } from 'next';
import { ThemeProvider } from 'next-themes';
import Script from 'next/script';
import { Toaster } from 'sonner';

Expand Down Expand Up @@ -102,7 +103,7 @@ export default function RootLayout({
<ErrorWrapper>
<div className="relative flex h-full w-full max-w-md flex-col">
<Header />
<div className="h-full p-4 shadow-sm">{children}</div>
<div className="h-full p-4">{children}</div>

<Footer />
</div>
Expand All @@ -123,24 +124,20 @@ export default function RootLayout({
);

return (
<html lang="ko">
<html lang="ko" suppressHydrationWarning>
<head>
<MonitoringScripts />
<meta name="theme-color" content="#1a1a2e" />
<meta name="naver-site-verification" content="85db7c6070d2f26d08e995cdab5a70caac28e80d" />
</head>
<body className="m-0 flex h-dvh w-full justify-center">
<QueryProvider>
<AuthProvider>
<AppContent />
{/* {isDevelopment ? (
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}>
<QueryProvider>
<AuthProvider>
<AppContent />
) : (
<PostHogProvider>
<AppContent />
</PostHogProvider>
)} */}
</AuthProvider>
</QueryProvider>
</AuthProvider>
</QueryProvider>
</ThemeProvider>
</body>
</html>
);
Expand Down
20 changes: 16 additions & 4 deletions apps/web/src/app/search/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,22 @@ export default function SearchPage() {
</div>

<Tabs defaultValue="all" value={searchType} onValueChange={handleSearchTypeChange}>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="all">전체</TabsTrigger>
<TabsTrigger value="title">제목</TabsTrigger>
<TabsTrigger value="artist">가수</TabsTrigger>
<TabsList className="dark:bg-muted/50 grid w-full grid-cols-3 dark:border">
{(
[
['all', '전체'],
['title', '제목'],
['artist', '가수'],
] as const
).map(([value, label]) => (
<TabsTrigger
key={value}
value={value}
className="dark:data-[state=active]:bg-accent/15 dark:data-[state=active]:text-accent dark:data-[state=active]:shadow-(--glow-accent)"
>
{label}
</TabsTrigger>
))}
</TabsList>
</Tabs>

Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/app/search/JpnArtistList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export default function JpnArtistList({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogTrigger asChild>
<Button variant="outline">
<Button
variant="outline"
className="dark:hover:bg-primary dark:hover:text-primary-foreground"
>
<UserRoundSearch className="h-4 w-4" />
J-POP 가수 찾기
</Button>
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/search/MusicCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ type MusicCardProps = {

export function MusicCard({ title, artist, reason, onClick }: MusicCardProps) {
return (
<div className="bg-card flex flex-col rounded-lg border p-4 shadow-sm">
<div className="bg-card flex flex-col rounded-lg border p-4">
<button
type="button"
onClick={() => onClick(title)}
className="mb-1 w-full cursor-pointer text-left text-sm font-semibold transition-colors hover:text-blue-500 hover:underline"
className="hover:text-accent mb-1 w-full cursor-pointer text-left text-sm font-semibold transition-colors hover:underline"
>
<MarqueeText>{title}</MarqueeText>
</button>
<button
type="button"
onClick={() => onClick(artist)}
className="text-muted-foreground mb-2 w-full cursor-pointer text-left text-xs transition-colors hover:text-blue-500 hover:underline"
className="text-muted-foreground hover:text-accent mb-2 w-full cursor-pointer text-left text-xs transition-colors hover:underline"
>
<MarqueeText>{artist}</MarqueeText>
</button>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/CheckInModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function CheckInModal({
idleView={trigger => (
<div className="flex flex-col items-center gap-4 text-center">
<h2 className="text-lg font-bold">오늘 출석하시겠어요?</h2>
<div className="flex w-full flex-col items-center gap-1 rounded-lg bg-gray-200 p-4">
<div className="bg-muted flex w-full flex-col items-center gap-1 rounded-lg p-4">
<span className="text-muted-foreground text-xs font-bold tracking-widest uppercase">
Current Points
</span>
Expand All @@ -77,7 +77,7 @@ export default function CheckInModal({
</div>
<Button
onClick={trigger} // 👈 여기서 애니메이션 시작!
className="rounded-full bg-blue-500 px-6 py-2 text-white active:scale-95"
className="bg-accent text-accent-foreground rounded-full px-6 py-2 active:scale-95"
>
출석하기
</Button>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/MessageDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function MessageDialog() {
variant === 'success' && 'text-green-500',
variant === 'error' && 'text-red-500',
variant === 'warning' && 'text-yellow-500',
variant === 'info' && 'text-blue-500',
variant === 'info' && 'text-accent',
)}
/>
{title && <DialogTitle>{title}</DialogTitle>}
Expand All @@ -82,7 +82,7 @@ export default function MessageDialog() {
variant === 'success' && 'bg-green-500 hover:bg-green-600',
variant === 'error' && 'bg-red-500 hover:bg-red-600',
variant === 'warning' && 'bg-yellow-500 hover:bg-yellow-600',
variant === 'info' && 'bg-blue-500 hover:bg-blue-600',
variant === 'info' && 'bg-accent hover:bg-accent/90 text-accent-foreground',
)}
>
{buttonText || '확인'}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/StaticLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Loader2 } from 'lucide-react';

export default function StaticLoading() {
return (
<div className="fixed top-0 right-1/2 z-9999 flex h-full w-full max-w-md translate-x-1/2 items-center justify-center bg-white/90">
<div className="bg-background/90 fixed top-0 right-1/2 z-9999 flex h-full w-full max-w-md translate-x-1/2 items-center justify-center">
<Loader2 className="h-16 w-16 animate-spin" />
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ThumbUpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ThumbUpModal({
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-6">
<div className="flex flex-col items-center">
{/* 레이블 추가로 가독성 향상 */}
<span className="mb-1 text-xs font-bold tracking-widest text-gray-400 uppercase">
<span className="text-muted-foreground mb-1 text-xs font-bold tracking-widest uppercase">
Total Points
</span>

Expand Down
Loading