diff --git a/astro/public/favicon.svg b/astro/public/favicon.svg index f157bd1..d2e98b6 100644 --- a/astro/public/favicon.svg +++ b/astro/public/favicon.svg @@ -1,9 +1,5 @@ - - - + + + + diff --git a/astro/public/kios-logo.png b/astro/public/kios-logo.png new file mode 100644 index 0000000..7e7da72 Binary files /dev/null and b/astro/public/kios-logo.png differ diff --git a/astro/public/lumbung-logo.png b/astro/public/lumbung-logo.png new file mode 100644 index 0000000..1a8efb9 Binary files /dev/null and b/astro/public/lumbung-logo.png differ diff --git a/astro/public/route-guide.png b/astro/public/route-guide.png new file mode 100644 index 0000000..688eb2f Binary files /dev/null and b/astro/public/route-guide.png differ diff --git a/astro/src/astroTypes.ts b/astro/src/astroTypes.ts new file mode 100644 index 0000000..a178f58 --- /dev/null +++ b/astro/src/astroTypes.ts @@ -0,0 +1,80 @@ +export interface User { + name: string; + id: string; + email: string; + phoneNumber: string; +} + +export interface Node { + name: string; + id: string; +} + +export interface Media { + id: string; + alt?: string; + updatedAt: string; + createdAt: string; + url?: string; + filename?: string; + mimeType?: string; + filesize?: number; + width?: number; + height?: number; +} + +export interface Product extends Node { + id: string; + name: string; + weight?: number; + picture: Media; + createdAt: string; + updatedAt: string; +}; + +// export interface Location = { +// latitude: number; +// longitude: number; +// } + +export interface Maker extends Node { + email: string; + phoneNumber?: string; + location: [number, number]; + stock: Product[]; + createdAt: string; + updatedAt: string; +}; + +export interface Retailer extends Node { + email: string; + phoneNumber?: string; + location: [number, number]; + stock: Product[]; + createdAt: string; + updatedAt: string; +}; + +const DISPATCH_STATUS = ['requested', 'accepted', 'archived'] as const; +export type DispatchStatus = typeof DISPATCH_STATUS[number]; + +export interface Dispatch { + id: string; + dispatchesCode?: string; //Human readable id + createdAt: string; + updatedAt: string; + + maker: Maker; + retailer: Retailer; + products: Product[]; + + courier?: User; + + timeSensitive: boolean; + status: DispatchStatus; + + departureDate: string; + arrivalDate: string; + weightAllowance: number; +} + diff --git a/astro/src/components/KiosMap.tsx b/astro/src/components/KiosMap.tsx index bdfc532..fedfedb 100644 --- a/astro/src/components/KiosMap.tsx +++ b/astro/src/components/KiosMap.tsx @@ -3,9 +3,10 @@ import { MapContainer, TileLayer, Marker, CircleMarker, Popup, Polyline, LayerGr import 'leaflet/dist/leaflet.css'; import L, { LatLngBounds } from 'leaflet'; import Contacts from './Contacts'; +import type { User, Node, Retailer, Maker, Product, Dispatch, DispatchStatus } from '../astroTypes'; import { useQuery, useMutation, useQueryClient, queryOptions } from "@tanstack/react-query"; -import axios from "axios"; +import { useGetMakers, useGetDispatches, useGetRetailers, useGetUser, useGetMyself } from "../utils/hooks" import { Button, buttonVariants } from './ui/Button'; @@ -17,156 +18,8 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog" - - -//Todo: Move types to own file -interface User { - name: string; - id: string; - email: string; - phoneNumber: string; -} - -interface Node { - name: string; - id: string; -} - -export interface Media { - id: string; - alt?: string; - updatedAt: string; - createdAt: string; - url?: string; - filename?: string; - mimeType?: string; - filesize?: number; - width?: number; - height?: number; -} - -interface Product extends Node { - id: string; - name: string; - weight?: number; - picture: Media; - createdAt: string; - updatedAt: string; -}; - -// interface Location = { -// latitude: number; -// longitude: number; -// } - -interface Maker extends Node { - email: string; - phoneNumber?: string; - location: [number, number]; - stock: Product[]; - createdAt: string; - updatedAt: string; -}; - -interface Retailer extends Node { - email: string; - phoneNumber?: string; - location: [number, number]; - stock: Product[]; - createdAt: string; - updatedAt: string; -}; - -const DISPATCH_STATUS = ['requested', 'accepted', 'archived'] as const; -type DispatchStatus = typeof DISPATCH_STATUS[number]; - -interface Dispatch { - id: string; - dispatchesCode?: string; //Human readable id - createdAt: string; - updatedAt: string; - - maker: Maker; - retailer: Retailer; - products: Product[]; - - courier?: User; - - timeSensitive: boolean; - status: DispatchStatus; - - departureDate: string; - arrivalDate: string; - weightAllowance: number; -} - - -//Todo: update fetch url endpoints -//Todo: Move queryclient and hooks to own file -//Todo: Move axios stuff -const API_URL = "http://localhost:3001" - -const headers = { - "Content-Type": "application/json", -} - -const getMakers = async () => { - const url = `${API_URL}/api/makers` - console.log("Fetching url:", url) - const response = await axios.get(url); - - const makers: Maker[] = response.data.docs; - console.log(`Fetch result from ${url}`, makers) - return makers; - -} - -const useGetMakers = () => { - return useQuery({ - queryFn: () => getMakers(), - queryKey: ['makers'], - enabled: true - }) -} - -const getRetailers = async () => { - const url = `${API_URL}/api/retailers` - console.log("Fetching url:", url) - const response = await axios.get(url); - - const retailers: Retailer[] = response.data.docs; - console.log(`Fetch result from ${url}`, retailers) - return retailers; - -} - -const useGetRetailers = () => { - return useQuery({ - queryFn: () => getRetailers(), - queryKey: ['retailers'], - enabled: true - }) -} - -const getDispatches = async () => { - const url = `${API_URL}/api/dispatches` - console.log("Fetching url:", url) - const response = await axios.get(url); - - const dispatches: Dispatch[] = response.data.docs; - - console.log(`Fetch result from ${url}`, dispatches) - return dispatches; - -} - -const useGetDispatches = () => { - return useQuery({ - queryFn: () => getDispatches(), - queryKey: ['dispatches'], - enabled: true - }) -} +import { LoginForm } from './LoginForm'; +import { hasAuthCookie } from '@/utils/authUtils'; //Payload longitude and latitude are mislabeled in payload (lol) const locationSwitcharoo = (location: number[]) => { @@ -206,10 +59,14 @@ interface NodeSelection { export const KiosMap = () => { + const [authToken, setAuthToken] = useState('') + const { data: makers, isLoading: isLoadingMakers } = useGetMakers(); const { data: retailers, isLoading: isLoadingRetailers } = useGetRetailers(); const { data: dispatches, isLoading: isLoadingDispatches } = useGetDispatches(); + const { data: myself, isLoading: isLoadingMyself } = useGetMyself(authToken); + const [selectedNode, setSelectedNode] = useState({ id: "", type: "none" }) let selectedMaker: Maker | undefined = undefined; @@ -267,31 +124,64 @@ export const KiosMap = () => { ); return ( -
- { - selectedNode.type !== 'none' && ( -
-
- {selectedMaker !== undefined && ( -
- - {(selectedMaker.stock !== undefined && selectedMaker.stock.length > 0) && - - - See catalogue - +
+ +
+ + + {(myself && myself.name) && +

Logged in as: {myself.name}

+ } + { + (!hasAuthCookie() && !authToken) && + + Login + + } +
- - - {selectedMaker.name}'s stock - + + + Login + + + +
+ +
+
+
+ +
+
+ { + selectedNode.type !== 'none' && ( +
+
+ {selectedMaker !== undefined && ( +
+ + {(selectedMaker.stock !== undefined && selectedMaker.stock.length > 0) && + + + See catalogue + + + + + {selectedMaker.name}'s stock
    {selectedMaker.stock.map((product, i) => { return ( @@ -322,175 +212,175 @@ export const KiosMap = () => { ) })}
- -
-
-
- } -
- )} - - {selectedRetailer !== undefined && ( - <> - - - )} - - {selectedDispatch !== undefined && ( -
-
-

- Product{selectedDispatch.products.length > 1 && 's'} -

- {selectedDispatch.products.map((product, i) => { - return ( -
- {product.picture.alt} -

{product.name}

-
- ) - })} + + +
+ }
+ )} + {selectedRetailer !== undefined && ( + <> + + + )} - + {selectedDispatch !== undefined && ( +
+
+

+ Product{selectedDispatch.products.length > 1 && 's'} +

+ {selectedDispatch.products.map((product, i) => { + return ( +
+ {product.picture.alt} +

{product.name}

+
+ ) + })} +
- - - {selectedDispatch.courier !== undefined ? ( - ) : -
-

No courier!

- -
- } -
- )} + + + + {selectedDispatch.courier !== undefined ? ( + + + ) : +
+

No courier!

+ +
+ } +
+ )} +
- - ) - } - - - handleSelectNode("", "none") - }} - attribution='© OpenStreetMap' - /> - - {(makers && !isLoadingMakers) && - - {makers.map((maker: any, index: number) => ( - handleSelectNode(maker.id, "maker") - }} - key={maker.id} - position={[locationSwitcharoo(maker.location)[0], locationSwitcharoo(maker.location)[1]]} - icon={selectedNode.id === maker.id ? selectedDotIcon : blackDotIcon} - > - {/* {maker.name} */} - - ))} - + ) } - {(retailers && !isLoadingRetailers) && - - {retailers.map((retailer: any, index: number) => ( - handleSelectNode(retailer.id, "retailer") - }} - key={retailer.id} - position={[locationSwitcharoo(retailer.location)[0], locationSwitcharoo(retailer.location)[1]]} - icon={selectedNode.id === retailer.id ? selectedDotIcon : blackDotIcon} - > - {/* {retailer.name} */} - - ))} - - } + + handleSelectNode("", "none") + }} + attribution='© OpenStreetMap' + /> - {(dispatches && !isLoadingDispatches) && - - {dispatches.map((dispatch: any, index: number) => { + {(makers && !isLoadingMakers) && + + {makers.map((maker: any, index: number) => ( + handleSelectNode(maker.id, "maker") + }} + key={maker.id} + position={[locationSwitcharoo(maker.location)[0], locationSwitcharoo(maker.location)[1]]} + icon={selectedNode.id === maker.id ? selectedDotIcon : blackDotIcon} + > + {/* {maker.name} */} + + ))} + + } - if (dispatch.maker && dispatch.retailer) { + {(retailers && !isLoadingRetailers) && + + {retailers.map((retailer: any, index: number) => ( + handleSelectNode(retailer.id, "retailer") + }} + key={retailer.id} + position={[locationSwitcharoo(retailer.location)[0], locationSwitcharoo(retailer.location)[1]]} + icon={selectedNode.id === retailer.id ? selectedDotIcon : blackDotIcon} + > + {/* {retailer.name} */} + + ))} + + } - const start = locationSwitcharoo(dispatch.maker.location); - const end = locationSwitcharoo(dispatch.retailer.location); + {(dispatches && !isLoadingDispatches) && + + {dispatches.map((dispatch: any, index: number) => { + + if (dispatch.maker && dispatch.retailer) { + + const start = locationSwitcharoo(dispatch.maker.location); + const end = locationSwitcharoo(dispatch.retailer.location); - let productsString = ''; - dispatch.products.forEach((product: any, i: number) => { - productsString += product.productTitle + (i + 1 < dispatch.products.length ? ', ' : ''); - }); + let productsString = ''; + dispatch.products.forEach((product: any, i: number) => { + productsString += product.productTitle + (i + 1 < dispatch.products.length ? ', ' : ''); + }); - //status type should already be inferred when list of dispatches is created, weird that is is required - const status: DispatchStatus = dispatch.status; + //status type should already be inferred when list of dispatches is created, weird that is is required + const status: DispatchStatus = dispatch.status; - const dashArray: string = dashArrays[status] - const dashColor: string = dashColors[status] - const dashOpacity: number = dashOpacities[status] + const dashArray: string = dashArrays[status] + const dashColor: string = dashColors[status] + const dashOpacity: number = dashOpacities[status] - return ( - handleSelectNode(dispatch.id, "dispatch") - }} - key={dispatch.id} - positions={[[start[0], start[1]], [end[0], end[1]]]} - pathOptions={{ color: selectedNode.id === dispatch.id ? dashColorSelected : dashColor }} - opacity={dashOpacity} - dashArray={dashArray} /> - ); - } - })} - - } - + return ( + handleSelectNode(dispatch.id, "dispatch") + }} + key={dispatch.id} + positions={[[start[0], start[1]], [end[0], end[1]]]} + pathOptions={{ color: selectedNode.id === dispatch.id ? dashColorSelected : dashColor }} + opacity={dashOpacity} + dashArray={dashArray} /> + ); + } + })} + + } + + ); }; diff --git a/astro/src/components/LoginForm.tsx b/astro/src/components/LoginForm.tsx new file mode 100644 index 0000000..3344fa4 --- /dev/null +++ b/astro/src/components/LoginForm.tsx @@ -0,0 +1,143 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" + +import { Button } from "@/components/ui/Button" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import axios from "axios"; +import { setAuthCookie } from "@/utils/authUtils" + +const API_URL = "http://localhost:3001"; + +const headers = { + "Content-Type": "application/json", + "Access-Control-Allow-Credentials": "true" +} + +const loginFetch = async (email: string, password: string) => { + try { + const response = await fetch(`${API_URL}/api/users/login`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: email, + password: password + }), + }) + } catch (err) { + console.log(err) + } +}; + +interface LoginFormProps { + setAuthToken: (token: string) => void; + authToken: string; +} + +export function LoginForm(props: LoginFormProps) { + + + const login = async (email: string, password: string) => { + try { + const response = await axios.post(`${API_URL}/api/users/login`, { + email: email, + password: password + }, { + withCredentials: true, // include cookies in the request + headers: headers + }); + const data = response.data; + + if (response.status !== 200) { + throw Error("Login failed") + } + + setAuthCookie(data.token, 30); + props.setAuthToken(data.token) + return + + } catch (error) { + window.alert(error) + } + }; + + + const formSchema = z.object({ + email: z.string().email({ + message: "Email must be valid" + }), + password: z.string().min(1, { + }), + }) + + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + }, + }) + + function onSubmit(values: z.infer) { + login(values.email, values.password) + } + + return ( +
+ {props.authToken ?

Login successful

+ : +
+
+ + ( + + Email + + + + + + + + )} + /> + ( + + Password + + + + + + + + )} + /> + + + +
+ } +
+ ) +} diff --git a/astro/src/components/ui/form.tsx b/astro/src/components/ui/form.tsx index d03ab7b..719d6a3 100644 --- a/astro/src/components/ui/form.tsx +++ b/astro/src/components/ui/form.tsx @@ -3,9 +3,9 @@ import * as LabelPrimitive from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot" import { Controller, - ControllerProps, - FieldPath, - FieldValues, + type ControllerProps, + type FieldPath, + type FieldValues, FormProvider, useFormContext, } from "react-hook-form" diff --git a/astro/src/types.ts b/astro/src/types.ts index 07caf0d..f4cd53e 100755 --- a/astro/src/types.ts +++ b/astro/src/types.ts @@ -22,8 +22,6 @@ export interface User { id: string; name: string; phoneNumber?: number; - adminOfMakers?: string[] | Maker[]; - adminOfRetailers?: string[] | Retailer[]; updatedAt: string; createdAt: string; email: string; @@ -35,28 +33,6 @@ export interface User { lockUntil?: string; password?: string; } -export interface Maker { - id: string; - name: string; - phoneNumber?: string; - email?: string; - /** - * @minItems 2 - * @maxItems 2 - */ - location: [number, number]; - stock?: string[] | Product[]; - updatedAt: string; - createdAt: string; -} -export interface Product { - id: string; - name: string; - picture: string | Media; - weight?: number; - updatedAt: string; - createdAt: string; -} export interface Media { id: string; alt?: string; @@ -69,20 +45,6 @@ export interface Media { width?: number; height?: number; } -export interface Retailer { - id: string; - name: string; - phoneNumber?: string; - email?: string; - /** - * @minItems 2 - * @maxItems 2 - */ - location: [number, number]; - stock?: string[] | Product[]; - updatedAt: string; - createdAt: string; -} export interface Courier { id: string; updatedAt: string; @@ -99,3 +61,41 @@ export interface Dispatch { updatedAt: string; createdAt: string; } +export interface Product { + id: string; + name: string; + picture: string | Media; + weight?: number; + updatedAt: string; + createdAt: string; +} +export interface Maker { + id: string; + name: string; + phoneNumber?: string; + email?: string; + /** + * @minItems 2 + * @maxItems 2 + */ + location: [number, number]; + admins?: string[] | User[]; + stock?: string[] | Product[]; + updatedAt: string; + createdAt: string; +} +export interface Retailer { + id: string; + name: string; + phoneNumber?: string; + email?: string; + /** + * @minItems 2 + * @maxItems 2 + */ + location: [number, number]; + admins?: string[] | User[]; + stock?: string[] | Product[]; + updatedAt: string; + createdAt: string; +} diff --git a/astro/src/utils/authUtils.ts b/astro/src/utils/authUtils.ts new file mode 100644 index 0000000..441e147 --- /dev/null +++ b/astro/src/utils/authUtils.ts @@ -0,0 +1,12 @@ +export const hasAuthCookie = () => { + const matches = document.cookie.match(/^(.*;)?\s*payload-token\s*=\s*[^;]+(.*)?$/) || [] + const hasMatch = matches.length > 0 + return hasMatch; +} + +export const setAuthCookie = (value: string, expirationDays: number) => { + const date = new Date(); + date.setTime(date.getTime() + (expirationDays * 24 * 60 * 60 * 1000)); + const expires = "expires=" + date.toUTCString(); + document.cookie = "payload-token=" + value + ";" + expires + ";path=/"; +} \ No newline at end of file diff --git a/astro/src/utils/hooks.ts b/astro/src/utils/hooks.ts index e69de29..6194167 100644 --- a/astro/src/utils/hooks.ts +++ b/astro/src/utils/hooks.ts @@ -0,0 +1,114 @@ +import type { User, Node, Retailer, Maker, Product, Dispatch } from '../astroTypes'; +import { useQuery, useMutation, useQueryClient, queryOptions } from "@tanstack/react-query"; +import axios from "axios"; +import { hasAuthCookie } from './authUtils'; + +const API_URL = "http://localhost:3001" + +const nonAuthHeaders = { + "Content-Type": "application/json", +} + +const authHeaders = { + "Content-Type": "application/json", +} + +const getMakers = async () => { + const url = `${API_URL}/api/makers` + console.log("Fetching url:", url) + const response = await axios.get(url); + + const makers: Maker[] = response.data.docs; + console.log(`Fetch result from ${url}`, makers) + return makers; + +} + +export const useGetMakers = () => { + return useQuery({ + queryFn: () => getMakers(), + queryKey: ['makers'], + enabled: true + }) +} + +const getRetailers = async () => { + const url = `${API_URL}/api/retailers` + console.log("Fetching url:", url) + const response = await axios.get(url); + + const retailers: Retailer[] = response.data.docs; + console.log(`Fetch result from ${url}`, retailers) + return retailers; + +} + +export const useGetRetailers = () => { + return useQuery({ + queryFn: () => getRetailers(), + queryKey: ['retailers'], + enabled: true + }) +} + +const getUser = async (userId: string) => { + const url = `${API_URL}/api/users/${userId}` + console.log("Fetching url:", url) + const response = await axios.get(url); + + const user: User = response.data.docs; + console.log(`Fetch result from ${url}`, user) + return user; + +} + +export const useGetUser = (userId: string) => { + return useQuery({ + queryFn: () => getUser(userId), + queryKey: ['user'], + enabled: true//If login cookie + }) +} + +const getMyself = async (authToken: string) => { + const url = `${API_URL}/api/users/me` + console.log("Fetching url:", url) + + const response = await axios.get(`${API_URL}/api/users/me`, { + withCredentials: true, + headers: authHeaders + }); + + const user: User = response.data.docs; + console.log(`Fetch result from ${url}`, user) + return user; + +} + +export const useGetMyself = (authToken: string) => { + return useQuery({ + queryFn: () => getMyself(authToken), + queryKey: ['myself'], + enabled: authToken !== '' + }) +} + +const getDispatches = async () => { + const url = `${API_URL}/api/dispatches` + console.log("Fetching url:", url) + const response = await axios.get(url); + + const dispatches: Dispatch[] = response.data.docs; + + console.log(`Fetch result from ${url}`, dispatches) + return dispatches; + +} + +export const useGetDispatches = () => { + return useQuery({ + queryFn: () => getDispatches(), + queryKey: ['dispatches'], + enabled: true + }) +} \ No newline at end of file diff --git a/astro/yarn.lock b/astro/yarn.lock index c7ea311..4ba7848 100644 --- a/astro/yarn.lock +++ b/astro/yarn.lock @@ -894,17 +894,17 @@ resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.2.4.tgz#1b380fad8eaccc6bec4ab1c265d70413e66e8034" integrity sha512-ClaUWpt8oTzjcF0MM1P81AeWyzc1sNSJlAjMG80CbwqbFqXSNz+NpQVUC0icobt3sZn43Sn27M4pHD/Jmp3zHw== -"@tanstack/query-core@5.28.13": - version "5.28.13" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.28.13.tgz#15c187c23b87a393e91d0fd2ea6dfc22b8a85b75" - integrity sha512-C3+CCOcza+mrZ7LglQbjeYEOTEC3LV0VN0eYaIN6GvqAZ8Foegdgch7n6QYPtT4FuLae5ALy+m+ZMEKpD6tMCQ== +"@tanstack/query-core@5.29.0": + version "5.29.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.29.0.tgz#d0b3d12c07d5a47f42ab0c1ed4f317106f3d4b20" + integrity sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww== "@tanstack/react-query@^5.28.14": - version "5.28.14" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.28.14.tgz#9585b6300eb8f167ed374e2748043dc8d6476709" - integrity sha512-cZqt03Igb3I9tM72qNX5TAAmeYl75Z+k4Mv92VkXIXc2hCrv0fIywd7GN3JV1BBJl4mr7Cc+OOKKOPy8sNVOkA== + version "5.29.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.29.0.tgz#42b3a2de4ed1d63666f0af04392a34b5e70d49c0" + integrity sha512-yxlhHB73jaBla6h5B6zPaGmQjokkzAhMHN4veotkPNiQ3Ac/mCxgABRZPsJJrgCTvhpcncBZcDBFxaR2B37vug== dependencies: - "@tanstack/query-core" "5.28.13" + "@tanstack/query-core" "5.29.0" "@testing-library/dom@^9.0.0": version "9.3.4" @@ -1018,9 +1018,9 @@ "@types/unist" "^2" "@types/node@*": - version "20.12.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.4.tgz#af5921bd75ccdf3a3d8b3fa75bf3d3359268cd11" - integrity sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw== + version "20.12.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.5.tgz#74c4f31ab17955d0b5808cdc8fd2839526ad00b3" + integrity sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw== dependencies: undici-types "~5.26.4" @@ -1867,9 +1867,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.4.668: - version "1.4.728" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.728.tgz#ac54d9d1b38752b920ec737a48c83dec2bf45ea1" - integrity sha512-Ud1v7hJJYIqehlUJGqR6PF1Ek8l80zWwxA6nGxigBsGJ9f9M2fciHyrIiNMerSHSH3p+0/Ia7jIlnDkt41h5cw== + version "1.4.729" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz#8477d21e2a50993781950885b2731d92ad532c00" + integrity sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA== emoji-regex@^10.2.1, emoji-regex@^10.3.0: version "10.3.0" diff --git a/payload/src/collections/Makers.ts b/payload/src/collections/Makers.ts index cbe8be2..ca4b133 100644 --- a/payload/src/collections/Makers.ts +++ b/payload/src/collections/Makers.ts @@ -30,12 +30,18 @@ const Makers: CollectionConfig = { label: 'Location', required: true }, + { + name: 'admins', + type: 'relationship', + relationTo: 'users', + hasMany: true, + }, { name: 'stock', type: 'relationship', relationTo: 'products', hasMany: true, - }, + } ], }; diff --git a/payload/src/collections/Retailers.ts b/payload/src/collections/Retailers.ts index 442724d..2c348d8 100644 --- a/payload/src/collections/Retailers.ts +++ b/payload/src/collections/Retailers.ts @@ -31,6 +31,12 @@ const Retailers: CollectionConfig = { label: 'Location', required: true }, + { + name: 'admins', + type: 'relationship', + relationTo: 'users', + hasMany: true, + }, { name: 'stock', type: 'relationship', diff --git a/payload/src/collections/Users.ts b/payload/src/collections/Users.ts index f202b9a..c5da1f2 100644 --- a/payload/src/collections/Users.ts +++ b/payload/src/collections/Users.ts @@ -2,12 +2,17 @@ import { CollectionConfig } from 'payload/types'; const Users: CollectionConfig = { slug: 'users', - auth: true, admin: { useAsTitle: 'email', }, access: { - read: () => true, + + }, + auth: { + tokenExpiration: 7200, // How many seconds to keep the user logged in + verify: false, // Require email verification before being allowed to authenticate + maxLoginAttempts: 5, // Automatically lock a user out after X amount of failed logins + lockTime: 600 * 1000, // Time period to allow the max login attempts }, fields: [ // Email added by default @@ -20,20 +25,7 @@ const Users: CollectionConfig = { name: 'phoneNumber', type: 'number', required: false - }, - { - name: 'adminOfMakers', - type: 'relationship', - relationTo: 'makers', - hasMany: true, - }, - { - name: 'adminOfRetailers', - type: 'relationship', - relationTo: 'retailers', - - hasMany: true, - }, + } ], }; diff --git a/payload/src/server.ts b/payload/src/server.ts index e50324a..3c53fad 100644 --- a/payload/src/server.ts +++ b/payload/src/server.ts @@ -21,6 +21,7 @@ payload.init({ const corsOptions = { origin: 'http://localhost:3000', + credentials: true }; app.use(cors(corsOptions));