feat: not

This commit is contained in:
juyoungk09 2025-09-13 10:13:39 +09:00
commit 2d95e0bf94
24 changed files with 728 additions and 612 deletions

View file

@ -19,6 +19,7 @@ export const AddFriends = component$(({ showAddFriends }: { showAddFriends: Sign
console.log(error); console.log(error);
} }
}) })
const errorMessage = useSignal('');
const handleAddFriend = $((username: string) => { const handleAddFriend = $((username: string) => {
try { try {
axios.post(`http://localhost:8000/api/friendship/request`, { axios.post(`http://localhost:8000/api/friendship/request`, {
@ -29,10 +30,12 @@ export const AddFriends = component$(({ showAddFriends }: { showAddFriends: Sign
}, },
}) })
.then((res) => { .then((res) => {
console.log(res.data);
myfriends.value = [...myfriends.value.filter((friend) => friend.username !== username) ];
}) })
} catch (error : any) { } catch (error : any) {
console.log(error.response?.data?.detail); console.log(error.response?.data?.detail);
errorMessage.value = error.response?.data?.ms;
} }
}) })
const handleSearch = $((query: string) => { const handleSearch = $((query: string) => {
@ -48,6 +51,7 @@ export const AddFriends = component$(({ showAddFriends }: { showAddFriends: Sign
}) })
const handleDeleteFriend = $((friendship_id : number) => { const handleDeleteFriend = $((friendship_id : number) => {
try { try {
console.log(friendship_id + getCookie("access_token")!);
axios.delete(`http://localhost:8000/api/friendship/${friendship_id}`, { axios.delete(`http://localhost:8000/api/friendship/${friendship_id}`, {
headers: { headers: {
Authorization: `Bearer ${getCookie("access_token")}`, Authorization: `Bearer ${getCookie("access_token")}`,
@ -55,10 +59,11 @@ export const AddFriends = component$(({ showAddFriends }: { showAddFriends: Sign
}) })
.then((res) => { .then((res) => {
console.log(res.data); console.log(res.data);
myfriends.value = myfriends.value.filter((friend) => friend.id !== friendship_id); myfriends.value = [...myfriends.value.filter((friend) => friend.id !== friendship_id) ];
}) })
} catch (error : any) { } catch (error : any) {
console.log(error.response?.data?.detail); console.log(error.response?.data?.detail);
errorMessage.value = error.response?.data?.detail;
} }
}) })
return ( return (
@ -115,6 +120,7 @@ export const AddFriends = component$(({ showAddFriends }: { showAddFriends: Sign
</div> </div>
))} ))}
</div> </div>
<p class="text-red-500">{errorMessage.value}</p>
</div></div> </div></div>
</div> </div>
); );

View file

@ -1,43 +1,43 @@
// src/components/PhotoUploadModal.tsx // // src/components/PhotoUploadModal.tsx
import { component$, useSignal, Signal, $ } from '@builder.io/qwik'; // import { component$, useSignal, Signal, $ } from '@builder.io/qwik';
type FileInfo = { // type FileInfo = {
name: string; // name: string;
size: number; // size: number;
type: string; // type: string;
lastModified: number; // lastModified: number;
url?: string; // url?: string;
}; // };
export const PhotoUploadModal = component$(({ selectedImages, showPhotoUploadModal }: { selectedImages: Signal<File[]>, showPhotoUploadModal: Signal<boolean> }) => { // export const PhotoUploadModal = component$(({ selectedImages, showPhotoUploadModal }: { selectedImages: Signal<File[]>, showPhotoUploadModal: Signal<boolean> }) => {
const fileInfo = useSignal<FileInfo | null>(null); // const fileInfo = useSignal<FileInfo | null>(null);
const fileRef = useSignal<File | null>(null); // const fileRef = useSignal<File | null>(null);
const error = useSignal(''); // const error = useSignal('');
const fileInputRef = useSignal<HTMLInputElement>(); // const fileInputRef = useSignal<HTMLInputElement>();
const handleFileSelect = $(async (selectedFile: File) => { // const handleFileSelect = $(async (selectedFile: File) => {
if (selectedFile && selectedFile.type.startsWith('image/')) { // if (selectedFile && selectedFile.type.startsWith('image/')) {
fileInfo.value = { // fileInfo.value = {
name: selectedFile.name, // name: selectedFile.name,
size: selectedFile.size, // size: selectedFile.size,
type: selectedFile.type, // type: selectedFile.type,
lastModified: selectedFile.lastModified, // lastModified: selectedFile.lastModified,
url: URL.createObjectURL(selectedFile) // url: URL.createObjectURL(selectedFile)
}; // };
fileRef.value = selectedFile; // fileRef.value = selectedFile;
error.value = ''; // error.value = '';
} else { // } else {
error.value = '이미지 파일만 업로드 가능합니다.'; // error.value = '이미지 파일만 업로드 가능합니다.';
fileInfo.value = null; // fileInfo.value = null;
fileRef.value = null; // fileRef.value = null;
} // }
}); // });
if (!showPhotoUploadModal.value) return null; // if (!showPhotoUploadModal.value) return null;
return ( // return (
); // );
}); // });

View file

@ -2,7 +2,7 @@ import { component$, Signal, $ } from "@builder.io/qwik";
import axios from "axios"; import axios from "axios";
import { getCookie } from "~/utils/cookie"; import { getCookie } from "~/utils/cookie";
export const BuyModal = component$(({ showBuyModal, item, mydotory, itemdotory}: {showBuyModal: Signal<boolean>, item: any, mydotory: number, itemdotory: number}) => { export const BuyModal = component$(({ showBuyModal, item, mydotory, itemdotory}: {showBuyModal: Signal<boolean>, item: any, mydotory: Signal<number>, itemdotory: number}) => {
const handleBuy = $(async() => { const handleBuy = $(async() => {
try { try {
const res = await axios.post(`http://localhost:8000/api/store/0?product_name=${item.name}`, { const res = await axios.post(`http://localhost:8000/api/store/0?product_name=${item.name}`, {
@ -12,6 +12,7 @@ export const BuyModal = component$(({ showBuyModal, item, mydotory, itemdotory}:
}, },
}) })
console.log(res.data); console.log(res.data);
mydotory.value -= itemdotory;
showBuyModal.value = false; showBuyModal.value = false;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -26,7 +27,7 @@ export const BuyModal = component$(({ showBuyModal, item, mydotory, itemdotory}:
<p> {item.name} </p> <p> {item.name} </p>
<p class="font-bold text-2xl"> ?</p> <p class="font-bold text-2xl"> ?</p>
<p class="text-gray-500"> : {mydotory - itemdotory}</p> <p class="text-gray-500"> : {mydotory.value - itemdotory}</p>
<div class="flex gap-2"> <div class="flex gap-2">
<button onclick$={handleBuy} class="bg-diary-color hover:bg-diary-icon-hover rounded-lg p-2"> <button onclick$={handleBuy} class="bg-diary-color hover:bg-diary-icon-hover rounded-lg p-2">

View file

@ -3,7 +3,6 @@ import terminate from "~/assets/images/terminate.svg";
import { useNavigate, useLocation } from "@builder.io/qwik-city"; import { useNavigate, useLocation } from "@builder.io/qwik-city";
import { Link } from "@builder.io/qwik-city"; import { Link } from "@builder.io/qwik-city";
import { deleteCookie, getCookie } from "~/utils/cookie"; import { deleteCookie, getCookie } from "~/utils/cookie";
import { AddFriends } from "~/components/features/AddFriends";
import axios from "axios"; import axios from "axios";
export default component$(({ showAddFriends }: { showAddFriends: Signal<boolean> }) => { export default component$(({ showAddFriends }: { showAddFriends: Signal<boolean> }) => {

View file

@ -1,287 +1,128 @@
import { $, component$, useSignal, useStylesScoped$, Signal, useVisibleTask$} from "@builder.io/qwik"; import { component$, useStylesScoped$, Signal } from "@builder.io/qwik";
import { typechecker } from "~/utils/func"; import { typechecker } from "~/utils/func";
interface FurnitureItem { interface FurnitureItem {
name: string; furniture_name: string;
image_path: string; image_path: string;
x: number; x: number;
y: number; y: number;
} }
export default component$(({ furniture, avatar, roomType }: { furniture: Signal<any>, avatar: Signal<any>, roomType: Signal<string> }) => { interface SetAvatarType {
useStylesScoped$(STYLES); avatar_type: string;
// 가구 아이템 상태 관리 top_clothe_type: string;
const checkedAvatar = useSignal({ bottom_clothe_type: string;
avatar_type: "", }
top_clothe_type: "", export default component$(({roomSrc, furnitures, avatars}: {roomSrc: Signal<string>, furnitures: Signal<FurnitureItem[]>, avatars: Signal<SetAvatarType>}) => {
bottom_clothe_type: "" console.log(roomSrc.value);
}); console.log(furnitures.value);
const furnitureItems = useSignal<FurnitureItem[]>([]); console.log(avatars.value + " avatars");
useVisibleTask$(({ track }) => { useStylesScoped$(STYLES);
track(() => avatar.value);
console.log(avatar.value);
checkedAvatar.value = {
...avatar.value,
avatar_type: typechecker(avatar.value.avatar_type),
top_clothe_type: typechecker(avatar.value.top_clothe_type),
bottom_clothe_type: typechecker(avatar.value.bottom_clothe_type),
};
furnitureItems.value = [furniture.value];
console.log(checkedAvatar.value);
})
// const furnitureItems = useSignal<FurnitureItem[]>([
// {
// name: '큰 식물1',
// image_path: 'public/funiture/큰 식물.png',
// x: 1,
// y: 1
// },
// {
// name: '녹색 침대1',
// image_path: 'public/funiture/녹색 침대-90.png',
// x: 3,
// y: 3
// },
// {
// name: '쓰레기통열림1',
// image_path: 'public/funiture/쓰레기통열림.png',
// x: 5,
// y: 5
// },
// {
// name: '어항2',
// image_path: 'public/funiture/어항-0.png',
// x: 7,
// y: 7
// }
// ]);
// 현재 드래그 중인 가구
// const draggedItem = useSignal<FurnitureItem | null>(null);
// // 드래그 시작 시 호출
// const handleDragStart = $((item: FurnitureItem, e: Event) => {
// e.preventDefault();
// draggedItem.value = item;
// });
// 드래그 오버 시 기본 동작 방지
const handleDragOver = $((e: Event) => {
e.preventDefault();
});
// 셀 클릭 시 호출
const handleCellClick = $((x: number, y: number, e: Event) => {
e.preventDefault();
console.log(`Cell clicked: (${x}, ${y})`);
furnitureItems.value = furnitureItems.value.map(item =>
item.name === furniture.value?.name
? { ...item, x: x, y: y }
: item
);
// draggedItem.value = null;
});
// 드롭 시 호출
// const handleDrop = $((cellX: number, cellY: number, e: Event) => {
// e.preventDefault();
// if (!draggedItem.value) return;
// // 이미 해당 위치에 다른 가구가 있는지 확인
// const isOccupied = furnitureItems.value.some(
// item => item.x === cellX && item.y === cellY
// );
// if (!isOccupied) {
// // 가구 위치 업데이트
// furnitureItems.value = furnitureItems.value.map(item =>
// item.name === draggedItem.value?.name
// ? { ...item, x: cellX, y: cellY }
// : item
// );
// }
// draggedItem.value = null; return (
// });
// 10x10 그리드 셀 생성
const gridCells = Array.from({ length: 100 }, (_, i) => {
const row = Math.floor(i / 10) + 1; // 1-10
const col = (i % 10) + 1; // 1-10
// 해당 위치의 가구 찾기
const furniture = furnitureItems.value.find(item => item.x === col && item.y === row);
return {
x: col,
y: row,
furniture: furniture ? {
name: furniture.name,
src: furniture.image_path,
} : null
};
});
return (
<div class="room-wrapper"> <div class="room-wrapper">
<img {/* 배경 */}
src={`http://localhost:8000/public/room/${roomType.value}.png`} <img src={"http://localhost:8000/public/room/"+roomSrc.value+".png"} class="room" />
class="room"
alt="Room background"
/>
{/* Grid overlay */}
<div class="grid-overlay ">
{gridCells.map((cell) => (
<div
key={`${cell.x}-${cell.y}`}
class="grid-cell"
// onDragOver$={handleDragOver}
// onDrop$={ e => handleDrop(cell.x, cell.y, e)}
onClick$={ e => handleCellClick(cell.x, cell.y, e)}
>
{cell.furniture && (
<img
src={`http://localhost:8000/${cell.furniture.src}`}
alt={cell.furniture.name}
// draggable
// onDragStart$={ e => handleDragStart(furnitureItems.value.find(f => f.name === cell.furniture?.name)!, e)}
/>
)}
</div>
))}
</div>
{/* Avatar */} {/* 10x10 격자 */}
<div class="avatar-wrapper"> <div class="grid-overlay">
<img {Array.from({ length: 100 }).map((_, idx) => {
src={`http://localhost:8000/${checkedAvatar.value.avatar_type}`} const item = furnitures.value.find((f: FurnitureItem) => f.x + f.y*10 === idx);
alt={checkedAvatar.value.avatar_type} console.log(item);
/> return (
<img <div
src={`http://localhost:8000/${checkedAvatar.value.top_clothe_type}`} key={idx+1}
alt={checkedAvatar.value.top_clothe_type} >
/> {item && <img src={"http://localhost:8000/" + item.image_path} />}
<img </div>
src={`http://localhost:8000/${checkedAvatar.value.bottom_clothe_type}`} );
alt={checkedAvatar.value.bottom_clothe_type} })}
/>
</div>
</div> </div>
);
{/* 아바타 */}
<div class="avatar-wrapper">
<img src={"http://localhost:8000/"+typechecker(avatars.value.avatar_type)} />
<img src={"http://localhost:8000/"+typechecker(avatars.value.top_clothe_type)} />
<img src={"http://localhost:8000/"+typechecker(avatars.value.bottom_clothe_type)} />
</div>
</div>
);
}); });
const STYLES = ` const STYLES = `
.room-wrapper { .room-wrapper {
position: relative; display: flex;
width: 100%; align-items: center;
height: 100%; justify-content: center;
overflow: hidden; width: 700px;
} height: 700px;
position: relative;
.room {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
.avatar-wrapper img {
position: absolute;
top: 0;
left: 0;
width: 75px;
height: 100px;
object-fit: contain;
image-rendering: pixelated;
}
.room-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 700px;
height: 700px;
position: relative;
}
.room {
position: absolute;
z-index: 1 !important;
width: 100%;
height: 100%;
image-rendering: pixelated;
border-radius: 0.5rem;
}
/* Grid */
.grid-overlay {
position: absolute;
top: 0;
left: 0;
width: 700px;
height: 700px;
display: grid;
grid-template-columns: repeat(10, 1fr);
grid-template-rows: repeat(10, 1fr);
z-index: 2;
}
.grid-overlay div {
box-sizing: border-box;
position: relative;
}
.grid-overlay > div {
position: relative;
height: 70px;
width: 100%;
overflow: visible;
}
.grid-overlay > div img {
pointer-events: none;
user-select: none;
-webkit-user-drag: none;
}
.avatar-wrapper {
width: 200px;
height: 200px;
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99;
}
.grid-overlay div#oneone>img {
width: 70px;
image-rendering: pixelated;
position: absolute;
bottom: 0;
} }
.grid-overlay div#twoone>img { .room {
width: 140px; position: absolute;
image-rendering: pixelated; z-index: 1 !important;
position: absolute; width: 100%;
transform: translate(-50%, -50%); height: 100%;
} image-rendering: pixelated;
.avatar-wrapper img { }
position: absolute;
top: 0; .grid-overlay {
left: 0; position: absolute;
width: 100%; top: 0;
image-rendering: pixelated; left: 0;
z-index: 99; width: 700px;
} height: 700px;
`; display: grid;
grid-template-columns: repeat(10, 1fr);
grid-template-rows: repeat(10, 1fr);
z-index: 2;
pointer-events: none;
}
.grid-overlay div {
/* border: 1px solid rgba(0,0,0,0.2); */
box-sizing: border-box;
width: 70px;
height: 70px;
position: relative;
}
.grid-overlay div>img {
width: 70px;
height: 70px;
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
// .grid-overlay div.oneone > img {
// width: 140px;
// image-rendering: pixelated;
// position: absolute;
// bottom: 0;
// }
// .grid-overlay div.twoone > img {
// width: 140px;
// image-rendering: pixelated;
// position: absolute;
// transform: translate(-50%, -50%);
// }
.avatar-wrapper {
width: 150px;
height: 150px;
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99;
}
.avatar-wrapper img {
position: absolute;
top: 0;
left: 0;
width: 100%;
image-rendering: pixelated;
z-index: 99;
}
`;

View file

@ -21,10 +21,11 @@
export default component$((props: ShowRoomGridProps) => { export default component$((props: ShowRoomGridProps) => {
console.log(props.roomSrc); console.log(props.roomSrc);
console.log(props.furnitures + " furnitures"); console.log(props.furnitures);
console.log(props.avatars + " avatars"); console.log(props.avatars + " avatars");
useStylesScoped$(STYLES); useStylesScoped$(STYLES);
useVisibleTask$(() => { useVisibleTask$(() => {
}); });
return ( return (
<div class="room-wrapper"> <div class="room-wrapper">
@ -34,23 +35,23 @@
{/* 10x10 격자 */} {/* 10x10 격자 */}
<div class="grid-overlay"> <div class="grid-overlay">
{Array.from({ length: 100 }).map((_, idx) => { {Array.from({ length: 100 }).map((_, idx) => {
const item = props.furnitures.find((f) => f.x*10 + f.y === idx); const item = props.furnitures.find((f) => f.x + f.y*10 === idx);
console.log(item); console.log(item);
return ( return (
<div <div
key={idx} key={idx}
class={ // class={
item?.size === "one" // item?.size === "one"
? "oneone" // ? "oneone"
: item?.size === "two" // : item?.size === "two"
? "twoone" // ? "twoone"
: undefined // : undefined
} // }
style={ // style={
item?.size === "two" // item?.size === "two"
? { gridColumn: `span 2`, gridRow: `span 1` } // ? { gridColumn: `span 2`, gridRow: `span 1` }
: undefined // : undefined
} // }
> >
{item && <img src={"http://localhost:8000/" + item.image_path} />} {item && <img src={"http://localhost:8000/" + item.image_path} />}
</div> </div>
@ -101,13 +102,20 @@
.grid-overlay div { .grid-overlay div {
/* border: 1px solid rgba(0,0,0,0.2); */ /* border: 1px solid rgba(0,0,0,0.2); */
box-sizing: border-box; box-sizing: border-box;
position: relative;
width: 70px; width: 70px;
height: 70px; height: 70px;
position: relative; position: relative;
} }
.grid-overlay div>img {
width: 70px;
height: 70px;
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
// .grid-overlay div.oneone > img { // .grid-overlay div.oneone > img {
// width: 140px; // width: 140px;
// image-rendering: pixelated; // image-rendering: pixelated;
@ -121,8 +129,8 @@
// transform: translate(-50%, -50%); // transform: translate(-50%, -50%);
// } // }
.avatar-wrapper { .avatar-wrapper {
width: 100px; width: 150px;
height: 100px; height: 150px;
position: absolute; position: absolute;
top: 70%; top: 70%;
left: 50%; left: 50%;

View file

@ -78,6 +78,13 @@ export default component$(() => {
username: userState.username.trim(), username: userState.username.trim(),
password: userState.password.trim(), password: userState.password.trim(),
}) })
const why = await axios.get('http://localhost:8000/api/avatar', {
headers: {
Authorization: `Bearer ${res.data.access_token}`,
},
});
console.log(why.data);
console.log('Login success'); console.log('Login success');
userState.username = ''; userState.username = '';
userState.password = ''; userState.password = '';

View file

@ -7,7 +7,7 @@ export const onGet: RequestHandler = async ({ cookie, redirect }) => {
console.log(jwt + "이것은 auth로 들어온 token"); console.log(jwt + "이것은 auth로 들어온 token");
if (jwt) { if (jwt) {
try { try {
const res = await axios.get('http://localhost:8000/api/user/profile/쌀숭이', { const res = await axios.get('http://localhost:8000/api/user/me', {
headers: { headers: {
Authorization: `Bearer ${jwt}`, Authorization: `Bearer ${jwt}`,
}, },

View file

@ -1,4 +1,4 @@
import { component$, Slot } from "@builder.io/qwik"; import { component$ } from "@builder.io/qwik";

View file

@ -5,6 +5,7 @@ import { DocumentHead } from "@builder.io/qwik-city";
import { useLocation } from "@builder.io/qwik-city"; import { useLocation } from "@builder.io/qwik-city";
import axios from "axios"; import axios from "axios";
import { routeLoader$ } from "@builder.io/qwik-city"; import { routeLoader$ } from "@builder.io/qwik-city";
// import { DotoryContext } from "~/utils/context";
export const onGet: RequestHandler = async ({ cookie, params, sharedMap}) => { export const onGet: RequestHandler = async ({ cookie, params, sharedMap}) => {
console.log(params.username + "의 상점 페이지로 들어옴"); console.log(params.username + "의 상점 페이지로 들어옴");
@ -28,16 +29,15 @@ export const useDotory = routeLoader$(({sharedMap}) => {
export default component$(() => { export default component$(() => {
const location = useLocation(); const location = useLocation();
const dotory = useDotory(); const dotory = useDotory();
console.log(location.url.pathname);
// console.log(location.url.pathname); console.log("/"+location.params.username+"/store/");
// console.log(location.params.username+"/store");
return ( return (
<div class="w-full h-full flex flex-col"> <div class="w-full h-full flex flex-col">
<div class="flex justify-center flex-col gap-4 p-2 items-center w-full"> <div class="flex justify-center flex-col gap-4 p-2 items-center w-full">
<div class="flex items-center justify-between w-full bg-diary-color rounded-lg p-2"> <div class="flex items-center justify-between w-full bg-diary-color rounded-lg p-2">
<div class="flex md:flex-row flex-col gap-2"> <div class="flex md:flex-row flex-col gap-2">
<Link href={`/${location.params.username}/store`} class={`${location.url.pathname === `/${location.params.username}/store` ? "bg-[#FAD659]" : "bg-white"} text-gray-800 px-4 py-2 rounded-lg font-medium`}> <i class="bi bi-shop"></i></Link> <Link href={`/${location.params.username}/store`} class={`${location.url.pathname === `/${location.params.username}/store/` ? "bg-white" : "bg-button-color-3"} text-gray-800 px-4 py-2 rounded-lg font-medium`}> <i class="bi bi-shop"></i></Link>
<Link href={`/${location.params.username}/room`} class={`${location.url.pathname === `/${location.params.username}/room` ? "bg-[#FAD659]" : "bg-white"} text-gray-800 px-4 py-2 rounded-lg font-medium`}></Link> <Link href={`/${location.params.username}/room`} class={`${location.url.pathname === `/${location.params.username}/room/` ? "bg-white" : "bg-button-color-3"} text-gray-800 px-4 py-2 rounded-lg font-medium`}></Link>
</div> </div>
<div class="flex md:flex-row flex-col"> <div class="flex md:flex-row flex-col">
<div class="ml-auto bg-[#CC8A6A] text-default px-4 py-2 rounded-lg font-medium">: {dotory.value}</div> <div class="ml-auto bg-[#CC8A6A] text-default px-4 py-2 rounded-lg font-medium">: {dotory.value}</div>

View file

@ -1,4 +1,4 @@
import { $, component$, useSignal, useTask$, useVisibleTask$ } from "@builder.io/qwik"; import { $, component$, useSignal, useVisibleTask$} from "@builder.io/qwik";
import { DocumentHead } from "@builder.io/qwik-city"; import { DocumentHead } from "@builder.io/qwik-city";
import RoomGrid from "~/components/room/RoomGrid"; import RoomGrid from "~/components/room/RoomGrid";
import { typechecker } from "~/utils/func"; import { typechecker } from "~/utils/func";
@ -11,22 +11,28 @@ interface Furniture {
x : number; x : number;
y : number; y : number;
} }
interface Avatar { interface SendFurniture {
id: number;
user_id: number; name: string;
avatar_type: { image_path : string;
name: string;
path: string
},
top_clothe_type: {
name: string;
path: string
},
bottom_clothe_type: {
name: string;
path: string
}
} }
// interface Avatar {
// id: number;
// user_id: number;
// avatar_type: {
// name: string;
// path: string
// },
// top_clothe_type: {
// name: string;
// path: string
// },
// bottom_clothe_type: {
// name: string;
// path: string
// }
// }
interface SetAvatarType { interface SetAvatarType {
avatar_type: string; avatar_type: string;
top_clothe_type: string; top_clothe_type: string;
@ -37,13 +43,13 @@ interface RoomType {
image_path: string; image_path: string;
} }
interface Room { // interface Room {
id: number; // id: number;
user_id: number; // user_id: number;
room_type: string; // room_type: string;
room_name: string; // room_name: string;
room_image_path: string; // room_image_path: string;
} // }
export const useRoomLoader = routeLoader$(async ({cookie}) => { export const useRoomLoader = routeLoader$(async ({cookie}) => {
const headers = { const headers = {
Authorization: `Bearer ${cookie.get("access_token")?.value}`, Authorization: `Bearer ${cookie.get("access_token")?.value}`,
@ -64,57 +70,186 @@ export const useRoomLoader = routeLoader$(async ({cookie}) => {
}) })
export default component$(() => { export default component$(() => {
const data = useRoomLoader(); const data = useRoomLoader();
const selectedFurnitures = useSignal<Array<Furniture & {x?: number, y?: number}>>([]); // const navigate = useNavigate();
const reloadPage = $(() => {
window.location.reload();
});
// 1. 소유한 가구 데이터 (layout API에서 가져옴)
const ownedFurnitures = useSignal<SendFurniture[]>([]);
// 2. 현재 배치된 가구 데이터 (room/my API에서 가져옴)
const placedFurnitures = useSignal<Furniture[]>([]);
// 3. 현재 선택된 가구 (새로 배치하려는 가구만) - 제거
// const selectedFurniture = useSignal<Furniture | null>(null);
const selectedType = useSignal<"furniture" | "avatar" | "background">("furniture"); const selectedType = useSignal<"furniture" | "avatar" | "background">("furniture");
const editingPosition = useSignal<number | null>(null); // const editingPosition = useSignal<number | null>(null);
const selectedRoomType = useSignal("room_1"); const selectedRoomType = useSignal("room_1");
const selectedAvatar = useSignal<SetAvatarType>({ const selectedAvatar = useSignal<SetAvatarType>({
avatar_type: "", avatar_type: "",
top_clothe_type: "", top_clothe_type: "",
bottom_clothe_type: "" bottom_clothe_type: ""
}); });
useVisibleTask$(() => { const mergedFurnitures = useSignal<Furniture[]>([] as Furniture[]);
// 오류 상태 관리
const errorMessage = useSignal<string>("");
const successMessage = useSignal<string>("");
useVisibleTask$(async () => {
// 기본 데이터 설정
selectedRoomType.value = data.value.myData.room.room.room_type; selectedRoomType.value = data.value.myData.room.room.room_type;
selectedAvatar.value.avatar_type = data.value.myData.avatar.avatar_type.name; selectedAvatar.value.avatar_type = data.value.myData.avatar.avatar_type.name;
selectedAvatar.value.top_clothe_type = data.value.myData.avatar.top_clothe_type.name; selectedAvatar.value.top_clothe_type = data.value.myData.avatar.top_clothe_type.name;
selectedAvatar.value.bottom_clothe_type = data.value.myData.avatar.bottom_clothe_type.name; selectedAvatar.value.bottom_clothe_type = data.value.myData.avatar.bottom_clothe_type.name;
ownedFurnitures.value = data.value.selectionData.furniture;
placedFurnitures.value = [...data.value.myData.room.furniture];
mergedFurnitures.value = ownedFurnitures.value.map(o => {
const placed = placedFurnitures.value.find(p => p.furniture_name === o.name);
return placed
? placed
: {
furniture_name: o.name,
image_path: o.image_path,
x: -1,
y: -1,
};
});
}) })
const deleteFurniture = $(async (selectedFurniture : Furniture) => { // 가구 배치 함수
const placeFurniture = $(async (furniture: Furniture, x: number, y: number) => {
try { try {
await axios.delete(`http://localhost:8000/api/room/furniture?x=${selectedFurniture.x}&y=${selectedFurniture.y}&furniture_name=${selectedFurniture.furniture_name}`, {headers: {Authorization: `Bearer ${getCookie("access_token")}`}}); await axios.post("http://localhost:8000/api/room/furniture", {
selectedFurnitures.value = selectedFurnitures.value.filter((f : Furniture) => f.furniture_name !== selectedFurniture.furniture_name); furniture_name: furniture.furniture_name,
} catch (error : any) {console.error(error);} x: x,
}) y: y
const addFurniture = $(async (selectedFurniture : Furniture) => { }, {
try { await axios.post("http://localhost:8000/api/room/furniture", {furniture_name: selectedFurniture.furniture_name,x: selectedFurniture.x,y: selectedFurniture.y,}, { headers: { Authorization: `Bearer ${getCookie("access_token")}`,},},) headers: { Authorization: `Bearer ${getCookie("access_token")}` }
} catch (error : any) {console.error(error);} });
})
useTask$(({track}) => { // 성공 시 placedFurnitures에 추가
track(() => selectedFurnitures.value); if (!mergedFurnitures.value.find(f => f.furniture_name === furniture.furniture_name)) {
try { mergedFurnitures.value = Array.from(new Set([
for (let i = 0; i < selectedFurnitures.value.length; i++) { ...mergedFurnitures.value,
if(data.value.myData.room.room.furniture.some((f : Furniture) => f.furniture_name === selectedFurnitures.value[i].furniture_name)) {deleteFurniture(selectedFurnitures.value[i]);} {
else { addFurniture(selectedFurnitures.value[i]);} furniture_name: furniture.furniture_name, // SendFurniture has 'name' property
image_path: furniture.image_path,
x: x,
y: y
}
]));
placedFurnitures.value = Array.from(new Set([...placedFurnitures.value, furniture]));
} }
} catch (error : any) {console.error(error);} else {
}) mergedFurnitures.value =Array.from(new Set([...mergedFurnitures.value]));
placedFurnitures.value = Array.from(new Set([...placedFurnitures.value]));
}
console.log('Placed fu rniture:', mergedFurnitures.value);
return true;
} catch (error: any) {
console.error('가구 배치 실패:', error);
errorMessage.value = error.response.data.detail;
setTimeout(() => { errorMessage.value = ""; }, 10000);
return false;
}
});
// 가구 제거 함수
const removeFurniture = $(async (furniture: Furniture) => {
console.log(furniture + " furniture");
try {
await axios.delete(`http://localhost:8000/api/room/furniture?x=${furniture.x}&y=${furniture.y}&furniture_name=${furniture.furniture_name}`, {
headers: { Authorization: `Bearer ${getCookie("access_token")}` }
});
placedFurnitures.value = Array.from(new Set([...placedFurnitures.value.filter(f =>
f.furniture_name !== furniture.furniture_name && (f.x !== furniture.x || f.y !== furniture.y)
)]));
mergedFurnitures.value = Array.from(new Set([...mergedFurnitures.value.map(f =>
f.furniture_name === furniture.furniture_name
? { ...f, x: -1, y: -1 }
: f
)]));
return true;
} catch (error: any) {
console.error('가구 제거 실패:', error);
errorMessage.value = `${furniture.furniture_name} 제거에 실패했습니다.`;
setTimeout(() => { errorMessage.value = ""; }, 10000);
return false;
}
});
// 가구 위치 업데이트 함수
const updateFurniturePosition = $(async (furniture:Furniture, newX: number, newY: number) => {
if ((newX < 0 || newX >= 10) || (newY < 0 || newY >= 10)) {
errorMessage.value = "위치가 이상합니다.";
console.log(newX, newY);
setTimeout(() => { errorMessage.value = ""; }, 10000);
return false;
}
try {
const foundedFurniture = mergedFurnitures.value.find(f => f.x === newX && f.y === newY)
if (foundedFurniture) {
await removeFurniture(foundedFurniture);
}
//같은 이름으 가구가 이미 있나
console.log(placedFurnitures.value, furniture + " furniture");
const foundedFurniture2 = placedFurnitures.value.find(f => f.furniture_name === furniture.furniture_name)
if (foundedFurniture2) {
await removeFurniture(foundedFurniture2);
}
// 새 위치에 가구 배치
console.log(furniture + " 가구 움직임");
await placeFurniture(furniture, newX, newY);
return true;
} catch (error: any) {
console.error('가구 위치 업데이트 실패:', error);
return false;
}
});
const handleChange = $( async () => { const handleChange = $( async () => {
try { try {
await axios.patch("http://localhost:8000/api/room/", {type: selectedRoomType.value}, {headers: {Authorization: `Bearer ${getCookie("access_token")}`}}); await axios.patch("http://localhost:8000/api/room/", {type: selectedRoomType.value}, {headers: {Authorization: `Bearer ${getCookie("access_token")}`}});
await axios.put("http://localhost:8000/api/avatar", {avatar_type: selectedAvatar.value.avatar_type,top_clothe_type: selectedAvatar.value.top_clothe_type,bottom_clothe_type: selectedAvatar.value.bottom_clothe_type,}, {headers: {Authorization: `Bearer ${getCookie("access_token")}`}}); await axios.put("http://localhost:8000/api/avatar", {avatar_type: selectedAvatar.value.avatar_type,top_clothe_type: selectedAvatar.value.top_clothe_type,bottom_clothe_type: selectedAvatar.value.bottom_clothe_type,}, {headers: {Authorization: `Bearer ${getCookie("access_token")}`}});
} catch (error) {console.log(error);}
} catch (error) {console.log(error);}
}) })
return ( return (
<div class="flex flex-col h-full p-4"> <div class="flex flex-col h-full p-4">
<h1 class="text-2xl font-bold text-[#4b3c28] mb-6"> </h1> <div class="flex flex-row justify-between items-center mb-4">
<h1 class="text-2xl font-bold text-[#4b3c28]"> </h1>
{/* 오류 메시지 표시 */}
{errorMessage.value && (
<div class="px-2 bg-red-100 border border-red-400 text-red-700 rounded-lg">
{errorMessage.value?errorMessage.value:" "}
</div>
)}
{/* 성공 메시지 표시 */}
{successMessage.value && (
<div class=" bg-green-100 border border-green-400 text-green-700 rounded-lg">
{successMessage.value?successMessage.value:" "}
</div>
)}
</div>
<div class="flex flex-col md:flex-row gap-6 flex-1"> <div class="flex flex-col md:flex-row gap-6 flex-1">
{/* Room Preview */} {/* Room Preview */}
<div class="flex-1 bg-white rounded-xl border border-gray-200 p-4"> <div class="flex-1 bg-white rounded-xl border border-gray-200 p-4">
<div class="h-full flex items-center justify-center"> <div class="h-full flex items-center justify-center">
<RoomGrid furniture={selectedFurnitures} avatar={selectedAvatar} roomType={selectedRoomType} />
<RoomGrid
furnitures={placedFurnitures}
avatars={selectedAvatar}
roomSrc={selectedRoomType}
/>
</div> </div>
</div> </div>
@ -164,82 +299,88 @@ export default component$(() => {
<div class="flex-1 overflow-y-auto p-4"> <div class="flex-1 overflow-y-auto p-4">
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
{selectedType.value === "furniture" ? ( {selectedType.value === "furniture" ? (
data.value.selectionData.furniture.map((furniture: Furniture) => { <div class="space-y-4">
const isSelected = selectedFurnitures.value.some(f => f.furniture_name === furniture.furniture_name); {/* 배치된 가구 목록 - 각각 입력창 표시 */}
const selectedItem = selectedFurnitures.value.find(f => f.furniture_name === furniture.furniture_name); <div class="space-y-3">
{mergedFurnitures.value.map((furniture: Furniture) => (
return ( <div key={`${furniture.furniture_name}-${furniture.x}-${furniture.y}`} class="bg-white border border-text-default rounded-lg p-4">
<div key={furniture.furniture_name} class="flex flex-col items-center gap-2"> <div class="flex items-center gap-3 mb-3">
<div <img
class={`flex flex-col items-center gap-2 rounded-t-lg ${isSelected ? 'ring-2 ring-yellow-400' : ''}`} src={`http://localhost:8000/${furniture.image_path}`}
onClick$={() => { alt={furniture.furniture_name}
if (isSelected) { style={{imageRendering: "pixelated"}}
selectedFurnitures.value = selectedFurnitures.value.filter( class="object-contain w-12 h-12"
item => item.furniture_name !== furniture.furniture_name />
); <div>
} else { <h5 class="font-medium text-text-default">
selectedFurnitures.value = [...selectedFurnitures.value, { {furniture.furniture_name}
...furniture, </h5>
x: 0, {furniture.x !== -1 && furniture.y !== -1 && (
y: 0 <p class="text-sm text-diary-icon-hover">
}]; : ({furniture.x}, {furniture.y})
</p>
)}
</div>
{furniture.x !== -1 && furniture.y !== -1 && (
<button
onClick$={() => {removeFurniture(furniture); reloadPage();}}
class="ml-auto px-3 py-1 bg-button-color-2 text-white text-sm rounded hover:bg-button-color-2-hover"
>
</button>
)
} }
}}
>
<img
src={`http://localhost:8000/${furniture.image_path}`}
alt={furniture.furniture_name}
style={{imageRendering: "pixelated"}}
class="object-contain w-24 h-24"
/>
<span class="text-center font-bold rounded-b-lg w-24 h-12 bg-[#E5E2B6] flex items-center justify-center">
{furniture.furniture_name}
</span>
</div>
{isSelected && (
<div class="flex gap-2 mt-1">
<input
type="number"
min="1"
max="10"
value={selectedItem?.x || ''}
onInput$={(e) => {
const value = (e.target as HTMLInputElement).value;
const index = selectedFurnitures.value.findIndex(f => f.furniture_name === furniture.furniture_name);
if (index !== -1) {
const updated = [...selectedFurnitures.value];
updated[index] = { ...updated[index], x: parseInt(value) || 0 };
selectedFurnitures.value = updated;
}
}}
class="w-12 px-1 text-center border rounded"
placeholder="X"
/>
<input
type="number"
min="1"
max="10"
value={selectedItem?.y || ''}
onInput$={(e) => {
const value = (e.target as HTMLInputElement).value;
const index = selectedFurnitures.value.findIndex(f => f.furniture_name === furniture.furniture_name);
if (index !== -1) {
const updated = [...selectedFurnitures.value];
updated[index] = { ...updated[index], y: parseInt(value) || 0 };
selectedFurnitures.value = updated;
}
}}
class="w-12 px-1 text-center border rounded"
placeholder="Y"
/>
</div> </div>
)}
</div> {/* 위치 수정 입력창 */}
); <div class="flex gap-3 items-center">
} <label class="text-sm font-medium text-green-900">X:</label>
)) <input
: type="number"
min="0"
max="9"
value={furniture.x === -1 ? 1 : furniture.x}
onInput$={
(e) => {
furniture.x = parseInt((e.target as HTMLInputElement).value);
}
}
class="w-16 px-2 py-1 text-center border rounded"
/>
<label class="text-sm font-medium text-green-900">Y:</label>
<input
type="number"
min="0"
max="9"
value={furniture.y === -1 ? 1 : furniture.y}
onInput$={
(e) => {
furniture.y = parseInt((e.target as HTMLInputElement).value);
}
}
class="w-16 px-2 py-1 text-center border rounded"
/>
<button
onClick$={() => {updateFurniturePosition(furniture, furniture.x === -1 ? 1 : furniture.x, furniture.y === -1 ? 1 : furniture.y); reloadPage();}}
class="ml-auto px-3 py-1 bg-button-color-2 text-white text-sm rounded hover:bg-button-color-2-hover"
>
</button>
</div>
</div>
))}
{/* 배치된 가구가 없을 때 */}
{ownedFurnitures.value.length === 0 && (
<div class="text-center py-8 text-gray-500">
<p> .</p>
<p class="text-sm"> .</p>
</div>
)}
</div>
</div>
) :
selectedType.value === "avatar" ? ( selectedType.value === "avatar" ? (
<div class="flex flex-col gap-3 "> <div class="flex flex-col gap-3 ">
<div> <div>
@ -346,9 +487,14 @@ export default component$(() => {
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
class="flex-1 bg-[#FAD659] hover:bg-[#f8ce3a] text-[#4b3c28] font-bold py-2 px-4 rounded-lg transition-colors" class="flex-1 bg-[#FAD659] hover:bg-[#f8ce3a] text-[#4b3c28] font-bold py-2 px-4 rounded-lg transition-colors"
onClick$={() => {handleChange() }} onClick$={(e) => { if (selectedType.value === "furniture") {
e.preventDefault();
} else {
handleChange();
reloadPage();
}}}
> >
{selectedType.value === "furniture" ? "가구를 배치해보세요" : "아바타 & 배경 저장하기"}
</button> </button>
{/* <button {/* <button
class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-lg transition-colors" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-lg transition-colors"

View file

@ -1,8 +1,8 @@
import { component$, useSignal } from "@builder.io/qwik"; import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { DocumentHead, routeLoader$ } from "@builder.io/qwik-city"; import { DocumentHead, routeLoader$ } from "@builder.io/qwik-city";
import axios from "axios"; import axios from "axios";
import { BuyModal } from "~/components/features/shop/BuyModal"; import { BuyModal } from "~/components/features/shop/BuyModal";
import RoomGrid from "~/components/room/RoomGrid"; // import { getCookie } from "~/utils/cookie";
interface ShopItem { interface ShopItem {
name: string; name: string;
@ -10,27 +10,27 @@ interface ShopItem {
width: number; width: number;
// Add other properties that your items might have // Add other properties that your items might have
} }
interface Avatar { // interface Avatar {
id: number; // id: number;
user_id: number; // user_id: number;
avatar_type: { // avatar_type: {
name: string; // name: string;
path: string // path: string
}, // },
top_clothe_type: { // top_clothe_type: {
name: string, // name: string,
path: string // path: string
}, // },
bottom_clothe_type: { // bottom_clothe_type: {
name: string, // name: string,
path: string // path: string
} // }
} // }
interface ShopResponse { // interface ShopResponse {
storeData: ShopItem[]; // storeData: ShopItem[];
avatarData: Avatar[]; // You might want to define a more specific type for avatar data // avatarData: Avatar[]; // You might want to define a more specific type for avatar data
dotory: number; // dotory: number;
} // }
export const useShopLoader = routeLoader$(async ({cookie}) => { export const useShopLoader = routeLoader$(async ({cookie}) => {
try { try {
@ -54,15 +54,15 @@ export const useShopLoader = routeLoader$(async ({cookie}) => {
Authorization: `Bearer ${cookie.get("access_token")?.value}`, Authorization: `Bearer ${cookie.get("access_token")?.value}`,
}, },
}); });
// const dotory = await axios.get(`http://localhost:8000/api/store`, { const dotory = await axios.get(`http://localhost:8000/api/store`, {
// headers: { headers: {
// Authorization: `Bearer ${cookie.get("access_token")?.value}`, Authorization: `Bearer ${cookie.get("access_token")?.value}`,
// }, },
// }); });
console.log(res.data); console.log(res.data);
console.log(avatar.data); console.log(avatar.data);
console.log(dotory.data);
return {storeData: res.data, avatarData: avatar.data, dotory: 80}; return {storeData: res.data, avatarData: avatar.data, dotory: dotory.data};
} catch (error : any) { } catch (error : any) {
console.error(error); console.error(error);
return { storeData: [], avatarData: null, dotory: 0}; return { storeData: [], avatarData: null, dotory: 0};
@ -70,32 +70,77 @@ export const useShopLoader = routeLoader$(async ({cookie}) => {
}); });
export default component$(() => { export default component$(() => {
const data = useShopLoader(); const data = useShopLoader();
const dotory = useSignal<number>(0);
const selectedItem = useSignal<ShopItem | null>(null); const selectedItem = useSignal<ShopItem | null>(null);
const showBuyModal = useSignal(false); const showBuyModal = useSignal(false);
useVisibleTask$(() => {
dotory.value = data.value.dotory.dotory as number;
});
return ( return (
<div class="relative flex p-4 justify-between items-center w-full h-full"> <div class="relative flex p-4 justify-between items-center w-full h-full">
{/* 좌측 */} {/* 좌측 */}
<div class="grid grid-cols-2 overflow-auto h-full max-h-[calc(100vh-100px)] gap-2"> <div class="grid grid-cols-2 overflow-auto h-full max-h-[calc(100vh-100px)] p-4 gap-8">
{data.value.storeData.slice(0, data.value.storeData.length / 2).map((item: ShopItem) => ( {data.value.storeData.slice(0, data.value.storeData.length / 2).map((item: ShopItem) => (
<div key={item.name} class={`flex items-center flex-col gap-2 rounded-t-lg` } onClick$={() => {if(selectedItem.value === item) selectedItem.value = null; else selectedItem.value = item;}}> <div
<img src={`http://localhost:8000/${item.image_path}`} alt={item.name} style={{imageRendering: "pixelated"}} class={` object-contain w-24 h-24 ${selectedItem.value === item ? "border-[2px] border-[#FAD659]" : ""}`} /> key={item.name}
<span class="text-center font-bold rounded-b-lg w-24 h-12 bg-[#E5E2B6]">{item.name}</span> class={`flex items-center flex-col gap-2 transition-all duration-200 ${
selectedItem.value === item
? 'scale-105 transform -translate-y-1'
: 'opacity-90 hover:opacity-100'
}`}
onClick$={() => {
selectedItem.value = selectedItem.value === item ? null : item;
}}
>
<div class="relative">
<img
src={`http://localhost:8000/${item.image_path}`}
alt={item.name}
style={{imageRendering: "pixelated"}}
class={`object-contain w-24 h-24 transition-all duration-200 ${
selectedItem.value === item
? 'ring-4 ring-yellow-400 ring-offset-2'
: 'border border-gray-200'
} rounded-lg`}
/>
{selectedItem.value === item && (
<div class="absolute -top-2 -right-2 w-6 h-6 bg-yellow-400 rounded-full flex items-center justify-center">
<i class="bi bi-check-lg text-white text-sm"></i>
</div>
)}
</div>
<span class={`text-center font-bold rounded-lg w-24 py-2 transition-colors ${
selectedItem.value === item
? 'bg-yellow-400 text-gray-900'
: 'bg-[#E5E2B6] text-gray-800'
}`}>
{item.name}
</span>
</div> </div>
))} ))}
</div> </div>
{/* 중앙 빈 박스 */} {/* 중앙 미리보기 */}
<div class="w-64 bg-white border-text-default border-[2px] rounded-lg h-80 flex items-center justify-center"> <div class="w-72 bg-white border-[3px] border-[#4b3c28] rounded-xl h-80 flex items-center justify-center shadow-lg">
{selectedItem.value && {selectedItem.value &&
<div class="flex flex-col gap-4 items-center"> <div class="flex flex-col items-center p-4 w-full">
<span class="font-bold">{selectedItem.value.name}</span> <div class="text-center mb-2">
<span>: {selectedItem.value.width}</span> <h3 class="text-lg font-bold text-[#4b3c28]">{selectedItem.value.name}</h3>
<img src={`http://localhost:8000/${selectedItem.value?.image_path}`} alt="avatar" style={{imageRendering: "pixelated"}} class="object-contain w-32 h-32" /> <p class="text-sm text-gray-600">: {selectedItem.value.width}</p>
</div>
<div class="flex-1 flex items-center justify-center my-2 w-full">
<img
src={`http://localhost:8000/${selectedItem.value?.image_path}`}
alt={selectedItem.value.name}
style={{imageRendering: "pixelated"}}
class="object-contain w-40 h-40"
/>
</div>
<button <button
class="bg-diary-color hover:bg-diary-icon-hover md:w-full text-black px-4 py-2 rounded-3xl font-bold transition-all duration-200 flex items-center gap-2 z-10 relative" class="mt-2 bg-[#FAD659] hover:bg-yellow-400 w-full text-[#4b3c28] px-4 py-3 rounded-xl font-bold transition-all duration-200 flex items-center justify-center gap-2 shadow-md hover:shadow-lg"
onClick$={() => {showBuyModal.value = true}}> onClick$={() => {showBuyModal.value = true}}>
<i class="bi bi-cart-fill"></i>
<i class="bi bi-cart text-3xl text-black"></i> <span></span>
</button> </button>
</div> </div>
} }
@ -103,18 +148,50 @@ export default component$(() => {
</div> </div>
{/* 우측 */} {/* 우측 */}
<div class="grid grid-cols-2 overflow-auto h-full max-h-[calc(100vh-100px)] gap-2"> <div class="grid grid-cols-2 overflow-auto h-full max-h-[calc(100vh-100px)] p-4 gap-8">
{data.value.storeData.slice(data.value.storeData.length / 2, data.value.storeData.length).map((item: ShopItem) => ( {data.value.storeData.slice(data.value.storeData.length / 2, data.value.storeData.length).map((item: ShopItem) => (
<div key={item.name} class={`flex items-center flex-col gap-2 rounded-t-lg` } onClick$={() => {if(selectedItem.value === item) selectedItem.value = null; else selectedItem.value = item;}}> <div
<img src={`http://localhost:8000/${item.image_path}`} alt={item.name} style={{imageRendering: "pixelated"}} class={` object-contain w-24 h-24 ${selectedItem.value === item ? "border-[2px] border-[#FAD659]" : ""}`} /> key={item.name}
<span class="text-center font-bold rounded-b-lg w-24 h-12 bg-[#E5E2B6]">{item.name}</span> class={`flex items-center flex-col gap-2 transition-all duration-200 ${
selectedItem.value === item
? 'scale-105 transform -translate-y-1'
: 'opacity-90 hover:opacity-100'
}`}
onClick$={() => {
selectedItem.value = selectedItem.value === item ? null : item;
}}
>
<div class="relative">
<img
src={`http://localhost:8000/${item.image_path}`}
alt={item.name}
style={{imageRendering: "pixelated"}}
class={`object-contain w-24 h-24 transition-all duration-200 ${
selectedItem.value === item
? 'ring-1 ring-yellow-400 ring-offset-1'
: 'border border-gray-200'
} rounded-lg`}
/>
{selectedItem.value === item && (
<div class="absolute -top-2 -right-2 w-6 h-6 bg-yellow-400 rounded-full flex items-center justify-center">
<i class="bi bi-check-lg text-white text-sm"></i>
</div>
)}
</div>
<span class={`text-center font-bold rounded-lg w-24 py-2 transition-colors ${
selectedItem.value === item
? 'bg-yellow-400 text-gray-900'
: 'bg-[#E5E2B6] text-gray-800'
}`}>
{item.name}
</span>
</div> </div>
))} ))}
</div> </div>
{showBuyModal.value && {showBuyModal.value &&
<div class="bg-opacity-45 z-[1000] fixed inset-0 w-full h-full bg-black" <div class="bg-opacity-45 z-[1000] fixed inset-0 w-full h-full bg-black"
onclick$={() => {showBuyModal.value = false}}> onclick$={() => {showBuyModal.value = false}}>
<BuyModal showBuyModal={showBuyModal} item={selectedItem.value} mydotory={data.value.dotory} itemdotory={80}/> <BuyModal showBuyModal={showBuyModal} item={selectedItem.value} mydotory={dotory} itemdotory={100}/>
</div> </div>
} }
</div> </div>

View file

@ -11,12 +11,12 @@ interface FileInfo {
lastModified: number; lastModified: number;
url: string; url: string;
} }
interface DiaryData { // interface DiaryData {
content: string; // content: string;
title: string; // title: string;
category: string; // category: string;
images: File[]; // images: File[];
} // }
export const diaryDataLoader = routeLoader$(async ({params, cookie}) => { export const diaryDataLoader = routeLoader$(async ({params, cookie}) => {
const access_token = cookie.get("access_token"); const access_token = cookie.get("access_token");
const diary_id = params.diary_id; const diary_id = params.diary_id;
@ -45,7 +45,7 @@ export default component$(() => {
content.value = diaryData.value.content; content.value = diaryData.value.content;
title.value = diaryData.value.title; title.value = diaryData.value.title;
category.value = diaryData.value.category; category.value = diaryData.value.category;
selectedImages.value = diaryData.value.images; // selectedImages.value = diaryData.value.images;
}) })
const handleFileSelect = $(async (selectedFile: File) => { const handleFileSelect = $(async (selectedFile: File) => {
if (selectedFile && selectedFile.type.startsWith('image/')) { if (selectedFile && selectedFile.type.startsWith('image/')) {
@ -118,6 +118,12 @@ export default component$(() => {
<button onClick$={() => nav(`/${location.params.username}/diary`)}> <button onClick$={() => nav(`/${location.params.username}/diary`)}>
<i class="bi bi-trash-fill text-3xl text-black"></i> <i class="bi bi-trash-fill text-3xl text-black"></i>
</button> </button>
<button
class="px-2"
onClick$={() => showPhotoUploadModal.value = true}
>
<i class="bi bi-image-fill text-black text-3xl"></i>
</button>
<button onClick$={() => handleSend()}> <button onClick$={() => handleSend()}>
<i class="bi bi-send-fill text-3xl text-black"></i> <i class="bi bi-send-fill text-3xl text-black"></i>
</button> </button>
@ -142,11 +148,7 @@ export default component$(() => {
</button> </button>
</div> </div>
))} ))}
<button
onClick$={() => showPhotoUploadModal.value = true}
>
<i class="bi bi-image-fill text-black text-2xl"></i>
</button>
</div> </div>
</div> </div>
@ -187,7 +189,19 @@ export default component$(() => {
<p class="text-xs p-2 text-gray-500 text-center border-b-2 border-black mb-6"> <p class="text-xs p-2 text-gray-500 text-center border-b-2 border-black mb-6">
</p> </p>
{/* <input
ref={fileInputRef}
type="file"
accept="image/*"
class="opacity-0"
id="fileInput"
onChange$={async (e) => {
const files = (e.target as HTMLInputElement).files;
if (files && files.length > 0) {
await handleFileSelect(files[0]);
}
}}
/> */}
<div <div
class={`border-2 border-dashed rounded-md h-32 flex flex-col items-center justify-center text-sm cursor-pointer transition-colors ${ class={`border-2 border-dashed rounded-md h-32 flex flex-col items-center justify-center text-sm cursor-pointer transition-colors ${
fileInfo.value fileInfo.value
@ -202,25 +216,12 @@ export default component$(() => {
await handleFileSelect(files[0]); await handleFileSelect(files[0]);
} }
}} }}
onClick$={async () => { // onClick$={(e) => {
if (fileRef.value) { // // input 클릭 트리거
try { // e.stopPropagation();
// console.log("click");
console.log('업로드됨:', { // fileInputRef.value?.click();
file: fileRef.value, // }}
fileInfo: fileInfo.value
});
// 업로드 후 초기화
fileInfo.value = null;
fileRef.value = null;
showPhotoUploadModal.value = false;
} catch (err) {
console.error('업로드 실패:', err);
error.value = '파일 업로드 중 오류가 발생했습니다.';
}
}
}}
> >
{fileInfo.value ? ( {fileInfo.value ? (
<div class="text-center p-4"> <div class="text-center p-4">

View file

@ -1,4 +1,4 @@
import { component$, $, useSignal, useContext } from "@builder.io/qwik"; import { component$, $, } from "@builder.io/qwik";
import { DocumentHead, useNavigate, routeLoader$, Link, useLocation } from "@builder.io/qwik-city"; import { DocumentHead, useNavigate, routeLoader$, Link, useLocation } from "@builder.io/qwik-city";
import axios from "axios"; import axios from "axios";
import { getCookie } from "~/utils/cookie"; import { getCookie } from "~/utils/cookie";
@ -7,7 +7,7 @@ import { getCookie } from "~/utils/cookie";
type DiaryResponse = {diary: DiaryEntry, myInfo: number } | { detail: string }; type DiaryResponse = {diary: DiaryEntry, myInfo: number } | { detail: string };
export const useDiaryLoader = routeLoader$<DiaryResponse>(async ({params, cookie, status}) => { export const useDiaryLoader = routeLoader$<DiaryResponse>(async ({params, cookie}) => {
const diary_id = params.diary_id; const diary_id = params.diary_id;
try { try {
console.log(diary_id); console.log(diary_id);
@ -56,36 +56,63 @@ export default component$(() => {
const { diary, myInfo } = diaryLoader.value; const { diary, myInfo } = diaryLoader.value;
return ( return (
<div class="p-6"> <div class="p-6 max-w-6xl mx-auto">
<div class="flex border-b-2 border-text-default justify-between items-center mb-8"> <div class="border-b-2 border-text-default pb-4 mb-8">
<h1 class="text-2xl font-bold">{diary.title}</h1> <div class="flex justify-between items-center">
<div class="flex flex-row justify-between items-center"> <h1 class="text-3xl font-bold text-gray-800">{diary.title}</h1>
{diary.user_id === myInfo && ( {diary.user_id === myInfo && (
<div class="flex gap-3"> <div class="flex gap-4">
<button onClick$={handleDelete} > <button
<i class="bi bi-trash-fill text-3xl text-black"></i> onClick$={handleDelete}
</button> class="text-gray-600 hover:text-red-500 transition-colors"
<Link href={`/${location.params.username}/diary/${location.params.diary_id}/edit/`}> aria-label="일기 삭제"
<i class="bi bi-send-fill text-3xl text-black"></i> >
</Link> <i class="bi bi-trash-fill text-2xl"></i>
</button>
<Link
href={`/${location.params.username}/diary/${location.params.diary_id}/edit/`}
class="text-gray-600 hover:text-blue-500 transition-colors"
aria-label="일기 수정"
>
<i class="bi bi-send-fill text-2xl"></i>
</Link>
</div>
)}
</div> </div>
)} <p class="text-sm text-gray-500 mt-2">
{new Date(diary.created_at).toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
})}
</p>
</div> </div>
</div>
<div class="mt-8 space-y-8">
<div class="mt-8 flex flex-col gap-4"> {diary.images.length > 0 && (
<div class="flex gap-4 overflow-auto border-t-[1px] border-b-[1px] border-text-default p-2"> <div class="flex overflow-x-auto py-4 gap-4">
{diaryLoader.value.diary.images.map((image, index) => ( {diary.images.map((image, index) => (
<img key={index} src={`http://localhost:8000/${image}`} alt={`일기 ${index + 1}`} class="max-w-full h-auto max-h-64" /> <img
))} src={`http://localhost:8000/${image}`}
</div> alt={`일기 이미지 ${index + 1}`}
class="w-auto h-64 object-contain border border-button-color-2 rounded-lg"
<p class="whitespace-pre-wrap ">{diaryLoader.value.diary.content}</p> loading="lazy"
/>
))}
</div>
)}
<div class="prose max-w-none">
<p class="whitespace-pre-wrap text-gray-700 leading-relaxed text-lg">
{diary.content}
</p>
</div>
</div> </div>
{('detail' in diaryLoader.value) && ( {('detail' in diaryLoader.value) && (
<div class="flex border-b-2 border-text-default justify-center items-center w-full h-full"> <div class="text-center py-12">
<p> .</p> <p class="text-gray-500"> .</p>
</div> </div>
)} )}
</div> </div>

View file

@ -1,4 +1,4 @@
import { component$, useSignal, $, useVisibleTask$, useContext } from "@builder.io/qwik"; import { component$, useSignal, $, useContext, useVisibleTask$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city"; import type { DocumentHead } from "@builder.io/qwik-city";
import { useNavigate, useLocation, Link, routeLoader$ } from "@builder.io/qwik-city"; import { useNavigate, useLocation, Link, routeLoader$ } from "@builder.io/qwik-city";
import axios from "axios"; import axios from "axios";
@ -32,7 +32,7 @@ export default component$(() => {
}); });
const userData = useContext(AppStateContext); const userData = useContext(AppStateContext);
const searchQuery = useSignal(''); const searchQuery = useSignal('');
const selectedMonth = useSignal(new Date().getMonth()); const selectedMonth = useSignal(new Date().getMonth() + 1);
const showMonth = useSignal(false); const showMonth = useSignal(false);
const handleMonthChange = $(() => { const handleMonthChange = $(() => {
showMonth.value = !showMonth.value; showMonth.value = !showMonth.value;
@ -40,14 +40,16 @@ export default component$(() => {
const handleMonthSelect = $((month : number) => { const handleMonthSelect = $((month : number) => {
selectedMonth.value = month; selectedMonth.value = month;
console.log(selectedMonth.value); console.log(selectedMonth.value);
filteredEntries.value = entries.value.filter((entry) => { filteredEntries.value = [...entries.value.filter((entry) => {
const entryMonth = new Date(entry.created_at).getMonth() + 1; const entryMonth = new Date(entry.created_at).getMonth() + 1;
return entryMonth === selectedMonth.value; return entryMonth === month;
}); })];
console.log(filteredEntries.value); console.log(filteredEntries.value);
showMonth.value = false; showMonth.value = false;
}); });
useVisibleTask$(() => {
handleMonthSelect(new Date().getMonth() + 1);
});
const handleSearch = $(() => { const handleSearch = $(() => {
if(searchQuery.value === '') { if(searchQuery.value === '') {
filteredEntries.value = [...entries.value]; filteredEntries.value = [...entries.value];
@ -92,7 +94,7 @@ export default component$(() => {
</div> </div>
</div> </div>
<div class="bg-white rounded-lg border-t-2 border-b-2 border-text-default overflow-hidden"> <div class="bg-white border-t-2 border-b-2 border-text-default overflow-hidden">
<div class="divide-y divide-text-default"> <div class="divide-y divide-text-default">
<div class="px-6 py-2 bg-diary-color transition-colors cursor-pointer"> <div class="px-6 py-2 bg-diary-color transition-colors cursor-pointer">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@ -100,8 +102,8 @@ export default component$(() => {
<span class="text-sm flex-1 text-end whitespace-nowrap overflow-hidden text-ellipsis text-black font-bold text-default"></span> <span class="text-sm flex-1 text-end whitespace-nowrap overflow-hidden text-ellipsis text-black font-bold text-default"></span>
</div> </div>
</div> </div>
{ filteredEntries.value.map((entry, index) => ( { filteredEntries.value.map((entry) => (
<div key={index} class="px-6 py-2 hover:bg-gray-50 transition-colors cursor-pointer"> <div key={entry.id} class="px-6 py-2 hover:bg-gray-50 transition-colors cursor-pointer">
<Link href={`/${location.params.username}/diary/${entry.id}`} class="flex items-center justify-between"> <Link href={`/${location.params.username}/diary/${entry.id}`} class="flex items-center justify-between">
<span class="text-base flex-1 text-center whitespace-nowrap overflow-hidden text-ellipsis font-medium text-default">{entry.title}</span> <span class="text-base flex-1 text-center whitespace-nowrap overflow-hidden text-ellipsis font-medium text-default">{entry.title}</span>
<span class="text-sm flex-1 text-end whitespace-nowrap overflow-hidden text-ellipsis font-medium text-default">{new Date(entry.created_at).toLocaleString()}</span> <span class="text-sm flex-1 text-end whitespace-nowrap overflow-hidden text-ellipsis font-medium text-default">{new Date(entry.created_at).toLocaleString()}</span>

View file

@ -90,12 +90,18 @@ export default component$(() => {
bind:value={title} bind:value={title}
type="text" type="text"
placeholder="제목을 입력해주세요." placeholder="제목을 입력해주세요."
class="w-full text-2xl font-bold p-2 focus:outline-none" class="w-full text-2xl font-bold p-2 text-black focus:outline-none"
/> />
<div class="flex gap-3"> <div class="flex gap-3">
<button onClick$={() => nav(`/${location.params.username}/diary`)}> <button onClick$={() => nav(`/${location.params.username}/diary`)}>
<i class="bi bi-trash-fill text-3xl text-black"></i> <i class="bi bi-trash-fill text-3xl text-black"></i>
</button> </button>
<button
class="px-2"
onClick$={() => showPhotoUploadModal.value = true}
>
<i class="bi bi-image-fill text-black text-3xl"></i>
</button>
<button onClick$={() => handleSend()}> <button onClick$={() => handleSend()}>
<i class="bi bi-send-fill text-3xl text-black"></i> <i class="bi bi-send-fill text-3xl text-black"></i>
</button> </button>
@ -104,7 +110,7 @@ export default component$(() => {
{/* Image upload and preview section */} {/* Image upload and preview section */}
<div class="p-4"> <div class="p-4">
<div class="flex gap-2 overflow-x-auto py-2"> <div class="flex gap-2 overflow-x-auto py-1">
{selectedImages.value.map((image, index) => ( {selectedImages.value.map((image, index) => (
<div key={index} class="relative"> <div key={index} class="relative">
<img <img
@ -114,17 +120,12 @@ export default component$(() => {
/> />
<button <button
onClick$={() => handleRemoveImage(index)} onClick$={() => handleRemoveImage(index)}
class="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center" class="absolute -top-2 -right-2 bg-button-color-1 text-white rounded-full w-6 h-6 flex items-center justify-center"
> >
× ×
</button> </button>
</div> </div>
))} ))}
<button
onClick$={() => showPhotoUploadModal.value = true}
>
<i class="bi bi-image-fill text-black text-2xl"></i>
</button>
</div> </div>
</div> </div>
@ -133,7 +134,7 @@ export default component$(() => {
ref={textareaRef} ref={textareaRef}
bind:value={content} bind:value={content}
placeholder="본문을 입력해주세요" placeholder="본문을 입력해주세요"
class="w-full min-h-[70vh] p-4 bg-white border border-text-default rounded-lg focus:outline-none resize-none leading-relaxed whitespace-pre-wrap" class="w-full min-h-[50vh] p-4 bg-white border border-text-default rounded-lg focus:outline-none resize-none leading-relaxed whitespace-pre-wrap"
style={{ style={{
backgroundImage: 'linear-gradient(#f1f1f1 1px, transparent 1px)', backgroundImage: 'linear-gradient(#f1f1f1 1px, transparent 1px)',
backgroundSize: '100% 1.5rem', backgroundSize: '100% 1.5rem',
@ -143,13 +144,6 @@ export default component$(() => {
}} }}
/> />
</div> </div>
<div >
{selectedImages.value.map((file, index) => (
<div key={index}>
<img src={URL.createObjectURL(file)} alt={`Preview ${index}`} />
</div>
))}
</div>
{showPhotoUploadModal.value && {showPhotoUploadModal.value &&
<div class="fixed inset-0 bg-black bg-opacity-45 flex items-center justify-center z-50" onClick$={() => showPhotoUploadModal.value = false}> <div class="fixed inset-0 bg-black bg-opacity-45 flex items-center justify-center z-50" onClick$={() => showPhotoUploadModal.value = false}>
<div class="bg-white rounded-lg w-[400px] p-6 shadow-xl relative" onClick$={(e) => e.stopPropagation()}> <div class="bg-white rounded-lg w-[400px] p-6 shadow-xl relative" onClick$={(e) => e.stopPropagation()}>

View file

@ -1,6 +1,6 @@
import { component$, useSignal, $, useVisibleTask$} from "@builder.io/qwik"; import { component$, useSignal, $, useVisibleTask$, useTask$} from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city"; import type { DocumentHead } from "@builder.io/qwik-city";
import { routeLoader$, useLocation } from "@builder.io/qwik-city"; import { routeLoader$ } from "@builder.io/qwik-city";
import { getCookie } from "~/utils/cookie"; import { getCookie } from "~/utils/cookie";
import axios from "axios"; import axios from "axios";
import DeleteModal from "~/components/features/guestbook/DeleteModal"; import DeleteModal from "~/components/features/guestbook/DeleteModal";
@ -60,8 +60,6 @@ export const useGuestbookLoader = routeLoader$(async ({params, cookie}) => {
} }
}); });
export default component$(() => { export default component$(() => {
const location = useLocation();
const username = location.params.username;
const loaderData = useGuestbookLoader(); const loaderData = useGuestbookLoader();
const messages = useSignal<GuestbookContent[]>([]); const messages = useSignal<GuestbookContent[]>([]);
const newMessage = useSignal(''); const newMessage = useSignal('');
@ -69,13 +67,14 @@ export default component$(() => {
const showDeleteModal = useSignal(false); const showDeleteModal = useSignal(false);
const selectedMessage = useSignal<GuestbookContent>({} as GuestbookContent); const selectedMessage = useSignal<GuestbookContent>({} as GuestbookContent);
const updatedMessage = useSignal(''); const updatedMessage = useSignal('');
const diaryContainer = useSignal<HTMLElement>();
const refreshMessages = $(async () => { const refreshMessages = $(async () => {
try { try {
const res = await axios.get<GuestbookContent[]>(`http://localhost:8000/api/guestbook/${loaderData.value.targetUser.id}`); const res = await axios.get<GuestbookContent[]>(`http://localhost:8000/api/guestbook/${loaderData.value.targetUser.id}`);
messages.value = res.data; messages.value = res.data;
console.log(messages.value + " loader"); console.log(messages.value + " loader");
} catch (error) { } catch (error) {
console.error(error);
console.log("방명록 가져오기 실패" +loaderData.value.targetUser.id); console.log("방명록 가져오기 실패" +loaderData.value.targetUser.id);
} }
}); });
@ -84,6 +83,16 @@ export default component$(() => {
refreshMessages(); refreshMessages();
}); });
useTask$(({track}) => {
track(() => messages.value);
// setTimeout(() => {
if (diaryContainer.value) {
setTimeout(() => {
diaryContainer.value!.scrollTop = diaryContainer.value!.scrollHeight;
}, 0);
}
// }, 100);
});
const handleSubmit = $(async () => { const handleSubmit = $(async () => {
if (!newMessage.value.trim()) return; if (!newMessage.value.trim()) return;
try { try {
@ -127,7 +136,7 @@ export default component$(() => {
return ( return (
<div class="flex flex-col h-screen bg-gray-50"> <div class="flex flex-col h-screen bg-gray-50">
{showDeleteModal.value && <DeleteModal messages={messages} showDeleteModal={showDeleteModal} selectedMessage={selectedMessage}/>} {showDeleteModal.value && <DeleteModal messages={messages} showDeleteModal={showDeleteModal} selectedMessage={selectedMessage}/>}
<div class="flex-1 overflow-y-auto p-4 space-y-4 divide-y-2 divide-gray-200"> <div ref={diaryContainer} class="flex-1 overflow-y-auto p-4 space-y-4 divide-y-[1px] divide-gray-200">
{messages.value.map((msg) => ( {messages.value.map((msg) => (
<div key={msg.id} class="flex relative items-start pt-2 space-x-3"> <div key={msg.id} class="flex relative items-start pt-2 space-x-3">
<div class={`w-14 h-14 rounded-full text-black`}> <div class={`w-14 h-14 rounded-full text-black`}>

View file

@ -30,15 +30,12 @@ export const useDataLoader = routeLoader$(async ({params, cookie}) => {
// console.log(userid.data.id + "<- 이것은 아이디"); // console.log(userid.data.id + "<- 이것은 아이디");
const user = await axios.get<User>(`http://localhost:8000/api/user/profile/${params.username}`); const user = await axios.get<User>(`http://localhost:8000/api/user/profile/${params.username}`);
const [room, avatar, friendRequest, diaries] = await Promise.all([ const [room, avatar, friendRequest, diaries, guestbooks] = await Promise.all([
axios.get(`http://localhost:8000/api/room/layout/${user.data.id}`, { headers }), axios.get(`http://localhost:8000/api/room/layout/${user.data.id}`, { headers }),
axios.get(`http://localhost:8000/api/avatar/${user.data.id}`, { headers }), axios.get(`http://localhost:8000/api/avatar/${user.data.id}`, { headers }),
axios.get(`http://localhost:8000/api/friendship/pending`, { headers }), axios.get(`http://localhost:8000/api/friendship/pending`, { headers }),
axios.get(`http://localhost:8000/api/diary/w/${user.data.id}?skip=0&limit=6`, { axios.get(`http://localhost:8000/api/diary/w/${user.data.id}?skip=0&limit=6`, { headers }),
headers: { axios.get(`http://localhost:8000/api/guestbook/${user.data.id}`, { headers }),
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
},
}),
]); ]);
// const avatar = await axios.get(`http://localhost:8000/api/avatar/${userid.data.id}`, { // const avatar = await axios.get(`http://localhost:8000/api/avatar/${userid.data.id}`, {
@ -48,10 +45,11 @@ export const useDataLoader = routeLoader$(async ({params, cookie}) => {
// }); // });
// console.log(res.data+ "다이어리 데이터 불러오기"); // console.log(res.data+ "다이어리 데이터 불러오기");
// console.log("??/" + res.data.diaries); // console.log("??/" + res.data.diaries);
return {diaries: diaries.data, friendRequest: friendRequest.data as FriendRequest[], userdata: {room: room.data, avatar: avatar.data} }; // console.log(room.room.room_type);
return {diaries: diaries.data, guestbooks: guestbooks.data, friendRequest: friendRequest.data as FriendRequest[], userdata: {room: room.data, avatar: avatar.data} };
} catch (error : any) { } catch (error : any) {
console.error("에러 : " + error.config.url); console.error("에러 : " + error.config.url);
return {diaries: [], friendRequest: [] as FriendRequest[], userdata: {room: [], avatar: [] } }; return {diaries: [], guestbooks: [], friendRequest: [] as FriendRequest[], userdata: {room: [], avatar: [] } };
} }
}); });
export default component$(() => { export default component$(() => {
@ -67,12 +65,13 @@ export default component$(() => {
}) })
const handleAcceptFriend = $( async (friendship_id : number) => { const handleAcceptFriend = $( async (friendship_id : number) => {
try { try {
console.log(`http://localhost:8000/api/friendship/${friendship_id}/accept`); console.log(getCookie("access_token"));
const res = await axios.put(`http://localhost:8000/api/friendship/${friendship_id}/accept`, { const res = await axios.put(`http://localhost:8000/api/friendship/${friendship_id}/accept`, {}, {
headers: { headers: {
Authorization: `Bearer ${getCookie("access_token")}`, Authorization: `Bearer ${getCookie("access_token")}`,
}, },
}) })
console.log(res.data);
data.value.friendRequest = data.value.friendRequest.filter((friend) => friend.id !== friendship_id); data.value.friendRequest = data.value.friendRequest.filter((friend) => friend.id !== friendship_id);
} catch (error : any) { } catch (error : any) {
@ -91,7 +90,7 @@ export default component$(() => {
<section class="mb-6"> <section class="mb-6">
<div class="flex justify-between"> <div class="flex justify-between">
<h2 class="text-xl font-bold mb-2">Mini Room</h2> <h2 class="text-xl font-bold mb-2">Mini Room</h2>
<Link href="/miniroom" class="text-base text-[#4b3c28] font-semibold"> <Link href="room" class="text-base text-[#4b3c28] font-semibold">
<i class="bi bi-caret-right"></i> <i class="bi bi-caret-right"></i>
</Link> </Link>
</div> </div>
@ -120,12 +119,12 @@ export default component$(() => {
<div class="flex-1 pt-7 justify-start w-full lg:w-[220px] text-base lg:mt-0"> <div class="flex-1 pt-7 justify-start w-full lg:w-[220px] text-base lg:mt-0">
<div class="border-t border-dashed border-[#4b3c28] pt-2 pb-2"> <div class="border-t border-dashed border-[#4b3c28] pt-2 pb-2">
<div class="flex justify-between"> <div class="flex justify-between">
<span class="flex-1"> {data.value.diaries.length}</span> <Link href={`/${location.params.username}/diary`} class="flex-1"> {data.value.diaries.length}</Link>
{userData.username === location.params.username && <span onclick$={() => {showMyFriendsRequest.value = true}} class="flex-1 hover:text-[#4b3c28] cursor-pointer hover:underline"> {data.value.friendRequest.length}</span>} {userData.username === location.params.username && <span onclick$={() => {showMyFriendsRequest.value = true}} class="flex-1 hover:text-[#4b3c28] cursor-pointer hover:underline"> {data.value.friendRequest.length}</span>}
</div> </div>
</div> </div>
<div class="border-t border-b border-dashed border-[#4b3c28] pt-2 pb-2"> <div class="border-t border-b border-dashed border-[#4b3c28] pt-2 pb-2">
<span class="flex-1"> 4</span> <Link href={`/${location.params.username}/guestbook`} class="flex-1"> {data.value.guestbooks.length}</Link>
</div> </div>
</div> </div>
</section> </section>
@ -141,9 +140,9 @@ export default component$(() => {
{data.value.friendRequest.length > 0 ? ( {data.value.friendRequest.length > 0 ? (
<div class="divide-y-2 divide-gray-200 max-h-64 w-full overflow-y-auto"> <div class="divide-y-2 divide-gray-200 max-h-64 w-full overflow-y-auto">
{data.value.friendRequest.map((friend, i) => ( {data.value.friendRequest.map((friend, i) => (
<div key={i} class="flex items-center justify-between border-b-2 border-gray-200 py-3"> <div key={i} class="flex p-2 items-center justify-between border-b-2 border-gray-200 py-3">
<div class="flex items-center gap-3"><span class="font-medium text-sm">{friend.friend_username}</span></div> <div class="flex items-center gap-3"><span class="font-medium text-lg">{friend.friend_username}</span></div>
<button class="text-black hover:text-red-500" onclick$={() => {handleAcceptFriend(friend.id)}}><i class="bi bi-check"></i></button> <button class="text-black hover:text-red-500 flex items-center justify-center" onclick$={() => {handleAcceptFriend(friend.id)}}><i class="bi bi-check text-lg"></i></button>
</div> </div>
))} ))}
</div> </div>

View file

@ -1,12 +1,10 @@
import { component$, Slot, useServerData, useSignal } from "@builder.io/qwik"; import { component$, Slot, useSignal } from "@builder.io/qwik";
import { type DocumentHead, RequestHandler, routeLoader$ } from "@builder.io/qwik-city"; import { type DocumentHead, RequestHandler, routeLoader$ } from "@builder.io/qwik-city";
import Header from "~/components/layout/Header"; import Header from "~/components/layout/Header";
import { AddFriends } from "~/components/features/AddFriends"; import { AddFriends } from "~/components/features/AddFriends";
import axios from "axios"; import axios from "axios";
import jwt from "jsonwebtoken";
import { useContextProvider } from "@builder.io/qwik"; import { useContextProvider } from "@builder.io/qwik";
import { AppStateContext } from "~/utils/context"; import { AppStateContext } from "~/utils/context";
import { deleteCookie } from "~/utils/cookie";
export const onRequest: RequestHandler = async ({ cookie, sharedMap, redirect , params}) => { export const onRequest: RequestHandler = async ({ cookie, sharedMap, redirect , params}) => {
const token = cookie.get("access_token")?.value; const token = cookie.get("access_token")?.value;
console.log(token + "이것은 유저페이지로 들어온 token"); console.log(token + "이것은 유저페이지로 들어온 token");

View file

@ -2,4 +2,5 @@
import { createContextId } from '@builder.io/qwik'; import { createContextId } from '@builder.io/qwik';
export const AppStateContext = createContextId<User>('auth-state'); export const AppStateContext = createContextId<User>('auth-state');
export const DotoryContext = createContextId<number>('dotory-state');

View file

@ -22,5 +22,5 @@
/* if you do not use CSS modules, remove this line and delete the typescript-plugin-css-modules module from package.json */ /* if you do not use CSS modules, remove this line and delete the typescript-plugin-css-modules module from package.json */
"plugins": [{ "name": "typescript-plugin-css-modules" }] "plugins": [{ "name": "typescript-plugin-css-modules" }]
}, },
"include": ["src", "./*.d.ts", "./*.config.ts"] "include": ["src", "src/types", "./*.d.ts", "./*.config.ts"]
} }