frontendchat
54
README.md
Normal file
@ -0,0 +1,54 @@
|
||||
## Download library component Tailwind CSS
|
||||
npm i preline
|
||||
|
||||
## tailwind.config.js
|
||||
module.exports = {
|
||||
content: [
|
||||
// './src/**/*.{html,js}',
|
||||
'node_modules/preline/dist/*.js',
|
||||
],
|
||||
plugins: [
|
||||
// require('@tailwindcss/forms'),
|
||||
require('preline/plugin'),
|
||||
],
|
||||
}
|
||||
|
||||
## index.html
|
||||
<script src="./node_modules/preline/dist/preline.js"></script>
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
## Download react icon
|
||||
npm install react-icons --save
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
## Download react dom
|
||||
npm install react-router-dom
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
## Scrollbar Hide
|
||||
npm install tailwind-scrollbar-hide
|
||||
|
||||
## tailwind.config.js
|
||||
plugins: [
|
||||
require('tailwind-scrollbar-hide')
|
||||
],
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
npm i -D daisyui@latest
|
||||
|
||||
plugins: [
|
||||
require('daisyui'),
|
||||
],
|
||||
|
||||
|
||||
npm install react-chartjs-2 chart.js
|
||||
|
||||
npm i react-otp-input
|
||||
|
||||
|
||||
## Run project
|
||||
npm install
|
10
db.dio
Normal file
@ -0,0 +1,10 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="5UqwItMIohfnWYXS8Qfq" name="Page-1">
|
||||
<mxGraphModel dx="746" dy="419" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
38
eslint.config.js
Normal file
@ -0,0 +1,38 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import react from 'eslint-plugin-react'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
ignores: ['dist'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
17
index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React</title>
|
||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script src="./node_modules/preline/dist/preline.js"></script>
|
||||
<script src="../scripts/js/open-modals-on-init.js"></script>
|
||||
</body>
|
||||
</html>
|
6164
package-lock.json
generated
Normal file
50
package.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "eskayvie",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-tailwind/react": "^2.1.9",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"axios": "^1.7.4",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.4",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"preline": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-slick": "^0.30.2",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"tailwind-scrollbar-hide": "^1.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"daisyui": "^4.12.10",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"postcss": "^8.4.41",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
public/image/100 natural.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
public/image/Frame 4.png
Normal file
After Width: | Height: | Size: 675 KiB |
BIN
public/image/Rectangle 48.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
public/image/Rectangle106.png
Normal file
After Width: | Height: | Size: 570 KiB |
BIN
public/image/Rectangle107.png
Normal file
After Width: | Height: | Size: 861 KiB |
BIN
public/image/Rectangle108.png
Normal file
After Width: | Height: | Size: 714 KiB |
BIN
public/image/Rectangle109.png
Normal file
After Width: | Height: | Size: 382 KiB |
BIN
public/image/Share.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/image/ThisIsEskayvie21.jpeg
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
public/image/Vector 1.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
public/image/Vector 2.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
public/image/Vector.png
Normal file
After Width: | Height: | Size: 411 B |
BIN
public/image/Vector1.png
Normal file
After Width: | Height: | Size: 426 B |
BIN
public/image/Web Security 1.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
public/image/alfamart.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/image/banner.png
Normal file
After Width: | Height: | Size: 434 KiB |
BIN
public/image/becea.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/image/bri.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/image/btn-whatsapp.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
public/image/catprofile.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
public/image/clock.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/image/cloud1.jpeg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/image/cloud2.jpeg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/image/cloud3.jpeg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/image/cloud4.jpeg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/image/dana.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/image/diffenz.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
public/image/diffenzjunior.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
public/image/doctor.png
Normal file
After Width: | Height: | Size: 221 KiB |
BIN
public/image/eskayvie1.png
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
public/image/eskayvie2.png
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
public/image/eskayvie3.png
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
public/image/eskayvie4.png
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
public/image/eskayvie5.png
Normal file
After Width: | Height: | Size: 190 KiB |
BIN
public/image/eskayviedoc.png
Normal file
After Width: | Height: | Size: 337 KiB |
BIN
public/image/forgetpw.png
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
public/image/halal100.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
public/image/image14.jpeg
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
public/image/image15.jpeg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
public/image/image16.jpeg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
public/image/imagegedung.png
Normal file
After Width: | Height: | Size: 398 KiB |
BIN
public/image/indomart.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
public/image/like.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/image/locate.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/image/login-pic.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
public/image/logoeskayvie.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
public/image/lovehand.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
public/image/mandiri.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/image/mie.png
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
public/image/modal.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
public/image/newpw.png
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
public/image/nodrugs.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
public/image/ovo.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/image/photoeskayvie.png
Normal file
After Width: | Height: | Size: 435 KiB |
BIN
public/image/phytax.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
public/image/produk.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
public/image/profile.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
public/image/register-pic.png
Normal file
After Width: | Height: | Size: 163 KiB |
BIN
public/image/reverse.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
public/image/savvacaramel.png
Normal file
After Width: | Height: | Size: 224 KiB |
BIN
public/image/savvakurmamerah.png
Normal file
After Width: | Height: | Size: 874 KiB |
BIN
public/image/savvamanisnya.png
Normal file
After Width: | Height: | Size: 543 KiB |
BIN
public/image/shield100.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
public/image/tehtarik.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
public/image/telegram.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
public/image/telephone.png
Normal file
After Width: | Height: | Size: 927 B |
BIN
public/image/yayasan.png
Normal file
After Width: | Height: | Size: 12 KiB |
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
55
src/api/axiosInstance.js
Normal file
@ -0,0 +1,55 @@
|
||||
import axios from 'axios';
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
// Create an Axios instance
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: 'http://localhost:4040/api/v1',
|
||||
});
|
||||
|
||||
// Intercept request to add token
|
||||
axiosInstance.interceptors.request.use(
|
||||
async (config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Intercept response to handle errors
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
// Handle 401 Unauthorized errors (e.g., expired or missing token)
|
||||
if (error.response.status === 401) {
|
||||
try {
|
||||
const response = await axios.post('http://localhost:4040/api/v1/refresh', {}, { withCredentials: true });
|
||||
const newToken = response.data.token;
|
||||
localStorage.setItem('token', newToken);
|
||||
// Retry the original request with the new token
|
||||
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
|
||||
return axiosInstance(originalRequest);
|
||||
} catch (error) {
|
||||
// If refresh fails, log out and redirect to login
|
||||
await axios.post('http://localhost:4040/api/v1/logout', {}, { withCredentials: true });
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
// Handle 403 Forbidden errors (e.g., insufficient permissions)
|
||||
if (error.response.status === 403) {
|
||||
window.location.href = '/home'; // Redirect to a custom forbidden page or home
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default axiosInstance;
|
139
src/api/chat.js
Normal file
@ -0,0 +1,139 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const socketIo = require('socket.io');
|
||||
const cors = require('cors');
|
||||
const session = require('express-session');
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = socketIo(server, {
|
||||
cors: {
|
||||
origin: "http://localhost:5173",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
}
|
||||
});
|
||||
|
||||
app.use(cors({
|
||||
origin: "http://localhost:5173",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
app.use(session({
|
||||
secret: 'your-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: false }
|
||||
}));
|
||||
|
||||
let users = {};
|
||||
let admins = {};
|
||||
let messages = [];
|
||||
|
||||
app.post('/register', (req, res) => {
|
||||
const { id, name } = req.body;
|
||||
if (!id || !name) {
|
||||
return res.status(400).json({ error: 'ID and Name are required' });
|
||||
}
|
||||
users[id] = { id, name };
|
||||
res.status(200).json(users);
|
||||
});
|
||||
|
||||
app.get('/users', (req, res) => {
|
||||
res.status(200).json(users);
|
||||
});
|
||||
|
||||
app.post('/registerAdmin', (req, res) => {
|
||||
const { name, password } = req.body;
|
||||
if (!name || !password) {
|
||||
return res.status(400).json({ error: 'Name and Password are required' });
|
||||
}
|
||||
if (admins[name]) {
|
||||
return res.status(400).json({ error: 'Admin already exists' });
|
||||
}
|
||||
admins[name] = { password };
|
||||
res.status(200).json({ message: 'Admin successfully registered' });
|
||||
});
|
||||
|
||||
app.post('/loginAdmin', (req, res) => {
|
||||
const { name, password } = req.body;
|
||||
if (!name || !password) {
|
||||
return res.status(400).json({ error: 'Name and Password are required' });
|
||||
}
|
||||
if (!admins[name] || admins[name].password !== password) {
|
||||
return res.status(400).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
req.session.user = { name };
|
||||
res.status(200).json({ message: 'Login successful' });
|
||||
});
|
||||
|
||||
app.post('/logoutAdmin', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Logout failed' });
|
||||
}
|
||||
res.status(200).json({ message: 'Logout successful' });
|
||||
});
|
||||
});
|
||||
|
||||
const checkAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
app.post('/sendMessage', checkAuth, (req, res) => {
|
||||
const { content, recipient } = req.body;
|
||||
if (!content || !recipient) {
|
||||
return res.status(400).json({ error: 'Content and Recipient are required' });
|
||||
}
|
||||
const message = { content, sender: req.session.user.name, recipient };
|
||||
messages.push(message);
|
||||
io.emit('receiveMessage', message);
|
||||
res.status(200).json(message);
|
||||
});
|
||||
|
||||
app.post('/endConversation', checkAuth, (req, res) => {
|
||||
const { recipient } = req.body;
|
||||
if (!recipient) {
|
||||
return res.status(400).json({ error: 'Recipient is required' });
|
||||
}
|
||||
messages = messages.filter(msg => msg.recipient !== recipient);
|
||||
io.emit('receiveMessage', { type: 'conversationEnded', recipient });
|
||||
res.status(200).json({ message: 'Conversation ended' });
|
||||
});
|
||||
|
||||
app.post('/deleteUser', checkAuth, (req, res) => {
|
||||
const { id } = req.body;
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: 'User ID is required' });
|
||||
}
|
||||
delete users[id];
|
||||
messages = messages.filter(msg => msg.recipient !== id);
|
||||
io.emit('updateUsers', Object.values(users));
|
||||
io.emit('receiveMessage', { type: 'userDeleted', id });
|
||||
res.status(200).json({ message: 'User successfully deleted' });
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('User connected');
|
||||
|
||||
socket.on('sendMessage', (message) => {
|
||||
io.emit('receiveMessage', message);
|
||||
});
|
||||
|
||||
socket.on('endConversation', (data) => {
|
||||
const { recipient } = data;
|
||||
messages = messages.filter(msg => msg.recipient !== recipient);
|
||||
io.emit('receiveMessage', { type: 'conversationEnded', recipient });
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected');
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(3001, () => {
|
||||
console.log('Server running on port 3001');
|
||||
});
|
163
src/app.jsx
Normal file
@ -0,0 +1,163 @@
|
||||
// src/App.js
|
||||
import React from 'react';
|
||||
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
|
||||
import Modals from "./components/Elements/Modals";
|
||||
|
||||
import ShareModals from "./components/Elements/Share";
|
||||
|
||||
|
||||
import Srr from "./pages/srr";
|
||||
import { Checkbox } from "@material-tailwind/react";
|
||||
|
||||
import ProtectedRoute from './context/ProtectedRoute';
|
||||
import GuestRoute from './context/GuestRoute';
|
||||
import useAuth from './context/AuthContext';
|
||||
import DetailProducts from "./pages/detailproducts";
|
||||
|
||||
import LandingPage from './pages/landingPage';
|
||||
import axios from 'axios';
|
||||
import LoginPage from './pages/loginPage';
|
||||
import RegisterPage from './pages/registerPage';
|
||||
import ForgotPassword from './pages/forgetPassword';
|
||||
import NewPassword from './pages/newpassword';
|
||||
import SeeMore from './pages/customer/seemore';
|
||||
import Dashboard from './pages/customer/dasboard';
|
||||
import Favorid from './pages/customer/favorid';
|
||||
import Cart from './pages/customer/cart';
|
||||
import DetailPrice from './pages/customer/detailprice';
|
||||
import Order from './pages/customer/order';
|
||||
import DashboardAdmin from './pages/admin';
|
||||
import ProductAdmin from './pages/Admin/ProductAdmin';
|
||||
import DetailSeller from './pages/Admin/seller';
|
||||
import Checkout from './pages/customer/checkout';
|
||||
import AdminChat from './pages/Admin/chatadmin';
|
||||
import LoginAdmin from './componentchat/AdminLogin'; // Import the LoginAdmin component
|
||||
import AdminRegister from './componentchat/AdminRegister';
|
||||
import DashboardChat from './componentchat/dashboardchat';
|
||||
import CustomerChat from './componentchat/CustomerChat';
|
||||
import AddUserForm from './componentchat/AddUserForm';
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
|
||||
function App() {
|
||||
const { isAuthenticated, userRole } = useAuth();
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <LandingPage/>,
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
element: (
|
||||
<GuestRoute>
|
||||
<LoginPage />
|
||||
</GuestRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
element: <RegisterPage />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/forget",
|
||||
element: <ForgotPassword />,
|
||||
},
|
||||
{
|
||||
path: "/newpw",
|
||||
element: <NewPassword />,
|
||||
},
|
||||
{
|
||||
path: "/seemore",
|
||||
element: <SeeMore />,
|
||||
},
|
||||
{
|
||||
path: "/home",
|
||||
element: (
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated} allowedRoles={['Admin']} userRole={userRole}>
|
||||
<Dashboard />
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/favorid",
|
||||
element: <Favorid />,
|
||||
},
|
||||
{
|
||||
path: "/AddUserForm",
|
||||
element: <AddUserForm/>
|
||||
},
|
||||
{
|
||||
path: "/CustomerChat",
|
||||
element: <CustomerChat/>
|
||||
},
|
||||
{
|
||||
path: "/cart",
|
||||
element: <Cart />,
|
||||
},
|
||||
{
|
||||
path: "/detail",
|
||||
element: <DetailPrice />,
|
||||
},
|
||||
{
|
||||
path: "/modals",
|
||||
element: <Modals />,
|
||||
},
|
||||
{
|
||||
path: "/AdminRegister",
|
||||
element: <AdminRegister />, // Add route for RegisterAdmin
|
||||
},
|
||||
{
|
||||
path: "/LoginAdmin",
|
||||
element: <LoginAdmin />, // Add route for LoginAdmin
|
||||
},
|
||||
{
|
||||
path: "/share",
|
||||
element: <ShareModals />,
|
||||
},
|
||||
{
|
||||
path: "/order",
|
||||
element: <Order />,
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
element: <DashboardAdmin />,
|
||||
},
|
||||
{
|
||||
path: "/ProductAdmin",
|
||||
element: <ProductAdmin />,
|
||||
},
|
||||
{
|
||||
path: "/AdminChat",
|
||||
element: <AdminChat />,
|
||||
},
|
||||
{
|
||||
path: "/Dashboardchat",
|
||||
element: <DashboardChat />,
|
||||
},
|
||||
{
|
||||
path: "/seller",
|
||||
element: <DetailSeller />,
|
||||
},
|
||||
{
|
||||
path: "/srr",
|
||||
element: <Srr />,
|
||||
},
|
||||
{
|
||||
path: "/cekot",
|
||||
element: <Checkout />,
|
||||
},
|
||||
{
|
||||
path: "/detailproducts",
|
||||
element: <DetailProducts />,
|
||||
},
|
||||
]);
|
||||
return (
|
||||
<RouterProvider router={router} />
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
1
src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
After Width: | Height: | Size: 4.0 KiB |
39
src/componentchat/AddUserForm.jsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const AddUserForm = ({ onAdd }) => {
|
||||
const [id, setId] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (id && name) {
|
||||
onAdd(id, name);
|
||||
setId('');
|
||||
setName('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="add-user-form">
|
||||
<div>
|
||||
<label>ID:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={id}
|
||||
onChange={(e) => setId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit">Add User</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUserForm;
|
43
src/componentchat/AdminLogin.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
// src/pages/admin/LoginAdmin.jsx
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import './LoginAdmin.css';
|
||||
|
||||
const LoginAdmin = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
// Send login request to the backend
|
||||
await axios.post('http://localhost:3001/loginAdmin', { name, password }, { withCredentials: true });
|
||||
// Redirect to DashboardChat after successful login
|
||||
window.location.href = '/Dashboardchat';
|
||||
} catch (err) {
|
||||
setError('Login failed. Please check your credentials and try again.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<h1>Admin Login</h1>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button onClick={handleLogin}>Login</button>
|
||||
{error && <p className="error-message">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginAdmin;
|
34
src/componentchat/AdminRegister.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
const AdminRegister = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const handleRegister = (e) => {
|
||||
e.preventDefault();
|
||||
axios.post('http://localhost:3001/admin/register', { name, email, password })
|
||||
.then(response => {
|
||||
alert('Admin registered successfully');
|
||||
// Redirect to login page or dashboard
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error registering admin:', error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin Register</h1>
|
||||
<form onSubmit={handleRegister}>
|
||||
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" required />
|
||||
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" required />
|
||||
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" required />
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminRegister;
|
170
src/componentchat/CustomerChat.css
Normal file
@ -0,0 +1,170 @@
|
||||
/* General Styles */
|
||||
.customer-chat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
background: #f5f5f5;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* Chat Container */
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 70px); /* Adjusted for header */
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Message List */
|
||||
.message-list {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
background: #f7f7f7;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
padding: 12px 16px;
|
||||
border-radius: 20px;
|
||||
margin-bottom: 12px;
|
||||
max-width: 75%;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.message-item.self {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
align-self: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message-item.other {
|
||||
background-color: #e0e0e0;
|
||||
color: #333;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* Input Container */
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-top: 1px solid #ddd;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
margin-right: 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* End Chat Button */
|
||||
.end-chat-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.end-chat-container button {
|
||||
background-color: #dc3545;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.end-chat-container button:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
/* Options Icon */
|
||||
.options-menu {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.options-menu .dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #333;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
/* Dropdown Menu */
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 0;
|
||||
background: #000000;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 8px 0;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-menu button {
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-menu button:hover {
|
||||
background: #ff0000;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 600px) {
|
||||
.chat-container {
|
||||
max-width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
84
src/componentchat/CustomerChat.jsx
Normal file
@ -0,0 +1,84 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import io from 'socket.io-client';
|
||||
import './CustomerChat.css'; // Import CSS for styling
|
||||
|
||||
const socket = io('http://localhost:3001');
|
||||
|
||||
const CustomerChat = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [dropdownVisible, setDropdownVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on('receiveMessage', (message) => {
|
||||
setMessages((prevMessages) => [...prevMessages, message]);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.off('receiveMessage');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const sendMessage = () => {
|
||||
if (message.trim() && name.trim()) {
|
||||
socket.emit('sendMessage', { content: message, sender: name });
|
||||
setMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setDropdownVisible(!dropdownVisible);
|
||||
};
|
||||
|
||||
const handleEndChat = () => {
|
||||
socket.emit('endConversation', { recipient: name });
|
||||
setMessages([]);
|
||||
setDropdownVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="customer-chat">
|
||||
<h1>Customer Chat</h1>
|
||||
<div className="chat-container">
|
||||
<div className="options-menu" onClick={toggleDropdown}>
|
||||
<span className="dot"></span>
|
||||
<span className="dot"></span>
|
||||
<span className="dot"></span>
|
||||
</div>
|
||||
{dropdownVisible && (
|
||||
<div className="dropdown-menu show">
|
||||
<button onClick={handleEndChat}>End Chat</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="message-list">
|
||||
{messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`message-item ${msg.sender === name ? 'self' : 'other'}`}
|
||||
>
|
||||
<strong>{msg.sender}:</strong> {msg.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="input-container">
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Enter your name..."
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
placeholder="Type your message..."
|
||||
/>
|
||||
<button onClick={sendMessage}>Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerChat;
|
192
src/componentchat/DashboardChat.css
Normal file
@ -0,0 +1,192 @@
|
||||
.dashboard-chat {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: #f4f4f9;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar ul li {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.sidebar ul li:hover,
|
||||
.sidebar ul li.active {
|
||||
background: #575757;
|
||||
}
|
||||
|
||||
.chat {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.header span {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.admin-profile {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.admin-profile img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: 2px solid #333;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.admin-profile img:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.profile-menu {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 0;
|
||||
background: #000000;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.profile-menu button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
background: #00fff2;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.profile-menu button:hover {
|
||||
background: #ff0000;
|
||||
}
|
||||
|
||||
.messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 5px;
|
||||
max-width: 60%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.message.sent {
|
||||
background: #007bff;
|
||||
color: #fff;
|
||||
align-self: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message.received {
|
||||
background: #e9ecef;
|
||||
color: #333;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.input-area input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
margin-right: 10px;
|
||||
outline: none;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-area input:focus {
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.input-area button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
background: #007bff;
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.input-area button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.end-conversation, .delete-messages {
|
||||
margin-top: 10px;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.end-conversation {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.end-conversation:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.delete-messages {
|
||||
background: #f0ad4e;
|
||||
}
|
||||
|
||||
.delete-messages:hover {
|
||||
background: #ec971f;
|
||||
}
|
||||
|
41
src/componentchat/LoginAdmin.css
Normal file
@ -0,0 +1,41 @@
|
||||
/* src/pages/admin/LoginAdmin.css */
|
||||
.login-container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-container h1 {
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.login-container input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.login-container button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login-container button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #e74c3c;
|
||||
margin-top: 10px;
|
||||
}
|
175
src/componentchat/dashboardchat.jsx
Normal file
@ -0,0 +1,175 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import io from 'socket.io-client';
|
||||
import './DashboardChat.css';
|
||||
|
||||
const socket = io('http://localhost:3001');
|
||||
|
||||
const DashboardChat = () => {
|
||||
const [message, setMessage] = useState('');
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [users, setUsers] = useState([]);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
const [profileMenuVisible, setProfileMenuVisible] = useState(false);
|
||||
const [adminProfile, setAdminProfile] = useState({
|
||||
name: 'Admin',
|
||||
avatar: 'https://i.pravatar.cc/150?u=admin'
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
socket.on('receiveMessage', (message) => {
|
||||
console.log('Received message in DashboardChat:', message);
|
||||
setMessages(prevMessages => [...prevMessages, message]);
|
||||
});
|
||||
|
||||
socket.on('updateUsers', (updatedUsers) => {
|
||||
console.log('Updated users:', updatedUsers);
|
||||
setUsers(updatedUsers);
|
||||
});
|
||||
|
||||
fetch('http://localhost:3001/users')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setUsers(Object.values(data));
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching users:', error);
|
||||
setError('Failed to fetch users. Please try again later.');
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.off('receiveMessage');
|
||||
socket.off('updateUsers');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const sendMessage = () => {
|
||||
if (message.trim() && selectedUser) {
|
||||
const messageData = { content: message, sender: 'admin', recipient: selectedUser.id };
|
||||
console.log('Sending message:', messageData);
|
||||
socket.emit('sendMessage', messageData);
|
||||
setMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
fetch('http://localhost:3001/logoutAdmin', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
}).then(() => {
|
||||
window.location.href = '/loginAdmin';
|
||||
}).catch(error => console.error('Logout failed:', error));
|
||||
};
|
||||
|
||||
const endConversation = () => {
|
||||
if (selectedUser) {
|
||||
socket.emit('endConversation', { recipient: selectedUser.id });
|
||||
setMessages(prevMessages => prevMessages.filter(msg => msg.recipient !== selectedUser.id));
|
||||
setSelectedUser(null);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteUser = (userId) => {
|
||||
console.log(`Attempting to delete user with ID ${userId}`);
|
||||
fetch('http://localhost:3001/deleteUser', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: userId }),
|
||||
credentials: 'include'
|
||||
}).then(() => {
|
||||
console.log(`User with ID ${userId} deleted successfully`);
|
||||
setUsers(prevUsers => prevUsers.filter(user => user.id !== userId));
|
||||
setSelectedUser(null);
|
||||
}).catch(error => console.error('Failed to delete user:', error));
|
||||
};
|
||||
|
||||
const deleteMessages = () => {
|
||||
if (selectedUser) {
|
||||
fetch('http://localhost:3001/deleteMessages', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ recipient: selectedUser.id }),
|
||||
credentials: 'include'
|
||||
}).then(() => {
|
||||
setMessages(prevMessages => prevMessages.filter(msg => msg.recipient !== selectedUser.id && msg.sender !== selectedUser.id));
|
||||
}).catch(error => console.error('Failed to delete messages:', error));
|
||||
}
|
||||
};
|
||||
|
||||
const toggleProfileMenu = () => {
|
||||
setProfileMenuVisible(prevState => !prevState);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dashboard-chat">
|
||||
<div className="sidebar">
|
||||
<h2>Users</h2>
|
||||
{loading ? (
|
||||
<div className="loading">Loading...</div>
|
||||
) : error ? (
|
||||
<div className="error">{error}</div>
|
||||
) : (
|
||||
<ul>
|
||||
{users.map(user => (
|
||||
<li
|
||||
key={user.id}
|
||||
onClick={() => setSelectedUser(user)}
|
||||
className={selectedUser?.id === user.id ? 'active' : ''}
|
||||
>
|
||||
{user.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
<div className="chat">
|
||||
<div className="header">
|
||||
<span>Chat with {selectedUser?.name || 'Select a user'}</span>
|
||||
<div className="admin-profile">
|
||||
<img
|
||||
src={adminProfile.avatar}
|
||||
alt="Admin"
|
||||
onClick={toggleProfileMenu}
|
||||
/>
|
||||
{profileMenuVisible && (
|
||||
<div className="profile-menu">
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="messages">
|
||||
{messages.filter(msg => msg.recipient === selectedUser?.id || msg.sender === selectedUser?.id)
|
||||
.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`message ${msg.sender === 'admin' ? 'sent' : 'received'}`}
|
||||
>
|
||||
<strong>{msg.sender}:</strong> {msg.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="input-area">
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
placeholder="Type your message here..."
|
||||
/>
|
||||
<button onClick={sendMessage}>Send</button>
|
||||
</div>
|
||||
{selectedUser && (
|
||||
<>
|
||||
<button className="end-conversation" onClick={endConversation}>End Chat</button>
|
||||
<button className="delete-messages" onClick={deleteMessages}>Delete Messages</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardChat;
|
33
src/components/AuthButton.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axiosInstance from '../api/axiosInstance';
|
||||
|
||||
//masih untuk coba coba, sudah login button logout, belum login button login
|
||||
|
||||
const AuthButton = ({ isAuthenticated, handleLogout }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLoginClick = () => {
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
// const handleLogout = () => {
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// localStorage.removeItem('token');
|
||||
// await axiosInstance.get('/logout');
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// };
|
||||
// fetchData
|
||||
// };
|
||||
|
||||
return isAuthenticated ? (
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
) : (
|
||||
<button onClick={handleLoginClick}>Login</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthButton;
|
36
src/components/Elements/BarChart/Payment.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
|
||||
|
||||
// Register Chart.js components
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
const data = {
|
||||
labels: ['COD', 'Pay Latter', 'kredit', 'Bank', 'E-Monoy'],
|
||||
datasets: [
|
||||
{
|
||||
data: [12, 50, 10, 20, 70],
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
},
|
||||
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const BarChartPayment = () => {
|
||||
return (
|
||||
<div className="mx-auto lg:max-w-screen-lg md:max-w-screen-md max-w-xs w-full">
|
||||
<Bar data={data} options={options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarChartPayment;
|
45
src/components/Elements/BarChart/index.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
|
||||
|
||||
// Register Chart.js components
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
const data = {
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Stock',
|
||||
data: [120, 150, 180, 200, 170, 210, 250, 220, 190, 240, 280, 260],
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
},
|
||||
{
|
||||
label: 'Sales',
|
||||
data: [100, 130, 160, 180, 150, 190, 230, 200, 170, 220, 260, 240],
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Data stocks & Sales',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const BarChart = () => {
|
||||
return (
|
||||
<div className="w-full mx-auto max-w-screen-lg ">
|
||||
<Bar data={data} options={options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarChart;
|
17
src/components/Elements/Buttons/index.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
const Button = (props) => {
|
||||
const {children, variant, type} = props
|
||||
return(
|
||||
<>
|
||||
<button
|
||||
type ={type}
|
||||
className={`w-full font-semibold rounded-md bg-[#AFA0D7] ${variant}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Button
|
64
src/components/Elements/CardProduct/index.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
const CardProduct = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex w-full max-w-[15rem] flex-col overflow-hidden rounded-lg border bg-gray-100 shadow-md">
|
||||
<a
|
||||
className="relative mx-3 mt-3 flex overflow-hidden rounded-xl justify-center"
|
||||
href="#"
|
||||
>
|
||||
<img
|
||||
className=" w-20 h-full "
|
||||
src="/image/produk.png"
|
||||
alt="product image"
|
||||
/>
|
||||
</a>
|
||||
<div className="mt-4 px-3 pb-5">
|
||||
<a href="#">
|
||||
<h5 className="text-lg tracking-tight text-slate-900">
|
||||
Nike Air MX Super 2500 - Red
|
||||
</h5>
|
||||
</a>
|
||||
<div className="flex justify-between items-center mb-3 gap-3">
|
||||
<p>
|
||||
<span className="text-lg font-bold text-slate-900">$449</span>
|
||||
<span className="text-sm text-red-500 line-through">$699</span>
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
<p className="font-bold text-md">4.5</p>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="h-7 w-7 text-yellow-300"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center justify-center rounded-md bg-slate-600 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-blue-300"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="mr-2 h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
|
||||
>
|
||||
<path
|
||||
|
||||
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/>
|
||||
</svg>
|
||||
Cart
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardProduct
|
24
src/components/Elements/Cards/index.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
|
||||
|
||||
const Card = (props) => {
|
||||
const { image } = props;
|
||||
return (
|
||||
<div className="flex justify-center items-center">
|
||||
<div className="relative bg-[#D0BFFF] w-[35rem] h-[35rem] rounded-lg flex items-center justify-center">
|
||||
<div className="relative bg-white w-[31rem] h-[31rem] rounded-lg flex items-center justify-center bg-opacity-50 ">
|
||||
<img
|
||||
src={image}
|
||||
alt="gambar"
|
||||
className="absolute top-[50px] left-[270px] transform -translate-x-1/2 w-[28rem] h-[25rem]"
|
||||
/>
|
||||
<div className="text-center">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Card;
|
||||
|
36
src/components/Elements/Carousel/index.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import Slider from "react-slick"; // Import Slider
|
||||
import "slick-carousel/slick/slick.css"; // Import slick CSS
|
||||
import "slick-carousel/slick/slick-theme.css";
|
||||
|
||||
const Carousel = () => {
|
||||
const settings = {
|
||||
dots: true,
|
||||
infinite: true,
|
||||
speed: 500,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
autoplay: true,
|
||||
autoplaySpeed: 3000,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="py-5">
|
||||
<Slider {...settings}>
|
||||
<div>
|
||||
<img src="/public/image/banner.png" alt="Product 1" className="w-full h-100 object-cover rounded-md mx-auto" /> {/* Changed height and added mx-auto for centering */}
|
||||
</div>
|
||||
<div>
|
||||
<img src="/public/image/banner.png" alt="Product 2" className="w-full h-100 object-cover rounded-md mx-auto" /> {/* Changed height and added mx-auto for centering */}
|
||||
</div>
|
||||
<div>
|
||||
<img src="/public/image/banner.png" alt="Product 3" className="w-full h-100 object-cover rounded-md mx-auto" /> {/* Changed height and added mx-auto for centering */}
|
||||
</div>
|
||||
{/* Add more images as needed */}
|
||||
</Slider>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Carousel;
|
56
src/components/Elements/Dropdown/index.jsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { useState } from "react";
|
||||
|
||||
function DropdownButton() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative inline-block text-left">
|
||||
<button
|
||||
onClick={toggleDropdown}
|
||||
className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none "
|
||||
>
|
||||
Incoming
|
||||
<svg
|
||||
className="ml-2 -mr-1 h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.23 7.21a.75.75 0 011.06.02L10 11.253l3.71-3.997a.75.75 0 011.08 1.04l-4.25 4.57a.75.75 0 01-1.08 0l-4.25-4.57a.75.75 0 01.02-1.06z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="origin-top-right absolute right-0 mt-2 w-40 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">
|
||||
<div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
||||
<a
|
||||
href="#"
|
||||
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
|
||||
role="menuitem"
|
||||
>
|
||||
Stock
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
|
||||
role="menuitem"
|
||||
>
|
||||
OutGoing
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownButton;
|
29
src/components/Elements/FAButton/index.jsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const FAButton = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleOptions = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-5 right-10 z-50">
|
||||
<button onClick={toggleOptions}>
|
||||
<img src="../public/image/btn-whatsapp.png" />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="absolute bottom-16 right-0 flex flex-col space-y-2 mb-4 mt-2">
|
||||
<button className="flex items-center justify-center">
|
||||
<img src="../public/image/btn-whatsapp.png" alt="whatsapp" />
|
||||
</button>
|
||||
<button className="flex items-center justify-center">
|
||||
<img src="../public/image/telegram.png" alt="telegram" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FAButton;
|
93
src/components/Elements/Footer/index.jsx
Normal file
@ -0,0 +1,93 @@
|
||||
import React from 'react'
|
||||
import { FaFacebookF, FaTwitter, FaWhatsapp } from "react-icons/fa"
|
||||
import { CiLinkedin } from "react-icons/ci"
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
|
||||
<div className="grid bg-[#AFA0D7] p-5">
|
||||
<div className="mb-6 flex items-center gap-5 ">
|
||||
<a href="">
|
||||
<img src="/public/image/logoeskayvie.png" alt="" className="w-14 h-14 rounded-full p-2 bg-white" />
|
||||
</a>
|
||||
<p className="text-2xl font-bold text-[#fefefe]">ESKAYVIE</p>
|
||||
</div>
|
||||
<div className="lg:flex lg:justify-between lg:gap-5 lg:mb-3">
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-md text-[#fefefe]">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Cum alias
|
||||
dignissimos animi qui aliquid aut temporibus quisquam accusamus
|
||||
soluta, voluptatum reiciendis libero optio assumenda et quidem
|
||||
quasi reprehenderit tempore consequatur. Lorem ipsum dolor sit
|
||||
amet consectetur adipisicing elit. Laborum quaerat quibusdam nisi
|
||||
commodi veniam, esse sunt voluptas officia autem dignissimos odio
|
||||
eos mollitia perspiciatis, placeat dolore? Tempore, dicta.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="gird">
|
||||
<h1 className="text-lg font-bold text-white">Connect With Us!</h1>
|
||||
<p className='text-white'>customercare@eskayvie.com
|
||||
</p>
|
||||
<p className='text-white'> 03-5511 1050</p>
|
||||
<div className="mt-3 mb-3 flex items-center gap-3">
|
||||
<a
|
||||
href=""
|
||||
className="flex p-2 items-center justify-center rounded-full bg-[#E2D7FF] text-xl"
|
||||
>
|
||||
<FaFacebookF className="text-xl" />
|
||||
</a>
|
||||
<a
|
||||
href=""
|
||||
className="flex p-2 items-center justify-center rounded-full bg-[#E2D7FF] "
|
||||
>
|
||||
<FaTwitter className="text-xl" />
|
||||
</a>
|
||||
<a
|
||||
href=""
|
||||
className="flex p-2 items-center justify-center rounded-full bg-[#E2D7FF] text-xl"
|
||||
>
|
||||
<FaWhatsapp className="text-xl" />
|
||||
</a>
|
||||
<a
|
||||
href=""
|
||||
className="flex p-2 items-center justify-center rounded-full bg-[#E2D7FF] text-xl "
|
||||
>
|
||||
<CiLinkedin className="text-xl" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid items-start mx-2 gap-3 md:flex md:justify-between text-white">
|
||||
<p>
|
||||
We shall continuously improve our products and services to enhance customer satisfaction and complying with ISO 9001 Quality Management System requirements.
|
||||
</p>
|
||||
<div className='grid gap-5'>
|
||||
<p className='flex justify-center text-xl font-bold'>Organization</p>
|
||||
<div className='lg:flex gap-5 grid grid-cols-2'>
|
||||
<div className='w-32 p-2 rounded-xl bg-white bg-opacity-50'>
|
||||
|
||||
<img src="/image/yayasan.png" alt="" />
|
||||
</div>
|
||||
<div className='w-32 p-2 rounded-xl bg-white bg-opacity-50'>
|
||||
|
||||
<img src="/image/yayasan.png" alt="" />
|
||||
</div>
|
||||
<div className='w-32 p-2 rounded-xl bg-white bg-opacity-50'>
|
||||
|
||||
<img src="/image/yayasan.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className=" boder-2 border-2 border-x-transparent border-b-transparent pt-2 border-white flex justify-center items-center text-white font-bold text-lg mt-5">
|
||||
@2024 ESKAYVIE
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
54
src/components/Elements/LineChart/index.jsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';
|
||||
|
||||
// Register Chart.js components
|
||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
|
||||
|
||||
const data = {
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Profit',
|
||||
data: [3000, 2500, 4000, 4500, 4200, 4800, 5000, 5500, 6000, 6500, 7000, 7500],
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
tension: 0.3,
|
||||
},
|
||||
{
|
||||
label: 'Loss',
|
||||
data: [1500, 1200, 1800, 2000, 100, 2200, 2500, 3000, 3500, 4000, 4500, 5000],
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
tension: 0.3,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Profit and Loss Over Time',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const LineChart = () => {
|
||||
return (
|
||||
<div className="w-full mx-auto max-w-screen-lg">
|
||||
<Line data={data} options={options} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LineChart;
|
135
src/components/Elements/Modals/edit.jsx
Normal file
@ -0,0 +1,135 @@
|
||||
const ModalEdit = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
|
||||
{/* <!-- Modal toggle --> */}
|
||||
|
||||
|
||||
{/* <!-- Main modal --> */}
|
||||
<div className="fixed inset-0 flex items-center justify-center overflow-y-auto z-50">
|
||||
<div className="relative p-4 w-full max-w-md max-h-full">
|
||||
{/* <!-- Modal content --> */}
|
||||
<div className="relative bg-white rounded-lg shadow border">
|
||||
{/* <!-- Modal header --> */}
|
||||
<div className="flex items-center justify-between p-4 md:p-5 border-b rounded-t ">
|
||||
<h3 className="text-lg font-bold text-black ">
|
||||
Add New Seller
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center "
|
||||
data-modal-toggle="crud-modal"
|
||||
>
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 14 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* <!-- Modal body --> */}
|
||||
<form className="p-4 md:p-5">
|
||||
<div className="grid gap-4 mb-4 grid-cols-1">
|
||||
<div className="col-span-1 flex justify-between">
|
||||
<img src="/image/profile.png" alt="" className="h-16 w-16 object-cover mr-7"/>
|
||||
<div className="w-full">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 "
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 "
|
||||
placeholder="Type product name"
|
||||
required=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 sm:col-span-1">
|
||||
<label
|
||||
htmlFor="price"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 "
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 "
|
||||
placeholder="RachelDerek14@gmail.com"
|
||||
required=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<label
|
||||
htmlFor="price"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 "
|
||||
>
|
||||
Telepon
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
name="talepon"
|
||||
id="telepon"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 "
|
||||
placeholder="08568119503"
|
||||
required=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2 sm:col-span-1">
|
||||
<label
|
||||
htmlFor="price"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 "
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 "
|
||||
placeholder="Your password"
|
||||
required=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="text-white w-full bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center "
|
||||
>
|
||||
|
||||
Invite
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalEdit
|
36
src/components/Elements/Modals/index.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import Button from "../Buttons"
|
||||
|
||||
|
||||
|
||||
const Modals = () => {
|
||||
return (
|
||||
<>
|
||||
{/* <!-- component --> */}
|
||||
<div class="min-w-screen h-screen animated fadeIn faster fixed left-0 top-0 flex justify-center items-center inset-0 z-50 outline-none focus:outline-none bg-no-repeat bg-center bg-cover" id="modal-id">
|
||||
<div class="absolute bg-black opacity-80 inset-0 z-0"></div>
|
||||
<div class="w-full max-w-lg relative mx-auto my-auto rounded-xl shadow-lg bg-white pt-10">
|
||||
{/* <!--content--> */}
|
||||
<div class="">
|
||||
{/* <!--body--> */}
|
||||
<div class="flex flex-col justify-center items-center text-center ">
|
||||
<img src="/image/modal.png" alt="" class="w-[50%]" />
|
||||
<h2 class="text-2xl mt-5">Are you sure you want to logout?</h2>
|
||||
</div>
|
||||
{/* <!--footer--> */}
|
||||
<div class="p-3 space-x-10 mx-10 mt-3 flex justify-between mb-3">
|
||||
|
||||
<Button variant=" text-lg shadow-sm font-medium tracking-wider border bg-[#E84C4C] text-white rounded-full hover:shadow-lg hover:opacity-90 max-w-xs py-2">Yes</Button>
|
||||
<Button variant=" text-lg shadow-sm font-medium tracking-wider border bg-[#7D6FA2] text-white rounded-full hover:shadow-lg hover:opacity-90 max-w-xs py-2">Cancel</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Modals
|