From 971945158068a9cf0224f23e0d7f4031a3a452fd Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Fri, 21 Feb 2025 09:16:07 -0600 Subject: [PATCH] feat(frontend): finished login form with validation --- frontend/package.json | 8 + .../src/components/extendedUI/LstCard.tsx | 18 ++ .../src/components/layout/lst-sidebar.tsx | 84 +--------- .../layout/side-components/header.tsx | 2 +- frontend/src/components/ui/avatar.tsx | 71 ++++---- frontend/src/components/ui/card.tsx | 68 ++++++++ frontend/src/components/ui/checkbox.tsx | 30 ++++ frontend/src/components/ui/input.tsx | 32 ++-- frontend/src/components/ui/label.tsx | 22 +++ frontend/src/components/ui/sonner.tsx | 27 +++ frontend/src/lib/hooks/useLogout.ts | 6 +- frontend/src/lib/store/sessionStore.ts | 17 +- frontend/src/main.tsx | 9 +- frontend/src/routeTree.gen.ts | 21 +++ frontend/src/routes/__root.tsx | 94 +++++++---- frontend/src/routes/_admin.tsx | 13 ++ frontend/src/routes/_auth.tsx | 2 +- frontend/src/routes/login.tsx | 157 +++++++++++++++--- frontend/src/utils/moduleActive.ts | 20 +++ frontend/src/utils/userAccess.ts | 52 ++++++ package.json | 8 +- server/src/app.ts | 5 +- server/src/services/auth/routes/login.ts | 2 + server/tsconfig.json | 7 - tsconfig.json | 6 +- 25 files changed, 551 insertions(+), 230 deletions(-) create mode 100644 frontend/src/components/extendedUI/LstCard.tsx create mode 100644 frontend/src/components/ui/card.tsx create mode 100644 frontend/src/components/ui/checkbox.tsx create mode 100644 frontend/src/components/ui/label.tsx create mode 100644 frontend/src/components/ui/sonner.tsx create mode 100644 frontend/src/routes/_admin.tsx create mode 100644 frontend/src/utils/moduleActive.ts create mode 100644 frontend/src/utils/userAccess.ts delete mode 100644 server/tsconfig.json diff --git a/frontend/package.json b/frontend/package.json index bec91bd..fb663c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,28 +11,36 @@ }, "dependencies": { "@antfu/ni": "^23.3.1", + "@hookform/resolvers": "^4.1.0", "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-collapsible": "^1.1.3", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.5", "@tanstack/react-router": "^1.106.0", + "@tanstack/zod-form-adapter": "^0.42.1", "@types/react-grid-layout": "^1.3.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "js-cookie": "^3.0.5", "lucide-react": "^0.475.0", + "next-themes": "^0.4.4", "react": "^19.0.0", "react-dom": "^19.0.0", "react-grid-layout": "^1.5.0", + "react-hook-form": "^7.54.2", "shadcn": "^2.4.0-canary.6", + "sonner": "^2.0.1", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.6", "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.2", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/frontend/src/components/extendedUI/LstCard.tsx b/frontend/src/components/extendedUI/LstCard.tsx new file mode 100644 index 0000000..1f3ed83 --- /dev/null +++ b/frontend/src/components/extendedUI/LstCard.tsx @@ -0,0 +1,18 @@ +import {ReactNode} from "react"; +import {Card} from "../ui/card"; + +interface LstCardProps { + children?: ReactNode; + className?: string; + style?: React.CSSProperties; +} + +export function LstCard({children, className = "", style = {}}: LstCardProps) { + return ( +
+ + {children} + +
+ ); +} diff --git a/frontend/src/components/layout/lst-sidebar.tsx b/frontend/src/components/layout/lst-sidebar.tsx index 75f9057..14ad5a4 100644 --- a/frontend/src/components/layout/lst-sidebar.tsx +++ b/frontend/src/components/layout/lst-sidebar.tsx @@ -6,91 +6,17 @@ import {QualitySideBar} from "./side-components/quality"; import {ForkliftSideBar} from "./side-components/forklift"; import {EomSideBar} from "./side-components/eom"; import {AdminSideBar} from "./side-components/admin"; - -type Feature = string; - -interface Module { - id: number; - name: string; - features: Feature[]; - active: boolean; -} - -interface RolePermissions { - [moduleName: string]: Feature[]; -} - -interface Roles { - [roleName: string]: RolePermissions; -} - -interface User { - id: number; - username: string; - role: keyof Roles; -} -const modules: Module[] = [ - {id: 1, name: "production", active: true, features: ["view", "edit", "approve"]}, - {id: 2, name: "logistics", active: true, features: ["view", "assign", "track"]}, - {id: 3, name: "quality", active: false, features: ["view", "audit", "approve"]}, - {id: 4, name: "forklift", active: true, features: ["view", "request", "operate"]}, - {id: 5, name: "admin", active: true, features: ["view", "manage_users", "view_logs", "settings"]}, -]; - -const rolePermissions: Roles = { - admin: { - production: ["view", "edit", "approve"], - logistics: ["view", "assign", "track"], - quality: ["view", "audit", "approve"], - forklift: ["view", "request", "operate"], - admin: ["view", "manage_users", "view_logs", "settings"], - }, - manager: { - production: ["view", "edit"], - logistics: ["view", "assign"], - quality: ["view", "audit"], - forklift: ["view", "request"], - admin: ["view_logs"], - }, - supervisor: { - production: ["view"], - logistics: ["view"], - quality: ["view"], - forklift: ["view", "request"], - admin: [], - }, - user: { - production: ["view"], - logistics: [], - quality: [], - forklift: [], - admin: [], - }, -}; - -// const users: User[] = [ -// {id: 1, username: "admin", role: "admin"}, -// {id: 2, username: "manager", role: "manager"}, -// {id: 3, username: "supervisor", role: "supervisor"}, -// {id: 4, username: "user", role: "user"}, -// ]; - -function hasAccess(user: User, moduleName: string, feature: Feature): boolean { - return rolePermissions[user.role]?.[moduleName]?.includes(feature) || false; -} - -function moduleActive(moduleName: string): boolean { - const module = modules.find((m) => m.name === moduleName); - return module ? module.active : false; -} +import {useSessionStore} from "../../lib/store/sessionStore"; +import {hasAccess} from "../../utils/userAccess"; +import {moduleActive} from "../../utils/moduleActive"; export function AppSidebar() { - const user = {id: 4, username: "admin", role: "admin"}; + const {user} = useSessionStore(); return (
- {hasAccess(user, "production", "view") && moduleActive("production") && } + {moduleActive("production") && } {hasAccess(user, "logistics", "view") && moduleActive("logistics") && } {hasAccess(user, "forklift", "view") && moduleActive("forklift") && } {hasAccess(user, "eom", "view") && moduleActive("admin") && } diff --git a/frontend/src/components/layout/side-components/header.tsx b/frontend/src/components/layout/side-components/header.tsx index 9726f03..9ca8efd 100644 --- a/frontend/src/components/layout/side-components/header.tsx +++ b/frontend/src/components/layout/side-components/header.tsx @@ -13,7 +13,7 @@ export function Header() {
Logistics Support Tool - v1.0.0 + v2.0.0
diff --git a/frontend/src/components/ui/avatar.tsx b/frontend/src/components/ui/avatar.tsx index b7224f0..57f9e3d 100644 --- a/frontend/src/components/ui/avatar.tsx +++ b/frontend/src/components/ui/avatar.tsx @@ -1,51 +1,36 @@ -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import { cn } from "@/lib/utils" +import {cn} from "../../lib/utils"; -function Avatar({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) +function Avatar({className, ...props}: React.ComponentProps) { + return ( + + ); } -function AvatarImage({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) +function AvatarImage({className, ...props}: React.ComponentProps) { + return ( + + ); } -function AvatarFallback({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) +function AvatarFallback({className, ...props}: React.ComponentProps) { + return ( + + ); } -export { Avatar, AvatarImage, AvatarFallback } +export {Avatar, AvatarImage, AvatarFallback}; diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx new file mode 100644 index 0000000..c964bb5 --- /dev/null +++ b/frontend/src/components/ui/card.tsx @@ -0,0 +1,68 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..7923185 --- /dev/null +++ b/frontend/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx index 8b652f6..4bfe9cc 100644 --- a/frontend/src/components/ui/input.tsx +++ b/frontend/src/components/ui/input.tsx @@ -1,19 +1,21 @@ -import * as React from "react"; +import * as React from "react" -import {cn} from "../../lib/utils"; +import { cn } from "@/lib/utils" -function Input({className, type, ...props}: React.ComponentProps<"input">) { - return ( - - ); +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) } -export {Input}; +export { Input } diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx new file mode 100644 index 0000000..a43fd7f --- /dev/null +++ b/frontend/src/components/ui/label.tsx @@ -0,0 +1,22 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/frontend/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx new file mode 100644 index 0000000..8bfe1d1 --- /dev/null +++ b/frontend/src/components/ui/sonner.tsx @@ -0,0 +1,27 @@ +import { useTheme } from "next-themes" +import { Toaster as Sonner, ToasterProps } from "sonner" + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme() + + return ( + + ) +} + +export { Toaster } diff --git a/frontend/src/lib/hooks/useLogout.ts b/frontend/src/lib/hooks/useLogout.ts index b53ddc3..99aae80 100644 --- a/frontend/src/lib/hooks/useLogout.ts +++ b/frontend/src/lib/hooks/useLogout.ts @@ -1,16 +1,14 @@ import {useRouter} from "@tanstack/react-router"; import {useSessionStore} from "../store/sessionStore"; -import {useQueryClient} from "@tanstack/react-query"; export const useLogout = () => { const {clearSession} = useSessionStore(); const router = useRouter(); - const queryClient = useQueryClient(); const logout = async () => { router.invalidate(); router.clearCache(); - await clearSession(); - queryClient.invalidateQueries(); + clearSession(); + window.location.reload(); }; diff --git a/frontend/src/lib/store/sessionStore.ts b/frontend/src/lib/store/sessionStore.ts index b73e226..9d54c7c 100644 --- a/frontend/src/lib/store/sessionStore.ts +++ b/frontend/src/lib/store/sessionStore.ts @@ -9,28 +9,31 @@ type User = { export type SessionState = { user: User | null; token: string | null; - setSession: (user: SessionState["user"], token: string) => void; + setSession: (user: User | null, token: string | null) => void; clearSession: () => void; }; export const useSessionStore = create((set) => { - // Initialize from localStorage - const storedUser = localStorage.getItem("user"); + // Initialize token from localStorage, but user remains in memory only const storedToken = localStorage.getItem("auth_token"); return { - user: storedUser ? JSON.parse(storedUser) : null, + user: null, // User is NOT stored in localStorage token: storedToken || null, setSession: (user, token) => { - localStorage.setItem("auth_token", token); - //localStorage.setItem("user", JSON.stringify(user)); + if (token) { + localStorage.setItem("auth_token", token); + } else { + localStorage.removeItem("auth_token"); + } + + console.log("Setting session:", {user, token}); set({user, token}); }, clearSession: () => { localStorage.removeItem("auth_token"); - //localStorage.removeItem("user"); set({user: null, token: null}); }, }; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 6b0b78a..4b59ec6 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,13 +1,14 @@ import {StrictMode} from "react"; import ReactDOM from "react-dom/client"; import "./styles.css"; - -import {SessionProvider} from "./components/providers/Providers.tsx"; import {RouterProvider, createRouter} from "@tanstack/react-router"; // Import the generated route tree import {routeTree} from "./routeTree.gen"; +import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; +// Create a client +const queryClient = new QueryClient(); // Create a new router instance const router = createRouter({routeTree}); @@ -24,9 +25,9 @@ if (!rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( - + - + ); } diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 279bef1..0848f87 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -14,6 +14,7 @@ import { Route as rootRoute } from './routes/__root' import { Route as LoginImport } from './routes/login' import { Route as AboutImport } from './routes/about' import { Route as AuthImport } from './routes/_auth' +import { Route as AdminImport } from './routes/_admin' import { Route as IndexImport } from './routes/index' import { Route as OcpIndexImport } from './routes/ocp/index' import { Route as OcpLotsImport } from './routes/ocp/lots' @@ -38,6 +39,11 @@ const AuthRoute = AuthImport.update({ getParentRoute: () => rootRoute, } as any) +const AdminRoute = AdminImport.update({ + id: '/_admin', + getParentRoute: () => rootRoute, +} as any) + const IndexRoute = IndexImport.update({ id: '/', path: '/', @@ -73,6 +79,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } + '/_admin': { + id: '/_admin' + path: '' + fullPath: '' + preLoaderRoute: typeof AdminImport + parentRoute: typeof rootRoute + } '/_auth': { id: '/_auth' path: '' @@ -153,6 +166,7 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute + '/_admin': typeof AdminRoute '/_auth': typeof AuthRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute @@ -169,6 +183,7 @@ export interface FileRouteTypes { id: | '__root__' | '/' + | '/_admin' | '/_auth' | '/about' | '/login' @@ -180,6 +195,7 @@ export interface FileRouteTypes { export interface RootRouteChildren { IndexRoute: typeof IndexRoute + AdminRoute: typeof AdminRoute AuthRoute: typeof AuthRouteWithChildren AboutRoute: typeof AboutRoute LoginRoute: typeof LoginRoute @@ -189,6 +205,7 @@ export interface RootRouteChildren { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + AdminRoute: AdminRoute, AuthRoute: AuthRouteWithChildren, AboutRoute: AboutRoute, LoginRoute: LoginRoute, @@ -207,6 +224,7 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", + "/_admin", "/_auth", "/about", "/login", @@ -217,6 +235,9 @@ export const routeTree = rootRoute "/": { "filePath": "index.tsx" }, + "/_admin": { + "filePath": "_admin.tsx" + }, "/_auth": { "filePath": "_auth.tsx", "children": [ diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 1fa43bf..1500181 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -1,4 +1,4 @@ -import {createRootRoute, Outlet} from "@tanstack/react-router"; +import {createRootRoute, Link, Outlet} from "@tanstack/react-router"; import {TanStackRouterDevtools} from "@tanstack/router-devtools"; import Cookies from "js-cookie"; import {SidebarProvider} from "../components/ui/sidebar"; @@ -14,48 +14,74 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../components/ui/dropdown-menu"; +import {SessionProvider} from "../components/providers/Providers"; +import {Toaster} from "sonner"; +import {Button} from "../components/ui/button"; +import {useLogout} from "../lib/hooks/useLogout"; +import {useSession} from "../lib/hooks/useSession"; +import {useSessionStore} from "../lib/store/sessionStore"; // same as the layout export const Route = createRootRoute({ component: () => { const sidebarState = Cookies.get("sidebar_state") === "true"; + const {session} = useSession(); + const {user} = useSessionStore(); + const logout = useLogout(); return ( <> - - - - - - - + + + + + + + + diff --git a/frontend/src/routes/_admin.tsx b/frontend/src/routes/_admin.tsx new file mode 100644 index 0000000..9bb2468 --- /dev/null +++ b/frontend/src/routes/_admin.tsx @@ -0,0 +1,13 @@ +import {createFileRoute, redirect} from "@tanstack/react-router"; + +// src/routes/_authenticated.tsx +export const Route = createFileRoute("/_admin")({ + beforeLoad: async () => { + const auth = localStorage.getItem("auth_token"); + if (!auth) { + throw redirect({ + to: "/login", + }); + } + }, +}); diff --git a/frontend/src/routes/_auth.tsx b/frontend/src/routes/_auth.tsx index a3723d0..e1edc58 100644 --- a/frontend/src/routes/_auth.tsx +++ b/frontend/src/routes/_auth.tsx @@ -6,7 +6,7 @@ export const Route = createFileRoute("/_auth")({ const auth = localStorage.getItem("auth_token"); if (!auth) { throw redirect({ - to: "/", + to: "/login", }); } }, diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 71be4d8..f70e228 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -1,38 +1,143 @@ -import {createFileRoute, useRouter} from "@tanstack/react-router"; -import {isAuthenticated, signIn, signOut} from "../utils/auth"; +import {createFileRoute, redirect, useRouter} from "@tanstack/react-router"; +import {useForm, Controller} from "react-hook-form"; +import {zodResolver} from "@hookform/resolvers/zod"; +import {LstCard} from "../components/extendedUI/LstCard"; import {Button} from "../components/ui/button"; +import {Input} from "../components/ui/input"; +import {CardHeader} from "../components/ui/card"; +import {Label} from "../components/ui/label"; +import {toast} from "sonner"; +import {useSessionStore} from "../lib/store/sessionStore"; +import {Checkbox} from "../components/ui/checkbox"; +import {z} from "zod"; export const Route = createFileRoute("/login")({ component: RouteComponent, + beforeLoad: () => { + const isLoggedIn = localStorage.getItem("auth_token"); + if (isLoggedIn) { + throw redirect({ + to: "/", + }); + } + }, +}); + +const FormSchema = z.object({ + username: z.string().min(1, "You must enter a valid username"), + password: z.string().min(4, "You must enter a valid password"), + rememberMe: z.boolean(), }); function RouteComponent() { + const {setSession} = useSessionStore(); + const rememeberMe = localStorage.getItem("rememberMe") === "true"; + const username = localStorage.getItem("username") || ""; const router = useRouter(); + const { + register, + handleSubmit, + control, + formState: {errors}, + } = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: username || "", + password: "", + rememberMe: rememeberMe, + }, + }); + + const onSubmitLogin = async (value: z.infer) => { + // Do something with form data + + // first update the rememberMe incase it was selected + if (value.rememberMe) { + localStorage.setItem("rememberMe", value.rememberMe.toString()); + localStorage.setItem("username", value.username); + } else { + localStorage.removeItem("rememberMe"); + localStorage.removeItem("username"); + } + + try { + const response = await fetch("/api/auth/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({username: value.username, password: value.password}), + }); + + // if (!response.ok) { + // throw new Error("Invalid credentials"); + // } + + const data = await response.json(); + + // Store token in localStorage + // localStorage.setItem("auth_token", data.data.token); + + setSession(data.data.user, data.data.token); + toast.success(`You are logged in as ${data.data.user.username}`); + router.navigate({to: "/"}); + } catch (err) { + toast.error("Invalid credentials"); + } + }; return ( -
-

Ligin

- {isAuthenticated() ? ( - <> -

Hello User!

- - - ) : ( - - )} +
+ + +
+

Login to LST

+
+
+
+
+
+ + + {errors.username &&

{errors.username.message}

} +
+
+ <> + + + {errors.password &&

{errors.password.message}

} + +
+
+
+ ( + + )} + control={control} + name="rememberMe" + defaultValue={rememeberMe} + /> + +
+ +
+ +
+
+
+
); } diff --git a/frontend/src/utils/moduleActive.ts b/frontend/src/utils/moduleActive.ts new file mode 100644 index 0000000..ce270fa --- /dev/null +++ b/frontend/src/utils/moduleActive.ts @@ -0,0 +1,20 @@ +type Feature = string; +interface Module { + id: number; + name: string; + features: Feature[]; + active: boolean; +} + +const modules: Module[] = [ + {id: 1, name: "production", active: true, features: ["view", "edit", "approve"]}, + {id: 2, name: "logistics", active: true, features: ["view", "assign", "track"]}, + {id: 3, name: "quality", active: false, features: ["view", "audit", "approve"]}, + {id: 4, name: "forklift", active: true, features: ["view", "request", "operate"]}, + {id: 5, name: "admin", active: true, features: ["view", "manage_users", "view_logs", "settings"]}, +]; + +export function moduleActive(moduleName: string): boolean { + const module = modules.find((m) => m.name === moduleName); + return module ? module.active : false; +} diff --git a/frontend/src/utils/userAccess.ts b/frontend/src/utils/userAccess.ts new file mode 100644 index 0000000..47b5c20 --- /dev/null +++ b/frontend/src/utils/userAccess.ts @@ -0,0 +1,52 @@ +interface User { + id: number; + username: string; + role: keyof Roles; +} + +interface Roles { + [roleName: string]: RolePermissions; +} + +interface RolePermissions { + [moduleName: string]: Feature[]; +} + +type Feature = string; + +const rolePermissions: Roles = { + admin: { + production: ["view", "manage", "update", "admin"], + logistics: ["view", "manage", "update", "admin"], + quality: ["view", "request", "manage", "update", "admin"], + forklift: ["view", "manage", "update", "admin"], + admin: ["view", "view_logs", "manage", "update", "admin"], + }, + manager: { + production: ["view", "manage"], + logistics: ["view", "manage"], + quality: ["view", "manage"], + forklift: ["view", "manage"], + admin: ["view_logs"], + }, + supervisor: { + production: ["view", "update"], + logistics: ["view", "update"], + quality: ["view", "update"], + forklift: ["view"], + admin: [], + }, + user: { + production: ["view"], + logistics: ["view"], + quality: ["view"], + forklift: [], + admin: [], + }, +}; + +// user will need access to the module. +// users role will determine there visual access +export function hasAccess(user: User | null, moduleName: string, feature: Feature): boolean { + return user?.role ? rolePermissions[user.role]?.[moduleName]?.includes(feature) || false : false; +} diff --git a/package.json b/package.json index 7ff2a31..9e4fd33 100644 --- a/package.json +++ b/package.json @@ -4,18 +4,18 @@ "description": "", "main": "index.ts", "scripts": { - "dev": "dotenvx run -- bun run -r --parallel --aggregate-output dev", - "dev:all": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'cd server && bun run dev' 'cd frontend && bun run dev'", + "dev": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'cd server && bun run dev' 'cd frontend && bun run dev'", "dev:server": "bun --env-file .env --watch server/index.ts", "dev:ocme": "bun --env-file .env --watch ocme/index.ts", "dev:frontend": "cd frontend && bunx --bun vite", "build:server": "cd apps/server && bun build index.js --outdir ../../dist/server", "build:ocme": "rimraf dist/ocme && cd apps/ocme && bun build index.js --outdir ../../dist/ocme", "build:front": "cd frontend && rimraf frontend/dist && bun run build", - "start": "cd server && bun run --env-file ../.env ./index.js", + "start": "bun --env-file .env server/index.js", "commit": "cz", "clean": "rimraf dist/server", - "deploy": "standard-version --conventional-commits" + "deploy": "standard-version --conventional-commits", + "ui:add": "cd frontend && bun shadcn add " }, "keywords": [], "author": "", diff --git a/server/src/app.ts b/server/src/app.ts index 9a0b0eb..7acae4c 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -10,7 +10,6 @@ import scalar from "./route/scalar"; // services import {ocmeService} from "./services/ocme/ocmeServer"; -console.log(process.env.JWT_SECRET); const app = new OpenAPIHono(); app.use("*", logger()); @@ -52,8 +51,8 @@ routes.forEach((route) => { // return c.json({success: true, message: "is authenticated"}); // }); -app.get("*", serveStatic({root: "../frontend/dist"})); -app.get("*", serveStatic({path: "../frontend/dist/index.html"})); +app.get("*", serveStatic({root: "./frontend/dist"})); +app.get("*", serveStatic({path: "./frontend/dist/index.html"})); export default app; diff --git a/server/src/services/auth/routes/login.ts b/server/src/services/auth/routes/login.ts index ecd4414..25c049e 100644 --- a/server/src/services/auth/routes/login.ts +++ b/server/src/services/auth/routes/login.ts @@ -63,6 +63,8 @@ const route = createRoute({ app.openapi(route, async (c) => { let body: {username: string; password: string}; + + console.log(`Trying to login`); try { body = await c.req.json(); } catch (error) { diff --git a/server/tsconfig.json b/server/tsconfig.json deleted file mode 100644 index b925aba..0000000 --- a/server/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src", "index.ts"] -} diff --git a/tsconfig.json b/tsconfig.json index 2cf0702..f1152c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,8 @@ "allowJs": true, "types": [ "bun-types" // add Bun global - ] - } + ], + "outDir": "dist" + }, + "include": ["./server/src", "./server/index.ts"] }