[#1] added base calendar and started to tweak ui
This commit is contained in:
@@ -2,12 +2,10 @@ import React from "react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import App from "../src/App";
|
||||
import { JSX } from "react/jsx-runtime";
|
||||
import { renderWithProviders } from "./utils/Renderwithproviders";
|
||||
|
||||
test("renders learn react link", () => {
|
||||
test("renders app", () => {
|
||||
renderWithProviders(<App />);
|
||||
const linkElement = screen.getByText("Twake");
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
function renderWithProviders(arg0: JSX.Element) {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Menubar } from "../../src/components/Menubar/Menubar";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
|
||||
describe("Calendar App Component Display Tests", () => {
|
||||
test("renders the Navbar component", () => {
|
||||
test("renders the Menubar component", () => {
|
||||
renderWithProviders(<Menubar />);
|
||||
const navbarElement = screen.getByText("Twake");
|
||||
expect(navbarElement).toBeInTheDocument();
|
||||
|
||||
Generated
+976
-149
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,27 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
"@fullcalendar/daygrid": "^6.1.17",
|
||||
"@fullcalendar/interaction": "^6.1.17",
|
||||
"@fullcalendar/list": "^6.1.17",
|
||||
"@fullcalendar/react": "^6.1.17",
|
||||
"@fullcalendar/timegrid": "^6.1.17",
|
||||
"@mui/icons-material": "^7.1.2",
|
||||
"@mui/material": "^7.1.2",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@schedule-x/events-service": "^2.34.0",
|
||||
"@schedule-x/react": "^2.34.0",
|
||||
"@schedule-x/theme-default": "^2.34.0",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"i18next": "^23.7.9",
|
||||
"openid-client": "^6.5.3",
|
||||
"react": "^19.1.0",
|
||||
"react-calendar": "^6.0.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-redux": "^9.2.0",
|
||||
|
||||
+6
-2
@@ -24,8 +24,12 @@
|
||||
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`.
|
||||
-->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cal+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Cal+Sans&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+1
-9
@@ -1,17 +1,9 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
font-family:"Roboto";
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
|
||||
+9
-6
@@ -4,19 +4,22 @@ import { HistoryRouter as Router } from "redux-first-history/rr6";
|
||||
import { Menubar } from "./components/Menubar/Menubar";
|
||||
import { CallbackResume } from "./features/User/LoginCallback";
|
||||
import { history } from "./app/store";
|
||||
import './App.css'
|
||||
import "./App.css";
|
||||
import { Loading } from "./components/Loading/Loading";
|
||||
import HandleLogin from "./features/User/HandleLogin";
|
||||
import CalendarApp from "./components/Calendar/Calendar";
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback="loading">
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Router history={history}>
|
||||
<Routes>
|
||||
<Route path="/" element={<HandleLogin />} />
|
||||
<Route
|
||||
path="/"
|
||||
path="/calendar"
|
||||
element={
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<Menubar />
|
||||
</header>
|
||||
<Menubar />
|
||||
<CalendarApp />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||
import userReducer from "../features/User/userSlice";
|
||||
|
||||
import eventsReducer from "../features/Events/EventsSlice";
|
||||
import { createReduxHistoryContext } from "redux-first-history";
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
@@ -10,6 +10,7 @@ const { createReduxHistory, routerMiddleware, routerReducer } =
|
||||
const rootReducer = combineReducers({
|
||||
router: routerReducer,
|
||||
user: userReducer,
|
||||
events: eventsReducer,
|
||||
});
|
||||
|
||||
export const setupStore = (preloadedState?: Partial<RootState>) => {
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sidebar{
|
||||
border-right: 2px gray;
|
||||
}
|
||||
|
||||
.fc .fc-daygrid-day {
|
||||
min-height: 10px !important; /* Default is ~40px */
|
||||
height: 10px !important;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.fc .fc-timegrid-slot {
|
||||
height: 60px !important; /* Default is ~40px */
|
||||
}
|
||||
|
||||
#calendar {
|
||||
flex: 1;
|
||||
overflow: hidden; /* Important: let FullCalendar handle internal scroll */
|
||||
}
|
||||
|
||||
.fc-scroller-liquid-absolute {
|
||||
overflow-y: auto !important; /* This targets the scrollable part */
|
||||
max-height: calc(100vh - 100px); /* Adjust based on your header size */
|
||||
}
|
||||
.scrollgrid-section-header {
|
||||
border: white;
|
||||
}
|
||||
|
||||
/* Base container */
|
||||
.fc-daygrid-day-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0;
|
||||
font-family: "Inter", sans-serif;
|
||||
color: #1e293b; /* dark navy */
|
||||
}
|
||||
|
||||
/* Day number */
|
||||
.fc-daygrid-day-top .fc-daygrid-day-number {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
/* Day name (e.g., MON) */
|
||||
.fc-daygrid-day-top small {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.05em;
|
||||
color: #94a3b8; /* slate gray */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Today’s date highlight */
|
||||
.current-date {
|
||||
background-color: orange;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.fc .fc-col-header-cell,
|
||||
.fc .fc-scrollgrid table,
|
||||
.fc-theme-standard .fc-scrollgrid {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Container de la sidebar */
|
||||
.sidebar-calendar {
|
||||
width: 280px;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* Style du mini calendrier */
|
||||
.react-calendar {
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Jours sélectionnés et hover */
|
||||
.react-calendar__tile {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background-color: white;
|
||||
width: 12px;
|
||||
height: 37.52px;
|
||||
}
|
||||
|
||||
.react-calendar__tile:hover {
|
||||
background-color: rgb(224, 224, 224);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-calendar__tile--active {
|
||||
background-color: orange;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Titre ou header de la sidebar */
|
||||
.sidebar-calendar h2 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
/* Liste des calendriers */
|
||||
.calendar-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.calendar-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
color: #444;
|
||||
padding: 10px 15px;
|
||||
border-radius: 10px;
|
||||
transition: background-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Couleurs distinctes pour chaque type de calendrier */
|
||||
.calendar-list li.work {
|
||||
background-color: #d0e8ff;
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
.calendar-list li.work:hover {
|
||||
background-color: #b0d4ff;
|
||||
}
|
||||
|
||||
.calendar-list li.personal {
|
||||
background-color: #ffe0e0;
|
||||
color: #d93025;
|
||||
}
|
||||
|
||||
.calendar-list li.personal:hover {
|
||||
background-color: #ffb3b3;
|
||||
}
|
||||
|
||||
/* Petit indicateur couleur à gauche */
|
||||
.calendar-list li::before {
|
||||
content: "";
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.calendar-list li.work::before {
|
||||
background-color: #1a73e8;
|
||||
}
|
||||
|
||||
.calendar-list li.personal::before {
|
||||
background-color: #d93025;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import FullCalendar from "@fullcalendar/react";
|
||||
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
||||
import ReactCalendar from "react-calendar";
|
||||
import "./Calendar.css";
|
||||
import { useRef, useState } from "react";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
|
||||
export default function CalendarApp() {
|
||||
const calendarRef = useRef<CalendarApi | null>(null);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const [selectedCalendars, setSelectedCalendars] = useState([
|
||||
"Work",
|
||||
"Personnal",
|
||||
]);
|
||||
const events = useAppSelector((state) => state.events);
|
||||
|
||||
const handleCalendarToggle = (name: string) => {
|
||||
setSelectedCalendars((prev) =>
|
||||
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
|
||||
);
|
||||
};
|
||||
const filteredEvent = events.filter((e) =>
|
||||
selectedCalendars.includes(e.calendar)
|
||||
);
|
||||
|
||||
const [date, setDate] = useState(new Date());
|
||||
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<div className="sidebar">
|
||||
<div className="calendar-label">
|
||||
<div className="calendar-label">
|
||||
<span>
|
||||
{months[date.getMonth()]} {date.getFullYear()}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() =>
|
||||
setDate(new Date(date.getFullYear(), date.getMonth() - 1))
|
||||
}
|
||||
>
|
||||
<
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setDate(new Date(date.getFullYear(), date.getMonth() + 1))
|
||||
}
|
||||
>
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<ReactCalendar
|
||||
showNeighboringMonth={false}
|
||||
calendarType="hebrew"
|
||||
formatShortWeekday={(locale, date) =>
|
||||
date.toLocaleDateString(locale, { weekday: "narrow" })
|
||||
}
|
||||
value={selectedDate}
|
||||
onClickDay={(date) => {
|
||||
setSelectedDate(date);
|
||||
calendarRef.current?.gotoDate(date);
|
||||
}}
|
||||
prevLabel={null}
|
||||
nextLabel={null}
|
||||
showNavigation={false}
|
||||
/>
|
||||
{["Work", "Personnal", "Holidays"].map((name) => (
|
||||
<div key={name}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedCalendars.includes(name)}
|
||||
onChange={() => handleCalendarToggle(name)}
|
||||
/>
|
||||
{name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="calendar">
|
||||
<FullCalendar
|
||||
ref={(ref) => {
|
||||
if (ref) {
|
||||
calendarRef.current = ref.getApi();
|
||||
}
|
||||
}}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
initialView="timeGridWeek"
|
||||
weekends={false}
|
||||
editable={true}
|
||||
selectable={true}
|
||||
nowIndicator
|
||||
selectMirror={true}
|
||||
views={{
|
||||
timeGridWeek: { titleFormat: { month: "long", year: "numeric" } },
|
||||
}}
|
||||
dayMaxEvents={true}
|
||||
events={filteredEvent}
|
||||
weekNumbers
|
||||
weekNumberFormat={{ week: "long" }}
|
||||
slotDuration={"00:30:00"}
|
||||
slotLabelInterval={"00:30:00"}
|
||||
scrollTime={"07:00:00"}
|
||||
allDayText=""
|
||||
slotLabelFormat={{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}}
|
||||
dayHeaderContent={(arg) => {
|
||||
const date = arg.date.getDate();
|
||||
const weekDay = arg.date
|
||||
.toLocaleDateString("en-US", { weekday: "short" })
|
||||
.toUpperCase();
|
||||
return (
|
||||
<div className="fc-daygrid-day-top">
|
||||
<small>{weekDay}</small>
|
||||
<span
|
||||
className={`fc-daygrid-day-number ${
|
||||
arg.isToday ? "current-date" : ""
|
||||
}`}
|
||||
>
|
||||
{date}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
eventClick={(info) => {
|
||||
info.jsEvent.preventDefault(); // don't let the browser navigate
|
||||
|
||||
if (info.event.url) {
|
||||
window.open(info.event.url);
|
||||
} else {
|
||||
console.log(info.event);
|
||||
}
|
||||
}}
|
||||
headerToolbar={{
|
||||
left: "title",
|
||||
center: "prev,today,next",
|
||||
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import logo from "../../static/images/calendar.svg";
|
||||
|
||||
export function Error() {
|
||||
return <p>Error</p>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import logo from "../../static/images/calendar.svg";
|
||||
|
||||
export function Loading() {
|
||||
return <img src={logo} />;
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
.calendar-text {
|
||||
font-family: "Cal Sans";
|
||||
padding: 0.2rem;
|
||||
font-weight: 400;
|
||||
@@ -39,12 +39,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #1a1a1a62;
|
||||
padding: 0.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1,27 @@
|
||||
import React from "react";
|
||||
import { Auth } from "../../features/User/oidcAuth";
|
||||
import logo from "../../static/images/calendar.svg";
|
||||
import "./Menubar.css";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
export function Menubar() {
|
||||
return (
|
||||
<div className="menubar">
|
||||
<div className="menubar-item tc-home">
|
||||
<img className="logo" src={logo} />
|
||||
<p>
|
||||
<span className="twake">Twake</span>
|
||||
<span className="calendar">Calendar</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="menubar-item nav-month">
|
||||
<p>Current Month</p>
|
||||
<p className="day-selector"> droite today gauche</p>
|
||||
</div>
|
||||
<header className="menubar">
|
||||
<MainTitle/>
|
||||
<div className="menubar-item search-bar">
|
||||
<p>big search bar</p>
|
||||
</div>
|
||||
<div className="menubar-item menu-tools">
|
||||
<HandleLogin />
|
||||
<div className="menubar-item">
|
||||
<p>Account stuff</p>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
export function MainTitle() {
|
||||
return (
|
||||
<div className="menubar-item tc-home">
|
||||
<img className="logo" src={logo} />
|
||||
<p>
|
||||
<span className="twake">Twake</span>
|
||||
<span className="calendar-text">Calendar</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HandleLogin() {
|
||||
const userData = useAppSelector((state) => state.user.userData);
|
||||
if (!userData) {
|
||||
return (
|
||||
<button
|
||||
onClick={async () => {
|
||||
const loginurl = await Auth();
|
||||
sessionStorage.setItem(
|
||||
"redirectState",
|
||||
JSON.stringify({
|
||||
code_verifier: loginurl.code_verifier,
|
||||
state: loginurl.state,
|
||||
})
|
||||
);
|
||||
|
||||
window.location.assign(loginurl.redirectTo);
|
||||
}}
|
||||
>
|
||||
login
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
return <p>{userData.sub}</p>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// components/EventModal.tsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { addEvent } from "./EventsSlice";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
import { DateSelectArg } from "@fullcalendar/core";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { Popover, TextField, Button, Box, Typography } from "@mui/material";
|
||||
|
||||
function EventPopover({
|
||||
anchorEl,
|
||||
open,
|
||||
onClose,
|
||||
selectedRange,
|
||||
}: {
|
||||
anchorEl: HTMLElement | null;
|
||||
open: boolean;
|
||||
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
||||
selectedRange: any;
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [location, setLocation] = useState("");
|
||||
const [calendar, setCalendar] = useState("");
|
||||
const [start, setStart] = useState("");
|
||||
const [end, setEnd] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRange) {
|
||||
setStart(selectedRange.startStr);
|
||||
setEnd(selectedRange.endStr ?? "");
|
||||
}
|
||||
}, [selectedRange]);
|
||||
|
||||
const handleSave = () => {
|
||||
const newEvent: CalendarEvent = {
|
||||
title,
|
||||
start,
|
||||
end,
|
||||
calendar,
|
||||
extendedProps: {
|
||||
description,
|
||||
location,
|
||||
},
|
||||
};
|
||||
dispatch(addEvent(newEvent));
|
||||
console.log(newEvent)
|
||||
onClose({}, "backdropClick");
|
||||
|
||||
// Reset
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
setLocation("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<Box p={2} width={300}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Create Event
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Start"
|
||||
type="datetime-local"
|
||||
value={start}
|
||||
onChange={(e) => setStart(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="End"
|
||||
type="datetime-local"
|
||||
value={end}
|
||||
onChange={(e) => setEnd(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
multiline
|
||||
rows={2}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Location"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
/>
|
||||
<Box mt={2} display="flex" justifyContent="flex-end" gap={1}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default EventPopover;
|
||||
@@ -0,0 +1,17 @@
|
||||
// store/eventsSlice.ts
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
|
||||
const eventsSlice = createSlice({
|
||||
name: "events",
|
||||
initialState: [] as CalendarEvent[],
|
||||
reducers: {
|
||||
addEvent: (state, action: PayloadAction<CalendarEvent>) => {
|
||||
state.push(action.payload);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const { addEvent } = eventsSlice.actions;
|
||||
export default eventsSlice.reducer;
|
||||
@@ -0,0 +1,11 @@
|
||||
// types/Event.ts
|
||||
export interface CalendarEvent {
|
||||
title: string;
|
||||
start: string; // ISO date
|
||||
end?: string;
|
||||
calendar:string;
|
||||
extendedProps?: {
|
||||
description?: string;
|
||||
location?: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { Auth } from "./oidcAuth";
|
||||
import { Loading } from "../../components/Loading/Loading";
|
||||
import { Error } from "../../components/Error/Error";
|
||||
import { push } from "redux-first-history";
|
||||
|
||||
export function HandleLogin() {
|
||||
const userData = useAppSelector((state) => state.user.userData);
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
const initiateLogin = async () => {
|
||||
if (!userData) {
|
||||
const loginurl = await Auth();
|
||||
|
||||
sessionStorage.setItem(
|
||||
"redirectState",
|
||||
JSON.stringify({
|
||||
code_verifier: loginurl.code_verifier,
|
||||
state: loginurl.state,
|
||||
})
|
||||
);
|
||||
|
||||
window.location.assign(loginurl.redirectTo);
|
||||
}
|
||||
};
|
||||
|
||||
initiateLogin();
|
||||
}, [userData]);
|
||||
|
||||
if (!userData) {
|
||||
return <Error />;
|
||||
}
|
||||
dispatch(push("/calendar"));
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
export default HandleLogin;
|
||||
@@ -4,6 +4,7 @@ import { Callback } from "./oidcAuth";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { push } from "redux-first-history";
|
||||
import { setUserData } from "./userSlice";
|
||||
import { Loading } from "../../components/Loading/Loading";
|
||||
|
||||
export function CallbackResume() {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -43,23 +44,5 @@ export function CallbackResume() {
|
||||
}
|
||||
}, [dispatch, saved]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Processing OIDC callback...</p>
|
||||
{/* Optionally show loading or debug info */}
|
||||
{tokens && (
|
||||
<ul>
|
||||
<li>
|
||||
ID_Token: <pre>{JSON.stringify(tokens?.id_token)}</pre>
|
||||
</li>
|
||||
<li>
|
||||
Access_Token: <pre>{JSON.stringify(tokens?.access_token)}</pre>
|
||||
</li>
|
||||
<li>
|
||||
User info: <pre>{JSON.stringify(userInfo, null, 2)}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user