Allow using acot.site to access
This commit is contained in:
@@ -35,7 +35,7 @@ global.pool = pool;
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(morgan('combined'));
|
app.use(morgan('combined'));
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw'],
|
origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw', 'https://acot.site'],
|
||||||
credentials: true
|
credentials: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ global.pool = pool;
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(morgan('combined'));
|
app.use(morgan('combined'));
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw'],
|
origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw', 'https://acot.site'],
|
||||||
credentials: true
|
credentials: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const corsMiddleware = cors({
|
|||||||
origin: [
|
origin: [
|
||||||
'https://inventory.kent.pw',
|
'https://inventory.kent.pw',
|
||||||
'http://localhost:5175',
|
'http://localhost:5175',
|
||||||
|
'https://acot.site',
|
||||||
/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/,
|
/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/,
|
||||||
/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/
|
/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/
|
||||||
],
|
],
|
||||||
@@ -26,7 +27,7 @@ const corsErrorHandler = (err, req, res, next) => {
|
|||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
error: 'CORS not allowed',
|
error: 'CORS not allowed',
|
||||||
origin: req.get('Origin'),
|
origin: req.get('Origin'),
|
||||||
message: 'Origin not in allowed list: https://inventory.kent.pw, localhost:5175, 192.168.x.x, or 10.x.x.x'
|
message: 'Origin not in allowed list: https://inventory.kent.pw, https://acot.site, localhost:5175, 192.168.x.x, or 10.x.x.x'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
next(err);
|
next(err);
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ function App() {
|
|||||||
</RequireAuth>
|
</RequireAuth>
|
||||||
}>
|
}>
|
||||||
{/* Core inventory app routes - will be lazy loaded */}
|
{/* Core inventory app routes - will be lazy loaded */}
|
||||||
<Route index element={
|
{/* Default route now prioritizes dashboard, then other pages */}
|
||||||
<Protected page="overview" fallback={<FirstAccessiblePage />}>
|
<Route index element={<FirstAccessiblePage />} />
|
||||||
|
<Route path="/overview" element={
|
||||||
|
<Protected page="overview">
|
||||||
<Suspense fallback={<PageLoading />}>
|
<Suspense fallback={<PageLoading />}>
|
||||||
<Overview />
|
<Overview />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { Navigate } from "react-router-dom";
|
|||||||
import { AuthContext } from "@/contexts/AuthContext";
|
import { AuthContext } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
// Define available pages in order of priority
|
// Define available pages in order of priority
|
||||||
|
// Dashboard is first so users with dashboard access default to it
|
||||||
const PAGES = [
|
const PAGES = [
|
||||||
|
{ path: "/dashboard", permission: "access:dashboard" },
|
||||||
|
{ path: "/overview", permission: "access:overview" },
|
||||||
{ path: "/products", permission: "access:products" },
|
{ path: "/products", permission: "access:products" },
|
||||||
{ path: "/categories", permission: "access:categories" },
|
{ path: "/categories", permission: "access:categories" },
|
||||||
{ path: "/vendors", permission: "access:vendors" },
|
{ path: "/vendors", permission: "access:vendors" },
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
BarChart2,
|
BarChart2,
|
||||||
Settings,
|
Settings,
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
LogOut,
|
|
||||||
Tags,
|
Tags,
|
||||||
PackagePlus,
|
PackagePlus,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
@@ -28,10 +27,11 @@ import {
|
|||||||
SidebarSeparator,
|
SidebarSeparator,
|
||||||
useSidebar
|
useSidebar
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useLocation, useNavigate, Link } from "react-router-dom";
|
import { useLocation, Link } from "react-router-dom";
|
||||||
import { Protected } from "@/components/auth/Protected";
|
import { Protected } from "@/components/auth/Protected";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { AuthContext } from "@/contexts/AuthContext";
|
import { AuthContext } from "@/contexts/AuthContext";
|
||||||
|
import { NavUser } from "./NavUser";
|
||||||
|
|
||||||
const dashboardItems = [
|
const dashboardItems = [
|
||||||
{
|
{
|
||||||
@@ -46,7 +46,7 @@ const inventoryItems = [
|
|||||||
{
|
{
|
||||||
title: "Overview",
|
title: "Overview",
|
||||||
icon: Home,
|
icon: Home,
|
||||||
url: "/",
|
url: "/overview",
|
||||||
permission: "access:overview"
|
permission: "access:overview"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -122,16 +122,9 @@ const chatItems = [
|
|||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
|
||||||
useSidebar();
|
useSidebar();
|
||||||
const { user } = useContext(AuthContext);
|
const { user } = useContext(AuthContext);
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
sessionStorage.removeItem('isLoggedIn');
|
|
||||||
navigate('/login');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if user has access to any items in a section
|
// Check if user has access to any items in a section
|
||||||
const hasAccessToSection = (items: any[]): boolean => {
|
const hasAccessToSection = (items: any[]): boolean => {
|
||||||
if (user?.is_admin) return true;
|
if (user?.is_admin) return true;
|
||||||
@@ -140,6 +133,8 @@ export function AppSidebar() {
|
|||||||
|
|
||||||
const renderMenuItems = (items: any[]) => {
|
const renderMenuItems = (items: any[]) => {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
|
// Check if current path matches the item URL
|
||||||
|
// For root path ("/"), only highlight Overview when at exactly "/"
|
||||||
const isActive =
|
const isActive =
|
||||||
location.pathname === item.url ||
|
location.pathname === item.url ||
|
||||||
(item.url !== "/" && item.url !== "#" && location.pathname.startsWith(item.url));
|
(item.url !== "/" && item.url !== "#" && location.pathname.startsWith(item.url));
|
||||||
@@ -284,16 +279,7 @@ export function AppSidebar() {
|
|||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarSeparator />
|
<SidebarSeparator />
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarMenu>
|
<NavUser />
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton asChild tooltip="Logout" onClick={handleLogout}>
|
|
||||||
<button>
|
|
||||||
<LogOut className="h-4 w-4" />
|
|
||||||
<span className="group-data-[collapsible=icon]:hidden">Logout</span>
|
|
||||||
</button>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
|
|||||||
102
inventory/src/components/layout/NavUser.tsx
Normal file
102
inventory/src/components/layout/NavUser.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import {
|
||||||
|
ChevronsUpDown,
|
||||||
|
LogOut,
|
||||||
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
} from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { AuthContext } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
|
export function NavUser() {
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
const { user } = useContext(AuthContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
sessionStorage.removeItem('isLoggedIn');
|
||||||
|
navigate('/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInitial = user.username?.charAt(0).toUpperCase() || 'U';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="lg"
|
||||||
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
|
<AvatarFallback className="rounded-lg bg-primary/10 text-primary font-semibold">
|
||||||
|
{userInitial}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-semibold">{user.username}</span>
|
||||||
|
{user.email && (
|
||||||
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown className="ml-auto size-4" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||||
|
side={isMobile ? "bottom" : "right"}
|
||||||
|
align="end"
|
||||||
|
sideOffset={4}
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
|
<AvatarFallback className="rounded-lg bg-primary/10 text-primary font-semibold">
|
||||||
|
{userInitial}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span className="truncate font-semibold">{user.username}</span>
|
||||||
|
{user.email && (
|
||||||
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
|
<LogOut />
|
||||||
|
Log out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ const isDev = import.meta.env.DEV;
|
|||||||
const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
||||||
|
|
||||||
// Use proxy paths when on inventory domains to avoid CORS
|
// Use proxy paths when on inventory domains to avoid CORS
|
||||||
const useProxy = !isLocal && (window.location.hostname === 'inventory.kent.pw' || window.location.hostname === 'inventory.acot.site');
|
const useProxy = !isLocal && (window.location.hostname === 'inventory.kent.pw' || window.location.hostname === 'inventory.acot.site' || window.location.hostname === 'acot.site');
|
||||||
|
|
||||||
const liveDashboardConfig = {
|
const liveDashboardConfig = {
|
||||||
auth: isDev || useProxy ? '/dashboard-auth' : 'https://dashboard.kent.pw/auth',
|
auth: isDev || useProxy ? '/dashboard-auth' : 'https://dashboard.kent.pw/auth',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user