Merge pull request #20 from linagora/1-application-basic-setup
Setting up a basic application with minimal features
This commit is contained in:
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
REACT_APP_SSO_BASE_URL="https://example.com"
|
PUBLIC_SSO_BASE_URL="https://example.com"
|
||||||
REACT_APP_SSO_CLIENT_ID="example"
|
PUBLIC_SSO_CLIENT_ID="example"
|
||||||
REACT_APP_SSO_CLIENT_SECRET=""
|
PUBLIC_SSO_SCOPE="openid name email"
|
||||||
REACT_APP_SSO_SCOPE="openid name email"
|
PUBLIC_SSO_REDIRECT_URI="https://example.com/callback"
|
||||||
REACT_APP_SSO_REDIRECT_URI="https://example.com/callback"
|
PUBLIC_SSO_RESPONSE_TYPE="code"
|
||||||
REACT_APP_SSO_RESPONSE_TYPE="code"
|
PUBLIC_SSO_CODE_CHALLENGE_METHOD="S256"
|
||||||
REACT_APP_SSO_CODE_CHALLENGE_METHOD="S256"
|
PUBLIC_SSO_POST_LOGOUT_REDIRECT="http://example.com?logout=1"
|
||||||
REACT_APP_SSO_POST_LOGOUT_REDIRECT="http://example.com?logout=1"
|
PUBLIC_CALENDAR_BASE_URL="https://calendar.example.com"
|
||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/dist
|
||||||
|
|
||||||
# secrets
|
# secrets
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
Generated
+2789
-7219
File diff suppressed because it is too large
Load Diff
+9
-4
@@ -29,10 +29,11 @@
|
|||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "PORT=5000 react-scripts start",
|
"start": "PORT=5000 rsbuild dev",
|
||||||
"build": "react-scripts build",
|
"build": "rsbuild build",
|
||||||
|
"preview": "rsbuild preview",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"lint": "eslint src"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -53,15 +54,19 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rsbuild/core": "^1.4.3",
|
||||||
|
"@rsbuild/plugin-react": "^1.3.3",
|
||||||
|
"@rsbuild/plugin-svgr": "^1.2.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"i18next-resources-for-ts": "1.4.0",
|
"i18next-resources-for-ts": "1.4.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"jest-preview": "^0.3.1",
|
"jest-preview": "^0.3.1",
|
||||||
"react-scripts": "^5.0.1",
|
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -2,25 +2,25 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="<%= assetPrefix %>/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="<%= assetPrefix %>/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="<%= assetPrefix %>/manifest.json" />
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of <%= assetPrefix %> in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
Unlike "/favicon.ico" or "favicon.ico", "<%= assetPrefix %>/favicon.ico" will
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from "@rsbuild/core";
|
||||||
|
import { pluginReact } from "@rsbuild/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [pluginReact()],
|
||||||
|
html: {
|
||||||
|
template: "./public/index.html",
|
||||||
|
},
|
||||||
|
server: { port: 5000 },
|
||||||
|
});
|
||||||
@@ -7,18 +7,33 @@ import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
|||||||
import ReactCalendar from "react-calendar";
|
import ReactCalendar from "react-calendar";
|
||||||
import "./Calendar.css";
|
import "./Calendar.css";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useAppSelector } from "../../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||||
import EventPopover from "../../features/Events/EventModal";
|
import EventPopover from "../../features/Events/EventModal";
|
||||||
import CalendarPopover from "../../features/Calendars/CalendarModal";
|
import CalendarPopover from "../../features/Calendars/CalendarModal";
|
||||||
|
import { CalendarEvent } from "../../features/Events/EventsTypes";
|
||||||
|
import {
|
||||||
|
getCalendar,
|
||||||
|
getCalendars,
|
||||||
|
} from "../../features/Calendars/CalendarApi";
|
||||||
|
import { access } from "node:fs";
|
||||||
|
import getOpenPaasUserId from "../../features/User/userAPI";
|
||||||
|
import { getCalendarsAsync } from "../../features/Calendars/CalendarSlice";
|
||||||
|
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||||
|
import { parseCalendarEvent } from "../../features/Events/eventUtils";
|
||||||
|
|
||||||
export default function CalendarApp() {
|
export default function CalendarApp() {
|
||||||
const calendarRef = useRef<CalendarApi | null>(null);
|
const calendarRef = useRef<CalendarApi | null>(null);
|
||||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||||
|
const tokens = useAppSelector((state) => state.user.tokens);
|
||||||
const calendars = useAppSelector((state) => state.calendars);
|
const calendars = useAppSelector((state) => state.calendars);
|
||||||
const [selectedCalendars, setSelectedCalendars] = useState(
|
const [selectedCalendars, setSelectedCalendars] = useState(
|
||||||
Object.keys(calendars).map((id) => calendars[id].name)
|
Object.keys(calendars).map((id) => id)
|
||||||
);
|
);
|
||||||
const events = useAppSelector((state) => state.events);
|
const dispatch = useAppDispatch();
|
||||||
|
let filteredEvents: CalendarEvent[] = [];
|
||||||
|
selectedCalendars.forEach((id) => {
|
||||||
|
filteredEvents = filteredEvents.concat(calendars[id].events);
|
||||||
|
});
|
||||||
|
|
||||||
const handleCalendarToggle = (name: string) => {
|
const handleCalendarToggle = (name: string) => {
|
||||||
setSelectedCalendars((prev) =>
|
setSelectedCalendars((prev) =>
|
||||||
@@ -103,9 +118,9 @@ export default function CalendarApp() {
|
|||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
style={{backgroundColor:calendars[id].color}}
|
style={{ backgroundColor: calendars[id].color }}
|
||||||
checked={selectedCalendars.includes(calendars[id].name)}
|
checked={selectedCalendars.includes(id)}
|
||||||
onChange={() => handleCalendarToggle(calendars[id].name)}
|
onChange={() => handleCalendarToggle(id)}
|
||||||
/>
|
/>
|
||||||
{calendars[id].name}
|
{calendars[id].name}
|
||||||
</label>
|
</label>
|
||||||
@@ -132,7 +147,7 @@ export default function CalendarApp() {
|
|||||||
timeGridWeek: { titleFormat: { month: "long", year: "numeric" } },
|
timeGridWeek: { titleFormat: { month: "long", year: "numeric" } },
|
||||||
}}
|
}}
|
||||||
dayMaxEvents={true}
|
dayMaxEvents={true}
|
||||||
events={events}
|
events={filteredEvents}
|
||||||
weekNumbers
|
weekNumbers
|
||||||
weekNumberFormat={{ week: "long" }}
|
weekNumberFormat={{ week: "long" }}
|
||||||
slotDuration={"00:30:00"}
|
slotDuration={"00:30:00"}
|
||||||
|
|||||||
@@ -1,3 +1,33 @@
|
|||||||
export default async function getCalendars() {
|
import { cp } from "node:fs";
|
||||||
return {}
|
|
||||||
|
export async function getCalendars(userId: string, opaque_token: string) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${process.env.PUBLIC_CALENDAR_BASE_URL}/dav/calendars/${userId}.json?personal=true&sharedDelegationStatus=accepted&sharedPublicSubscription=true&withRights=true`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: "application/calendar+json",
|
||||||
|
Authorization: `Bearer ${opaque_token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const calendars = await response.json();
|
||||||
|
return calendars;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCalendar(id: string, opaque_token: string) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${process.env.PUBLIC_CALENDAR_BASE_URL}/dav/calendars/${id}.json`,
|
||||||
|
{
|
||||||
|
method: "REPORT",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
Authorization: `Bearer ${opaque_token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
match: { start: "20250525T000000", end: "20250708T000000" },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const calendar = await response.json();
|
||||||
|
return calendar;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,44 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { Calendars } from "./CalendarTypes";
|
import { Calendars } from "./CalendarTypes";
|
||||||
import { CalendarEvent } from "../Events/EventsTypes";
|
import { CalendarEvent } from "../Events/EventsTypes";
|
||||||
|
import { getCalendar, getCalendars } from "./CalendarApi";
|
||||||
|
import getOpenPaasUserId from "../User/userAPI";
|
||||||
|
import { parseCalendarEvent } from "../Events/eventUtils";
|
||||||
|
|
||||||
|
export const getCalendarsAsync = createAsyncThunk<
|
||||||
|
Record<string, Calendars>, // Return type
|
||||||
|
string // Arg type (access_token)
|
||||||
|
>("calendars/getCalendars", async (access_token: string) => {
|
||||||
|
const importedCalendars: Record<string, Calendars> = {};
|
||||||
|
const user = await getOpenPaasUserId(access_token);
|
||||||
|
const calendars = await getCalendars(user.id, access_token);
|
||||||
|
const rawCalendars = calendars._embedded["dav:calendar"];
|
||||||
|
|
||||||
|
for (const cal of rawCalendars) {
|
||||||
|
const name = cal["dav:name"];
|
||||||
|
const description = cal["dav:description"];
|
||||||
|
const id = cal["calendarserver:source"]
|
||||||
|
? cal["calendarserver:source"]._links.self.href
|
||||||
|
.replace("/calendars/", "")
|
||||||
|
.replace(".json", "")
|
||||||
|
: cal._links.self.href
|
||||||
|
.replace("/calendars/", "")
|
||||||
|
.replace(".json", "")
|
||||||
|
|
||||||
|
const color = cal["apple:color"];
|
||||||
|
const calendarDetails = await getCalendar(id, access_token);
|
||||||
|
const events = calendarDetails._embedded["dav:item"].map(
|
||||||
|
(eventdata: any) => {
|
||||||
|
const datas = eventdata.data[2][0][1];
|
||||||
|
return parseCalendarEvent(datas, color);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
importedCalendars[id] = { id, name, description, color, events };
|
||||||
|
}
|
||||||
|
|
||||||
|
return importedCalendars;
|
||||||
|
});
|
||||||
|
|
||||||
const CalendarSlice = createSlice({
|
const CalendarSlice = createSlice({
|
||||||
name: "calendars",
|
name: "calendars",
|
||||||
@@ -18,6 +56,9 @@ const CalendarSlice = createSlice({
|
|||||||
state,
|
state,
|
||||||
action: PayloadAction<{ calendarUid: string; event: CalendarEvent }>
|
action: PayloadAction<{ calendarUid: string; event: CalendarEvent }>
|
||||||
) => {
|
) => {
|
||||||
|
if (!state[action.payload.calendarUid].events) {
|
||||||
|
state[action.payload.calendarUid].events = [];
|
||||||
|
}
|
||||||
state[action.payload.calendarUid].events.push(action.payload.event);
|
state[action.payload.calendarUid].events.push(action.payload.event);
|
||||||
},
|
},
|
||||||
removeEvent: (
|
removeEvent: (
|
||||||
@@ -33,6 +74,16 @@ const CalendarSlice = createSlice({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(
|
||||||
|
getCalendarsAsync.fulfilled,
|
||||||
|
(state, action: PayloadAction<Record<string, Calendars>>) => {
|
||||||
|
Object.keys(action.payload).forEach((id) => {
|
||||||
|
state[id] = action.payload[id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { addEvent, removeEvent, createCalendar } = CalendarSlice.actions;
|
export const { addEvent, removeEvent, createCalendar } = CalendarSlice.actions;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { CalendarEvent } from "../Events/EventsTypes";
|
|||||||
export interface Calendars {
|
export interface Calendars {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
prodid: string;
|
prodid?: string;
|
||||||
color: string;
|
color?: string;
|
||||||
description: string;
|
description?: string;
|
||||||
calscale?: string;
|
calscale?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
events: CalendarEvent[];
|
events: CalendarEvent[];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { addEvent } from "./EventsSlice";
|
import { addEvent } from "../Calendars/CalendarSlice";
|
||||||
import { CalendarEvent } from "./EventsTypes";
|
import { CalendarEvent } from "./EventsTypes";
|
||||||
import { DateSelectArg } from "@fullcalendar/core";
|
import { DateSelectArg } from "@fullcalendar/core";
|
||||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Select,
|
Select,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
SelectChangeEvent,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
function EventPopover({
|
function EventPopover({
|
||||||
@@ -22,13 +23,13 @@ function EventPopover({
|
|||||||
anchorEl: HTMLElement | null;
|
anchorEl: HTMLElement | null;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
||||||
selectedRange: any;
|
selectedRange: DateSelectArg | null;
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const organizer = useAppSelector((state) => state.user.organiserData);
|
const organizer = useAppSelector((state) => state.user.organiserData);
|
||||||
const calendars = useAppSelector((state) => state.calendars);
|
const calendars = useAppSelector((state) => state.calendars);
|
||||||
const [summary, setSummary] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [location, setLocation] = useState("");
|
const [location, setLocation] = useState("");
|
||||||
const [start, setStart] = useState("");
|
const [start, setStart] = useState("");
|
||||||
@@ -44,9 +45,9 @@ function EventPopover({
|
|||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
const newEvent: CalendarEvent = {
|
const newEvent: CalendarEvent = {
|
||||||
summary,
|
title,
|
||||||
start: new Date(start),
|
start: new Date(start || ""),
|
||||||
end: new Date(end),
|
end: new Date(end || ""),
|
||||||
uid: Date.now().toString(36),
|
uid: Date.now().toString(36),
|
||||||
description,
|
description,
|
||||||
location,
|
location,
|
||||||
@@ -62,13 +63,13 @@ function EventPopover({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
transp: "OPAQUE",
|
transp: "OPAQUE",
|
||||||
|
color: calendars[calendarid].color,
|
||||||
};
|
};
|
||||||
dispatch(addEvent(newEvent));
|
dispatch(addEvent({ calendarUid: calendarid, event: newEvent }));
|
||||||
console.log(newEvent);
|
|
||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
setSummary("");
|
setTitle("");
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setLocation("");
|
setLocation("");
|
||||||
};
|
};
|
||||||
@@ -91,16 +92,18 @@ function EventPopover({
|
|||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
Create Event
|
Create Event
|
||||||
</Typography>
|
</Typography>
|
||||||
<Select onClick={(e) => console.log(e.target)}>
|
<Select
|
||||||
|
onChange={(e: SelectChangeEvent) => setCalendarid(e.target.value)}
|
||||||
|
>
|
||||||
{Object.keys(calendars).map((calendar) => (
|
{Object.keys(calendars).map((calendar) => (
|
||||||
<MenuItem value={calendar}>{calendars[calendar].name}</MenuItem>
|
<MenuItem value={calendar}>{calendars[calendar].name}</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="summary"
|
label="title"
|
||||||
value={summary}
|
value={title}
|
||||||
onChange={(e) => setSummary(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
size="small"
|
size="small"
|
||||||
margin="dense"
|
margin="dense"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ import { userAttendee, userOrganiser } from "../User/userDataTypes";
|
|||||||
|
|
||||||
export interface CalendarEvent {
|
export interface CalendarEvent {
|
||||||
uid: string;
|
uid: string;
|
||||||
transp: string;
|
transp?: string;
|
||||||
start: Date; // ISO date
|
start: Date; // ISO date
|
||||||
end?: Date;
|
end?: Date;
|
||||||
class?: string;
|
class?: string;
|
||||||
x_openpass_videoconference?: unknown;
|
x_openpass_videoconference?: unknown;
|
||||||
summary?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
location?: string;
|
location?: string;
|
||||||
organizer: userOrganiser;
|
organizer?: userOrganiser;
|
||||||
attendee: userAttendee[];
|
attendee: userAttendee[];
|
||||||
stamp?: Date;
|
stamp?: Date;
|
||||||
sequence?: Number;
|
sequence?: Number;
|
||||||
|
color?: string;
|
||||||
|
allday?:Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { userAttendee } from "../User/userDataTypes";
|
||||||
|
import { CalendarEvent } from "./EventsTypes";
|
||||||
|
|
||||||
|
type RawEntry = [string, Record<string, string>, string, any];
|
||||||
|
|
||||||
|
export function parseCalendarEvent(
|
||||||
|
data: RawEntry[],
|
||||||
|
color: string
|
||||||
|
): CalendarEvent {
|
||||||
|
const event: Partial<CalendarEvent> = { color, attendee: [] };
|
||||||
|
|
||||||
|
for (const [key, params, type, value] of data) {
|
||||||
|
switch (key.toLowerCase()) {
|
||||||
|
case "uid":
|
||||||
|
event.uid = value;
|
||||||
|
break;
|
||||||
|
case "transp":
|
||||||
|
event.transp = value;
|
||||||
|
break;
|
||||||
|
case "dtstart":
|
||||||
|
event.start = value;
|
||||||
|
break;
|
||||||
|
case "dtend":
|
||||||
|
event.end = value;
|
||||||
|
break;
|
||||||
|
case "class":
|
||||||
|
event.class = value;
|
||||||
|
break;
|
||||||
|
case "x-openpaas-videoconference":
|
||||||
|
event.x_openpass_videoconference = value;
|
||||||
|
break;
|
||||||
|
case "summary":
|
||||||
|
event.title = value;
|
||||||
|
break;
|
||||||
|
case "description":
|
||||||
|
event.description = value;
|
||||||
|
break;
|
||||||
|
case "location":
|
||||||
|
event.location = value;
|
||||||
|
break;
|
||||||
|
case "organizer":
|
||||||
|
event.organizer = {
|
||||||
|
cn: params?.cn || "",
|
||||||
|
cal_address: value.replace(/^mailto:/, ""),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "attendee":
|
||||||
|
(event.attendee as userAttendee[]).push({
|
||||||
|
cn: params?.cn || "",
|
||||||
|
cal_address: value.replace(/^mailto:/, ""),
|
||||||
|
partstat: params?.partstat || "",
|
||||||
|
rsvp: params?.rsvp || "",
|
||||||
|
role: params?.role || "",
|
||||||
|
cutype: params?.cutype || "",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "dtstamp":
|
||||||
|
event.stamp = new Date(value);
|
||||||
|
break;
|
||||||
|
case "sequence":
|
||||||
|
event.sequence = Number(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event.uid || !event.start) {
|
||||||
|
throw new Error(`Missing required event fields`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event as CalendarEvent;
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ import { useEffect, useRef } from "react";
|
|||||||
import { Callback } from "./oidcAuth";
|
import { Callback } from "./oidcAuth";
|
||||||
import { useAppDispatch } from "../../app/hooks";
|
import { useAppDispatch } from "../../app/hooks";
|
||||||
import { push } from "redux-first-history";
|
import { push } from "redux-first-history";
|
||||||
import { setUserData } from "./userSlice";
|
import { setTokens, setUserData } from "./userSlice";
|
||||||
import { Loading } from "../../components/Loading/Loading";
|
import { Loading } from "../../components/Loading/Loading";
|
||||||
|
import { getCalendarsAsync } from "../Calendars/CalendarSlice";
|
||||||
|
|
||||||
export function CallbackResume() {
|
export function CallbackResume() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -19,8 +20,9 @@ export function CallbackResume() {
|
|||||||
const runCallback = async () => {
|
const runCallback = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await Callback(saved?.code_verifier, saved?.state);
|
const data = await Callback(saved?.code_verifier, saved?.state);
|
||||||
console.log("data:", data);
|
|
||||||
dispatch(setUserData(data?.userinfo));
|
dispatch(setUserData(data?.userinfo));
|
||||||
|
dispatch(setTokens(data?.tokenSet));
|
||||||
|
dispatch(getCalendarsAsync(data?.tokenSet.access_token || ""));
|
||||||
sessionStorage.removeItem("redirectState");
|
sessionStorage.removeItem("redirectState");
|
||||||
// Redirect to main page after successful callback
|
// Redirect to main page after successful callback
|
||||||
dispatch(push("/"));
|
dispatch(push("/"));
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
import * as client from "openid-client";
|
import * as client from "openid-client";
|
||||||
|
|
||||||
export const clientConfig = {
|
export const clientConfig = {
|
||||||
url: process.env.REACT_APP_SSO_BASE_URL || "",
|
url: process.env.PUBLIC_SSO_BASE_URL || "",
|
||||||
client_id: process.env.REACT_APP_SSO_CLIENT_ID || "",
|
client_id: process.env.PUBLIC_SSO_CLIENT_ID || "",
|
||||||
client_secret: process.env.REACT_APP_SSO_CLIENT_SECRET || "",
|
scope: process.env.PUBLIC_SSO_SCOPE || "",
|
||||||
scope: process.env.REACT_APP_SSO_SCOPE || "",
|
redirect_uri: process.env.PUBLIC_SSO_REDIRECT_URI || "",
|
||||||
redirect_uri: process.env.REACT_APP_SSO_REDIRECT_URI || "",
|
response_type: process.env.PUBLIC_SSO_RESPONSE_TYPE || "",
|
||||||
response_type: process.env.REACT_APP_SSO_RESPONSE_TYPE || "",
|
code_challenge_method: process.env.PUBLIC_SSO_CODE_CHALLENGE_METHOD || "",
|
||||||
code_challenge_method: process.env.REACT_APP_SSO_CODE_CHALLENGE_METHOD || "",
|
post_logout_redirect_uri: process.env.PUBLIC_SSO_POST_LOGOUT_REDIRECT || "",
|
||||||
post_logout_redirect_uri:
|
|
||||||
process.env.REACT_APP_SSO_POST_LOGOUT_REDIRECT || "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getClientConfig() {
|
export async function getClientConfig() {
|
||||||
console.log(process.env.REACT_APP_SSO_BASE_URL)
|
|
||||||
return await client.discovery(
|
return await client.discovery(
|
||||||
new URL(clientConfig.url),
|
new URL(clientConfig.url),
|
||||||
clientConfig.client_id,
|
clientConfig.client_id
|
||||||
clientConfig.client_secret
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +33,6 @@ export async function Auth() {
|
|||||||
parameters.state = state;
|
parameters.state = state;
|
||||||
}
|
}
|
||||||
let redirectTo = client.buildAuthorizationUrl(openIdClientConfig, parameters);
|
let redirectTo = client.buildAuthorizationUrl(openIdClientConfig, parameters);
|
||||||
console.log(redirectTo);
|
|
||||||
|
|
||||||
return { redirectTo, code_verifier, state };
|
return { redirectTo, code_verifier, state };
|
||||||
}
|
}
|
||||||
@@ -58,7 +53,6 @@ export async function Callback(code_verifier: string, state: any) {
|
|||||||
|
|
||||||
console.log("Callback URL:", currentUrl.toString());
|
console.log("Callback URL:", currentUrl.toString());
|
||||||
console.log("Code verifier:", code_verifier);
|
console.log("Code verifier:", code_verifier);
|
||||||
console.log("State:", state);
|
|
||||||
|
|
||||||
const tokenSet = await client.authorizationCodeGrant(
|
const tokenSet = await client.authorizationCodeGrant(
|
||||||
openIdClientConfig,
|
openIdClientConfig,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default async function getOpenPaasUserId(opaque_token: string) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${process.env.PUBLIC_CALENDAR_BASE_URL}/api/user`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${opaque_token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const user = await response.json();
|
||||||
|
return user;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export const userSlice = createSlice({
|
|||||||
initialState: {
|
initialState: {
|
||||||
userData: null as unknown as userData,
|
userData: null as unknown as userData,
|
||||||
organiserData: null as unknown as userOrganiser,
|
organiserData: null as unknown as userOrganiser,
|
||||||
|
tokens: null as unknown as Record<string, string>,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setUserData: (state, action) => {
|
setUserData: (state, action) => {
|
||||||
@@ -16,10 +17,13 @@ export const userSlice = createSlice({
|
|||||||
state.organiserData.cn = action.payload.sub;
|
state.organiserData.cn = action.payload.sub;
|
||||||
state.organiserData.cal_address = `mailto:${action.payload.email}`;
|
state.organiserData.cal_address = `mailto:${action.payload.email}`;
|
||||||
},
|
},
|
||||||
|
setTokens: (state, action) => {
|
||||||
|
state.tokens = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Action creators are generated for each case reducer function
|
// Action creators are generated for each case reducer function
|
||||||
export const { setUserData } = userSlice.actions;
|
export const { setUserData, setTokens } = userSlice.actions;
|
||||||
|
|
||||||
export default userSlice.reducer;
|
export default userSlice.reducer;
|
||||||
|
|||||||
Reference in New Issue
Block a user