Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
de8e72b
feat: ์ด๋ฉ”์ผ ์ฐพ๊ธฐ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์ถ”๊ฐ€
wkdjh Aug 17, 2025
4858769
fix: ๋กœ๊ทธ์ธ ์ƒํƒœ ์œ ์ง€ ๋ฌธ์ œ ์ˆ˜์ •
wkdjh Aug 17, 2025
1da5b08
fix: ๋กœ๊ทธ์ธ ์ƒํƒœ ์œ ์ง€ ๋ฌธ์ œ ์ „์ฒด ์ ์šฉ
wkdjh Aug 17, 2025
b64e128
Merge pull request #50 from CLD-3rd/develop
alex052525 Aug 17, 2025
6b80831
Revert "fix: ํŽ˜์ด์ง€๋„ค์ด์…˜ + ๋ชจ๋‹ฌ ํ•ด๊ฒฐ ์™„๋ฃŒ"
alex052525 Aug 17, 2025
7e91d25
Merge pull request #51 from CLD-3rd/revert-50-develop
alex052525 Aug 17, 2025
2359639
Merge conflict ํ•ด๊ฒฐ
alex052525 Aug 13, 2025
d65abd2
์ถฉ๋Œ ํ•ด๊ฒฐ
wkdjh Aug 17, 2025
4ac3744
Merge branch 'test404' into fix/s3-modal
alex052525 Aug 17, 2025
7a6824a
Merge pull request #52 from CLD-3rd/fix/s3-modal
alex052525 Aug 17, 2025
fe2b604
Update api-client.ts
alex052525 Aug 17, 2025
6afe2d5
Update page.tsx
alex052525 Aug 17, 2025
1e66c0c
fix: pagination ์žฌ์ ์šฉ
alex052525 Aug 17, 2025
2519155
Merge pull request #53 from CLD-3rd/test404
alex052525 Aug 17, 2025
43acb94
Merge branch 'fix/s3-modal' of https://github.com/CLD-3rd/Final-Team3โ€ฆ
wkdjh Aug 17, 2025
f9fbfb4
Update ci-cd.yaml
wkdjh Aug 18, 2025
6dab782
feat: ์ƒ์„ธ ์œ„์น˜ ์ž…๋ ฅ์‹œ ์ง€์—ญ ์ž๋™์™„์„ฑ
sinascode Aug 18, 2025
10471d1
feat: ์‚ญ์ œ ๋ฒ„ํŠผ ๊ตฌํ˜„
alex052525 Aug 18, 2025
569b196
fix: sortBy -> sortType์œผ๋กœ ์ˆ˜์ •
alex052525 Aug 18, 2025
7250161
Merge pull request #54 from CLD-3rd/feat/my-posts-deleteButton
alex052525 Aug 18, 2025
94d8086
fix: ์„ธ์…˜ ์ˆ˜์ •
sinascode Aug 18, 2025
3422a48
feat: ํ—ค๋” ๊ณ ์ •
sinascode Aug 18, 2025
6c32828
fix: ๋ชจ์ง‘๊ธ€ ์ƒ์„ฑ ์‹œ ์ฐธ๊ฐ€๋น„ ์„ค์ • ์ˆ˜์ •
wkdjh Aug 18, 2025
252f757
fix: ๋ชจ์ง‘๊ธ€ ์ƒ์„ฑ ์‹œ JPG,PNG๋งŒ ์—…๋กœ๋“œ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ˆ˜์ •
wkdjh Aug 18, 2025
f314989
feat: ๋ชจ์ง‘ ๋งŒ๋ฃŒ ์‹œ, ๋งŒ๋ฃŒ์ƒํƒœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
alex052525 Aug 18, 2025
595ed0c
fix: ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„ ํ˜„์žฌ ์‹œ๊ฐ„ ์ดํ›„๋งŒ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ˆ˜์ •
wkdjh Aug 18, 2025
0aaa826
Merge branch 'fix/s3-modal' of https://github.com/CLD-3rd/Final-Team3โ€ฆ
wkdjh Aug 18, 2025
1fc8109
hotFix: ๊ฐ™์€ ๋‚ ์งœ์˜ ์ด์ „ ์‹œ๊ฐ„๋Œ€์˜ ๊ธ€์ด ๋ณด์ด์ง€ ์•Š๊ฒŒ๋” ์ˆ˜์ •
alex052525 Aug 18, 2025
1394254
Merge branch 'fix/s3-modal' of https://github.com/CLD-3rd/Final-Team3โ€ฆ
wkdjh Aug 18, 2025
63cfed6
fix: ๋ชจ์ง‘๊ธ€ ์ƒ์„ฑ ์‹œ ์ฐธ๊ฐ€๋น„ ์—๋Ÿฌ ์ˆ˜์ •
wkdjh Aug 18, 2025
b5106ac
fix: ๋ชจ์ง‘๊ธ€ ์ˆ˜์ •์— ๋ชจ์ง‘๊ธ€ ์ƒ์„ฑ ๊ธฐ์ค€ ์ ์šฉ
wkdjh Aug 18, 2025
16c31bd
fix: ์ฐธ๊ฐ€ ์‹ ์ฒญ ์ทจ์†Œ ๋ฒ„ํŠผ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
sinascode Aug 18, 2025
7cbf702
feat: ๋‚ ์”จ ์ •๋ณด ์ถ”๊ฐ€
sinascode Aug 19, 2025
815539d
fix: ๊ฐ•์ˆ˜, ์Šต๋„ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
sinascode Aug 19, 2025
7acce2a
fix: ๊ฐ•์ˆ˜, ์Šต๋„ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
sinascode Aug 19, 2025
9e37e9e
fix: ์˜จ๋„ ์‚ญ์ œ
sinascode Aug 19, 2025
00a0028
fix: ๋‚ ์”จ '์•Œ ์ˆ˜ ์—†์Œ' ์ˆ˜์ •
sinascode Aug 20, 2025
69a6ecd
fix: ์‹ ์ฒญ์ž ์Šน์ธํ•  ๋•Œ ์ตœ๋Œ€์ธ์› ๋„๋‹ฌํ•˜๋ฉด ์Šน์ธ ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™”
wkdjh Aug 20, 2025
05cd3de
Merge branch 'fix/s3-modal' of https://github.com/CLD-3rd/Final-Team3โ€ฆ
wkdjh Aug 20, 2025
1699d66
fix: ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์—๋Ÿฌ ์ˆ˜์ •
wkdjh Aug 21, 2025
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
112 changes: 93 additions & 19 deletions app/create-post/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const genderOptions = [
{ id: "FEMALE", name: "์—ฌ์„ฑ๋งŒ" },
]

export const MAX_COST = 1_000_000;
export const krFormat = new Intl.NumberFormat("ko-KR");
export const digitsOnly = (s: string) => s.replace(/[^\d]/g, "");

// Google Maps API ํƒ€์ž… ์„ ์–ธ
declare global {
interface Window {
Expand Down Expand Up @@ -228,7 +232,7 @@ export default function CreatePostPage() {
const [isLoadingPlaces, setIsLoadingPlaces] = useState(false)
const locationInputRef = useRef<HTMLInputElement>(null)
const predictionsRef = useRef<HTMLDivElement>(null)
const GOOGLE_PLACES_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_PLACES_API_KEY || ""
const GOOGLE_PLACES_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_PLACES_API_KEY || "AIzaSyAEdB2-APCc2ml50ipMsoTdtKEGOvT6Flc"

// Google Places Autocomplete Service ์ดˆ๊ธฐํ™”
useEffect(() => {
Expand Down Expand Up @@ -363,6 +367,14 @@ export default function CreatePostPage() {
setToasts(prev => prev.filter(toast => toast.id !== id))
}

const pad2 = (n: number) => String(n).padStart(2, "0");

// ๋กœ์ปฌ ์‹œ๊ฐ„ ๊ธฐ์ค€ "YYYY-MM-DD"
const getTodayStr = () => {
const now = new Date();
return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`;
};

const handleParticipantChange = (increment: boolean) => {
setFormData((prev) => ({
...prev,
Expand All @@ -375,11 +387,14 @@ export default function CreatePostPage() {
const file = e.target.files?.[0]
if (!file) return

// ํŒŒ์ผ ํƒ€์ž… ์ฒดํฌ
if (!file.type.startsWith('image/')) {
addToast("์ด๋ฏธ์ง€ ํŒŒ์ผ๋งŒ ์—…๋กœ๋“œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", 'error')
return
}
const allowedTypes = ['image/jpeg', 'image/png']

if (!allowedTypes.includes(file.type)) {
addToast("์ด๋ฏธ์ง€๋Š” JPG, PNG๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", 'error')

e.currentTarget.value = ""
return
}

setSelectedImage(file)
setError("")
Expand Down Expand Up @@ -411,6 +426,43 @@ export default function CreatePostPage() {
setError("")
setSuccessMessage("")

let hasError = false
if (!formData.title) {
addToast("์ œ๋ชฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”.", "error")
hasError = true
}
if (!formData.sport) {
addToast("์šด๋™ ์ข…๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”.", "error")
hasError = true
}
if (!formData.location) {
addToast("์ƒ์„ธ ์œ„์น˜๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", "error")
hasError = true
}
if (hasError) {
setLoading(false)
return
}

if (!formData.date || !formData.time) {
setLoading(false)
setError("๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.")
addToast("๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.", "error")
return
}

// "YYYY-MM-DDTHH:mm(:ss)" โ†’ ๋กœ์ปฌ ๊ธฐ์ค€ Date
const timeStr = formData.time.length === 5 ? formData.time + ":00" : formData.time
const selected = new Date(`${formData.date}T${timeStr}`)
const now = new Date()
// '์ดํ›„'๋งŒ ํ—ˆ์šฉ โ†’ ๊ฐ™๊ฑฐ๋‚˜ ๊ณผ๊ฑฐ๋ฉด ๋ง‰๊ธฐ
if (selected <= now) {
setLoading(false)
setError("ํ˜„์žฌ ์‹œ๊ฐ„ ์ดํ›„๋กœ ์„ค์ •ํ•ด์•ผ ๋ฉ๋‹ˆ๋‹ค.")
addToast("ํ˜„์žฌ ์‹œ๊ฐ„ ์ดํ›„๋กœ ์„ค์ •ํ•ด์•ผ ๋ฉ๋‹ˆ๋‹ค.", "error")
return
}

try {
const isoDateTime = `${formData.date}T${formData.time}`

Expand Down Expand Up @@ -441,7 +493,7 @@ export default function CreatePostPage() {
}

// ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ
const token = localStorage.getItem('auth_token')
const token = sessionStorage.getItem('auth_token')
console.log('์‚ฌ์šฉ ์ค‘์ธ ํ† ํฐ:', token ? 'ํ† ํฐ ์žˆ์Œ' : 'ํ† ํฐ ์—†์Œ')

const headers: HeadersInit = {}
Expand Down Expand Up @@ -546,7 +598,6 @@ export default function CreatePostPage() {
value={formData.title}
onChange={(e) => setFormData((prev) => ({ ...prev, title: e.target.value }))}
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-50"
required
/>
</div>

Expand Down Expand Up @@ -593,7 +644,6 @@ export default function CreatePostPage() {
}
}}
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-50 pr-12"
required
/>
<MapPin className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />

Expand Down Expand Up @@ -649,7 +699,6 @@ export default function CreatePostPage() {
value={townOptions.find(opt => opt.value === formData.town)?.label || ""}
readOnly
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-100 pr-14 cursor-not-allowed"
required
/>

</div>
Expand All @@ -666,9 +715,23 @@ export default function CreatePostPage() {
<Input
type="date"
value={formData.date}
onChange={(e) => setFormData((prev) => ({ ...prev, date: e.target.value }))}
min={getTodayStr()}
onChange={(e) => {
const today = getTodayStr();
const selected = e.target.value;

// ์˜ค๋Š˜ ์ด์ „์ด๋ฉด ๊ฐ•์ œ๋กœ ์˜ค๋Š˜๋กœ ๋งž์ถ”๊ณ  ์—๋Ÿฌ/ํ† ์ŠคํŠธ
if (selected < today) {
setError("์˜ค๋Š˜ ์ดํ›„ ๋‚ ์งœ๋งŒ ์„ ํƒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.");
addToast?.("์˜ค๋Š˜ ์ดํ›„ ๋‚ ์งœ๋งŒ ์„ ํƒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", "error");
setFormData((prev) => ({ ...prev, date: today }));
return;
} else {
setError("");
setFormData((prev) => ({ ...prev, date: selected }));
}
}}
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-50"
required
/>
</div>
<div className="space-y-2">
Expand All @@ -678,7 +741,6 @@ export default function CreatePostPage() {
value={formData.time}
onChange={(e) => setFormData((prev) => ({ ...prev, time: e.target.value }))}
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-50"
required
/>
</div>
</div>
Expand Down Expand Up @@ -738,15 +800,27 @@ export default function CreatePostPage() {
<Label className="text-lg font-semibold text-gray-900">1์ธ๋‹น ์ฐธ๊ฐ€๋น„</Label>
<div className="relative">
<Input
type="number"
type="text"
inputMode="numeric"
placeholder="0"
value={formData.cost}
onChange={(e) => setFormData((prev) => ({ ...prev, cost: e.target.value }))}
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-50 pr-12"
/>
value={formData.cost ? krFormat.format(Number(formData.cost)) : ""}
onChange={(e) => {
// ์ž…๋ ฅ๊ฐ’์—์„œ ์ˆซ์ž๋งŒ ์ถ”์ถœ
const digits = e.target.value.replace(/[^\d]/g, "")
if (digits === "") {
setFormData(prev => ({ ...prev, cost: "" }))
return
}
// ์ƒํ•œ์„  ์ ์šฉ
const clamped = Math.min(parseInt(digits, 10), MAX_COST)
// ์ƒํƒœ์—” "์ˆซ์ž ๋ฌธ์ž์—ด"๋กœ๋งŒ ์ €์žฅ (์ฝค๋งˆ ์—†์Œ)
setFormData(prev => ({ ...prev, cost: String(clamped) }))
}}
className="h-14 text-lg border-2 border-gray-200 rounded-2xl focus:border-black focus:ring-0 bg-gray-50 pr-12"
/>
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-lg font-medium text-gray-600">์›</span>
</div>
</div>
</div>

<div className="space-y-4">
<Label className="text-lg font-semibold text-gray-900">๊ตฌ์žฅ ์ด๋ฏธ์ง€</Label>
Expand Down
75 changes: 75 additions & 0 deletions app/login/find-email/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client"

import { useState } from "react"
import { apiClient } from "@/lib/api-client"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import Link from "next/link"

export default function FindEmailPage() {
const [nickname, setNickname] = useState("")
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [maskedEmail, setMaskedEmail] = useState<string | null>(null)

const onSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true); setError(null); setMaskedEmail(null)
try {
const res = await apiClient.findEmail(nickname.trim())
const email = (res as any)?.data?.email ?? (res as any)?.email
if (!email) {
setError("์ผ์น˜ํ•˜๋Š” ๊ณ„์ •์ด ์—†์Šต๋‹ˆ๋‹ค.")
} else {
setMaskedEmail(email) // ๋งˆ์Šคํ‚น๋œ ์ด๋ฉ”์ผ์ด ๋‚ด๋ ค์˜ด
}
} catch (e: any) {
setError(e?.message || "์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.")
} finally {
setLoading(false)
}
}

return (
<div className="min-h-screen flex items-center justify-center px-6">
<div className="w-full max-w-sm">
<h1 className="text-2xl font-bold mb-6">์ด๋ฉ”์ผ ์ฐพ๊ธฐ</h1>

{error && <div className="mb-4 p-3 rounded bg-red-50 text-red-600">{error}</div>}
{maskedEmail && (
<div className="mb-4 p-3 rounded bg-green-50 text-green-700">
์ด๋ฉ”์ผ: <span className="font-semibold">{maskedEmail}</span>
</div>
)}

<form onSubmit={onSubmit} className="space-y-6">
<div>
<Label htmlFor="nickname" className="mb-3 block">๋‹‰๋„ค์ž„</Label>
<Input
id="nickname"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
placeholder="๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•˜์„ธ์š”"
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading || !nickname.trim()}>
{loading ? "์กฐํšŒ ์ค‘..." : "์ด๋ฉ”์ผ ์กฐํšŒ"}
</Button>
</form>

<div className="mt-6 text-sm text-gray-600">
๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ธฐ์–ต๋‚˜์ง€ ์•Š๋‚˜์š”?{" "}
<Link href="/login/forgot-password" className="text-blue-500 hover:text-blue-600 font-semibold text-sm transition-colors">
๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •
</Link>
</div>

<div className="mt-3 text-sm">
<Link href="/login" className="text-blue-500 hover:text-blue-600 font-semibold transition-colors">โ† ๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</Link>
</div>
</div>
</div>
)
}
79 changes: 79 additions & 0 deletions app/login/forgot-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client"

import { useState } from "react"
import { apiClient } from "@/lib/api-client"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import Link from "next/link"

export default function ForgotPasswordPage() {
const [email, setEmail] = useState("")
const [loading, setLoading] = useState(false)
const [notice, setNotice] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)

const onSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true); setError(null); setNotice(null)
try {
const res = await apiClient.requestPasswordReset(email.trim())

if (res?.code === "USER208") {
setNotice("๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋งํฌ๋ฅผ ์ด๋ฉ”์ผ๋กœ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ๋ฐ›์€ ํŽธ์ง€ํ•จ/์ŠคํŒธํ•จ์„ ํ™•์ธํ•˜์„ธ์š”.")
return
}

if (res?.code === "USER408") {
setError("๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.")
return
}

} catch (err: any) {
const code =
err?.code ||
err?.response?.data?.code ||
err?.data?.code

if (code === "USRE208") {
setNotice("๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ๋งํฌ๋ฅผ ์ด๋ฉ”์ผ๋กœ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ๋ฐ›์€ ํŽธ์ง€ํ•จ/์ŠคํŒธํ•จ์„ ํ™•์ธํ•˜์„ธ์š”.")
} else if (code === "USER408") {
setError("๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.")
}
else {
setError(err?.response?.data?.message || err?.message || "๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์š”์ฒญ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.")
}
} finally {
setLoading(false)
}
}


return (
<div className="min-h-screen flex items-center justify-center px-6">
<div className="w-full max-w-sm">
<h1 className="text-2xl font-bold mb-6">๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •</h1>

{error && <div className="mb-4 p-3 rounded bg-red-50 text-red-600">{error}</div>}
{notice && <div className="mb-4 p-3 rounded bg-green-50 text-green-700">{notice}</div>}

<form onSubmit={onSubmit} className="space-y-6">
<div>
<Label htmlFor="email" className="mb-3 block">๊ฐ€์ž… ์ด๋ฉ”์ผ</Label>
<Input id="email" type="email" value={email}
onChange={(e) => setEmail(e.target.value)} placeholder="email@example.com" required />
</div>
<Button type="submit" className="w-full" disabled={loading || !email.trim()}>
{loading ? "์š”์ฒญ ์ค‘..." : "์žฌ์„ค์ • ๋งํฌ ๋ณด๋‚ด๊ธฐ"}
</Button>
</form>

<div className="mt-6 text-sm">
<Link href="/login" className="text-blue-500 hover:text-blue-600 font-semibold transition-colors">
โ† ๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
</Link>
</div>
</div>
</div>
)
}
Loading