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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ stash 프로젝트의 프론트엔드 애플리케이션

## Recent Changes

[![v0.0.6](https://img.shields.io/badge/v0.0.6-purple)](./docs/CHANGELOG.md#v006) `2025.11.30 15:00`
[![v0.0.7](https://img.shields.io/badge/v0.0.7-purple)](./docs/CHANGELOG.md#v007) `2025.11.30 16:30`
- 404 Not Found 페이지 추가 (터미널 스타일 UI)
- 500 Error 페이지 추가 (retry 버튼, 5초 카운트다운)
- Global Error 페이지 추가 (root layout 에러 처리)
- 에러 발생 시 자동 리다이렉트 (같은 도메인 = 뒤로가기, 외부 = 홈으로)
- 터미널 한글 입력 시 너비 계산 수정

[![v0.0.6](https://img.shields.io/badge/v0.0.6-gray)](./docs/CHANGELOG.md#v006) `2025.11.30 15:00`
- Tab/→/Enter 키로 명령어 자동완성
- 힌트 목록 세로 표시 (여러 개일 때)
- ↑/↓ 키로 힌트 목록 탐색
Expand Down
70 changes: 70 additions & 0 deletions app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
const router = useRouter()
const [countdown, setCountdown] = useState(5)

useEffect(() => {
console.error(error)
}, [error])

useEffect(() => {
const timer = setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(timer)
if (document.referrer && new URL(document.referrer).origin === window.location.origin) {
router.back()
} else {
router.push('/')
}
return 0
}
return prev - 1
})
}, 1000)

return () => clearInterval(timer)
}, [router])

return (
<main className="flex h-screen items-center justify-center bg-[#0d1117] text-[#c9d1d9]">
<div className="w-full max-w-md rounded-lg border border-[#30363d] bg-[#161b22] shadow-2xl sm:mx-4">
<div className="flex items-center gap-2 border-b border-[#30363d] bg-[#21262d] px-4 py-3">
<div className="h-3 w-3 rounded-full bg-[#ff5f56]" />
<div className="h-3 w-3 rounded-full bg-[#ffbd2e]" />
<div className="h-3 w-3 rounded-full bg-[#27c93f]" />
<span className="ml-4 font-mono text-xs text-[#8b949e]">error — 500</span>
</div>
<div className="p-6 font-mono text-sm">
<div>
<span className="text-[#7ee787]">~</span>
<span className="text-[#8b949e]"> $ </span>
<span>./run</span>
</div>
<div className="mt-2 text-[#f85149]">
Error: Something went wrong
</div>
<div className="mt-4 flex gap-4">
<button
onClick={reset}
className="text-[#58a6ff] hover:underline"
>
[retry]
</button>
<span className="text-[#8b949e]">Redirecting in {countdown}...</span>
</div>
</div>
</div>
</main>
)
}
44 changes: 44 additions & 0 deletions app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client'

export default function GlobalError({
error: _error,

Check warning on line 4 in app/global-error.tsx

View workflow job for this annotation

GitHub Actions / lint-and-build

'_error' is defined but never used
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html lang="ko">
<body className="bg-[#0d1117]">
<main className="flex h-screen items-center justify-center text-[#c9d1d9]">
<div className="w-full max-w-md rounded-lg border border-[#30363d] bg-[#161b22] shadow-2xl sm:mx-4">
<div className="flex items-center gap-2 border-b border-[#30363d] bg-[#21262d] px-4 py-3">
<div className="h-3 w-3 rounded-full bg-[#ff5f56]" />
<div className="h-3 w-3 rounded-full bg-[#ffbd2e]" />
<div className="h-3 w-3 rounded-full bg-[#27c93f]" />
<span className="ml-4 font-mono text-xs text-[#8b949e]">error — fatal</span>
</div>
<div className="p-6 font-mono text-sm">
<div>
<span className="text-[#7ee787]">~</span>
<span className="text-[#8b949e]"> $ </span>
<span>./init</span>
</div>
<div className="mt-2 text-[#f85149]">
Fatal: Application crashed
</div>
<div className="mt-4">
<button
onClick={reset}
className="text-[#58a6ff] hover:underline"
>
[restart]
</button>
</div>
</div>
</div>
</main>
</body>
</html>
)
}
57 changes: 57 additions & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'

export default function NotFound() {
const router = useRouter()
const [countdown, setCountdown] = useState(3)

useEffect(() => {
const timer = setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(timer)
// 같은 도메인에서 왔으면 뒤로가기, 아니면 홈으로
if (document.referrer && new URL(document.referrer).origin === window.location.origin) {
router.back()
} else {
router.push('/')
}
return 0
}
return prev - 1
})
}, 1000)

return () => clearInterval(timer)
}, [router])

return (
<main className="flex h-screen items-center justify-center bg-[#0d1117] text-[#c9d1d9]">
<div className="w-full max-w-md rounded-lg border border-[#30363d] bg-[#161b22] shadow-2xl sm:mx-4">
<div className="flex items-center gap-2 border-b border-[#30363d] bg-[#21262d] px-4 py-3">
<div className="h-3 w-3 rounded-full bg-[#ff5f56]" />
<div className="h-3 w-3 rounded-full bg-[#ffbd2e]" />
<div className="h-3 w-3 rounded-full bg-[#27c93f]" />
<span className="ml-4 font-mono text-xs text-[#8b949e]">error — 404</span>
</div>
<div className="p-6 font-mono text-sm">
<div>
<span className="text-[#7ee787]">~</span>
<span className="text-[#8b949e]"> $ </span>
<span>find /page</span>
</div>
<div className="mt-2 text-[#f85149]">
find: /page: No such file or directory
</div>
<div className="mt-4">
<span className="text-[#7ee787]">~</span>
<span className="text-[#8b949e]"> $ </span>
<span className="text-[#8b949e]">Redirecting in {countdown}...</span>
</div>
</div>
</div>
</main>
)
}
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ export default function Home() {
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
className="bg-transparent outline-none caret-[#c9d1d9] placeholder:text-[#484f58]"
style={{ width: inputValue ? `${inputValue.length}ch` : '22ch' }}
style={{ width: inputValue ? `${[...inputValue].reduce((w, c) => w + (c.charCodeAt(0) > 127 ? 2 : 1), 0)}ch` : '22ch' }}
spellCheck={false}
autoComplete="off"
placeholder="type 'help' for commands"
Expand Down
13 changes: 13 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

<!-- CHANGELOG_START -->

## v0.0.7
`2025.11.30 16:30`

에러 페이지 추가 및 한글 입력 너비 수정

- 404 Not Found 페이지 추가 (터미널 스타일 UI)
- 500 Error 페이지 추가 (retry 버튼, 5초 카운트다운)
- Global Error 페이지 추가 (root layout 에러 처리)
- 에러 발생 시 자동 리다이렉트 (같은 도메인이면 뒤로가기, 아니면 홈으로)
- 터미널 한글 입력 시 너비 계산 수정 (charCode > 127 = 2ch)

---

## v0.0.6
`2025.11.30 15:00`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pixel",
"version": "0.0.6",
"version": "0.0.7",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down