| @@ -1,15 +1,24 @@ | |||
| import { Metadata } from "next"; | |||
| import { I18nProvider } from "@/i18n"; | |||
| import UserWorkspacePage from "@/components/UserWorkspacePage"; | |||
| import { fetchTimesheets } from "@/app/api/timesheets"; | |||
| import { authOptions } from "@/config/authConfig"; | |||
| import { getServerSession } from "next-auth"; | |||
| export const metadata: Metadata = { | |||
| title: "User Workspace", | |||
| }; | |||
| const Home: React.FC = async () => { | |||
| const session = await getServerSession(authOptions); | |||
| // Get name for caching | |||
| const username = session!.user!.name!; | |||
| await fetchTimesheets(username); | |||
| return ( | |||
| <I18nProvider namespaces={["home"]}> | |||
| <UserWorkspacePage /> | |||
| <UserWorkspacePage username={username} /> | |||
| </I18nProvider> | |||
| ); | |||
| }; | |||
| @@ -31,10 +31,10 @@ export default async function MainLayout({ | |||
| padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | |||
| }} | |||
| > | |||
| <Stack spacing={2}> | |||
| <Breadcrumb /> | |||
| {children} | |||
| </Stack> | |||
| <Stack spacing={2}> | |||
| <Breadcrumb /> | |||
| {children} | |||
| </Stack> | |||
| </Box> | |||
| </> | |||
| ); | |||
| @@ -1,13 +1,16 @@ | |||
| "use server"; | |||
| import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
| import { | |||
| serverFetchJson, | |||
| serverFetchWithNoContent, | |||
| } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { Task, TaskGroup } from "../tasks"; | |||
| import { Customer } from "../customer"; | |||
| import { revalidateTag } from "next/cache"; | |||
| export interface CreateProjectInputs { | |||
| // Project | |||
| // Project | |||
| projectId: number | null; | |||
| projectDeleted: boolean | null; | |||
| projectCode: string; | |||
| @@ -67,19 +70,22 @@ export interface PaymentInputs { | |||
| } | |||
| export interface CreateProjectResponse { | |||
| id: number, | |||
| name: string, | |||
| code: string, | |||
| category: string, | |||
| team: string, | |||
| client: string, | |||
| id: number; | |||
| name: string; | |||
| code: string; | |||
| category: string; | |||
| team: string; | |||
| client: string; | |||
| } | |||
| export const saveProject = async (data: CreateProjectInputs) => { | |||
| const newProject = await serverFetchJson<CreateProjectResponse>(`${BASE_API_URL}/projects/new`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| const newProject = await serverFetchJson<CreateProjectResponse>( | |||
| `${BASE_API_URL}/projects/new`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }, | |||
| ); | |||
| revalidateTag("projects"); | |||
| return newProject; | |||
| @@ -94,5 +100,7 @@ export const deleteProject = async (id: number) => { | |||
| }, | |||
| ); | |||
| return project | |||
| revalidateTag("projects"); | |||
| revalidateTag("assignedProjects"); | |||
| return project; | |||
| }; | |||
| @@ -1,9 +1,13 @@ | |||
| "use server"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { ProjectResult } from "../projects"; | |||
| import { Task, TaskGroup } from "../tasks"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { revalidateTag } from "next/cache"; | |||
| export interface TimeEntry { | |||
| id: number; | |||
| projectId: ProjectResult["id"]; | |||
| taskGroupId: TaskGroup["id"]; | |||
| taskId: Task["id"]; | |||
| @@ -13,3 +17,21 @@ export interface TimeEntry { | |||
| export interface RecordTimesheetInput { | |||
| [date: string]: TimeEntry[]; | |||
| } | |||
| export const saveTimesheet = async ( | |||
| data: RecordTimesheetInput, | |||
| username: string, | |||
| ) => { | |||
| const savedRecords = await serverFetchJson<RecordTimesheetInput>( | |||
| `${BASE_API_URL}/timesheets/save`, | |||
| { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }, | |||
| ); | |||
| revalidateTag(`timesheets_${username}`); | |||
| return savedRecords; | |||
| }; | |||
| @@ -0,0 +1,10 @@ | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { cache } from "react"; | |||
| import { RecordTimesheetInput } from "./actions"; | |||
| export const fetchTimesheets = cache(async (username: string) => { | |||
| return serverFetchJson<RecordTimesheetInput>(`${BASE_API_URL}/timesheets`, { | |||
| next: { tags: [`timesheets_${username}`] }, | |||
| }); | |||
| }); | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import DoneIcon from '@mui/icons-material/Done' | |||
| import DoneIcon from "@mui/icons-material/Done"; | |||
| import Check from "@mui/icons-material/Check"; | |||
| import Close from "@mui/icons-material/Close"; | |||
| import Button from "@mui/material/Button"; | |||
| @@ -22,7 +22,11 @@ import { | |||
| SubmitHandler, | |||
| useForm, | |||
| } from "react-hook-form"; | |||
| import { CreateProjectInputs, deleteProject, saveProject } from "@/app/api/projects/actions"; | |||
| import { | |||
| CreateProjectInputs, | |||
| deleteProject, | |||
| saveProject, | |||
| } from "@/app/api/projects/actions"; | |||
| import { Delete, Error, PlayArrow } from "@mui/icons-material"; | |||
| import { | |||
| BuildingType, | |||
| @@ -38,7 +42,12 @@ import { Typography } from "@mui/material"; | |||
| import { Grade } from "@/app/api/grades"; | |||
| import { Customer, Subsidiary } from "@/app/api/customer"; | |||
| import { isEmpty } from "lodash"; | |||
| import { deleteDialog, errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { | |||
| deleteDialog, | |||
| errorDialog, | |||
| submitDialog, | |||
| successDialog, | |||
| } from "../Swal/CustomAlerts"; | |||
| import dayjs from "dayjs"; | |||
| export interface Props { | |||
| @@ -103,16 +112,15 @@ const CreateProject: React.FC<Props> = ({ | |||
| const handleDelete = () => { | |||
| deleteDialog(async () => { | |||
| await deleteProject(formProps.getValues("projectId")!!) | |||
| await deleteProject(formProps.getValues("projectId")!); | |||
| const clickSuccessDialog = await successDialog("Delete Success", t) | |||
| const clickSuccessDialog = await successDialog("Delete Success", t); | |||
| if (clickSuccessDialog) { | |||
| router.replace("/projects"); | |||
| } | |||
| }, t) | |||
| } | |||
| }, t); | |||
| }; | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| @@ -124,51 +132,55 @@ const CreateProject: React.FC<Props> = ({ | |||
| const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>( | |||
| async (data, event) => { | |||
| try { | |||
| console.log("first") | |||
| console.log("first"); | |||
| setServerError(""); | |||
| let title = t("Do you want to submit?") | |||
| let confirmButtonText = t("Submit") | |||
| let successTitle = t("Submit Success") | |||
| let errorTitle = t("Submit Fail") | |||
| const buttonName = (event?.nativeEvent as any).submitter.name | |||
| let title = t("Do you want to submit?"); | |||
| let confirmButtonText = t("Submit"); | |||
| let successTitle = t("Submit Success"); | |||
| let errorTitle = t("Submit Fail"); | |||
| const buttonName = (event?.nativeEvent as any).submitter.name; | |||
| if (buttonName === "start") { | |||
| title = t("Do you want to start?") | |||
| confirmButtonText = t("Start") | |||
| successTitle = t("Start Success") | |||
| errorTitle = t("Start Fail") | |||
| title = t("Do you want to start?"); | |||
| confirmButtonText = t("Start"); | |||
| successTitle = t("Start Success"); | |||
| errorTitle = t("Start Fail"); | |||
| } else if (buttonName === "complete") { | |||
| title = t("Do you want to complete?") | |||
| confirmButtonText = t("Complete") | |||
| successTitle = t("Complete Success") | |||
| errorTitle = t("Complete Fail") | |||
| title = t("Do you want to complete?"); | |||
| confirmButtonText = t("Complete"); | |||
| successTitle = t("Complete Success"); | |||
| errorTitle = t("Complete Fail"); | |||
| } | |||
| submitDialog(async () => { | |||
| if (buttonName === "start") { | |||
| data.projectActualStart = dayjs().format("YYYY-MM-DD") | |||
| } else if (buttonName === "complete") { | |||
| data.projectActualEnd = dayjs().format("YYYY-MM-DD") | |||
| } | |||
| submitDialog( | |||
| async () => { | |||
| if (buttonName === "start") { | |||
| data.projectActualStart = dayjs().format("YYYY-MM-DD"); | |||
| } else if (buttonName === "complete") { | |||
| data.projectActualEnd = dayjs().format("YYYY-MM-DD"); | |||
| } | |||
| const response = await saveProject(data); | |||
| const response = await saveProject(data); | |||
| if (response.id > 0) { | |||
| successDialog(successTitle, t).then(() => { | |||
| router.replace("/projects"); | |||
| }) | |||
| } else { | |||
| errorDialog(errorTitle, t).then(() => { | |||
| return false | |||
| }) | |||
| } | |||
| }, t, { title: title, confirmButtonText: confirmButtonText }) | |||
| if (response.id > 0) { | |||
| successDialog(successTitle, t).then(() => { | |||
| router.replace("/projects"); | |||
| }); | |||
| } else { | |||
| errorDialog(errorTitle, t).then(() => { | |||
| return false; | |||
| }); | |||
| } | |||
| }, | |||
| t, | |||
| { title: title, confirmButtonText: confirmButtonText }, | |||
| ); | |||
| } catch (e) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| } | |||
| }, | |||
| [router, t, isEditMode], | |||
| [router, t], | |||
| ); | |||
| const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>( | |||
| @@ -196,8 +208,8 @@ const CreateProject: React.FC<Props> = ({ | |||
| // manhourPercentageByGrade should have a sensible default | |||
| manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade) | |||
| ? grades.reduce((acc, grade) => { | |||
| return { ...acc, [grade.id]: 1 / grades.length }; | |||
| }, {}) | |||
| return { ...acc, [grade.id]: 1 / grades.length }; | |||
| }, {}) | |||
| : defaultInputs?.manhourPercentageByGrade, | |||
| }, | |||
| }); | |||
| @@ -214,15 +226,42 @@ const CreateProject: React.FC<Props> = ({ | |||
| > | |||
| {isEditMode && !(formProps.getValues("projectDeleted") === true) && ( | |||
| <Stack direction="row" gap={1}> | |||
| {!formProps.getValues("projectActualStart") && <Button name="start" type="submit" variant="contained" startIcon={<PlayArrow />} color="success"> | |||
| {t("Start Project")} | |||
| </Button>} | |||
| {formProps.getValues("projectActualStart") && !formProps.getValues("projectActualEnd") && <Button name="complete" type="submit" variant="contained" startIcon={<DoneIcon />} color="info"> | |||
| {t("Complete Project")} | |||
| </Button>} | |||
| {!(formProps.getValues("projectActualStart") && formProps.getValues("projectActualEnd")) && <Button variant="outlined" startIcon={<Delete />} color="error" onClick={handleDelete}> | |||
| {t("Delete Project")} | |||
| </Button>} | |||
| {!formProps.getValues("projectActualStart") && ( | |||
| <Button | |||
| name="start" | |||
| type="submit" | |||
| variant="contained" | |||
| startIcon={<PlayArrow />} | |||
| color="success" | |||
| > | |||
| {t("Start Project")} | |||
| </Button> | |||
| )} | |||
| {formProps.getValues("projectActualStart") && | |||
| !formProps.getValues("projectActualEnd") && ( | |||
| <Button | |||
| name="complete" | |||
| type="submit" | |||
| variant="contained" | |||
| startIcon={<DoneIcon />} | |||
| color="info" | |||
| > | |||
| {t("Complete Project")} | |||
| </Button> | |||
| )} | |||
| {!( | |||
| formProps.getValues("projectActualStart") && | |||
| formProps.getValues("projectActualEnd") | |||
| ) && ( | |||
| <Button | |||
| variant="outlined" | |||
| startIcon={<Delete />} | |||
| color="error" | |||
| onClick={handleDelete} | |||
| > | |||
| {t("Delete Project")} | |||
| </Button> | |||
| )} | |||
| </Stack> | |||
| )} | |||
| <Tabs | |||
| @@ -290,7 +329,16 @@ const CreateProject: React.FC<Props> = ({ | |||
| > | |||
| {t("Cancel")} | |||
| </Button> | |||
| <Button variant="contained" startIcon={<Check />} type="submit" disabled={formProps.getValues("projectDeleted") === true || (!!formProps.getValues("projectActualStart") && !!formProps.getValues("projectActualEnd"))}> | |||
| <Button | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| disabled={ | |||
| formProps.getValues("projectDeleted") === true || | |||
| (!!formProps.getValues("projectActualStart") && | |||
| !!formProps.getValues("projectActualEnd")) | |||
| } | |||
| > | |||
| {isEditMode ? t("Save") : t("Confirm")} | |||
| </Button> | |||
| </Stack> | |||
| @@ -56,7 +56,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => { | |||
| ]); | |||
| const projectInfo = props.isEditMode | |||
| ? await fetchProjectDetails(props.projectId!!) | |||
| ? await fetchProjectDetails(props.projectId!) | |||
| : undefined; | |||
| return ( | |||
| @@ -12,8 +12,11 @@ import { | |||
| import TimesheetTable from "../TimesheetTable"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Check, Close } from "@mui/icons-material"; | |||
| import { FormProvider, useForm } from "react-hook-form"; | |||
| import { RecordTimesheetInput } from "@/app/api/timesheets/actions"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { | |||
| RecordTimesheetInput, | |||
| saveTimesheet, | |||
| } from "@/app/api/timesheets/actions"; | |||
| import dayjs from "dayjs"; | |||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| import { AssignedProject } from "@/app/api/projects"; | |||
| @@ -23,6 +26,8 @@ interface Props { | |||
| onClose: () => void; | |||
| timesheetType: "time" | "leave"; | |||
| assignedProjects: AssignedProject[]; | |||
| username: string; | |||
| defaultTimesheets?: RecordTimesheetInput; | |||
| } | |||
| const modalSx: SxProps = { | |||
| @@ -40,6 +45,8 @@ const TimesheetModal: React.FC<Props> = ({ | |||
| onClose, | |||
| timesheetType, | |||
| assignedProjects, | |||
| username, | |||
| defaultTimesheets, | |||
| }) => { | |||
| const { t } = useTranslation("home"); | |||
| @@ -48,15 +55,37 @@ const TimesheetModal: React.FC<Props> = ({ | |||
| return Array(7) | |||
| .fill(undefined) | |||
| .reduce<RecordTimesheetInput>((acc, _, index) => { | |||
| const date = today.subtract(index, "day").format(INPUT_DATE_FORMAT); | |||
| return { | |||
| ...acc, | |||
| [today.subtract(index, "day").format(INPUT_DATE_FORMAT)]: [], | |||
| [date]: defaultTimesheets?.[date] ?? [], | |||
| }; | |||
| }, {}); | |||
| }, []); | |||
| }, [defaultTimesheets]); | |||
| const formProps = useForm<RecordTimesheetInput>({ defaultValues }); | |||
| const onSubmit = useCallback<SubmitHandler<RecordTimesheetInput>>( | |||
| async (data) => { | |||
| const savedRecords = await saveTimesheet(data, username); | |||
| const today = dayjs(); | |||
| const newFormValues = Array(7) | |||
| .fill(undefined) | |||
| .reduce<RecordTimesheetInput>((acc, _, index) => { | |||
| const date = today.subtract(index, "day").format(INPUT_DATE_FORMAT); | |||
| return { | |||
| ...acc, | |||
| [date]: savedRecords[date] ?? [], | |||
| }; | |||
| }, {}); | |||
| formProps.reset(newFormValues); | |||
| onClose(); | |||
| }, | |||
| [formProps, onClose, username], | |||
| ); | |||
| const onCancel = useCallback(() => { | |||
| formProps.reset(defaultValues); | |||
| onClose(); | |||
| @@ -66,7 +95,10 @@ const TimesheetModal: React.FC<Props> = ({ | |||
| <Modal open={isOpen} onClose={onClose}> | |||
| <Card sx={modalSx}> | |||
| <FormProvider {...formProps}> | |||
| <CardContent> | |||
| <CardContent | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| {t(timesheetType === "time" ? "Timesheet Input" : "Record Leave")} | |||
| </Typography> | |||
| @@ -86,13 +118,8 @@ const TimesheetModal: React.FC<Props> = ({ | |||
| > | |||
| {t("Cancel")} | |||
| </Button> | |||
| <Button | |||
| onClick={onClose} | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| > | |||
| {t("Confirm")} | |||
| <Button variant="contained" startIcon={<Check />} type="submit"> | |||
| {t("Save")} | |||
| </Button> | |||
| </CardActions> | |||
| </CardContent> | |||
| @@ -36,7 +36,6 @@ type TimeEntryRow = Partial< | |||
| _isNew: boolean; | |||
| _error: string; | |||
| isPlanned: boolean; | |||
| id: string; | |||
| } | |||
| >; | |||
| @@ -74,21 +73,19 @@ const EntryInputTable: React.FC<Props> = ({ day, assignedProjects }) => { | |||
| const { getValues, setValue } = useFormContext<RecordTimesheetInput>(); | |||
| const currentEntries = getValues(day); | |||
| const [entries, setEntries] = useState<TimeEntryRow[]>( | |||
| currentEntries.map((e, index) => ({ ...e, id: `${day}-${index}` })) || [], | |||
| ); | |||
| const [entries, setEntries] = useState<TimeEntryRow[]>(currentEntries || []); | |||
| const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||
| const apiRef = useGridApiRef(); | |||
| const addRow = useCallback(() => { | |||
| const id = `${day}-${Date.now()}`; | |||
| const id = Date.now(); | |||
| setEntries((e) => [...e, { id, _isNew: true }]); | |||
| setRowModesModel((model) => ({ | |||
| ...model, | |||
| [id]: { mode: GridRowModes.Edit, fieldToFocus: "projectId" }, | |||
| })); | |||
| }, [day]); | |||
| }, []); | |||
| const validateRow = useCallback( | |||
| (id: GridRowId) => { | |||
| @@ -318,9 +315,11 @@ const EntryInputTable: React.FC<Props> = ({ day, assignedProjects }) => { | |||
| e.inputHours && | |||
| e.projectId && | |||
| e.taskId && | |||
| e.taskGroupId, | |||
| e.taskGroupId && | |||
| e.id, | |||
| ) | |||
| .map((e) => ({ | |||
| id: e.id!, | |||
| inputHours: e.inputHours!, | |||
| projectId: e.projectId!, | |||
| taskId: e.taskId!, | |||
| @@ -14,9 +14,11 @@ import { Clear, Search } from "@mui/icons-material"; | |||
| import ProjectGrid from "./ProjectGrid"; | |||
| import { Props as UserWorkspaceProps } from "./UserWorkspacePage"; | |||
| const AssignedProjects: React.FC<UserWorkspaceProps> = ({ | |||
| assignedProjects, | |||
| }) => { | |||
| interface Props { | |||
| assignedProjects: UserWorkspaceProps["assignedProjects"]; | |||
| } | |||
| const AssignedProjects: React.FC<Props> = ({ assignedProjects }) => { | |||
| const { t } = useTranslation("home"); | |||
| // Projects | |||
| @@ -10,12 +10,19 @@ import ButtonGroup from "@mui/material/ButtonGroup"; | |||
| import AssignedProjects from "./AssignedProjects"; | |||
| import TimesheetModal from "../TimesheetModal"; | |||
| import { AssignedProject } from "@/app/api/projects"; | |||
| import { RecordTimesheetInput } from "@/app/api/timesheets/actions"; | |||
| export interface Props { | |||
| assignedProjects: AssignedProject[]; | |||
| username: string; | |||
| defaultTimesheets: RecordTimesheetInput; | |||
| } | |||
| const UserWorkspacePage: React.FC<Props> = ({ assignedProjects }) => { | |||
| const UserWorkspacePage: React.FC<Props> = ({ | |||
| assignedProjects, | |||
| username, | |||
| defaultTimesheets, | |||
| }) => { | |||
| const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); | |||
| const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | |||
| const { t } = useTranslation("home"); | |||
| @@ -75,12 +82,15 @@ const UserWorkspacePage: React.FC<Props> = ({ assignedProjects }) => { | |||
| isOpen={isTimeheetModalVisible} | |||
| onClose={handleCloseTimesheetModal} | |||
| assignedProjects={assignedProjects} | |||
| username={username} | |||
| defaultTimesheets={defaultTimesheets} | |||
| /> | |||
| <TimesheetModal | |||
| timesheetType="leave" | |||
| isOpen={isLeaveModalVisible} | |||
| onClose={handleCloseLeaveModal} | |||
| assignedProjects={assignedProjects} | |||
| username={username} | |||
| /> | |||
| <AssignedProjects assignedProjects={assignedProjects} /> | |||
| </> | |||
| @@ -1,9 +1,24 @@ | |||
| import { fetchAssignedProjects } from "@/app/api/projects"; | |||
| import UserWorkspacePage from "./UserWorkspacePage"; | |||
| import { fetchTimesheets } from "@/app/api/timesheets"; | |||
| const UserWorkspaceWrapper: React.FC = async () => { | |||
| const assignedProjects = await fetchAssignedProjects(); | |||
| return <UserWorkspacePage assignedProjects={assignedProjects} />; | |||
| interface Props { | |||
| username: string; | |||
| } | |||
| const UserWorkspaceWrapper: React.FC<Props> = async ({ username }) => { | |||
| const [assignedProjects, timesheets] = await Promise.all([ | |||
| fetchAssignedProjects(), | |||
| fetchTimesheets(username), | |||
| ]); | |||
| return ( | |||
| <UserWorkspacePage | |||
| assignedProjects={assignedProjects} | |||
| username={username} | |||
| defaultTimesheets={timesheets} | |||
| /> | |||
| ); | |||
| }; | |||
| export default UserWorkspaceWrapper; | |||
| @@ -8,12 +8,10 @@ export interface SessionWithTokens extends Session { | |||
| refreshToken?: string; | |||
| } | |||
| export interface ability { | |||
| actionSubjectCombo: string; | |||
| } | |||
| export const authOptions: AuthOptions = { | |||
| debug: process.env.NODE_ENV === "development", | |||
| providers: [ | |||
| @@ -55,11 +53,12 @@ export const authOptions: AuthOptions = { | |||
| const sessionWithToken: SessionWithTokens = { | |||
| ...session, | |||
| // Add the data from the token to the session | |||
| abilities: (token.abilities as ability[]).map((item: ability) => item.actionSubjectCombo) as string[], | |||
| abilities: (token.abilities as ability[]).map( | |||
| (item: ability) => item.actionSubjectCombo, | |||
| ) as string[], | |||
| accessToken: token.accessToken as string | undefined, | |||
| refreshToken: token.refreshToken as string | undefined, | |||
| }; | |||
| // console.log(sessionWithToken) | |||
| return sessionWithToken; | |||
| }, | |||