frontendchat

This commit is contained in:
Rangga Arya Putra 2024-09-11 09:18:12 +07:00
commit 6f9f008418
150 changed files with 13461 additions and 0 deletions

54
README.md Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View 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
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/image/Frame 4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

BIN
public/image/Share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
public/image/Vector 1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
public/image/Vector 2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
public/image/Vector.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

BIN
public/image/Vector1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
public/image/alfamart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/image/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

BIN
public/image/becea.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/image/bri.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
public/image/catprofile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
public/image/clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/image/cloud1.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/image/cloud2.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/image/cloud3.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/image/cloud4.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/image/dana.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/image/diffenz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
public/image/doctor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
public/image/eskayvie1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
public/image/eskayvie2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
public/image/eskayvie3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
public/image/eskayvie4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

BIN
public/image/eskayvie5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

BIN
public/image/forgetpw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
public/image/halal100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
public/image/image14.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/image/image15.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/image/image16.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

BIN
public/image/indomart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
public/image/like.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/image/locate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/image/login-pic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
public/image/lovehand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/image/mandiri.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/image/mie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
public/image/modal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
public/image/newpw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
public/image/nodrugs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
public/image/ovo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

BIN
public/image/phytax.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
public/image/produk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
public/image/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
public/image/reverse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

BIN
public/image/shield100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
public/image/tehtarik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
public/image/telegram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
public/image/telephone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

BIN
public/image/yayasan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

1
public/vite.svg Normal file
View 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
View 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
View 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
View 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
View 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

View 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;

View 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;

View 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;

View 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;
}
}

View 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;

View 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;
}

View 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;
}

View 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;

View 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;

View 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;

View 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;

View 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

View 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

View 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;

View 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;

View 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;

View 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;

View 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

View 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;

View 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

View 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

Some files were not shown because too many files have changed in this diff Show More