Skip to content

Commit 4d721ac

Browse files
authored
Merge pull request #39 from youznn/main
feature(Fe) - connect rest api
2 parents 4f4f5f5 + 67cfd05 commit 4d721ac

27 files changed

+1814
-362
lines changed

frontend/app/(auth)/login/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function SignInPage() {
2121
});
2222

2323
if (res && !res.error) {
24-
console.log("Signed in: ", res);
24+
//console.log("Signed in: ", res);
2525
window.location.href = "/";
2626
} else {
2727
setError("Failed to sign in. Please check your credentials.");

frontend/app/_components/CustomBadge.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { Badge } from "@/components/ui/badge";
22
import { cn } from "@/lib/utils";
33

44
const borderColors = {
5-
kpop: "border-violet-400",
6-
drama: "border-yellow-400",
7-
novel: "border-rose-400",
8-
movie: "border-sky-400",
5+
K_POP: "border-violet-400",
6+
DRAMA: "border-yellow-400",
7+
NOVEL: "border-rose-400",
8+
MOVIE: "border-sky-400",
99
};
1010

11-
type categoryType = "kpop" | "drama" | "novel" | "movie";
11+
type categoryType = "K_POP" | "DRAMA" | "NOVEL" | "MOVIE";
1212

1313
export default function CustomBadge({
1414
title,
@@ -20,7 +20,10 @@ export default function CustomBadge({
2020
return (
2121
<Badge
2222
variant="outline"
23-
className={cn("h-6 rounded-3xl", borderColors[category])}
23+
className={cn(
24+
"h-6 overflow-hidden whitespace-nowrap rounded-3xl",
25+
borderColors[category],
26+
)}
2427
>
2528
{title}
2629
</Badge>

frontend/app/_components/NaverMap.tsx

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,93 @@
11
"use client";
22

3+
import { useRef } from "react";
34
import {
45
Container as MapDiv,
56
NaverMap,
67
Marker,
78
useNavermaps,
89
} from "react-naver-maps";
910

10-
function MyMap() {
11-
// instead of window.naver.maps
11+
type Category = "K_POP" | "DRAMA" | "MOVIE" | "NOVEL";
12+
interface Location {
13+
id: number;
14+
title: string;
15+
latitude: number;
16+
longitude: number;
17+
}
18+
19+
function MyMap({
20+
locations,
21+
category,
22+
setCurrentLocationId,
23+
}: {
24+
locations: Location[];
25+
category: Category;
26+
setCurrentLocationId: (id: number) => void;
27+
}) {
1228
const navermaps = useNavermaps();
1329

30+
const makerImage = {
31+
K_POP: "/icons/marker-1.png",
32+
DRAMA: "/icons/marker-3.png",
33+
MOVIE: "/icons/marker-4.png",
34+
NOVEL: "/icons/marker-2.png",
35+
};
36+
37+
const mapRef = useRef<InstanceType<typeof navermaps.Map> | null>(null);
1438
return (
15-
<NaverMap
16-
defaultCenter={new navermaps.LatLng(37.3595704, 127.105399)}
17-
defaultZoom={15}
18-
>
19-
<Marker defaultPosition={new navermaps.LatLng(37.3595704, 127.105399)} />
20-
</NaverMap>
39+
<MapDiv style={{ width: "100%", height: "350px" }}>
40+
<NaverMap
41+
defaultCenter={new navermaps.LatLng(36.4109466, 126.976882)}
42+
defaultZoom={5}
43+
ref={mapRef}
44+
>
45+
<>
46+
{locations.map((location) => (
47+
<Marker
48+
key={location.id}
49+
position={
50+
new navermaps.LatLng(location.latitude, location.longitude)
51+
}
52+
icon={{
53+
url: makerImage[category],
54+
size: new navermaps.Size(25, 30),
55+
scaledSize: new navermaps.Size(25, 30),
56+
origin: new navermaps.Point(0, 0),
57+
anchor: new navermaps.Point(16, 16),
58+
}}
59+
// marker 클릭 시 해당 위치로 이동
60+
onClick={() => {
61+
mapRef?.current?.setZoom(15);
62+
mapRef?.current?.panTo(
63+
new navermaps.LatLng(location.latitude, location.longitude),
64+
);
65+
setCurrentLocationId(location.id);
66+
}}
67+
/>
68+
))}
69+
</>
70+
</NaverMap>
71+
</MapDiv>
2172
);
2273
}
2374

24-
export default function SimpleMap() {
75+
export default function MapWithMarker({
76+
locations,
77+
category,
78+
setCurrentLocationId,
79+
}: {
80+
locations: Location[];
81+
category: Category;
82+
setCurrentLocationId: (id: number) => void;
83+
}) {
2584
return (
26-
<MapDiv style={{ width: "100%", height: "300px" }}>
27-
<MyMap />
85+
<MapDiv style={{ width: "100%", height: "400px" }}>
86+
<MyMap
87+
locations={locations}
88+
category={category}
89+
setCurrentLocationId={setCurrentLocationId}
90+
/>
2891
</MapDiv>
2992
);
3093
}

frontend/app/_components/PhotoUploader.tsx

Lines changed: 101 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,82 @@
1-
import React, { useRef } from "react";
1+
import { fetcherWithAuth } from "@/lib/utils";
2+
import React, { useRef, useState } from "react";
23
import { MdAddToPhotos } from "react-icons/md";
34

4-
const PhotoUploader = () => {
5+
const PhotoUploader = ({ locationId }: { locationId?: number }) => {
56
const inputRef = useRef<HTMLInputElement | null>(null);
7+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
8+
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
69

710
const handleClick = () => {
811
inputRef.current?.click();
912
};
1013

1114
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
12-
const files = event.target.files;
13-
const gallery = document.getElementById("photoGallery");
14-
15-
if (gallery) gallery.innerHTML = ""; // 기존 사진 초기화
16-
17-
if (files) {
18-
Array.from(files).forEach((file) => {
19-
if (file.type.startsWith("image/")) {
20-
const reader = new FileReader();
21-
reader.onload = (e) => {
22-
const img = document.createElement("img");
23-
img.src = e.target?.result as string;
24-
img.style.width = "150px";
25-
img.style.margin = "10px";
26-
img.style.borderRadius = "8px";
27-
img.style.boxShadow = "0 4px 6px rgba(0, 0, 0, 0.1)";
28-
if (gallery) gallery.appendChild(img);
29-
};
30-
reader.readAsDataURL(file);
31-
}
15+
const file = event.target.files?.[0];
16+
17+
if (file) {
18+
setSelectedFile(file);
19+
setIsModalOpen(true); // 파일을 선택하면 모달을 엽니다.
20+
}
21+
};
22+
23+
const handleUpload = async () => {
24+
if (!selectedFile) {
25+
alert("Please select a file to upload.");
26+
return;
27+
}
28+
29+
if (!locationId) {
30+
alert("Please provide a location ID.");
31+
return;
32+
}
33+
34+
const formData = new FormData();
35+
36+
// ProofShotRequestDto를 JSON으로 변환하여 추가
37+
const proofShotRequestDto = {
38+
location_id: locationId,
39+
description: ".",
40+
};
41+
42+
try {
43+
// JSON 데이터를 Blob으로 추가하면서 Content-Type을 명시합니다.
44+
const proofShotBlob = new Blob([JSON.stringify(proofShotRequestDto)], {
45+
type: "application/json",
3246
});
47+
formData.append("proof_shot", proofShotBlob);
48+
formData.append("image", selectedFile);
49+
50+
const response = await fetcherWithAuth.post(
51+
"api/v1/user/proof-shot/upload",
52+
{
53+
body: formData,
54+
},
55+
);
56+
57+
if (!response.ok) {
58+
const errorText = await response.text();
59+
//console.error("Server Response:", response.status, errorText);
60+
throw new Error(
61+
`Failed to upload photo: ${response.status} ${errorText}`,
62+
);
63+
}
64+
65+
const result = await response.json();
66+
//console.log("Upload successful:", result);
67+
alert("Upload successful!");
68+
setIsModalOpen(false); // 업로드 성공 시 모달 닫기
69+
} catch (error) {
70+
console.error("Error uploading file:", error);
71+
alert("Error uploading file. Please try again.");
3372
}
3473
};
3574

75+
const handleCloseModal = () => {
76+
setIsModalOpen(false);
77+
setSelectedFile(null);
78+
};
79+
3680
return (
3781
<div className="flex flex-col items-center">
3882
{/* 클릭 트리거 */}
@@ -49,17 +93,45 @@ const PhotoUploader = () => {
4993
ref={inputRef}
5094
type="file"
5195
accept="image/*"
52-
name="proofshot"
53-
multiple
5496
className="hidden"
5597
onChange={handleFileChange}
5698
/>
5799

58-
{/* 갤러리 */}
59-
<div
60-
id="photoGallery"
61-
className="mt-4 flex flex-wrap justify-center gap-4"
62-
></div>
100+
{/* 모달 */}
101+
{isModalOpen && (
102+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
103+
<div className="w-[90%] max-w-md rounded-lg bg-white p-6 shadow-lg">
104+
<h2 className="mb-4 text-lg font-semibold">Upload Photo</h2>
105+
<div id="photoGallery" className="mb-4 flex justify-center">
106+
{selectedFile && (
107+
<img
108+
src={URL.createObjectURL(selectedFile)}
109+
alt="Selected preview"
110+
style={{
111+
width: "150px",
112+
borderRadius: "8px",
113+
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
114+
}}
115+
/>
116+
)}
117+
</div>
118+
<div className="flex justify-end gap-4">
119+
<button
120+
className="rounded-md bg-gray-300 px-4 py-2 text-gray-700 hover:bg-gray-400"
121+
onClick={handleCloseModal}
122+
>
123+
Cancel
124+
</button>
125+
<button
126+
className="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
127+
onClick={handleUpload}
128+
>
129+
Upload
130+
</button>
131+
</div>
132+
</div>
133+
</div>
134+
)}
63135
</div>
64136
);
65137
};

frontend/app/layout.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@ export default function RootLayout({
3737
<html lang="en" className={`${pretendard.variable}`}>
3838
<Script
3939
type="text/javascript"
40-
src={`https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_CLIENT_ID}&submodules=geocoder`}
40+
src={`https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_CLIENT_ID}&submodules=geocoder&language=en`}
4141
/>
42-
43-
<body
44-
className={`${geistSans.variable} ${geistMono.variable} w-dvw px-8 py-5 antialiased`}
45-
>
46-
<AuthProvider>{children}</AuthProvider>
42+
<body className={`${geistSans.variable} ${geistMono.variable} `}>
43+
<div className="w-dvw bg-background px-8 py-5 antialiased">
44+
<AuthProvider>{children}</AuthProvider>
45+
</div>
4746
</body>
4847
</html>
4948
);

frontend/app/map/[id]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
44
return (
55
<div>
66
<BackButton />
7-
<div className="px-10">{children}</div>
7+
<div>{children}</div>
88
</div>
99
);
1010
}

0 commit comments

Comments
 (0)