socket.io 추가

This commit is contained in:
익희 김 2024-12-10 11:55:52 +09:00
parent e5c206c11e
commit 3a724936a1
16 changed files with 461 additions and 21 deletions

View File

@ -7,4 +7,4 @@ PAYPAL_CLIENT_ID=
CLOUDINARY_CLOUD_NAME="dmjztlmj4"
BASE_CURRENCY=KRW
SHIPPING_FEE=
JWT_SECRET="123123"
JWT_SECRET=

View File

@ -16,7 +16,10 @@ const nextConfig = {
},
images: {
domains: ['nextall.vercel.app"', 'res.cloudinary.com']
}
},
// experimental: {
// appDir: true,
// }
};
module.exports = nextConfig;

255
package-lock.json generated
View File

@ -59,6 +59,8 @@
"redux-persist": "^6.0.0",
"server-only": "^0.0.1",
"simplebar-react": "^3.2.4",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"stripe": "^14.9.0",
"stylis": "^4.3.0",
"stylis-plugin-rtl": "^2.1.1",
@ -1881,6 +1883,12 @@
"integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==",
"license": "MIT"
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@stripe/react-stripe-js": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.9.0.tgz",
@ -1977,6 +1985,21 @@
"tslib": "^2.8.0"
}
},
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"license": "MIT"
},
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -2323,6 +2346,28 @@
"integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==",
"license": "MIT"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@ -2718,6 +2763,15 @@
],
"license": "MIT"
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
@ -3054,6 +3108,15 @@
"license": "MIT",
"peer": true
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/core-js": {
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
@ -3065,6 +3128,19 @@
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@ -3360,6 +3436,91 @@
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"license": "MIT"
},
"node_modules/engine.io": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
"integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-client": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
"integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/enhanced-resolve": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
@ -7164,6 +7325,83 @@
"node": ">= 10"
}
},
"node_modules/socket.io": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/socket.io-client": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@ -7916,6 +8154,15 @@
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vite-compatible-readable-stream": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
@ -8252,6 +8499,14 @@
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@ -61,6 +61,8 @@
"redux-persist": "^6.0.0",
"server-only": "^0.0.1",
"simplebar-react": "^3.2.4",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"stripe": "^14.9.0",
"stylis": "^4.3.0",
"stylis-plugin-rtl": "^2.1.1",

0
server.js Normal file
View File

View File

@ -0,0 +1,72 @@
'use client';
import { Button } from '@mui/material';
import React, { useEffect, useState } from 'react';
import io from 'socket.io-client';
let socket;
export default function Chat() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
// Socket.io
socket = io({ path: '/api/socketio' });
//
socket.on('connect', () => {
console.log('Connected to server');
});
//
socket.on('message', (data) => {
setMessages((prevMessages) => [...prevMessages, data]);
});
//
return () => {
if (socket) {
socket.disconnect();
}
};
}, []);
const sendMessage = () => {
if (!socket) {
console.error('Socket not initialized');
return;
}
if (input.trim()) {
const message = { user: '셀러테스트', text: input };
//
socket.emit('message', message);
setMessages((prevMessages) => [...prevMessages, message]);
setInput('');
}
};
return (
<div>
<div style={{ width:'320px', height: '400px', overflowY: 'scroll', border: '1px solid #ccc', padding: '10px', margin:'auto', borderRadius:'12px'}}>
{messages.map((msg, index) => (
<div key={index}>
<strong>{msg.user}:</strong> {msg.text}
</div>
))}
</div>
<div style={{display:'flex', alignItems:'center', justifyContent:'center', marginTop:'10px'}}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="입력"
style={{height:'32px', borderRadius:'8px', width:'250px'}}
/>
<Button onClick={sendMessage}>Send</Button>
</div>
</div>
);
}

34
src/app/api/socket.js Normal file
View File

@ -0,0 +1,34 @@
import { Server } from 'socket.io';
export default function handler(req, res) {
if (!res.socket.server.io) {
console.log('Initializing Socket.io...');
const io = new Server(res.socket.server, {
path: '/api/socketio',
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
});
res.socket.server.io = io;
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
socket.on('message', (data) => {
console.log('Message received:', data);
socket.broadcast.emit('message', data);
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
} else {
console.log('Socket.io is already initialized.');
}
res.end();
}

5
src/app/socket.js Normal file
View File

@ -0,0 +1,5 @@
"use client";
import { io } from "socket.io-client";
export const socket = io();

View File

@ -38,7 +38,6 @@ export default function UserProfile({ id }) {
if (isLoading) {
return null;
} else {
console.log(data, 'dadsasdas');
const { user } = data;
return user;
}

View File

@ -19,7 +19,6 @@ const TABLE_HEAD = [
{ id: 'orders', label: 'Orders', alignRight: false, sort: true },
{ id: 'role', label: 'Role', alignRight: false, sort: true },
{ id: 'joined', label: 'Joined', alignRight: false, sort: true },
{ id: '', label: 'Actions', alignRight: true }
];

View File

@ -55,7 +55,7 @@ export default function LoginForm() {
onSuccess: async (data) => {
dispatch(setLogin(data.user));
dispatch(setWishlist(data.user.wishlist));
// await createCookies('token', data.token);
await createCookies('token', data.token);
setloading(false);
toast.success(lang['Logged in successfully!']);
const isAdmin = data.user.role.includes('admin');
@ -95,9 +95,11 @@ export default function LoginForm() {
return (
<>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">SELECT LANGUAGE</InputLabel>
<Select
<FormControl fullWidth sx={{ mb: '10px' }}>
<Typography variant="overline" color="text.primary" htmlFor="email" component={'label'}>
Select Language
</Typography>
<Select
onChange={(e) => setLangState(e.target.value)}
value={langIs}
>

View File

@ -413,7 +413,7 @@ export default function ProductForm({
/>
</Grid>
<Grid item xs={12} md={6}>
<Grid item xs={12} md={6} sx={{display:'none'}}>
<FormControl fullWidth>
{isInitialized ? (
<Skeleton variant="text" width={80} />
@ -443,7 +443,7 @@ export default function ProductForm({
)}
</FormControl>
</Grid>
<Grid item xs={12} md={4}>
<Grid item xs={12} md={4} sx={{display:'none'}}>
<FormControl fullWidth>
{isInitialized ? (
<Skeleton variant="text" width={80} />
@ -671,7 +671,7 @@ export default function ProductForm({
)}
</div>
<div>
<div style={{display:'none'}}>
<LabelStyle component={'label'} htmlFor="quantity">
{'Quantity'}
</LabelStyle>
@ -685,7 +685,7 @@ export default function ProductForm({
/>
</div>
<div>
<div style={{display:'none'}}>
<LabelStyle component={'label'} htmlFor="regular-price">
{'Regular Price'}
</LabelStyle>
@ -702,7 +702,7 @@ export default function ProductForm({
helperText={touched.price && errors.price}
/>
</div>
<div>
<div style={{display:'none'}}>
<LabelStyle component={'label'} htmlFor="sale-price">
{'Sale Price'}
</LabelStyle>

View File

@ -519,7 +519,7 @@ export default function ShopSettingFrom({ data: currentShop, isLoading: category
{categoryLoading ? (
<Skeleton variant="text" width={150} />
) : (
<LabelStyle component={'label'} htmlFor="country">
<LabelStyle component={'label'} htmlFor="country" sx={{display:'none'}}>
Country
</LabelStyle>
)}

View File

@ -422,7 +422,7 @@ export default function CreateShopSettingFrom() {
helperText={touched.phone && errors.phone}
/>
</div>
<div>
<div style={{display:'none'}}>
<LabelStyle component={'label'} htmlFor="country">
Country
</LabelStyle>
@ -444,7 +444,7 @@ export default function CreateShopSettingFrom() {
))}
</TextField>
</div>
<div>
<div style={{display:'none'}}>
<LabelStyle component={'label'} htmlFor="city">
City
</LabelStyle>
@ -457,7 +457,7 @@ export default function CreateShopSettingFrom() {
helperText={touched.address?.city && errors.address?.city}
/>
</div>
<div>
<div style={{display:'none'}}>
<LabelStyle component={'label'} htmlFor="state">
State
</LabelStyle>
@ -472,7 +472,7 @@ export default function CreateShopSettingFrom() {
</div>
<div>
<LabelStyle component={'label'} htmlFor="streetAddress">
Street Address
Address
</LabelStyle>
<TextField

View File

@ -4,7 +4,7 @@ import { useRouter } from 'next-nprogress-bar';
// mui
import { styled } from '@mui/material/styles';
import { Box, TableRow, Skeleton, TableCell, Typography, Stack, IconButton, Avatar, Tooltip } from '@mui/material';
import { Box, TableRow, Skeleton, TableCell, Typography, Stack, IconButton, Avatar, Tooltip, Select, MenuItem } from '@mui/material';
// utils
import { fDateShort } from 'src/utils/formatTime';
@ -75,7 +75,7 @@ export default function UserRow({ isLoading, row, setId }) {
<TableCell style={{ minWidth: 80 }}>{isLoading ? <Skeleton variant="text" /> : row?.phone}</TableCell>
<TableCell style={{ minWidth: 40 }}>{isLoading ? <Skeleton variant="text" /> : row?.totalOrders || 0}</TableCell>
<TableCell style={{ minWidth: 40, textTransform: 'capitalize' }}>
{isLoading ? <Skeleton variant="text" /> : row.role}
{isLoading ? <Skeleton variant="text" /> : <Select defaultValue={row.role}><MenuItem value="user">Buyer</MenuItem><MenuItem value="seller">Seller</MenuItem><MenuItem value="super admin">Admin</MenuItem></Select>}
</TableCell>
<TableCell style={{ minWidth: 40 }}>
{isLoading ? <Skeleton variant="text" /> : fDateShort(row.createdAt, enUS)}

View File

@ -44,6 +44,14 @@ UploadMultiFile.propTypes = {
export default function UploadMultiFile({ ...props }) {
const { error, files, onRemove, blob, isEdit, onRemoveAll, loading, sx, ...other } = props;
const [modalOpen, setModalOpen] = useState(false);
const [priceModalOpen, setPriceModalOpen] = useState(false); //
const [clientPosition, setClientPosition] = useState({ x: 0, y: 0 }); //
const [price, setPrice] = useState(''); //
const [annotations, setAnnotations] = useState([]); //
const [image, setImage] = useState('');
const [predictions, setPredictions] = useState([]); // Roboflow
const canvasRef = useRef(null); //
@ -116,6 +124,22 @@ export default function UploadMultiFile({ ...props }) {
detectWithRoboflow();
};
//
const priceTagHandler = (event) => {
const rect = event.target.getBoundingClientRect();
const x = event.clientX - rect.left; // X
const y = event.clientY - rect.top; // Y
setClientPosition({ x, y });
setPriceModalOpen(true); //
};
//
const handleSavePrice = () => {
setAnnotations([...annotations, { x: clientPosition.x, y: clientPosition.y, price }]);
setPriceModalOpen(false); //
setPrice(''); //
};
return (
<Box sx={{ width: '100%', ...sx }}>
<DropZoneStyle
@ -232,7 +256,52 @@ export default function UploadMultiFile({ ...props }) {
>
AI 상품 검출
</Button>
<canvas ref={canvasRef} style={{ width: '100%' }} />
{/* 금액 입력 모달 */}
<Modal open={priceModalOpen} onClose={() => setPriceModalOpen(false)}>
<Stack
spacing={2}
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: '#fff',
padding: 3,
borderRadius: 1,
boxShadow: 24,
}}
>
<Typography variant="h6">Enter Price</Typography>
<input
type="text"
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="Enter price"
style={{ padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/>
<Button variant="contained" onClick={handleSavePrice}>
Save
</Button>
</Stack>
</Modal>
<canvas ref={canvasRef} style={{ width: '100%' }} onClick={(event) => priceTagHandler(event)} />
{/* 저장된 금액 표시 */}
{annotations.map((annotation, idx) => (
<Typography
key={idx}
sx={{
position: 'absolute',
top: annotation.y,
left: annotation.x,
backgroundColor: 'rgba(0,0,0,0.7)',
color: '#fff',
padding: '2px 5px',
borderRadius: '4px',
}}
>
{annotation.price}
</Typography>
))}
</Stack>
</Modal>