Add auth server, login page, users table, create user script, etc
This commit is contained in:
22
ecosystem.config.js
Normal file
22
ecosystem.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
...commonSettings,
|
||||
name: 'new-auth-server',
|
||||
script: './inventory-server/auth/server.js',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
AUTH_PORT: 3011,
|
||||
JWT_SECRET: process.env.JWT_SECRET
|
||||
},
|
||||
error_file: 'inventory-server/auth/logs/pm2/err.log',
|
||||
out_file: 'inventory-server/auth/logs/pm2/out.log',
|
||||
log_file: 'inventory-server/auth/logs/pm2/combined.log',
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
AUTH_PORT: 3011,
|
||||
JWT_SECRET: process.env.JWT_SECRET
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
41
inventory-server/auth/add_user.js
Normal file
41
inventory-server/auth/add_user.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const mysql = require('mysql2/promise');
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
require('dotenv').config({ path: '../.env' });
|
||||
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
};
|
||||
|
||||
async function addUser() {
|
||||
const username = await askQuestion('Enter username: ');
|
||||
const password = await askQuestion('Enter password: ');
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const connection = await mysql.createConnection(dbConfig);
|
||||
|
||||
try {
|
||||
await connection.query('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword]);
|
||||
console.log(`User ${username} added successfully.`);
|
||||
} catch (error) {
|
||||
console.error('Error adding user:', error);
|
||||
} finally {
|
||||
connection.end();
|
||||
readline.close();
|
||||
}
|
||||
}
|
||||
|
||||
function askQuestion(query) {
|
||||
return new Promise(resolve => readline.question(query, ans => {
|
||||
resolve(ans);
|
||||
}));
|
||||
}
|
||||
|
||||
addUser();
|
||||
1915
inventory-server/auth/package-lock.json
generated
Normal file
1915
inventory-server/auth/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
inventory-server/auth/package.json
Normal file
21
inventory-server/auth/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "auth-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Authentication server for inventory management",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"add_user": "node add_user.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
6
inventory-server/auth/schema.sql
Normal file
6
inventory-server/auth/schema.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE `users` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`username` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
135
inventory-server/auth/server.js
Normal file
135
inventory-server/auth/server.js
Normal file
@@ -0,0 +1,135 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const cors = require('cors');
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config({ path: '../.env' });
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.AUTH_PORT || 3011;
|
||||
|
||||
// Database configuration
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
};
|
||||
|
||||
// Create a connection pool
|
||||
const pool = mysql.createPool(dbConfig);
|
||||
|
||||
app.use(cors({
|
||||
origin: [
|
||||
'https://inventory.kent.pw',
|
||||
'http://localhost:5173',
|
||||
'http://127.0.0.1:5173',
|
||||
/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/,
|
||||
/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/
|
||||
],
|
||||
methods: ['GET', 'POST', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||
credentials: true,
|
||||
exposedHeaders: ['set-cookie']
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
// Debug middleware to log request details
|
||||
app.use((req, res, next) => {
|
||||
console.log('Request details:', {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
origin: req.get('Origin'),
|
||||
headers: req.headers,
|
||||
body: req.body,
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
// Registration endpoint
|
||||
app.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const connection = await pool.getConnection();
|
||||
await connection.query('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword]);
|
||||
connection.release();
|
||||
|
||||
res.status(201).json({ message: 'User registered successfully' });
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({ error: 'Registration failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Login endpoint
|
||||
app.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
console.log(`Login attempt for user: ${username}`);
|
||||
|
||||
const connection = await pool.getConnection();
|
||||
const [rows] = await connection.query(
|
||||
'SELECT * FROM users WHERE username = ?',
|
||||
[username],
|
||||
);
|
||||
connection.release();
|
||||
|
||||
if (rows.length === 1) {
|
||||
const user = rows[0];
|
||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (passwordMatch) {
|
||||
console.log(`User ${username} authenticated successfully`);
|
||||
const token = jwt.sign(
|
||||
{ username: user.username },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '1h' },
|
||||
);
|
||||
res.json({ token });
|
||||
} else {
|
||||
console.error(`Invalid password for user: ${username}`);
|
||||
res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
} else {
|
||||
console.error(`User not found: ${username}`);
|
||||
res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: 'Login failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Protected endpoint example
|
||||
app.get('/protected', async (req, res) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
|
||||
// Optionally, you can fetch the user from the database here
|
||||
// to verify that the user still exists or to get more user information
|
||||
const connection = await pool.getConnection();
|
||||
const [rows] = await connection.query('SELECT * FROM users WHERE username = ?', [decoded.username]);
|
||||
connection.release();
|
||||
|
||||
if (rows.length === 0) {
|
||||
return res.status(401).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
res.json({ message: 'Protected resource accessed', user: decoded });
|
||||
} catch (error) {
|
||||
console.error('Protected endpoint error:', error);
|
||||
res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, "0.0.0.0", () => {
|
||||
console.log(`Auth server running on port ${PORT}`);
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { Routes, Route, useNavigate, Navigate } from 'react-router-dom';
|
||||
import { MainLayout } from './components/layout/MainLayout';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { Products } from './pages/Products';
|
||||
@@ -9,14 +9,62 @@ import { Settings } from './pages/Settings';
|
||||
import { Analytics } from './pages/Analytics';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import PurchaseOrders from './pages/PurchaseOrders';
|
||||
import { Login } from './pages/Login';
|
||||
import { useEffect } from 'react';
|
||||
import config from './config';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function App() {
|
||||
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
if (token) {
|
||||
try {
|
||||
const response = await fetch(`${config.authUrl}/protected`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
sessionStorage.removeItem('token');
|
||||
sessionStorage.removeItem('isLoggedIn');
|
||||
navigate('/login');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token verification failed:', error);
|
||||
sessionStorage.removeItem('token');
|
||||
sessionStorage.removeItem('isLoggedIn');
|
||||
navigate('/login');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
}, [navigate]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Router>
|
||||
<Toaster richColors position="top-center" />
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
isLoggedIn ? (
|
||||
<Navigate to="/dashboard" replace />
|
||||
) : (
|
||||
<Navigate to="/login" replace />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
isLoggedIn ? (
|
||||
<MainLayout>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
@@ -28,9 +76,15 @@ function App() {
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
</Routes>
|
||||
</MainLayout>
|
||||
</Router>
|
||||
) : (
|
||||
<Navigate to="/login" replace />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
|
||||
@@ -1,24 +1,118 @@
|
||||
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import { AppSidebar } from "./AppSidebar";
|
||||
import { useState } from 'react';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Settings,
|
||||
Package,
|
||||
ShoppingBag,
|
||||
PackagePlus,
|
||||
BarChart4,
|
||||
LogOut,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '../ui/button';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface MainLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
const links = [
|
||||
{ title: 'Dashboard', path: '/', icon: <LayoutDashboard /> },
|
||||
{ title: 'Products', path: '/products', icon: <Package /> },
|
||||
{ title: 'Import', path: '/import', icon: <PackagePlus /> },
|
||||
{ title: 'Orders', path: '/orders', icon: <ShoppingBag /> },
|
||||
{ title: 'Purchase Orders', path: '/purchase-orders', icon: <ShoppingBag /> },
|
||||
{ title: 'Analytics', path: '/analytics', icon: <BarChart4 /> },
|
||||
{ title: 'Settings', path: '/settings', icon: <Settings /> },
|
||||
];
|
||||
|
||||
export function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () => {
|
||||
sessionStorage.removeItem('isLoggedIn');
|
||||
sessionStorage.removeItem('token');
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
export function MainLayout({ children }: MainLayoutProps) {
|
||||
return (
|
||||
<SidebarProvider defaultOpen>
|
||||
<div className="flex min-h-screen w-full pr-2">
|
||||
<AppSidebar />
|
||||
<main className="flex-1 overflow-hidden">
|
||||
<div className="flex h-14 w-full items-center border-b px-4 gap-4">
|
||||
<SidebarTrigger />
|
||||
<div className="flex h-screen">
|
||||
<aside
|
||||
className={cn(
|
||||
'border-r h-full transition-all duration-300 ease-in-out',
|
||||
open ? 'w-64' : 'w-20',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<h1
|
||||
className={cn(
|
||||
'text-xl font-bold transition-all duration-300 ease-in-out',
|
||||
open ? 'block' : 'hidden',
|
||||
)}
|
||||
>
|
||||
Inventory
|
||||
</h1>
|
||||
<button
|
||||
className="p-1.5 rounded-md hover:bg-gray-200"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M4 6l16 0"></path>
|
||||
<path d="M4 12l16 0"></path>
|
||||
<path d="M4 18l12 0"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-auto h-[calc(100vh-3.5rem)] max-w-[1500px]">
|
||||
{children}
|
||||
<nav className="p-4 space-y-2">
|
||||
{links.map((link) => (
|
||||
<NavLink
|
||||
key={link.path}
|
||||
to={link.path}
|
||||
className={cn(
|
||||
'flex items-center gap-2 p-2 rounded-md transition-all duration-300 ease-in-out',
|
||||
location.pathname === link.path
|
||||
? 'bg-gray-200'
|
||||
: 'hover:bg-gray-100',
|
||||
)}
|
||||
>
|
||||
{link.icon}
|
||||
<span
|
||||
className={cn(
|
||||
'transition-all duration-300 ease-in-out',
|
||||
open ? 'block' : 'hidden',
|
||||
)}
|
||||
>
|
||||
{link.title}
|
||||
</span>
|
||||
</NavLink>
|
||||
))}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full flex items-center gap-2"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut />
|
||||
<span
|
||||
className={cn(
|
||||
'transition-all duration-300 ease-in-out',
|
||||
open ? 'block' : 'hidden',
|
||||
)}
|
||||
>
|
||||
Logout
|
||||
</span>
|
||||
</Button>
|
||||
</nav>
|
||||
</aside>
|
||||
<main className="flex-1 p-8 overflow-y-auto">{children}</main>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,8 @@ const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
const config = {
|
||||
apiUrl: isDev ? '/api' : 'https://inventory.kent.pw/api',
|
||||
baseUrl: isDev ? '' : 'https://inventory.kent.pw'
|
||||
baseUrl: isDev ? '' : 'https://inventory.kent.pw',
|
||||
authUrl: isDev ? '/auth-inv' : 'https://inventory.kent.pw/auth-inv'
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -2,9 +2,12 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { BrowserRouter as Router } from 'react-router-dom'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
108
inventory/src/pages/Login.tsx
Normal file
108
inventory/src/pages/Login.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import config from '../config';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
export function Login() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
const url = isDev ? "/auth-inv/login" : `${config.authUrl}/login`;
|
||||
console.log('Making login request:', {
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: { username, password },
|
||||
config
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
console.log('Login response status:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(e => ({ error: 'Failed to parse error response' }));
|
||||
console.error('Login failed:', data);
|
||||
throw new Error(data.error || 'Login failed');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Login successful:', data);
|
||||
|
||||
sessionStorage.setItem('token', data.token);
|
||||
sessionStorage.setItem('isLoggedIn', 'true');
|
||||
navigate('/');
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'An unexpected error occurred',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<Card className="w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Login</CardTitle>
|
||||
<CardDescription>Enter your credentials to access the inventory.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form>
|
||||
<div className="grid w-full items-center gap-4">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input
|
||||
id="username"
|
||||
placeholder="Enter your username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<Button onClick={handleLogin}>Login</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/dashboard/inventorystats.tsx","./src/components/dashboard/overview.tsx","./src/components/dashboard/recentsales.tsx","./src/components/dashboard/salesbycategory.tsx","./src/components/dashboard/trendingproducts.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/products/producteditdialog.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/hooks/use-mobile.tsx","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/dashboard.tsx","./src/pages/import.tsx","./src/pages/orders.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx"],"version":"5.6.3"}
|
||||
{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/dashboard/inventorystats.tsx","./src/components/dashboard/overview.tsx","./src/components/dashboard/recentsales.tsx","./src/components/dashboard/salesbycategory.tsx","./src/components/dashboard/trendingproducts.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/products/producteditdialog.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/settings/calculationsettings.tsx","./src/components/settings/datamanagement.tsx","./src/components/settings/performancemetrics.tsx","./src/components/settings/stockmanagement.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/hooks/use-mobile.tsx","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/dashboard.tsx","./src/pages/import.tsx","./src/pages/login.tsx","./src/pages/orders.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx"],"version":"5.6.3"}
|
||||
@@ -1,141 +0,0 @@
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
||||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
import path from "path";
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { loadEnv } from "vite";
|
||||
import fs from 'fs-extra';
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(function (_a) {
|
||||
var mode = _a.mode;
|
||||
var env = loadEnv(mode, process.cwd(), "");
|
||||
var isDev = mode === 'development';
|
||||
return {
|
||||
plugins: [
|
||||
react(),
|
||||
{
|
||||
name: 'copy-build',
|
||||
closeBundle: function () { return __awaiter(void 0, void 0, void 0, function () {
|
||||
var sourcePath, targetPath, error_1;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
if (!!isDev) return [3 /*break*/, 6];
|
||||
sourcePath = path.resolve(__dirname, 'build');
|
||||
targetPath = path.resolve(__dirname, '../inventory-server/frontend/build');
|
||||
_a.label = 1;
|
||||
case 1:
|
||||
_a.trys.push([1, 5, , 6]);
|
||||
return [4 /*yield*/, fs.ensureDir(path.dirname(targetPath))];
|
||||
case 2:
|
||||
_a.sent();
|
||||
return [4 /*yield*/, fs.remove(targetPath)];
|
||||
case 3:
|
||||
_a.sent();
|
||||
return [4 /*yield*/, fs.copy(sourcePath, targetPath)];
|
||||
case 4:
|
||||
_a.sent();
|
||||
console.log('Build files copied successfully to server directory!');
|
||||
return [3 /*break*/, 6];
|
||||
case 5:
|
||||
error_1 = _a.sent();
|
||||
console.error('Error copying build files:', error_1);
|
||||
process.exit(1);
|
||||
return [3 /*break*/, 6];
|
||||
case 6: return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); }
|
||||
}
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 5173,
|
||||
proxy: isDev ? {
|
||||
"/api": {
|
||||
target: "https://inventory.kent.pw",
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
xfwd: true,
|
||||
cookieDomainRewrite: "",
|
||||
withCredentials: true,
|
||||
rewrite: function (path) { return path.replace(/^\/api/, "/api"); },
|
||||
configure: function (proxy, _options) {
|
||||
proxy.on("error", function (err, req, res) {
|
||||
console.log("API proxy error:", err);
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
res.end(JSON.stringify({ error: "Proxy Error", message: err.message }));
|
||||
});
|
||||
proxy.on("proxyReq", function (proxyReq, req, _res) {
|
||||
console.log("Outgoing request:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: proxyReq.getHeaders(),
|
||||
});
|
||||
});
|
||||
proxy.on("proxyRes", function (proxyRes, req, _res) {
|
||||
console.log("Proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
} : {},
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ["react", "react-dom", "react-router-dom"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -32,6 +32,9 @@ export default defineConfig(({ mode }) => {
|
||||
}
|
||||
}
|
||||
],
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
@@ -40,7 +43,7 @@ export default defineConfig(({ mode }) => {
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 5173,
|
||||
proxy: isDev ? {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "https://inventory.kent.pw",
|
||||
changeOrigin: true,
|
||||
@@ -61,14 +64,14 @@ export default defineConfig(({ mode }) => {
|
||||
)
|
||||
})
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing request:", {
|
||||
console.log("Outgoing request to API:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: proxyReq.getHeaders(),
|
||||
})
|
||||
})
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("Proxy response:", {
|
||||
console.log("API Proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
@@ -76,7 +79,48 @@ export default defineConfig(({ mode }) => {
|
||||
})
|
||||
},
|
||||
},
|
||||
} : {},
|
||||
"/auth-inv": {
|
||||
target: "https://inventory.kent.pw",
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
xfwd: true,
|
||||
cookieDomainRewrite: {
|
||||
"inventory.kent.pw": "localhost"
|
||||
},
|
||||
withCredentials: true,
|
||||
onProxyReq: (proxyReq, req) => {
|
||||
// Add origin header to match CORS policy
|
||||
proxyReq.setHeader('Origin', 'http://localhost:5173');
|
||||
},
|
||||
rewrite: (path) => path.replace(/^\/auth-inv/, "/auth-inv"),
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, req, res) => {
|
||||
console.log("Auth proxy error:", err)
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
res.end(
|
||||
JSON.stringify({ error: "Proxy Error", message: err.message })
|
||||
)
|
||||
})
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing request to Auth:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: proxyReq.getHeaders(),
|
||||
})
|
||||
})
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("Auth Proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
|
||||
Reference in New Issue
Block a user