Skip to content
Snippets Groups Projects
Commit ee29e46e authored by 정원제's avatar 정원제 :guitar:
Browse files

refactor: uuid를 통해 공유할 수 있도록 변경

parent f18a1823
No related branches found
No related tags found
1 merge request!42refactor: uuid를 통해 공유할 수 있도록 변경
Pipeline #10837 failed
import api from "../axios";
const createUUID = async (pcId) => {
try {
const response = await api.post(`/my/pc/${pcId}/uuid`);
return response.data.data.uuid.uuid;
} catch (error) {
throw error;
}
};
export default createUUID;
\ No newline at end of file
import api from "../axios";
const getCombinationByUUID = async (uuid) => {
try {
const response = await api.get(`/parts/combination/by-uuid/${uuid}`);
return response.data.data;
} catch (error) {
throw error;
}
};
export default getCombinationByUUID;
\ No newline at end of file
......@@ -7,6 +7,8 @@
}
.sidebar {
position: sticky;
top: 2rem;
flex: 1;
max-width: 300px;
padding: 1.5rem;
......@@ -195,16 +197,17 @@
align-items: center;
padding: 1.5rem;
border-radius: 12px;
background-color: var(--background-white);
background: linear-gradient(135deg, var(--background-white) 0%, rgba(var(--primary-rgb), 0.03) 100%);
box-shadow: var(--shadow-sm);
transition: all 0.3s ease;
border: 1px solid transparent;
border: 1px solid rgba(var(--primary-rgb), 0.08);
}
.part-item:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: var(--primary-light);
background: linear-gradient(135deg, var(--background-white) 0%, rgba(var(--primary-rgb), 0.07) 100%);
border-color: rgba(var(--primary-rgb), 0.15);
}
.part-image {
......@@ -261,6 +264,7 @@
}
.sidebar {
position: static;
max-width: 100%;
}
......@@ -307,3 +311,118 @@
transform: none;
box-shadow: none;
}
.part-list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, var(--background-white) 0%, var(--primary-light) 100%);
border-radius: 12px;
box-shadow: var(--shadow-sm);
border: 1px solid rgba(var(--primary-rgb), 0.1);
position: relative;
overflow: hidden;
}
/* 배경에 부드러운 장식 효과 추가 */
.part-list-header::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(var(--primary-rgb), 0.1) 0%, transparent 70%);
transform: translate(30%, -30%);
}
.part-list-header h2 {
margin: 0;
font-size: 1.8rem;
font-weight: 600;
color: var(--text-primary);
position: relative; /* 텍스트를 장식 위에 표시 */
}
.share-button {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark, #0056b3) 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1.1rem;
font-weight: 500;
transition: all 0.2s ease;
box-shadow: 0 4px 15px rgba(var(--primary-rgb), 0.2);
position: relative;
overflow: hidden;
}
/* 버튼에 빛나는 효과 추가 */
.share-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
120deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
transition: 0.5s;
}
.share-button:hover {
background: linear-gradient(135deg, var(--primary-dark, #0056b3) 0%, var(--primary-color) 100%);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(var(--primary-rgb), 0.3);
}
.share-button:hover::before {
left: 100%;
}
.share-button:active {
transform: translateY(0);
}
/* 부품 아이템 카드도 파스텔톤으로 개선 */
.part-item {
display: flex;
align-items: center;
padding: 1.5rem;
border-radius: 12px;
background: linear-gradient(135deg, var(--background-white) 0%, rgba(var(--primary-rgb), 0.03) 100%);
box-shadow: var(--shadow-sm);
transition: all 0.3s ease;
border: 1px solid rgba(var(--primary-rgb), 0.08);
}
.part-item:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
background: linear-gradient(135deg, var(--background-white) 0%, rgba(var(--primary-rgb), 0.07) 100%);
border-color: rgba(var(--primary-rgb), 0.15);
}
/* 모바일 대응 */
@media (max-width: 768px) {
.part-list-header {
padding: 1.2rem;
}
.part-list-header h2 {
font-size: 1.5rem;
}
.share-button {
padding: 0.5rem 1rem;
font-size: 1rem;
}
}
......@@ -7,6 +7,7 @@ import PartItem from "@/components/PartItem";
import updatePCName from "@/api/my/updatePCName";
import deletePC from "@/api/my/deletePC";
import PCList from "@/components/PCList";
import createUUID from "@/api/my/createUUID";
const CertifiedCombination = () => {
const [pcs, setPcs] = useState([]);
......@@ -92,6 +93,21 @@ const CertifiedCombination = () => {
}
};
const handleShare = async () => {
if (!selectedPc) return;
try {
const uuid = await createUUID(selectedPc.id);
const shareUrl = `${window.location.origin}/shared?uuid=${uuid}`;
await navigator.clipboard.writeText(shareUrl);
alert('공유 링크가 클립보드에 복사되었습니다.');
} catch (error) {
console.error("공유 링크 생성 중 오류 발생:", error);
alert("공유 링크 생성에 실패했습니다.");
}
};
return (
<div className="layout">
<PCList
......@@ -103,6 +119,17 @@ const CertifiedCombination = () => {
onAddPC={handleAddPcClick}
/>
<main className={`part-list ${isPartsLoading ? 'loading' : ''}`}>
{selectedPc && (
<div className="part-list-header">
<h2>{selectedPc.name}</h2>
<button
className="share-button"
onClick={handleShare}
>
공유하기
</button>
</div>
)}
{partsData.map((part, index) => (
<PartItem key={index} part={part} />
))}
......
......@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
import CombinationGrid from "@/components/CombinationGrid/CombinationGrid";
import getPartById from "@/api/parts/getPartById";
const CombinationBox = ({ title, combination }) => {
const CombinationBox = ({ title, combination, showShareButton = false }) => {
const [partDetails, setPartDetails] = useState([]);
useEffect(() => {
const fetchPartDetails = async () => {
......@@ -67,9 +67,11 @@ const CombinationBox = ({ title, combination }) => {
<div className="combination-box">
<div className="combination-header">
<h1 className="title">{title}</h1>
{showShareButton && (
<button className="share-button" onClick={handleShare}>
공유하기
</button>
)}
</div>
<CombinationGrid combination={partDetails} />
</div>
......
.shared-combination-page {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.shared-loading,
.shared-error {
text-align: center;
padding: 2rem;
.shared-info {
background: linear-gradient(135deg, var(--background-white) 0%, rgba(var(--primary-rgb), 0.03) 100%);
padding: 1.5rem 2rem;
border-radius: 12px;
box-shadow: var(--shadow-sm);
border: 1px solid rgba(var(--primary-rgb), 0.08);
}
.shared-info p {
margin: 0.5rem 0;
font-size: 1.1rem;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.shared-info p::before {
content: '';
width: 6px;
height: 6px;
background-color: var(--primary-color);
border-radius: 50%;
}
.shared-info p:first-child {
color: var(--primary-color);
font-weight: 600;
font-size: 1.2rem;
}
.shared-error {
color: #ff4444;
.shared-loading {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
font-size: 1.2rem;
color: var(--text-secondary);
}
.combination-header {
.shared-error {
display: flex;
justify-content: space-between;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
min-height: 200px;
font-size: 1.2rem;
color: var(--error-color);
background-color: var(--background-white);
border-radius: 12px;
padding: 2rem;
box-shadow: var(--shadow-sm);
}
.share-button {
padding: 0.5rem 1rem;
background-color: var(--primary-color);
color: white;
border: 1px solid var(--primary-color);
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
@media (max-width: 768px) {
.shared-combination-page {
padding: 1rem;
}
.share-button:hover {
background-color: var(--primary-color);
box-shadow: 0 0 0 3px var(--primary-light);
transform: translateY(-1px);
.shared-info {
padding: 1rem 1.5rem;
}
.share-button:disabled {
background-color: var(--primary-light);
border-color: var(--primary-light);
cursor: not-allowed;
transform: none;
box-shadow: none;
.shared-info p {
font-size: 1rem;
}
.shared-info p:first-child {
font-size: 1.1rem;
}
}
\ No newline at end of file
import React, { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import CombinationBox from "../SearchCombinationPage/components/CombinationBox";
import getCombinationByUUID from "@/api/parts/getCombinationByUUID";
import getCombination from "@/api/parts/getCombination";
import "./SharedCombinationPage.css";
const SharedCombinationPage = () => {
const [searchParams] = useSearchParams();
const [combination, setCombination] = useState(null);
const [sharedInfo, setSharedInfo] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchSharedCombination = async () => {
try {
const partsParam = searchParams.get('parts');
if (!partsParam) {
throw new Error('부품 정보가 없습니다.');
const uuid = searchParams.get('uuid');
if (!uuid) {
throw new Error('공유 정보가 없습니다.');
}
const partIds = partsParam.split(',');
const data = await getCombination({
cpuId: partIds[0],
gpuId: partIds[1],
mbId: partIds[2],
ramId: partIds[3],
ssdId: partIds[4],
hddId: partIds[5],
const sharedData = await getCombinationByUUID(uuid);
setSharedInfo(sharedData);
const combinationData = await getCombination({
cpuId: sharedData.parts[0],
gpuId: sharedData.parts[1],
mbId: sharedData.parts[2],
ramId: sharedData.parts[3],
ssdId: sharedData.parts[4],
hddId: sharedData.parts[5],
});
if (data && data.length > 0) {
setCombination(data[0]);
if (combinationData && combinationData.length > 0) {
setCombination(combinationData[0]);
} else {
throw new Error('조합을 찾을 수 없습니다.');
}
......@@ -53,11 +57,18 @@ const SharedCombinationPage = () => {
return (
<div className="shared-combination-page">
{combination && (
{combination && sharedInfo && (
<>
<div className="shared-info">
<p>공유자: {sharedInfo.email}</p>
<p>등록일: {new Date(sharedInfo.created_at).toLocaleDateString()}</p>
<p>공유일: {new Date(sharedInfo.verified_at).toLocaleDateString()}</p>
</div>
<CombinationBox
title="공유된 조합"
combination={combination}
/>
</>
)}
</div>
);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment