functional auth
This commit is contained in:
parent
51bfb1513a
commit
dfea6814e9
17 changed files with 483 additions and 143 deletions
|
|
@ -33,11 +33,14 @@ app.all("/trpc/*", async (c) => {
|
|||
createContext: () => createContext({ req: c.req.raw, resHeaders }),
|
||||
});
|
||||
|
||||
resHeaders.forEach((value, key) => {
|
||||
response.headers.set(key, value);
|
||||
});
|
||||
const merged = new Headers(response.headers);
|
||||
resHeaders.forEach((value, key) => merged.set(key, value));
|
||||
|
||||
return response;
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: merged,
|
||||
});
|
||||
});
|
||||
|
||||
export { app };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { t } from "../../init";
|
||||
import { protectedProcedure, router } from "../../init";
|
||||
import { dataManager } from "../../../lib/data-manager-instance";
|
||||
|
||||
export const DistributorSchema = z.object({
|
||||
|
|
@ -25,39 +25,54 @@ export const UpdateDistributorInput = z
|
|||
|
||||
export type Distributor = z.infer<typeof DistributorSchema>;
|
||||
|
||||
export const distributorRouter = t.router({
|
||||
list: t.procedure
|
||||
export const distributorRouter = router({
|
||||
list: protectedProcedure
|
||||
.output(z.array(DistributorSchema))
|
||||
.query(() => dataManager.distributors.getDistributors()),
|
||||
|
||||
byId: t.procedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(DistributorSchema.nullable())
|
||||
.query(({ input }) =>
|
||||
dataManager.distributors.getDistributorById(input.id),
|
||||
.query(({ ctx }) =>
|
||||
dataManager.distributors.getDistributors(ctx.staff.enterpriseId),
|
||||
),
|
||||
|
||||
create: t.procedure
|
||||
byId: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(DistributorSchema.nullable())
|
||||
.query(({ ctx, input }) =>
|
||||
dataManager.distributors.getDistributorById(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(CreateDistributorInput)
|
||||
.output(DistributorSchema)
|
||||
.mutation(({ input }) =>
|
||||
dataManager.distributors.createDistributor(input),
|
||||
.mutation(({ ctx, input }) =>
|
||||
dataManager.distributors.createDistributor(
|
||||
input,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
update: t.procedure
|
||||
update: protectedProcedure
|
||||
.input(UpdateDistributorInput)
|
||||
.output(DistributorSchema.nullable())
|
||||
.mutation(({ input }) => {
|
||||
.mutation(({ ctx, input }) => {
|
||||
const { id, ...patch } = input;
|
||||
return dataManager.distributors.updateDistributor(id, patch);
|
||||
return dataManager.distributors.updateDistributor(
|
||||
id,
|
||||
patch,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
}),
|
||||
|
||||
remove: t.procedure
|
||||
remove: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(z.object({ ok: z.boolean() }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const ok =
|
||||
await dataManager.distributors.deleteDistributor(input.id);
|
||||
await dataManager.distributors.deleteDistributor(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
return { ok };
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { t } from "../../init";
|
||||
import { protectedProcedure, router } from "../../init";
|
||||
import { dataManager } from "../../../lib/data-manager-instance";
|
||||
import {
|
||||
ProductSchema,
|
||||
|
|
@ -7,34 +7,53 @@ import {
|
|||
UpdateProductInput,
|
||||
} from "@repo/shared";
|
||||
|
||||
export const productRouter = t.router({
|
||||
list: t.procedure
|
||||
export const productRouter = router({
|
||||
list: protectedProcedure
|
||||
.output(z.array(ProductSchema))
|
||||
.query(() => dataManager.products.getProducts()),
|
||||
.query(({ ctx }) =>
|
||||
dataManager.products.getProducts(ctx.staff.enterpriseId),
|
||||
),
|
||||
|
||||
byId: t.procedure
|
||||
byId: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(ProductSchema.nullable())
|
||||
.query(({ input }) => dataManager.products.getProductById(input.id)),
|
||||
.query(({ ctx, input }) =>
|
||||
dataManager.products.getProductById(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
create: t.procedure
|
||||
create: protectedProcedure
|
||||
.input(CreateProductInput)
|
||||
.output(ProductSchema)
|
||||
.mutation(({ input }) => dataManager.products.createProduct(input)),
|
||||
.mutation(({ ctx, input }) =>
|
||||
dataManager.products.createProduct(
|
||||
input,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
update: t.procedure
|
||||
update: protectedProcedure
|
||||
.input(UpdateProductInput)
|
||||
.output(ProductSchema.nullable())
|
||||
.mutation(({ input }) => {
|
||||
.mutation(({ ctx, input }) => {
|
||||
const { id, ...patch } = input;
|
||||
return dataManager.products.updateProduct(id, patch);
|
||||
return dataManager.products.updateProduct(
|
||||
id,
|
||||
patch,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
}),
|
||||
|
||||
remove: t.procedure
|
||||
remove: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(z.object({ ok: z.boolean() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const ok = await dataManager.products.deleteProduct(input.id);
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const ok = await dataManager.products.deleteProduct(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
return { ok };
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { t } from "../../init";
|
||||
import { protectedProcedure, router } from "../../init";
|
||||
import { dataManager } from "../../../lib/data-manager-instance";
|
||||
|
||||
export const StockBatchSchema = z.object({
|
||||
|
|
@ -11,6 +11,7 @@ export const StockBatchSchema = z.object({
|
|||
expiry: z.string(),
|
||||
rack: z.object({ id: z.number().int(), name: z.string() }).nullable(),
|
||||
distributor: z.object({ id: z.number().int(), agency: z.string() }).nullable(),
|
||||
quantity: z.number().int(),
|
||||
is_default: z.boolean(),
|
||||
});
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ export const CreateStockBatchInput = z.object({
|
|||
expiry: shape.expiry.min(1),
|
||||
rack_id: z.number().int().nullable().optional(),
|
||||
distributor_id: z.number().int().nullable().optional(),
|
||||
quantity: z.number().int().min(1),
|
||||
is_default: shape.is_default.default(false),
|
||||
});
|
||||
|
||||
|
|
@ -33,34 +35,53 @@ export const UpdateStockBatchInput = z
|
|||
|
||||
export type StockBatch = z.infer<typeof StockBatchSchema>;
|
||||
|
||||
export const stockRouter = t.router({
|
||||
list: t.procedure
|
||||
export const stockRouter = router({
|
||||
list: protectedProcedure
|
||||
.output(z.array(StockBatchSchema))
|
||||
.query(() => dataManager.stockBatches.getStockBatches()),
|
||||
.query(({ ctx }) =>
|
||||
dataManager.stockBatches.getStockBatches(ctx.staff.enterpriseId),
|
||||
),
|
||||
|
||||
byId: t.procedure
|
||||
byId: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(StockBatchSchema.nullable())
|
||||
.query(({ input }) => dataManager.stockBatches.getStockBatchById(input.id)),
|
||||
.query(({ ctx, input }) =>
|
||||
dataManager.stockBatches.getStockBatchById(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
create: t.procedure
|
||||
create: protectedProcedure
|
||||
.input(CreateStockBatchInput)
|
||||
.output(StockBatchSchema)
|
||||
.mutation(({ input }) => dataManager.stockBatches.createStockBatch(input)),
|
||||
.mutation(({ ctx, input }) =>
|
||||
dataManager.stockBatches.createStockBatch(
|
||||
input,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
update: t.procedure
|
||||
update: protectedProcedure
|
||||
.input(UpdateStockBatchInput)
|
||||
.output(StockBatchSchema.nullable())
|
||||
.mutation(({ input }) => {
|
||||
.mutation(({ ctx, input }) => {
|
||||
const { id, ...patch } = input;
|
||||
return dataManager.stockBatches.updateStockBatch(id, patch);
|
||||
return dataManager.stockBatches.updateStockBatch(
|
||||
id,
|
||||
patch,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
}),
|
||||
|
||||
remove: t.procedure
|
||||
remove: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(z.object({ ok: z.boolean() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const ok = await dataManager.stockBatches.deleteStockBatch(input.id);
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const ok = await dataManager.stockBatches.deleteStockBatch(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
return { ok };
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import {t} from '../../init'
|
||||
import { protectedProcedure, router } from "../../init";
|
||||
import { dataManager } from "../../../lib/data-manager-instance";
|
||||
|
||||
export const StorageSpaceSchema = z.object({
|
||||
|
|
@ -25,39 +25,54 @@ export const UpdateStorageInput = z
|
|||
|
||||
export type StorageSpace = z.infer<typeof StorageSpaceSchema>;
|
||||
|
||||
export const storageRouter = t.router({
|
||||
list: t.procedure
|
||||
export const storageRouter = router({
|
||||
list: protectedProcedure
|
||||
.output(z.array(StorageSpaceSchema))
|
||||
.query(() => dataManager.storageSpaces.getStorageSpaces()),
|
||||
|
||||
byId: t.procedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(StorageSpaceSchema.nullable())
|
||||
.query(({ input }) =>
|
||||
dataManager.storageSpaces.getStorageSpaceById(input.id),
|
||||
.query(({ ctx }) =>
|
||||
dataManager.storageSpaces.getStorageSpaces(ctx.staff.enterpriseId),
|
||||
),
|
||||
|
||||
create: t.procedure
|
||||
byId: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(StorageSpaceSchema.nullable())
|
||||
.query(({ ctx, input }) =>
|
||||
dataManager.storageSpaces.getStorageSpaceById(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(CreateStorageInput)
|
||||
.output(StorageSpaceSchema)
|
||||
.mutation(({ input }) =>
|
||||
dataManager.storageSpaces.createStorageSpace(input),
|
||||
.mutation(({ ctx, input }) =>
|
||||
dataManager.storageSpaces.createStorageSpace(
|
||||
input,
|
||||
ctx.staff.enterpriseId,
|
||||
),
|
||||
),
|
||||
|
||||
update: t.procedure
|
||||
update: protectedProcedure
|
||||
.input(UpdateStorageInput)
|
||||
.output(StorageSpaceSchema.nullable())
|
||||
.mutation(({ input }) => {
|
||||
.mutation(({ ctx, input }) => {
|
||||
const { id, ...patch } = input;
|
||||
return dataManager.storageSpaces.updateStorageSpace(id, patch);
|
||||
return dataManager.storageSpaces.updateStorageSpace(
|
||||
id,
|
||||
patch,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
}),
|
||||
|
||||
remove: t.procedure
|
||||
remove: protectedProcedure
|
||||
.input(z.object({ id: z.number().int() }))
|
||||
.output(z.object({ ok: z.boolean() }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const ok =
|
||||
await dataManager.storageSpaces.deleteStorageSpace(input.id);
|
||||
await dataManager.storageSpaces.deleteStorageSpace(
|
||||
input.id,
|
||||
ctx.staff.enterpriseId,
|
||||
);
|
||||
return { ok };
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { Route as StockRouteImport } from './routes/stock'
|
|||
import { Route as StaffRouteImport } from './routes/staff'
|
||||
import { Route as ProfileRouteImport } from './routes/profile'
|
||||
import { Route as ProductsRouteImport } from './routes/products'
|
||||
import { Route as LoginRouteImport } from './routes/login'
|
||||
import { Route as DistributorsRouteImport } from './routes/distributors'
|
||||
import { Route as CustomersRouteImport } from './routes/customers'
|
||||
import { Route as BillingRouteImport } from './routes/billing'
|
||||
|
|
@ -56,6 +57,11 @@ const ProductsRoute = ProductsRouteImport.update({
|
|||
path: '/products',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const LoginRoute = LoginRouteImport.update({
|
||||
id: '/login',
|
||||
path: '/login',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DistributorsRoute = DistributorsRouteImport.update({
|
||||
id: '/distributors',
|
||||
path: '/distributors',
|
||||
|
|
@ -142,6 +148,7 @@ export interface FileRoutesByFullPath {
|
|||
'/billing': typeof BillingRoute
|
||||
'/customers': typeof CustomersRoute
|
||||
'/distributors': typeof DistributorsRouteWithChildren
|
||||
'/login': typeof LoginRoute
|
||||
'/products': typeof ProductsRouteWithChildren
|
||||
'/profile': typeof ProfileRoute
|
||||
'/staff': typeof StaffRoute
|
||||
|
|
@ -164,6 +171,7 @@ export interface FileRoutesByTo {
|
|||
'/': typeof IndexRoute
|
||||
'/billing': typeof BillingRoute
|
||||
'/customers': typeof CustomersRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/profile': typeof ProfileRoute
|
||||
'/staff': typeof StaffRoute
|
||||
'/distributors/$id': typeof DistributorsIdRoute
|
||||
|
|
@ -185,6 +193,7 @@ export interface FileRoutesById {
|
|||
'/billing': typeof BillingRoute
|
||||
'/customers': typeof CustomersRoute
|
||||
'/distributors': typeof DistributorsRouteWithChildren
|
||||
'/login': typeof LoginRoute
|
||||
'/products': typeof ProductsRouteWithChildren
|
||||
'/profile': typeof ProfileRoute
|
||||
'/staff': typeof StaffRoute
|
||||
|
|
@ -210,6 +219,7 @@ export interface FileRouteTypes {
|
|||
| '/billing'
|
||||
| '/customers'
|
||||
| '/distributors'
|
||||
| '/login'
|
||||
| '/products'
|
||||
| '/profile'
|
||||
| '/staff'
|
||||
|
|
@ -232,6 +242,7 @@ export interface FileRouteTypes {
|
|||
| '/'
|
||||
| '/billing'
|
||||
| '/customers'
|
||||
| '/login'
|
||||
| '/profile'
|
||||
| '/staff'
|
||||
| '/distributors/$id'
|
||||
|
|
@ -252,6 +263,7 @@ export interface FileRouteTypes {
|
|||
| '/billing'
|
||||
| '/customers'
|
||||
| '/distributors'
|
||||
| '/login'
|
||||
| '/products'
|
||||
| '/profile'
|
||||
| '/staff'
|
||||
|
|
@ -276,6 +288,7 @@ export interface RootRouteChildren {
|
|||
BillingRoute: typeof BillingRoute
|
||||
CustomersRoute: typeof CustomersRoute
|
||||
DistributorsRoute: typeof DistributorsRouteWithChildren
|
||||
LoginRoute: typeof LoginRoute
|
||||
ProductsRoute: typeof ProductsRouteWithChildren
|
||||
ProfileRoute: typeof ProfileRoute
|
||||
StaffRoute: typeof StaffRoute
|
||||
|
|
@ -320,6 +333,13 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof ProductsRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/login': {
|
||||
id: '/login'
|
||||
path: '/login'
|
||||
fullPath: '/login'
|
||||
preLoaderRoute: typeof LoginRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/distributors': {
|
||||
id: '/distributors'
|
||||
path: '/distributors'
|
||||
|
|
@ -501,6 +521,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||
BillingRoute: BillingRoute,
|
||||
CustomersRoute: CustomersRoute,
|
||||
DistributorsRoute: DistributorsRouteWithChildren,
|
||||
LoginRoute: LoginRoute,
|
||||
ProductsRoute: ProductsRouteWithChildren,
|
||||
ProfileRoute: ProfileRoute,
|
||||
StaffRoute: StaffRoute,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
Outlet,
|
||||
createRootRoute,
|
||||
useNavigate,
|
||||
useRouterState,
|
||||
} from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
||||
import { TanStackDevtools } from "@tanstack/react-devtools";
|
||||
import { AppLayout } from "#/components/AppLayout";
|
||||
import { AuthGate, useAuthStore } from "shared-react";
|
||||
|
||||
import "../styles.css";
|
||||
|
||||
|
|
@ -9,12 +16,46 @@ export const Route = createRootRoute({
|
|||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
function AuthGuard() {
|
||||
const navigate = useNavigate();
|
||||
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
||||
const isLoading = useAuthStore((s) => s.isLoading);
|
||||
const staff = useAuthStore((s) => s.staff);
|
||||
const isLoginPage = pathname === "/login";
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !staff && !isLoginPage) {
|
||||
navigate({ to: "/login" });
|
||||
}
|
||||
}, [isLoading, staff, isLoginPage, navigate]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<AppLayout>
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="text-sm text-slate-600">Loading...</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!staff && !isLoginPage) return null;
|
||||
|
||||
if (isLoginPage) return <Outlet />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppLayout>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
<AuthGate>
|
||||
<AuthGuard />
|
||||
</AuthGate>
|
||||
<TanStackDevtools
|
||||
config={{
|
||||
position: "bottom-right",
|
||||
|
|
|
|||
117
apps/pharmanager/src/routes/login.tsx
Normal file
117
apps/pharmanager/src/routes/login.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { useLogin, useAuthStore } from "shared-react";
|
||||
import { Button, Input } from "#/components/ui";
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(1, "Username is required"),
|
||||
password: z.string().min(1, "Password is required"),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const Route = createFileRoute("/login")({
|
||||
component: LoginPage,
|
||||
staticData: {
|
||||
title: "Login",
|
||||
subtitle: "Sign in to your account",
|
||||
},
|
||||
});
|
||||
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const loginMutation = useLogin();
|
||||
const setUser = useAuthStore((s) => s.setUser);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
function onSubmit(values: FormValues) {
|
||||
loginMutation.mutate(values, {
|
||||
onSuccess: (data) => {
|
||||
setUser(data.staff, {
|
||||
id: data.enterprise_id,
|
||||
name: "",
|
||||
type: "",
|
||||
});
|
||||
navigate({ to: "/" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="bg-white rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] p-8 w-full max-w-sm"
|
||||
>
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 mb-1">
|
||||
Sign In
|
||||
</h2>
|
||||
<p className="text-sm text-slate-600">
|
||||
Enter your credentials to continue
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
||||
Username
|
||||
</label>
|
||||
<Input
|
||||
{...register("username")}
|
||||
variant={errors.username ? "error" : "default"}
|
||||
placeholder="Enter your username"
|
||||
autoComplete="username"
|
||||
/>
|
||||
{errors.username && (
|
||||
<p className="text-sm text-red-600 mt-1">
|
||||
{errors.username.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-900 mb-1.5">
|
||||
Password
|
||||
</label>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("password")}
|
||||
variant={errors.password ? "error" : "default"}
|
||||
placeholder="Enter your password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="text-sm text-red-600 mt-1">
|
||||
{errors.password.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loginMutation.error && (
|
||||
<p className="text-sm text-red-600 mt-4">
|
||||
Invalid username or password
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full mt-6"
|
||||
disabled={loginMutation.isPending}
|
||||
>
|
||||
{loginMutation.isPending ? "Signing in..." : "Sign In"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -15,8 +15,8 @@ sqlite.run("INSERT OR IGNORE INTO units (name) VALUES ('piece')")
|
|||
sqlite.run("INSERT OR IGNORE INTO enterprises (id, name, type, owner_name, mobile) VALUES (1, 'Main Pharmacy', 'Pharmacy', 'Admin User', '0000000000')")
|
||||
|
||||
// Seed staff (admin user)
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const adminHash = bcrypt.hashSync('admin123', 10)
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const adminHash = bcrypt.hashSync('admin123', 10) || ''
|
||||
sqlite.run(
|
||||
"INSERT OR IGNORE INTO staff (id, name, username, password, added_on, is_password_reset_needed) VALUES (1, 'Admin', 'admin', ?, ?, 1)",
|
||||
[adminHash, today],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { eq } from 'drizzle-orm'
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
|
||||
import { db } from './db-instance'
|
||||
import { distributors } from './schema/distributors'
|
||||
|
|
@ -21,11 +21,11 @@ export type CreateDistributorInput = {
|
|||
export type UpdateDistributorPatch = Partial<CreateDistributorInput>
|
||||
|
||||
export type DistributorsRepo = {
|
||||
getDistributors: () => Promise<Distributor[]>
|
||||
getDistributorById: (id: number) => Promise<Distributor | null>
|
||||
createDistributor: (input: CreateDistributorInput) => Promise<Distributor>
|
||||
updateDistributor: (id: number, patch: UpdateDistributorPatch) => Promise<Distributor | null>
|
||||
deleteDistributor: (id: number) => Promise<boolean>
|
||||
getDistributors: (enterpriseId: number) => Promise<Distributor[]>
|
||||
getDistributorById: (id: number, enterpriseId: number) => Promise<Distributor | null>
|
||||
createDistributor: (input: CreateDistributorInput, enterpriseId: number) => Promise<Distributor>
|
||||
updateDistributor: (id: number, patch: UpdateDistributorPatch, enterpriseId: number) => Promise<Distributor | null>
|
||||
deleteDistributor: (id: number, enterpriseId: number) => Promise<boolean>
|
||||
}
|
||||
|
||||
function toDistributor(row: {
|
||||
|
|
@ -48,21 +48,25 @@ export function createDistributorsRepo(): {
|
|||
repo: DistributorsRepo
|
||||
} {
|
||||
const repo: DistributorsRepo = {
|
||||
getDistributors() {
|
||||
const rows = db.select().from(distributors).all()
|
||||
getDistributors(enterpriseId) {
|
||||
const rows = db
|
||||
.select()
|
||||
.from(distributors)
|
||||
.where(eq(distributors.enterpriseId, enterpriseId))
|
||||
.all()
|
||||
return Promise.resolve(rows.map(toDistributor))
|
||||
},
|
||||
|
||||
getDistributorById(id) {
|
||||
getDistributorById(id, enterpriseId) {
|
||||
const row = db
|
||||
.select()
|
||||
.from(distributors)
|
||||
.where(eq(distributors.id, id))
|
||||
.where(and(eq(distributors.id, id), eq(distributors.enterpriseId, enterpriseId)))
|
||||
.get()
|
||||
return Promise.resolve(row ? toDistributor(row) : null)
|
||||
},
|
||||
|
||||
createDistributor(input) {
|
||||
createDistributor(input, enterpriseId) {
|
||||
const created = db
|
||||
.insert(distributors)
|
||||
.values({
|
||||
|
|
@ -70,13 +74,14 @@ export function createDistributorsRepo(): {
|
|||
contact: input.contact,
|
||||
mobile: input.mobile,
|
||||
address: input.address ?? null,
|
||||
enterpriseId,
|
||||
})
|
||||
.returning()
|
||||
.get()
|
||||
return Promise.resolve(toDistributor(created))
|
||||
},
|
||||
|
||||
updateDistributor(id, patch) {
|
||||
updateDistributor(id, patch, enterpriseId) {
|
||||
const updated = db
|
||||
.update(distributors)
|
||||
.set({
|
||||
|
|
@ -85,16 +90,16 @@ export function createDistributorsRepo(): {
|
|||
...(patch.mobile !== undefined ? { mobile: patch.mobile } : {}),
|
||||
...(patch.address !== undefined ? { address: patch.address } : {}),
|
||||
})
|
||||
.where(eq(distributors.id, id))
|
||||
.where(and(eq(distributors.id, id), eq(distributors.enterpriseId, enterpriseId)))
|
||||
.returning()
|
||||
.get()
|
||||
return Promise.resolve(updated ? toDistributor(updated) : null)
|
||||
},
|
||||
|
||||
deleteDistributor(id) {
|
||||
deleteDistributor(id, enterpriseId) {
|
||||
const deleted = db
|
||||
.delete(distributors)
|
||||
.where(eq(distributors.id, id))
|
||||
.where(and(eq(distributors.id, id), eq(distributors.enterpriseId, enterpriseId)))
|
||||
.returning({ id: distributors.id })
|
||||
.get()
|
||||
return Promise.resolve(Boolean(deleted))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { eq } from 'drizzle-orm'
|
||||
import type { Database } from 'bun:sqlite'
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
|
||||
import { db, sqlite } from './db-instance'
|
||||
import { products } from './schema/products'
|
||||
|
|
@ -56,11 +55,11 @@ export type CreateProductInput =
|
|||
export type UpdateProductPatch = Partial<CreateProductInput>
|
||||
|
||||
export type ProductsRepo = {
|
||||
getProducts: () => Promise<Product[]>
|
||||
getProductById: (id: number) => Promise<Product | null>
|
||||
createProduct: (input: CreateProductInput) => Promise<Product>
|
||||
updateProduct: (id: number, patch: UpdateProductPatch) => Promise<Product | null>
|
||||
deleteProduct: (id: number) => Promise<boolean>
|
||||
getProducts: (enterpriseId: number) => Promise<Product[]>
|
||||
getProductById: (id: number, enterpriseId: number) => Promise<Product | null>
|
||||
createProduct: (input: CreateProductInput, enterpriseId: number) => Promise<Product>
|
||||
updateProduct: (id: number, patch: UpdateProductPatch, enterpriseId: number) => Promise<Product | null>
|
||||
deleteProduct: (id: number, enterpriseId: number) => Promise<boolean>
|
||||
}
|
||||
|
||||
function getOrCreateDrug(name: string): number {
|
||||
|
|
@ -151,8 +150,8 @@ function setCompositions(productId: number, comps: CompositionInput[]) {
|
|||
|
||||
export function createProductsRepo(): { repo: ProductsRepo } {
|
||||
const repo: ProductsRepo = {
|
||||
getProducts() {
|
||||
const rows = db.select().from(products).all()
|
||||
getProducts(enterpriseId) {
|
||||
const rows = db.select().from(products).where(eq(products.enterpriseId, enterpriseId)).all()
|
||||
return Promise.resolve(
|
||||
rows.map((r) => {
|
||||
const distributor = fetchDistributor(r.distributorId)
|
||||
|
|
@ -165,8 +164,8 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
)
|
||||
},
|
||||
|
||||
getProductById(id) {
|
||||
const row = db.select().from(products).where(eq(products.id, id)).get()
|
||||
getProductById(id, enterpriseId) {
|
||||
const row = db.select().from(products).where(and(eq(products.id, id), eq(products.enterpriseId, enterpriseId))).get()
|
||||
if (!row) return Promise.resolve(null)
|
||||
const distributor = fetchDistributor(row.distributorId)
|
||||
const unit = fetchUnit(row.unitId)
|
||||
|
|
@ -176,7 +175,7 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
})
|
||||
},
|
||||
|
||||
createProduct(input) {
|
||||
createProduct(input, enterpriseId) {
|
||||
const result = sqlite.transaction(() => {
|
||||
const unitId = getOrCreateUnit(input.unit_name)
|
||||
|
||||
|
|
@ -196,6 +195,7 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
unitsPerStrip: input.units_per_strip ?? null,
|
||||
hideProductFromPublic: input.hide_product_from_public ?? false,
|
||||
hidePriceFromPublic: input.hide_price_from_public ?? false,
|
||||
enterpriseId,
|
||||
})
|
||||
.returning()
|
||||
.get()
|
||||
|
|
@ -214,8 +214,8 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
})
|
||||
},
|
||||
|
||||
updateProduct(id, patch) {
|
||||
const existing = db.select().from(products).where(eq(products.id, id)).get()
|
||||
updateProduct(id, patch, enterpriseId) {
|
||||
const existing = db.select().from(products).where(and(eq(products.id, id), eq(products.enterpriseId, enterpriseId))).get()
|
||||
if (!existing) return Promise.resolve(null)
|
||||
|
||||
sqlite.transaction(() => {
|
||||
|
|
@ -236,7 +236,7 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
if (patch.hide_price_from_public !== undefined) setData.hidePriceFromPublic = patch.hide_price_from_public
|
||||
|
||||
if (Object.keys(setData).length > 0) {
|
||||
db.update(products).set(setData).where(eq(products.id, id)).run()
|
||||
db.update(products).set(setData).where(and(eq(products.id, id), eq(products.enterpriseId, enterpriseId))).run()
|
||||
}
|
||||
|
||||
if (patch.compositions) {
|
||||
|
|
@ -244,7 +244,7 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
}
|
||||
})()
|
||||
|
||||
const updated = db.select().from(products).where(eq(products.id, id)).get()!
|
||||
const updated = db.select().from(products).where(and(eq(products.id, id), eq(products.enterpriseId, enterpriseId))).get()!
|
||||
const distributor = fetchDistributor(updated.distributorId)
|
||||
const unit = fetchUnit(updated.unitId)
|
||||
|
||||
|
|
@ -254,9 +254,9 @@ export function createProductsRepo(): { repo: ProductsRepo } {
|
|||
})
|
||||
},
|
||||
|
||||
deleteProduct(id) {
|
||||
deleteProduct(id, enterpriseId) {
|
||||
db.delete(productCompositions).where(eq(productCompositions.productId, id)).run()
|
||||
const deleted = db.delete(products).where(eq(products.id, id)).returning({ id: products.id }).get()
|
||||
const deleted = db.delete(products).where(and(eq(products.id, id), eq(products.enterpriseId, enterpriseId))).returning({ id: products.id }).get()
|
||||
return Promise.resolve(Boolean(deleted))
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { eq } from 'drizzle-orm'
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
|
||||
import { db, sqlite } from './db-instance'
|
||||
import { stockBatches } from './schema/stockBatches'
|
||||
|
|
@ -34,11 +34,11 @@ export type CreateStockBatchInput = {
|
|||
export type UpdateStockBatchPatch = Partial<CreateStockBatchInput>
|
||||
|
||||
export type StockBatchesRepo = {
|
||||
getStockBatches: () => Promise<StockBatch[]>
|
||||
getStockBatchById: (id: number) => Promise<StockBatch | null>
|
||||
createStockBatch: (input: CreateStockBatchInput) => Promise<StockBatch>
|
||||
updateStockBatch: (id: number, patch: UpdateStockBatchPatch) => Promise<StockBatch | null>
|
||||
deleteStockBatch: (id: number) => Promise<boolean>
|
||||
getStockBatches: (enterpriseId: number) => Promise<StockBatch[]>
|
||||
getStockBatchById: (id: number, enterpriseId: number) => Promise<StockBatch | null>
|
||||
createStockBatch: (input: CreateStockBatchInput, enterpriseId: number) => Promise<StockBatch>
|
||||
updateStockBatch: (id: number, patch: UpdateStockBatchPatch, enterpriseId: number) => Promise<StockBatch | null>
|
||||
deleteStockBatch: (id: number, enterpriseId: number) => Promise<boolean>
|
||||
}
|
||||
|
||||
function fetchProduct(productId: number): { id: number; name: string; brand: string } | null {
|
||||
|
|
@ -78,22 +78,22 @@ function toStockBatch(row: typeof stockBatches.$inferSelect): StockBatch {
|
|||
|
||||
export function createStockBatchesRepo(): { repo: StockBatchesRepo } {
|
||||
const repo: StockBatchesRepo = {
|
||||
getStockBatches() {
|
||||
const rows = db.select().from(stockBatches).all()
|
||||
getStockBatches(enterpriseId) {
|
||||
const rows = db.select().from(stockBatches).where(eq(stockBatches.enterpriseId, enterpriseId)).all()
|
||||
return Promise.resolve(rows.map(toStockBatch))
|
||||
},
|
||||
|
||||
getStockBatchById(id) {
|
||||
const row = db.select().from(stockBatches).where(eq(stockBatches.id, id)).get()
|
||||
getStockBatchById(id, enterpriseId) {
|
||||
const row = db.select().from(stockBatches).where(and(eq(stockBatches.id, id), eq(stockBatches.enterpriseId, enterpriseId))).get()
|
||||
return Promise.resolve(row ? toStockBatch(row) : null)
|
||||
},
|
||||
|
||||
createStockBatch(input) {
|
||||
createStockBatch(input, enterpriseId) {
|
||||
const result = sqlite.transaction(() => {
|
||||
if (input.is_default) {
|
||||
db.update(stockBatches)
|
||||
.set({ isDefault: false })
|
||||
.where(eq(stockBatches.productId, input.product_id))
|
||||
.where(and(eq(stockBatches.productId, input.product_id), eq(stockBatches.enterpriseId, enterpriseId)))
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +109,7 @@ export function createStockBatchesRepo(): { repo: StockBatchesRepo } {
|
|||
distributorId: input.distributor_id ?? null,
|
||||
quantity: input.quantity ?? 0,
|
||||
isDefault: input.is_default ?? false,
|
||||
enterpriseId,
|
||||
})
|
||||
.returning()
|
||||
.get()
|
||||
|
|
@ -119,15 +120,15 @@ export function createStockBatchesRepo(): { repo: StockBatchesRepo } {
|
|||
return Promise.resolve(toStockBatch(result))
|
||||
},
|
||||
|
||||
updateStockBatch(id, patch) {
|
||||
const existing = db.select().from(stockBatches).where(eq(stockBatches.id, id)).get()
|
||||
updateStockBatch(id, patch, enterpriseId) {
|
||||
const existing = db.select().from(stockBatches).where(and(eq(stockBatches.id, id), eq(stockBatches.enterpriseId, enterpriseId))).get()
|
||||
if (!existing) return Promise.resolve(null)
|
||||
|
||||
sqlite.transaction(() => {
|
||||
if (patch.is_default) {
|
||||
db.update(stockBatches)
|
||||
.set({ isDefault: false })
|
||||
.where(eq(stockBatches.productId, patch.product_id ?? existing.productId))
|
||||
.where(and(eq(stockBatches.productId, patch.product_id ?? existing.productId), eq(stockBatches.enterpriseId, enterpriseId)))
|
||||
.run()
|
||||
}
|
||||
|
||||
|
|
@ -143,16 +144,16 @@ export function createStockBatchesRepo(): { repo: StockBatchesRepo } {
|
|||
if (patch.is_default !== undefined) setData.isDefault = patch.is_default
|
||||
|
||||
if (Object.keys(setData).length > 0) {
|
||||
db.update(stockBatches).set(setData).where(eq(stockBatches.id, id)).run()
|
||||
db.update(stockBatches).set(setData).where(and(eq(stockBatches.id, id), eq(stockBatches.enterpriseId, enterpriseId))).run()
|
||||
}
|
||||
})()
|
||||
|
||||
const updated = db.select().from(stockBatches).where(eq(stockBatches.id, id)).get()!
|
||||
const updated = db.select().from(stockBatches).where(and(eq(stockBatches.id, id), eq(stockBatches.enterpriseId, enterpriseId))).get()!
|
||||
return Promise.resolve(toStockBatch(updated))
|
||||
},
|
||||
|
||||
deleteStockBatch(id) {
|
||||
const deleted = db.delete(stockBatches).where(eq(stockBatches.id, id)).returning({ id: stockBatches.id }).get()
|
||||
deleteStockBatch(id, enterpriseId) {
|
||||
const deleted = db.delete(stockBatches).where(and(eq(stockBatches.id, id), eq(stockBatches.enterpriseId, enterpriseId))).returning({ id: stockBatches.id }).get()
|
||||
return Promise.resolve(Boolean(deleted))
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { eq } from 'drizzle-orm'
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
|
||||
import { db } from './db-instance'
|
||||
import { storageSpaces } from './schema/storageSpacesSchema'
|
||||
|
|
@ -22,11 +22,11 @@ export type CreateStorageSpaceInput = {
|
|||
export type UpdateStorageSpacePatch = Partial<CreateStorageSpaceInput>
|
||||
|
||||
export type StorageSpacesRepo = {
|
||||
getStorageSpaces: () => Promise<StorageSpace[]>
|
||||
getStorageSpaceById: (id: number) => Promise<StorageSpace | null>
|
||||
createStorageSpace: (input: CreateStorageSpaceInput) => Promise<StorageSpace>
|
||||
updateStorageSpace: (id: number, patch: UpdateStorageSpacePatch) => Promise<StorageSpace | null>
|
||||
deleteStorageSpace: (id: number) => Promise<boolean>
|
||||
getStorageSpaces: (enterpriseId: number) => Promise<StorageSpace[]>
|
||||
getStorageSpaceById: (id: number, enterpriseId: number) => Promise<StorageSpace | null>
|
||||
createStorageSpace: (input: CreateStorageSpaceInput, enterpriseId: number) => Promise<StorageSpace>
|
||||
updateStorageSpace: (id: number, patch: UpdateStorageSpacePatch, enterpriseId: number) => Promise<StorageSpace | null>
|
||||
deleteStorageSpace: (id: number, enterpriseId: number) => Promise<boolean>
|
||||
}
|
||||
|
||||
function toStorageSpace(row: {
|
||||
|
|
@ -50,21 +50,25 @@ export function createStorageSpacesRepo(): {
|
|||
} {
|
||||
|
||||
const repo: StorageSpacesRepo = {
|
||||
getStorageSpaces() {
|
||||
const rows = db.select().from(storageSpaces).all()
|
||||
getStorageSpaces(enterpriseId) {
|
||||
const rows = db
|
||||
.select()
|
||||
.from(storageSpaces)
|
||||
.where(eq(storageSpaces.enterpriseId, enterpriseId))
|
||||
.all()
|
||||
return Promise.resolve(rows.map(toStorageSpace))
|
||||
},
|
||||
|
||||
getStorageSpaceById(id) {
|
||||
getStorageSpaceById(id, enterpriseId) {
|
||||
const row = db
|
||||
.select()
|
||||
.from(storageSpaces)
|
||||
.where(eq(storageSpaces.id, id))
|
||||
.where(and(eq(storageSpaces.id, id), eq(storageSpaces.enterpriseId, enterpriseId)))
|
||||
.get()
|
||||
return Promise.resolve(row ? toStorageSpace(row) : null)
|
||||
},
|
||||
|
||||
createStorageSpace(input) {
|
||||
createStorageSpace(input, enterpriseId) {
|
||||
const created = db
|
||||
.insert(storageSpaces)
|
||||
.values({
|
||||
|
|
@ -72,13 +76,14 @@ export function createStorageSpacesRepo(): {
|
|||
description: input.description ?? null,
|
||||
aliases: serializeStringArrJson(input.aliases),
|
||||
imageUrls: serializeStringArrJson(input.image_urls),
|
||||
enterpriseId,
|
||||
})
|
||||
.returning()
|
||||
.get()
|
||||
return Promise.resolve(toStorageSpace(created))
|
||||
},
|
||||
|
||||
updateStorageSpace(id, patch) {
|
||||
updateStorageSpace(id, patch, enterpriseId) {
|
||||
const updated = db
|
||||
.update(storageSpaces)
|
||||
.set({
|
||||
|
|
@ -91,16 +96,16 @@ export function createStorageSpacesRepo(): {
|
|||
? { imageUrls: serializeStringArrJson(patch.image_urls) }
|
||||
: {}),
|
||||
})
|
||||
.where(eq(storageSpaces.id, id))
|
||||
.where(and(eq(storageSpaces.id, id), eq(storageSpaces.enterpriseId, enterpriseId)))
|
||||
.returning()
|
||||
.get()
|
||||
return Promise.resolve(updated ? toStorageSpace(updated) : null)
|
||||
},
|
||||
|
||||
deleteStorageSpace(id) {
|
||||
deleteStorageSpace(id, enterpriseId) {
|
||||
const deleted = db
|
||||
.delete(storageSpaces)
|
||||
.where(eq(storageSpaces.id, id))
|
||||
.where(and(eq(storageSpaces.id, id), eq(storageSpaces.enterpriseId, enterpriseId)))
|
||||
.returning({ id: storageSpaces.id })
|
||||
.get()
|
||||
return Promise.resolve(Boolean(deleted))
|
||||
|
|
|
|||
24
packages/shared-react/src/auth-gate.tsx
Normal file
24
packages/shared-react/src/auth-gate.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { useEffect, type ReactNode } from "react";
|
||||
import { useWhoAmI, useAuthStore } from "./auth";
|
||||
|
||||
export function AuthGate({ children }: { children: ReactNode }) {
|
||||
const { data, isLoading } = useWhoAmI();
|
||||
const setUser = useAuthStore((s) => s.setUser);
|
||||
const setLoading = useAuthStore((s) => s.setLoading);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(isLoading);
|
||||
}, [isLoading, setLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
if (data) {
|
||||
setUser(data.staff, data.enterprise);
|
||||
} else {
|
||||
setUser(null, null);
|
||||
}
|
||||
}
|
||||
}, [data, isLoading, setUser]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
48
packages/shared-react/src/auth.ts
Normal file
48
packages/shared-react/src/auth.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { create } from "zustand";
|
||||
import { trpc } from "./trpc";
|
||||
|
||||
export interface AuthStaff {
|
||||
id: number;
|
||||
name: string;
|
||||
username: string;
|
||||
isPasswordResetNeeded: boolean;
|
||||
}
|
||||
|
||||
export interface AuthEnterprise {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
staff: AuthStaff | null;
|
||||
enterprise: AuthEnterprise | null;
|
||||
isLoading: boolean;
|
||||
setUser: (
|
||||
staff: AuthStaff | null,
|
||||
enterprise: AuthEnterprise | null,
|
||||
) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
staff: null,
|
||||
enterprise: null,
|
||||
isLoading: true,
|
||||
setUser: (staff, enterprise) =>
|
||||
set({ staff, enterprise, isLoading: false }),
|
||||
setLoading: (isLoading) => set({ isLoading }),
|
||||
logout: () => set({ staff: null, enterprise: null, isLoading: false }),
|
||||
}));
|
||||
|
||||
export function useWhoAmI() {
|
||||
return trpc.auth.whoAmI.useQuery(undefined, {
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLogin() {
|
||||
return trpc.auth.login.useMutation();
|
||||
}
|
||||
|
|
@ -8,3 +8,6 @@ export * from './hooks/drugInfo'
|
|||
export * from './hooks/units'
|
||||
export * from './hooks/stockBatches'
|
||||
export { trpc } from './trpc'
|
||||
export { useAuthStore, useWhoAmI, useLogin } from './auth'
|
||||
export type { AuthStaff, AuthEnterprise } from './auth'
|
||||
export { AuthGate } from './auth-gate'
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export function createTrpcClient(baseUrl: string) {
|
|||
links: [
|
||||
httpBatchLink({
|
||||
url: `${baseUrl}/trpc`,
|
||||
fetch: (url, options) =>
|
||||
fetch(url, { ...options, credentials: "include" }),
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue