[#1] getting the calendars from the api

This commit is contained in:
Camille Moussu
2025-07-03 14:21:04 +02:00
parent c11c65c697
commit 65621924a2
10 changed files with 199 additions and 12 deletions
+1
View File
@@ -6,3 +6,4 @@ REACT_APP_SSO_REDIRECT_URI="https://example.com/callback"
REACT_APP_SSO_RESPONSE_TYPE="code"
REACT_APP_SSO_CODE_CHALLENGE_METHOD="S256"
REACT_APP_SSO_POST_LOGOUT_REDIRECT="http://example.com?logout=1"
REACT_APP_CALENDAR_BASE_URL="https://calendar.example.com"
+12 -2
View File
@@ -7,19 +7,29 @@ import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import ReactCalendar from "react-calendar";
import "./Calendar.css";
import { useRef, useState } from "react";
import { useAppSelector } from "../../app/hooks";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import EventPopover from "../../features/Events/EventModal";
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() {
const calendarRef = useRef<CalendarApi | null>(null);
const [selectedDate, setSelectedDate] = useState(new Date());
const tokens = useAppSelector((state) => state.user.tokens);
const calendars = useAppSelector((state) => state.calendars);
const [selectedCalendars, setSelectedCalendars] = useState(
Object.keys(calendars).map((id) => id)
);
const dispatch = useAppDispatch();
let filteredEvents: CalendarEvent[] = [];
selectedCalendars.forEach((id) => {
filteredEvents = filteredEvents.concat(calendars[id].events);
+32 -2
View File
@@ -1,3 +1,33 @@
export default async function getCalendars() {
return {}
import { cp } from "node:fs";
export async function getCalendars(userId: string, opaque_token: string) {
const response = await fetch(
`${process.env.REACT_APP_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.REACT_APP_CALENDAR_BASE_URL}/dav/calendars/${id}/${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;
}
+55 -2
View File
@@ -1,6 +1,46 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Calendars } from "./CalendarTypes";
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", "")
.split("/")[0]
: cal._links.self.href
.replace("/calendars/", "")
.replace(".json", "")
.split("/")[0];
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({
name: "calendars",
@@ -18,7 +58,7 @@ const CalendarSlice = createSlice({
state,
action: PayloadAction<{ calendarUid: string; event: CalendarEvent }>
) => {
console.log(action.payload)
console.log(action.payload);
if (!state[action.payload.calendarUid].events) {
state[action.payload.calendarUid].events = [];
}
@@ -37,6 +77,19 @@ const CalendarSlice = createSlice({
});
},
},
extraReducers: (builder) => {
builder.addCase(
getCalendarsAsync.fulfilled,
(state, action: PayloadAction<Record<string, Calendars>>) => {
console.log("ony est!!!!: ", action.payload);
console.log(Object.keys(action.payload));
Object.keys(action.payload).forEach((id) => {
console.log(id);
state[id] = action.payload[id];
});
}
);
},
});
export const { addEvent, removeEvent, createCalendar } = CalendarSlice.actions;
+2 -2
View File
@@ -3,9 +3,9 @@ import { CalendarEvent } from "../Events/EventsTypes";
export interface Calendars {
id: string;
name: string;
prodid: string;
prodid?: string;
color: string;
description: string;
description?: string;
calscale?: string;
version?: string;
events: CalendarEvent[];
+3 -2
View File
@@ -2,7 +2,7 @@ import { userAttendee, userOrganiser } from "../User/userDataTypes";
export interface CalendarEvent {
uid: string;
transp: string;
transp?: string;
start: Date; // ISO date
end?: Date;
class?: string;
@@ -10,9 +10,10 @@ export interface CalendarEvent {
title?: string;
description?: string;
location?: string;
organizer: userOrganiser;
organizer?: userOrganiser;
attendee: userAttendee[];
stamp?: Date;
sequence?: Number;
color?: string;
allday?:Boolean
}
+72
View File
@@ -0,0 +1,72 @@
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) {
console.log(event);
throw new Error(`Missing required event fields`);
}
return event as CalendarEvent;
}
+4 -1
View File
@@ -2,8 +2,9 @@ import { useEffect, useRef } from "react";
import { Callback } from "./oidcAuth";
import { useAppDispatch } from "../../app/hooks";
import { push } from "redux-first-history";
import { setUserData } from "./userSlice";
import { setTokens, setUserData } from "./userSlice";
import { Loading } from "../../components/Loading/Loading";
import { getCalendarsAsync } from "../Calendars/CalendarSlice";
export function CallbackResume() {
const dispatch = useAppDispatch();
@@ -21,6 +22,8 @@ export function CallbackResume() {
const data = await Callback(saved?.code_verifier, saved?.state);
console.log("data:", data);
dispatch(setUserData(data?.userinfo));
dispatch(setTokens(data?.tokenSet));
dispatch(getCalendarsAsync(data?.tokenSet.access_token || ""));
sessionStorage.removeItem("redirectState");
// Redirect to main page after successful callback
dispatch(push("/"));
+12
View File
@@ -0,0 +1,12 @@
export default async function getOpenPaasUserId(opaque_token: string) {
const response = await fetch(
`${process.env.REACT_APP_CALENDAR_BASE_URL}/api/user`,
{
headers: {
Authorization: `Bearer ${opaque_token}`,
},
}
);
const user = await response.json();
return user;
}
+6 -1
View File
@@ -6,6 +6,7 @@ export const userSlice = createSlice({
initialState: {
userData: null as unknown as userData,
organiserData: null as unknown as userOrganiser,
tokens: null as unknown as Record<string, string>,
},
reducers: {
setUserData: (state, action) => {
@@ -16,10 +17,14 @@ export const userSlice = createSlice({
state.organiserData.cn = action.payload.sub;
state.organiserData.cal_address = `mailto:${action.payload.email}`;
},
setTokens: (state, action) => {
console.log(state.tokens);
state.tokens = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { setUserData } = userSlice.actions;
export const { setUserData, setTokens } = userSlice.actions;
export default userSlice.reducer;