mirror of
https://github.com/sunrin-ana/2025-SSF-Frontend.git
synced 2026-03-09 18:30:00 +00:00
Compare commits
2 commits
master
...
feat/guest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d95e0bf94 | ||
|
|
ba3b85d622 |
23 changed files with 1183 additions and 694 deletions
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,17 @@ 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/${item.id}?product_name=${item.name}`, {
|
const res = await axios.post(`http://localhost:8000/api/store/0?product_name=${item.name}`, {
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${getCookie("access_token")}`,
|
Authorization: `Bearer ${getCookie("access_token")}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
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">
|
||||||
구매하기
|
구매하기
|
||||||
|
|
|
||||||
|
|
@ -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> }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,299 +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);
|
||||||
|
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),
|
|
||||||
};
|
|
||||||
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: '어항1',
|
|
||||||
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})`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 드롭 시 호출
|
|
||||||
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"
|
|
||||||
style={{
|
|
||||||
gridColumn: cell.x,
|
|
||||||
gridRow: cell.y,
|
|
||||||
position: 'relative',
|
|
||||||
width: '64px',
|
|
||||||
height: '64px',
|
|
||||||
// border: '1px dashed rgba(255, 255, 255, 0.1)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: cell.furniture ? 'rgba(0, 0, 0, 0.2)' : 'transparent'
|
|
||||||
}}
|
|
||||||
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)}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
objectFit: 'contain',
|
|
||||||
imageRendering: 'pixelated',
|
|
||||||
cursor: 'move',
|
|
||||||
zIndex: 10
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</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 {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1 !important;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
.room {
|
.grid-overlay {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
height: 100%;
|
top: 0;
|
||||||
object-fit: cover;
|
left: 0;
|
||||||
}
|
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 {
|
}
|
||||||
position: absolute;
|
// .grid-overlay div.oneone > img {
|
||||||
top: 0;
|
// width: 140px;
|
||||||
left: 0;
|
// image-rendering: pixelated;
|
||||||
width: 100%;
|
// position: absolute;
|
||||||
height: 100%;
|
// bottom: 0;
|
||||||
display: grid;
|
// }
|
||||||
grid-template-columns: repeat(10, 1fr);
|
// .grid-overlay div.twoone > img {
|
||||||
grid-template-rows: repeat(10, 1fr);
|
// width: 140px;
|
||||||
pointer-events: auto;
|
// image-rendering: pixelated;
|
||||||
}
|
// position: absolute;
|
||||||
|
// transform: translate(-50%, -50%);
|
||||||
.avatar-wrapper {
|
// }
|
||||||
position: absolute;
|
.avatar-wrapper {
|
||||||
top: 50%;
|
width: 150px;
|
||||||
left: 50%;
|
height: 150px;
|
||||||
transform: translate(-50%, -50%);
|
position: absolute;
|
||||||
pointer-events: none;
|
top: 70%;
|
||||||
}
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
.avatar-wrapper img {
|
z-index: 99;
|
||||||
position: absolute;
|
}
|
||||||
top: 0;
|
.avatar-wrapper img {
|
||||||
left: 0;
|
position: absolute;
|
||||||
width: 75px;
|
top: 0;
|
||||||
height: 100px;
|
left: 0;
|
||||||
object-fit: contain;
|
width: 100%;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
z-index: 99;
|
||||||
|
}
|
||||||
.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;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrapper img {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
z-index: 99;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
148
src/components/room/ShowRoomGrid.tsx
Normal file
148
src/components/room/ShowRoomGrid.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { component$, useStylesScoped$ , useVisibleTask$ } from "@builder.io/qwik";
|
||||||
|
|
||||||
|
interface FurnitureItem {
|
||||||
|
image_path: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
size?: "one" | "two"; // 크기 구분 (one = 70px, two = 140px)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvatarItem {
|
||||||
|
avatar_type: {name: string, path: string};
|
||||||
|
top_clothe_type: {name: string, path: string};
|
||||||
|
bottom_clothe_type: {name: string, path: string};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShowRoomGridProps {
|
||||||
|
roomSrc: string; // 방 배경
|
||||||
|
furnitures: FurnitureItem[]; // 가구 리스트
|
||||||
|
avatars: AvatarItem; // 아바타 이미지 리스트
|
||||||
|
}
|
||||||
|
|
||||||
|
export default component$((props: ShowRoomGridProps) => {
|
||||||
|
console.log(props.roomSrc);
|
||||||
|
console.log(props.furnitures);
|
||||||
|
console.log(props.avatars + " avatars");
|
||||||
|
useStylesScoped$(STYLES);
|
||||||
|
useVisibleTask$(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div class="room-wrapper">
|
||||||
|
{/* 배경 */}
|
||||||
|
<img src={"http://localhost:8000/public/room/"+props.roomSrc+".png"} class="room" />
|
||||||
|
|
||||||
|
{/* 10x10 격자 */}
|
||||||
|
<div class="grid-overlay">
|
||||||
|
{Array.from({ length: 100 }).map((_, idx) => {
|
||||||
|
const item = props.furnitures.find((f) => f.x + f.y*10 === idx);
|
||||||
|
console.log(item);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
// class={
|
||||||
|
// item?.size === "one"
|
||||||
|
// ? "oneone"
|
||||||
|
// : item?.size === "two"
|
||||||
|
// ? "twoone"
|
||||||
|
// : undefined
|
||||||
|
// }
|
||||||
|
// style={
|
||||||
|
// item?.size === "two"
|
||||||
|
// ? { gridColumn: `span 2`, gridRow: `span 1` }
|
||||||
|
// : undefined
|
||||||
|
// }
|
||||||
|
>
|
||||||
|
{item && <img src={"http://localhost:8000/" + item.image_path} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 아바타 */}
|
||||||
|
<div class="avatar-wrapper">
|
||||||
|
|
||||||
|
<img src={"http://localhost:8000/"+props.avatars.avatar_type.path} />
|
||||||
|
<img src={"http://localhost:8000/"+props.avatars.top_clothe_type.path} />
|
||||||
|
<img src={"http://localhost:8000/"+props.avatars.bottom_clothe_type.path} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const STYLES = `
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
@ -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 = '';
|
||||||
|
|
|
||||||
|
|
@ -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}`,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { component$, Slot } from "@builder.io/qwik";
|
import { component$ } from "@builder.io/qwik";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ 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";
|
||||||
export const onRequest: RequestHandler = async ({ cookie, params, sharedMap}) => {
|
// import { DotoryContext } from "~/utils/context";
|
||||||
|
export const onGet: RequestHandler = async ({ cookie, params, sharedMap}) => {
|
||||||
|
|
||||||
console.log(params.username + "의 상점 페이지로 들어옴");
|
console.log(params.username + "의 상점 페이지로 들어옴");
|
||||||
|
|
||||||
|
|
@ -15,7 +16,7 @@ export const onRequest: RequestHandler = async ({ cookie, params, sharedMap}) =>
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sharedMap.set("dotory", res.data ?? 0);
|
sharedMap.set("dotory", res.data.dotory ?? 0);
|
||||||
console.log(res.data ?? "no data");
|
console.log(res.data ?? "no data");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
@ -28,20 +29,17 @@ 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="w-12 h-12 bg-[#33231A] [clip-path:polygon(20%_0,100%_0,80%_100%,20%_100%,0_80%,0_20%)]">
|
|
||||||
</div> */}
|
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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,47 +43,26 @@ 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 = {
|
||||||
|
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const myRoom = await axios.get("http://localhost:8000/api/room/layout", {
|
const [myRoom, myAvatar, roomType, furniture, avatarType] = await Promise.all([
|
||||||
headers: {
|
axios.get("http://localhost:8000/api/room/layout", { headers }),
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
axios.get("http://localhost:8000/api/avatar", { headers }),
|
||||||
},
|
axios.get("http://localhost:8000/api/room/types", { headers }),
|
||||||
})
|
axios.get("http://localhost:8000/api/room/my", { headers }),
|
||||||
const myAvatar = await axios.get("http://localhost:8000/api/avatar", {
|
axios.get("http://localhost:8000/api/avatar/options", { headers }),
|
||||||
headers: {
|
]);
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
return {myData : {room: myRoom.data, avatar: myAvatar.data}, selectionData: {roomType: roomType.data, furniture: furniture.data, avatarType: avatarType.data}};
|
||||||
},
|
|
||||||
})
|
|
||||||
const roomType = await axios.get("http://localhost:8000/api/room/types", {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const furniture = await axios.get("http://localhost:8000/api/room/my", {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const avatarType = await axios.get("http://localhost:8000/api/avatar/options", {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const roomTypeData = roomType.data;
|
|
||||||
const avatar : Avatar = myAvatar.data;
|
|
||||||
const room : Room = myRoom.data;
|
|
||||||
const furnitureData = furniture.data;
|
|
||||||
const avatarTypeData = avatarType.data;
|
|
||||||
console.log(myRoom.data);
|
|
||||||
return {myData : {room: room, avatar: avatar}, selectionData: {roomType: roomTypeData, furniture: furnitureData, avatarType: avatarTypeData}};
|
|
||||||
} catch (error : any) {
|
} catch (error : any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return error.response?.data?.detail || 'Room not found';
|
return error.response?.data?.detail || 'Room not found';
|
||||||
|
|
@ -85,123 +70,186 @@ export const useRoomLoader = routeLoader$(async ({cookie}) => {
|
||||||
})
|
})
|
||||||
export default component$(() => {
|
export default component$(() => {
|
||||||
const data = useRoomLoader();
|
const data = useRoomLoader();
|
||||||
const selectedFurniture = useSignal<Furniture[]>([]); // 유저 선택 가구
|
// const navigate = useNavigate();
|
||||||
const selectedType = useSignal("furniture"); // 헤더에서 가구, 아바타, 배경 구분
|
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 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 = $((selectedFurniture : Furniture) => {
|
// 가구 배치 함수
|
||||||
const furnitureRes = axios.delete("http://localhost:8000/api/room/furniture", {
|
const placeFurniture = $(async (furniture: Furniture, x: number, y: number) => {
|
||||||
data: {
|
|
||||||
furniture_name: selectedFurniture.furniture_name,
|
|
||||||
x: selectedFurniture.x,
|
|
||||||
y: selectedFurniture.y,
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${getCookie("access_token")}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// const getNewState = async () => {
|
|
||||||
// const myRoom = await axios.get("http://localhost:8000/api/room/layout", {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${getCookie("access_token")}`,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// const myAvatar = await axios.get("http://localhost:8000/api/avatar", {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${getCookie("access_token")}`,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// const roomType = await axios.get("http://localhost:8000/api/room/types", {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${getCookie("access_token")}`,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// const furniture = await axios.get("http://localhost:8000/api/room/my", {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${getCookie("access_token")}`,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// const avatarType = await axios.get("http://localhost:8000/api/avatar/options", {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${getCookie("access_token")}`,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// const roomTypeData = roomType.data;
|
|
||||||
// const avatar : Avatar = myAvatar.data.avatar;
|
|
||||||
// const room : Room = myRoom.data;
|
|
||||||
// const furnitureData = furniture.data;
|
|
||||||
// const avatarTypeData = avatarType.data;
|
|
||||||
// console.log(myRoom.data);
|
|
||||||
// }
|
|
||||||
const handleChange = $( async () => {
|
|
||||||
console.log("handleChange");
|
|
||||||
try {
|
try {
|
||||||
// 배경 설정
|
await axios.post("http://localhost:8000/api/room/furniture", {
|
||||||
console.log(selectedRoomType.value);
|
furniture_name: furniture.furniture_name,
|
||||||
const roomRes = await axios.patch("http://localhost:8000/api/room/",
|
x: x,
|
||||||
{
|
y: y
|
||||||
type: selectedRoomType.value
|
}, {
|
||||||
},
|
headers: { Authorization: `Bearer ${getCookie("access_token")}` }
|
||||||
{
|
});
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${getCookie("access_token")}`,
|
// 성공 시 placedFurnitures에 추가
|
||||||
},
|
if (!mergedFurnitures.value.find(f => f.furniture_name === furniture.furniture_name)) {
|
||||||
}
|
mergedFurnitures.value = Array.from(new Set([
|
||||||
);
|
...mergedFurnitures.value,
|
||||||
const avatarRes = await axios.put("http://localhost:8000/api/avatar", {
|
{
|
||||||
|
furniture_name: furniture.furniture_name, // SendFurniture has 'name' property
|
||||||
avatar_type: selectedAvatar.value.avatar_type,
|
image_path: furniture.image_path,
|
||||||
top_clothe_type: selectedAvatar.value.top_clothe_type,
|
x: x,
|
||||||
bottom_clothe_type: selectedAvatar.value.bottom_clothe_type,
|
y: y
|
||||||
},
|
}
|
||||||
{
|
]));
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${getCookie("access_token")}`,
|
placedFurnitures.value = Array.from(new Set([...placedFurnitures.value, furniture]));
|
||||||
},
|
}
|
||||||
}
|
else {
|
||||||
);
|
mergedFurnitures.value =Array.from(new Set([...mergedFurnitures.value]));
|
||||||
// for (let i = 0; i < selectedFurniture.value.length; i++) {
|
placedFurnitures.value = Array.from(new Set([...placedFurnitures.value]));
|
||||||
// const furnitureRes = await axios.post("http://localhost:8000/api/room/furniture", {
|
}
|
||||||
// furniture_name: selectedFurniture.value[i].furniture_name,
|
console.log('Placed fu rniture:', mergedFurnitures.value);
|
||||||
// x: selectedFurniture.value[i].x,
|
|
||||||
// y: selectedFurniture.value[i].y,
|
return true;
|
||||||
// },
|
} catch (error: any) {
|
||||||
// {
|
console.error('가구 배치 실패:', error);
|
||||||
// headers: {
|
errorMessage.value = error.response.data.detail;
|
||||||
// Authorization: `Bearer ${getCookie("access_token")}`,
|
setTimeout(() => { errorMessage.value = ""; }, 10000);
|
||||||
// },
|
return false;
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// getNewState();
|
|
||||||
console.log(roomRes.data);
|
|
||||||
console.log(avatarRes.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
const handleCancel = $(() => {
|
|
||||||
console.log("handleCancel");
|
// 가구 제거 함수
|
||||||
|
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 () => {
|
||||||
|
try {
|
||||||
|
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")}`}});
|
||||||
|
|
||||||
|
} 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={selectedFurniture} avatar={selectedAvatar} roomType={selectedRoomType} />
|
|
||||||
|
<RoomGrid
|
||||||
|
furnitures={placedFurnitures}
|
||||||
|
avatars={selectedAvatar}
|
||||||
|
roomSrc={selectedRoomType}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -251,13 +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">
|
||||||
<div key={furniture.furniture_name} class={`flex items-center flex-col gap-2 rounded-t-lg` } onClick$={() => {if(selectedFurniture.value.includes(furniture)) selectedFurniture.value = selectedFurniture.value.filter((item: Furniture) => item !== furniture); else selectedFurniture.value.push(furniture);}}>
|
{/* 배치된 가구 목록 - 각각 입력창 표시 */}
|
||||||
<img src={`http://localhost:8000/${furniture.image_path}`} alt={furniture.furniture_name} style={{imageRendering: "pixelated"}} class={` object-contain w-24 h-24 ${selectedFurniture.value.includes(furniture) ? "border-[2px] border-[#FAD659]" : ""}`} />
|
<div class="space-y-3">
|
||||||
<span class="text-center font-bold rounded-b-lg w-24 h-12 bg-[#E5E2B6]">{furniture.furniture_name}</span>
|
{mergedFurnitures.value.map((furniture: Furniture) => (
|
||||||
|
<div key={`${furniture.furniture_name}-${furniture.x}-${furniture.y}`} class="bg-white border border-text-default rounded-lg p-4">
|
||||||
|
<div class="flex items-center gap-3 mb-3">
|
||||||
|
<img
|
||||||
|
src={`http://localhost:8000/${furniture.image_path}`}
|
||||||
|
alt={furniture.furniture_name}
|
||||||
|
style={{imageRendering: "pixelated"}}
|
||||||
|
class="object-contain w-12 h-12"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h5 class="font-medium text-text-default">
|
||||||
|
{furniture.furniture_name}
|
||||||
|
</h5>
|
||||||
|
{furniture.x !== -1 && furniture.y !== -1 && (
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</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>
|
||||||
)))
|
</div>
|
||||||
:
|
) :
|
||||||
selectedType.value === "avatar" ? (
|
selectedType.value === "avatar" ? (
|
||||||
<div class="flex flex-col gap-3 ">
|
<div class="flex flex-col gap-3 ">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -364,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"
|
||||||
|
|
|
||||||
|
|
@ -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,30 +10,40 @@ 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 {
|
||||||
|
// const getDotory = await axios.put(`http://localhost:8000/api/store?dotory_num=100`, {
|
||||||
|
// headers: {
|
||||||
|
// Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
|
// },
|
||||||
|
// // });
|
||||||
|
// const dotory = await axios.get(`http://localhost:8000/api/store`, {
|
||||||
|
// headers: {
|
||||||
|
// Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
const res = await axios.get(`http://localhost:8000/api/room/catalog`, {
|
const res = await axios.get(`http://localhost:8000/api/room/catalog`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
|
|
@ -44,47 +54,93 @@ 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);
|
||||||
return {storeData: res.data, avatarData: avatar.data, dotory: 0};
|
console.log(dotory.data);
|
||||||
|
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};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export default component$(() => {
|
export default component$(() => {
|
||||||
const data = useShopLoader() as { value: ShopResponse };
|
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-fill text-3xl text-black"></i>
|
<span>구매하기</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
@ -92,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>
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,273 @@
|
||||||
import { component$, useSignal } from "@builder.io/qwik";
|
import { $, component$, useSignal, useVisibleTask$, NoSerialize, useTask$ } from "@builder.io/qwik";
|
||||||
import { DocumentHead , RequestHandler, routeLoader$} from "@builder.io/qwik-city";
|
import { routeLoader$, useLocation, useNavigate } from "@builder.io/qwik-city";
|
||||||
import { useNavigate } from "@builder.io/qwik-city";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
const diaryLoader = routeLoader$<{ message: string }>(async ({params, cookie, redirect}) => {
|
import { noSerialize } from "@builder.io/qwik";
|
||||||
|
// import { PhotoUploadModal } from "~/components/features/diary/UploadImages";
|
||||||
try {
|
import { getCookie } from "~/utils/cookie";
|
||||||
console.log(params.username + "의 방명록 가져오기");
|
interface FileInfo {
|
||||||
const myInfo = await axios.get(`http://localhost:8000/api/user/me`, {
|
name: string;
|
||||||
headers: {
|
size: number;
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
type: string;
|
||||||
},
|
lastModified: number;
|
||||||
});
|
url: string;
|
||||||
if(myInfo.data.username !== params.username) {
|
}
|
||||||
return redirect(302, `/diary/${params.diary_id}`);
|
// interface DiaryData {
|
||||||
}
|
// content: string;
|
||||||
const res = await axios.get(`http://localhost:8000/api/diary/${params.diary_id}`);
|
// title: string;
|
||||||
console.log(res.data);
|
// category: string;
|
||||||
return res.data;
|
// images: File[];
|
||||||
} catch (error : any) {
|
// }
|
||||||
console.error(error);
|
export const diaryDataLoader = routeLoader$(async ({params, cookie}) => {
|
||||||
return error.response?.data?.detail || 'Diary not found';
|
const access_token = cookie.get("access_token");
|
||||||
|
const diary_id = params.diary_id;
|
||||||
|
const diaryData = await axios.get(`http://localhost:8000/api/diary/${diary_id}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access_token?.value}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return diaryData.data;
|
||||||
|
})
|
||||||
|
export default component$(() => {
|
||||||
|
const diaryData = diaryDataLoader();
|
||||||
|
const content = useSignal('');
|
||||||
|
const title = useSignal('');
|
||||||
|
const category = useSignal('');
|
||||||
|
const selectedImages = useSignal<File[]>([] );
|
||||||
|
const textareaRef = useSignal<HTMLTextAreaElement>();
|
||||||
|
const nav = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const showPhotoUploadModal = useSignal(false);
|
||||||
|
const fileInfo = useSignal<FileInfo | null>(null);
|
||||||
|
const fileRef = useSignal<NoSerialize<File> | null>(null);
|
||||||
|
const error = useSignal('');
|
||||||
|
// const fileInputRef = useSignal<HTMLInputElement>();
|
||||||
|
useTask$(() => {
|
||||||
|
content.value = diaryData.value.content;
|
||||||
|
title.value = diaryData.value.title;
|
||||||
|
category.value = diaryData.value.category;
|
||||||
|
// selectedImages.value = diaryData.value.images;
|
||||||
|
})
|
||||||
|
const handleFileSelect = $(async (selectedFile: File) => {
|
||||||
|
if (selectedFile && selectedFile.type.startsWith('image/')) {
|
||||||
|
fileInfo.value = {
|
||||||
|
name: selectedFile.name,
|
||||||
|
size: selectedFile.size,
|
||||||
|
type: selectedFile.type,
|
||||||
|
lastModified: selectedFile.lastModified,
|
||||||
|
url: URL.createObjectURL(selectedFile)
|
||||||
|
};
|
||||||
|
fileRef.value = noSerialize(selectedFile);
|
||||||
|
error.value = '';
|
||||||
|
console.log(fileInfo.value);
|
||||||
|
} else {
|
||||||
|
error.value = '이미지 파일만 업로드 가능합니다.';
|
||||||
|
fileInfo.value = null;
|
||||||
|
fileRef.value = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export default component$(() => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const diaryData = diaryLoader();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>일기 수정</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const head: DocumentHead = {
|
const handleRemoveImage = $((index: number) => {
|
||||||
title: "일기수정하기",
|
selectedImages.value = selectedImages.value.filter((_, i) => i !== index);
|
||||||
meta: [
|
});
|
||||||
{
|
|
||||||
name: "description",
|
const handleSend = $(async () => {
|
||||||
content: "유저가 쓴 일기의 상세정보",
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('title', title.value);
|
||||||
|
formData.append('content', content.value);
|
||||||
|
formData.append('category', category.value);
|
||||||
|
|
||||||
|
// Append each image file
|
||||||
|
selectedImages.value.forEach((file) => {
|
||||||
|
formData.append('file', file);
|
||||||
|
});
|
||||||
|
|
||||||
|
await axios.put(`http://localhost:8000/api/diary/${location.params.diary_id}`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
Authorization: `Bearer ${getCookie("access_token")}`,
|
||||||
},
|
},
|
||||||
],
|
});
|
||||||
};
|
|
||||||
|
nav(`/${location.params.username}/diary`);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error creating diary:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useVisibleTask$(({ track }) => {
|
||||||
|
track(() => content.value);
|
||||||
|
if (textareaRef.value) {
|
||||||
|
textareaRef.value.style.height = 'auto';
|
||||||
|
textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex justify-between items-center mb-8">
|
||||||
|
<h1 class="text-2xl font-bold">일기 쓰기</h1>
|
||||||
|
</div>
|
||||||
|
<div class="border-b-2 border-text-default flex flex-row justify-between items-center">
|
||||||
|
<input
|
||||||
|
bind:value={title}
|
||||||
|
type="text"
|
||||||
|
placeholder="제목을 입력해주세요."
|
||||||
|
class="w-full text-2xl font-bold p-2 focus:outline-none"
|
||||||
|
/>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<button onClick$={() => nav(`/${location.params.username}/diary`)}>
|
||||||
|
<i class="bi bi-trash-fill text-3xl text-black"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="px-2"
|
||||||
|
onClick$={() => showPhotoUploadModal.value = true}
|
||||||
|
>
|
||||||
|
<i class="bi bi-image-fill text-black text-3xl"></i>
|
||||||
|
</button>
|
||||||
|
<button onClick$={() => handleSend()}>
|
||||||
|
<i class="bi bi-send-fill text-3xl text-black"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Image upload and preview section */}
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex gap-2 overflow-x-auto py-2">
|
||||||
|
{selectedImages.value.map((image, index) => (
|
||||||
|
<div key={index} class="relative">
|
||||||
|
<img
|
||||||
|
src={URL.createObjectURL(image)}
|
||||||
|
alt={`Preview ${index}`}
|
||||||
|
class="h-24 w-24 object-cover rounded"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
<textarea
|
||||||
|
ref={textareaRef}
|
||||||
|
bind:value={content}
|
||||||
|
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"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'linear-gradient(#f1f1f1 1px, transparent 1px)',
|
||||||
|
backgroundSize: '100% 1.5rem',
|
||||||
|
lineHeight: '1.5rem',
|
||||||
|
backgroundAttachment: 'local',
|
||||||
|
backgroundPositionY: '0.75rem',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div >
|
||||||
|
{selectedImages.value.map((file, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<img src={URL.createObjectURL(file)} alt={`Preview ${index}`} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{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="bg-white rounded-lg w-[400px] p-6 shadow-xl relative" onClick$={(e) => e.stopPropagation()}>
|
||||||
|
<button
|
||||||
|
class="absolute top-4 right-4 text-xl text-black"
|
||||||
|
onClick$={() => showPhotoUploadModal.value = false}
|
||||||
|
aria-label="닫기"
|
||||||
|
>
|
||||||
|
<i class="bi bi-x-lg"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2 class="text-3xl font-bold text-center text-black">사진 업로드</h2>
|
||||||
|
<p class="text-xs p-2 text-gray-500 text-center border-b-2 border-black mb-6">
|
||||||
|
소중한 순간들을 기록하고 공유하세요
|
||||||
|
</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
|
||||||
|
class={`border-2 border-dashed rounded-md h-32 flex flex-col items-center justify-center text-sm cursor-pointer transition-colors ${
|
||||||
|
fileInfo.value
|
||||||
|
? 'border-green-300 bg-green-50 text-green-700'
|
||||||
|
: 'border-gray-300 bg-gray-100 text-gray-500 hover:border-blue-400'
|
||||||
|
}`}
|
||||||
|
onDragOver$={(e) => e.preventDefault()}
|
||||||
|
onDrop$={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const files = e.dataTransfer?.files;
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
await handleFileSelect(files[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
// onClick$={(e) => {
|
||||||
|
// // input 클릭 트리거
|
||||||
|
// e.stopPropagation();
|
||||||
|
// console.log("click");
|
||||||
|
// fileInputRef.value?.click();
|
||||||
|
// }}
|
||||||
|
>
|
||||||
|
{fileInfo.value ? (
|
||||||
|
<div class="text-center p-4">
|
||||||
|
<i class="bi bi-check-circle-fill text-2xl mb-1"></i>
|
||||||
|
<p class="font-medium">{fileInfo.value.name}</p>
|
||||||
|
<p class="text-xs">{(fileInfo.value.size / 1024).toFixed(1)} KB</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="text-center p-4">
|
||||||
|
<i class="bi bi-image-fill text-2xl mb-1"></i>
|
||||||
|
<p>클릭 또는 사진을 드래그하세요</p>
|
||||||
|
<p class="text-xs mt-1">JPG, PNG, GIF</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error.value && (
|
||||||
|
<p class="text-red-500 text-sm mt-2">{error.value}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="mt-6 w-full bg-[#FFAD84] hover:bg-[#ff9e6b] text-[#4b3c28] font-semibold py-2 rounded-md flex justify-center items-center gap-1 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled={!fileInfo.value}
|
||||||
|
onClick$={async () => {
|
||||||
|
if (fileRef.value) {
|
||||||
|
try {
|
||||||
|
console.log('Uploading:', {
|
||||||
|
file: fileRef.value,
|
||||||
|
fileInfo: fileInfo.value
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedImages.value = [...selectedImages.value, fileRef.value];
|
||||||
|
fileInfo.value = null;
|
||||||
|
fileRef.value = null;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('업로드 실패:', err);
|
||||||
|
error.value = '파일 업로드 중 오류가 발생했습니다.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
업로드 <i class="bi bi-cloud-upload"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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">
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { component$, useSignal, $, useTask$, 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";
|
||||||
|
|
@ -7,7 +7,7 @@ export const useDiaryLoader = routeLoader$( async ({cookie, params}) => {
|
||||||
try {
|
try {
|
||||||
const userid = await axios.get(`http://localhost:8000/api/user/profile/${params.username}`);
|
const userid = await axios.get(`http://localhost:8000/api/user/profile/${params.username}`);
|
||||||
console.log(userid.data.id + "<- 이건 아이디");
|
console.log(userid.data.id + "<- 이건 아이디");
|
||||||
const res = await axios.get(`http://localhost:8000/api/diary/w/{user_id}?target_user_id=${userid.data.id}&skip=0&limit=6`, {
|
const res = await axios.get(`http://localhost:8000/api/diary/w/${userid.data.id}?skip=0&limit=6`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
},
|
},
|
||||||
|
|
@ -25,35 +25,37 @@ export default component$(() => {
|
||||||
const entryLoader = useDiaryLoader();
|
const entryLoader = useDiaryLoader();
|
||||||
const entries = useSignal<DiaryEntry[]>([]);
|
const entries = useSignal<DiaryEntry[]>([]);
|
||||||
const filteredEntries = useSignal<DiaryEntry[]>([]);
|
const filteredEntries = useSignal<DiaryEntry[]>([]);
|
||||||
useTask$(() => {
|
useVisibleTask$(() => {
|
||||||
console.log( entryLoader.value.user);
|
console.log( entryLoader.value.user);
|
||||||
entries.value = entryLoader.value.diaries as DiaryEntry[];
|
entries.value = entryLoader.value.diaries as DiaryEntry[];
|
||||||
filteredEntries.value = entries.value;
|
filteredEntries.value = entries.value;
|
||||||
});
|
});
|
||||||
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;
|
||||||
});
|
});
|
||||||
const handleMonthSelect = $((month : number) => {
|
const handleMonthSelect = $((month : number) => {
|
||||||
selectedMonth.value = month;
|
selectedMonth.value = month;
|
||||||
filteredEntries.value = entries.value.filter((entry) => {
|
console.log(selectedMonth.value);
|
||||||
|
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);
|
||||||
console.log("<->" + selectedMonth.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];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
filteredEntries.value = entries.value.filter((entry) => entry.title.includes(searchQuery.value));
|
filteredEntries.value = [...entries.value.filter((entry) => entry.title.includes(searchQuery.value))];
|
||||||
}
|
}
|
||||||
console.log(filteredEntries.value);
|
console.log(filteredEntries.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>
|
||||||
|
|
|
||||||
|
|
@ -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()}>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { component$, useSignal, $, useTask$} 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,19 +67,37 @@ 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 {
|
||||||
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");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
console.log("방명록 가져오기 실패" +loaderData.value.targetUser.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
useTask$(() => {
|
useVisibleTask$(() => {
|
||||||
console.log("useTask");
|
console.log("useTask");
|
||||||
refreshMessages();
|
refreshMessages();
|
||||||
messages.value = loaderData.value.messages;
|
|
||||||
});
|
});
|
||||||
const handleSubmit = $(() => {
|
useTask$(({track}) => {
|
||||||
|
track(() => messages.value);
|
||||||
|
// setTimeout(() => {
|
||||||
|
if (diaryContainer.value) {
|
||||||
|
setTimeout(() => {
|
||||||
|
diaryContainer.value!.scrollTop = diaryContainer.value!.scrollHeight;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
// }, 100);
|
||||||
|
});
|
||||||
|
const handleSubmit = $(async () => {
|
||||||
if (!newMessage.value.trim()) return;
|
if (!newMessage.value.trim()) return;
|
||||||
try {
|
try {
|
||||||
axios.post(`http://localhost:8000/api/guestbook/`, {
|
console.log(newMessage.value);
|
||||||
|
await axios.post(`http://localhost:8000/api/guestbook`, {
|
||||||
content: newMessage.value,
|
content: newMessage.value,
|
||||||
target_user_id: loaderData.value.targetUser.id,
|
target_user_id: loaderData.value.targetUser.id,
|
||||||
}, {
|
}, {
|
||||||
|
|
@ -96,16 +112,20 @@ export default component$(() => {
|
||||||
}
|
}
|
||||||
newMessage.value = '';
|
newMessage.value = '';
|
||||||
});
|
});
|
||||||
const handleEdit = $(() => {
|
const handleEdit = $(async () => {
|
||||||
|
console.log("수정 요청 보내기");
|
||||||
|
if (!updatedMessage.value.trim()) {
|
||||||
|
console.log("수정 내용이 없습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
axios.put(`http://localhost:8000/api/guestbook/${selectedMessage.value.id}`, {
|
await axios.put(`http://localhost:8000/api/guestbook/${selectedMessage.value.id}`, {
|
||||||
content: updatedMessage.value,
|
content: updatedMessage.value,
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${getCookie("access_token")}`,
|
Authorization: `Bearer ${getCookie("access_token")}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
refreshMessages();
|
refreshMessages();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -116,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`}>
|
||||||
|
|
@ -154,9 +174,11 @@ export default component$(() => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{showUpdateModal.value && msg.id === selectedMessage.value.id && (
|
{showUpdateModal.value && msg.id === selectedMessage.value.id && (
|
||||||
<div
|
<form
|
||||||
class="absolute z-50 bg-white right-0 top-0 p-2 rounded-xl focus:outline-none w-full min-w-[220px]"
|
class="absolute z-50 bg-white right-0 top-0 p-2 rounded-xl focus:outline-none w-full min-w-[220px]"
|
||||||
onClick$={(e) => e.stopPropagation()} /* 외부 클릭으로 닫히는 건 부모가 처리하게 둘 수 있음 */
|
// onClick$={(e) => e.stopPropagation()} /* 외부 클릭으로 닫히는 건 부모가 처리하게 둘 수 있음 */
|
||||||
|
preventdefault:submit
|
||||||
|
onSubmit$={handleEdit}
|
||||||
>
|
>
|
||||||
<div class="flex mt-2 justify-between">
|
<div class="flex mt-2 justify-between">
|
||||||
<p>수정할 내용을 입력해주세요.</p>
|
<p>수정할 내용을 입력해주세요.</p>
|
||||||
|
|
@ -164,7 +186,7 @@ export default component$(() => {
|
||||||
<button class="btn-cancel text-button-color-1 border-black" onClick$={() => showUpdateModal.value = false}>
|
<button class="btn-cancel text-button-color-1 border-black" onClick$={() => showUpdateModal.value = false}>
|
||||||
<i class="bi bi-x"></i>
|
<i class="bi bi-x"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-save text-diary-color border-black" onClick$={handleEdit}>
|
<button class="btn-save text-diary-color border-black">
|
||||||
<i class="bi bi-check"></i>
|
<i class="bi bi-check"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -174,7 +196,7 @@ export default component$(() => {
|
||||||
onInput$={(ev: any) => (updatedMessage.value = ev.target.value)}
|
onInput$={(ev: any) => (updatedMessage.value = ev.target.value)}
|
||||||
class="w-full p-2 border border-input-border-color rounded-xl"
|
class="w-full p-2 border border-input-border-color rounded-xl"
|
||||||
/>
|
/>
|
||||||
</div>
|
</form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -191,7 +213,7 @@ export default component$(() => {
|
||||||
{/* <button class="absolute right-12 w-6 h-6 top-1/2 rounded-full bg-gray-500 p-0.5 transform -translate-x-1/2 -translate-y-1/2 text-center">
|
{/* <button class="absolute right-12 w-6 h-6 top-1/2 rounded-full bg-gray-500 p-0.5 transform -translate-x-1/2 -translate-y-1/2 text-center">
|
||||||
<i class="bi bi-images text-white text-center"></i>
|
<i class="bi bi-images text-white text-center"></i>
|
||||||
</button> */}
|
</button> */}
|
||||||
<button onclick$={handleSubmit} class="absolute right-4 w-6 h-6 top-1/2 rounded-full bg-gray-500 p-0.5 transform -translate-x-1/2 -translate-y-1/2 text-center">
|
<button class="absolute right-4 w-6 h-6 top-1/2 rounded-full bg-gray-500 p-0.5 transform -translate-x-1/2 -translate-y-1/2 text-center">
|
||||||
<i class="bi bi-send text-white text-center"></i>
|
<i class="bi bi-send text-white text-center"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { component$, useContext, useVisibleTask$ } from "@builder.io/qwik";
|
import { component$, useContext, useVisibleTask$ } from "@builder.io/qwik";
|
||||||
import { type DocumentHead, routeLoader$, Link, useLocation } from "@builder.io/qwik-city";
|
import { type DocumentHead, routeLoader$, Link, useLocation } from "@builder.io/qwik-city";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import RoomGrid from "~/components/room/RoomGrid";
|
import { useSignal, $ } from "@builder.io/qwik";
|
||||||
import { useContextProvider, useSignal, $ } from "@builder.io/qwik";
|
|
||||||
import { AppStateContext } from "~/utils/context";
|
import { AppStateContext } from "~/utils/context";
|
||||||
import { getCookie } from "~/utils/cookie";
|
import { getCookie } from "~/utils/cookie";
|
||||||
|
import ShowRoomGrid from "~/components/room/ShowRoomGrid";
|
||||||
interface FriendRequest {
|
interface FriendRequest {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
|
@ -13,42 +13,48 @@ interface FriendRequest {
|
||||||
status: string;
|
status: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
created_at: string;
|
||||||
|
profile_image_path: string;
|
||||||
|
is_active: boolean;
|
||||||
|
}
|
||||||
export const useDataLoader = routeLoader$(async ({params, cookie}) => {
|
export const useDataLoader = routeLoader$(async ({params, cookie}) => {
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// console.log(cookie.get("access_token")?.value + "이것은 토큰");
|
// console.log(cookie.get("access_token")?.value + "이것은 토큰");
|
||||||
const userid = await axios.get(`http://localhost:8000/api/user/profile/${params.username}`);
|
|
||||||
// console.log(userid.data.id + "<- 이것은 아이디");
|
// console.log(userid.data.id + "<- 이것은 아이디");
|
||||||
const res = await axios.get(`http://localhost:8000/api/diary/w/{userid.data.id}?target_user_id=${userid.data.id}&skip=0&limit=6`, {
|
|
||||||
headers: {
|
const user = await axios.get<User>(`http://localhost:8000/api/user/profile/${params.username}`);
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
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/avatar/${user.data.id}`, { headers }),
|
||||||
const friendRequest = await axios.get(`http://localhost:8000/api/friendship/pending`, {
|
axios.get(`http://localhost:8000/api/friendship/pending`, { headers }),
|
||||||
headers: {
|
axios.get(`http://localhost:8000/api/diary/w/${user.data.id}?skip=0&limit=6`, { headers }),
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
axios.get(`http://localhost:8000/api/guestbook/${user.data.id}`, { headers }),
|
||||||
},
|
]);
|
||||||
});
|
|
||||||
const room = await axios.get(`http://localhost:8000/api/room/layout`, {
|
// const avatar = await axios.get(`http://localhost:8000/api/avatar/${userid.data.id}`, {
|
||||||
headers: {
|
// headers: {
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
// Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
const avatar = await axios.get(`http://localhost:8000/api/avatar`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${cookie.get("access_token")?.value}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// console.log(res.data+ "다이어리 데이터 불러오기");
|
// console.log(res.data+ "다이어리 데이터 불러오기");
|
||||||
// console.log("??/" + res.data.diaries);
|
// console.log("??/" + res.data.diaries);
|
||||||
return {diaries: res.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);
|
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$(() => {
|
||||||
const data = useDataLoader();
|
const data = useDataLoader();
|
||||||
|
console.log(data.value.userdata.avatar);
|
||||||
const showMyFriendsRequest = useSignal<boolean>(false);
|
const showMyFriendsRequest = useSignal<boolean>(false);
|
||||||
const userData = useContext(AppStateContext);
|
const userData = useContext(AppStateContext);
|
||||||
const selectedRoom = useSignal({});
|
const selectedRoom = useSignal({});
|
||||||
|
|
@ -59,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) {
|
||||||
|
|
@ -83,13 +90,18 @@ 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>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col lg:flex-row gap-8">
|
<div class="flex flex-col lg:flex-row gap-8">
|
||||||
{/* <RoomGrid furniture={data.value.room} avatar={data.value.avatar} roomType={data.value.roomType} /> */}
|
{/* <RoomGrid furniture={data.value.room} avatar={data.value.avatar} roomType={data.value.roomType} /> */}
|
||||||
|
<ShowRoomGrid
|
||||||
|
roomSrc={data.value.userdata.room.room.room_type}
|
||||||
|
furnitures={data.value.userdata.room.furniture}
|
||||||
|
avatars={data.value.userdata.avatar}
|
||||||
|
/>
|
||||||
<section class="mb-6 flex flex-col justify-between gap-8">
|
<section class="mb-6 flex flex-col justify-between gap-8">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h2 class="text-xl w-full pl-3 pb-1 font-bold border-b-2 border-[#4b3c28] inline-block mb-2">Update</h2>
|
<h2 class="text-xl w-full pl-3 pb-1 font-bold border-b-2 border-[#4b3c28] inline-block mb-2">Update</h2>
|
||||||
|
|
@ -107,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>
|
||||||
|
|
@ -128,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>
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
0
src/types/diary.ts → src/types/diary.d.ts
vendored
0
src/types/diary.ts → src/types/diary.d.ts
vendored
|
|
@ -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');
|
||||||
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue