Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Vendored
+5
@@ -26,6 +26,11 @@ pipeline {
|
||||
sh 'npm run test'
|
||||
}
|
||||
}
|
||||
stage('Check Formatting') {
|
||||
steps {
|
||||
sh 'npx prettier --check .'
|
||||
}
|
||||
}
|
||||
stage('Deploy docker images') {
|
||||
when {
|
||||
anyOf {
|
||||
|
||||
@@ -8,11 +8,20 @@ This project aims at service a Single Page Application allowing a user to intera
|
||||
|
||||
This frontend-only application will interact with:
|
||||
|
||||
- [esn-sabre](https://github.com/linagora/esn-sabre/) CalDAV + CardDAV server, tailor made for LINAGORA needs.
|
||||
- [Twake Calendar side service](https://github.com/linagora/twake-calendar-side-service) that delivers additional backend features for Sabre.
|
||||
- [esn-sabre](https://github.com/linagora/esn-sabre/) CalDAV + CardDAV server, tailor made for LINAGORA needs.
|
||||
- [Twake Calendar side service](https://github.com/linagora/twake-calendar-side-service) that delivers additional backend features for Sabre.
|
||||
|
||||
It is meant as a drop in replacement of [esn-frontend-calendar](https://github.com/linagora/esn-frontend-calendar).
|
||||
|
||||
## Contributing
|
||||
|
||||
### Formating
|
||||
|
||||
We use [Prettier](https://prettier.io/) to keep code style consistent.
|
||||
A `.prettierrc` file is already included in the repo, so formatting rules are predefined.
|
||||
|
||||
Before committing, make sure you format your files either using your IDE Prettier extension or Prettier CLI.
|
||||
|
||||
## Running it
|
||||
|
||||
Requirement: node 24+
|
||||
@@ -77,7 +86,7 @@ And then visit [https://localhost:5000](https://localhost:5000).
|
||||
|
||||
### App grid
|
||||
|
||||
An applist is configurable in the public folder to setup the grid of app accessible within Twake Calendar.
|
||||
An applist is configurable in the public folder to setup the grid of app accessible within Twake Calendar.
|
||||
|
||||
Each app has three fields:
|
||||
|
||||
@@ -89,7 +98,11 @@ Example:
|
||||
|
||||
```js
|
||||
var appList = [
|
||||
{ name: "My App", icon: "https://myapp.com/myapp.png", link: "https://myapp.com" },
|
||||
{
|
||||
name: "My App",
|
||||
icon: "https://myapp.com/myapp.png",
|
||||
link: "https://myapp.com",
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("CalendarApp integration", () => {
|
||||
openpaasId: "667037022b752d0026472254",
|
||||
},
|
||||
tokens: {
|
||||
accessToken: "token"
|
||||
accessToken: "token",
|
||||
},
|
||||
},
|
||||
calendars: {
|
||||
@@ -133,14 +133,16 @@ describe("CalendarApp integration", () => {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
},
|
||||
attendee: [{
|
||||
cn: "Alice",
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
cal_address: "alice@example.com",
|
||||
}, ],
|
||||
attendee: [
|
||||
{
|
||||
cn: "Alice",
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "TRUE",
|
||||
role: "REQ-PARTICIPANT",
|
||||
cutype: "INDIVIDUAL",
|
||||
cal_address: "alice@example.com",
|
||||
},
|
||||
],
|
||||
...eventProps,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -57,33 +57,34 @@ describe("MiniCalendar", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ today: new Date(2025, 8, 1), label: "Monday 1 Sept 2025" },
|
||||
{ today: new Date(2025, 8, 3), label: "Wednesday 3 Sept 2025" },
|
||||
{ today: new Date(2025, 8, 7), label: "Sunday 7 Sept 2025" },
|
||||
{ today: new Date(2025, 8, 1), label: "Monday 1 Sept 2025" },
|
||||
{ today: new Date(2025, 8, 3), label: "Wednesday 3 Sept 2025" },
|
||||
{ today: new Date(2025, 8, 7), label: "Sunday 7 Sept 2025" },
|
||||
])(
|
||||
"renders mini calendar with the week in gray (except for today) when today is $label",
|
||||
({ today }) => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(today);
|
||||
"renders mini calendar with the week in gray (except for today) when today is $label",
|
||||
({ today }) => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(today);
|
||||
|
||||
renderCalendar();
|
||||
renderCalendar();
|
||||
|
||||
const monday = new Date(2025, 8, 1); // Monday Sept 1
|
||||
const monday = new Date(2025, 8, 1); // Monday Sept 1
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(monday);
|
||||
date.setDate(monday.getDate() + i);
|
||||
const dateTestId = `date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
||||
|
||||
const tile = screen.getByTestId(dateTestId);
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(monday);
|
||||
date.setDate(monday.getDate() + i);
|
||||
const dateTestId = `date-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
||||
|
||||
if (date.getTime() !== today.setHours(0, 0, 0, 0)) {
|
||||
expect(tile?.parentElement).toHaveClass("selectedWeek");
|
||||
const tile = screen.getByTestId(dateTestId);
|
||||
|
||||
if (date.getTime() !== today.setHours(0, 0, 0, 0)) {
|
||||
expect(tile?.parentElement).toHaveClass("selectedWeek");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
jest.useRealTimers();
|
||||
}
|
||||
);
|
||||
|
||||
it("renders mini calendar with the day in gray (except for today) when full calendar in day view", async () => {
|
||||
renderCalendar();
|
||||
|
||||
@@ -95,15 +95,13 @@ describe("Event Preview Display", () => {
|
||||
};
|
||||
|
||||
it("renders correctly event data", () => {
|
||||
jest
|
||||
.spyOn(Date.prototype, "toLocaleString")
|
||||
.mockImplementation(function (
|
||||
this: Date,
|
||||
locales?: Intl.LocalesArgument,
|
||||
options?: Intl.DateTimeFormatOptions | undefined
|
||||
): string {
|
||||
return RealDateToLocaleString.call(this, "en-UK", options);
|
||||
});
|
||||
jest.spyOn(Date.prototype, "toLocaleString").mockImplementation(function (
|
||||
this: Date,
|
||||
locales?: Intl.LocalesArgument,
|
||||
options?: Intl.DateTimeFormatOptions | undefined
|
||||
): string {
|
||||
return RealDateToLocaleString.call(this, "en-UK", options);
|
||||
});
|
||||
renderWithProviders(
|
||||
<EventPreviewModal
|
||||
anchorPosition={{ top: 0, left: 0 }}
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
['@babel/preset-react', { runtime: 'automatic', importSource: 'preact' }],
|
||||
["@babel/preset-env", { targets: { node: "current" } }],
|
||||
["@babel/preset-react", { runtime: "automatic", importSource: "preact" }],
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,4 +6,4 @@ var SSO_RESPONSE_TYPE = "code";
|
||||
var SSO_CODE_CHALLENGE_METHOD = "S256";
|
||||
var SSO_POST_LOGOUT_REDIRECT = "http://example.com?logout=1";
|
||||
var CALENDAR_BASE_URL = "https://calendar.example.com";
|
||||
var MAIL_SPA_URL = "https://mail.example.com"
|
||||
var MAIL_SPA_URL = "https://mail.example.com";
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
Vendored
+8
-2
@@ -1,5 +1,11 @@
|
||||
declare namespace Intl {
|
||||
type Key = 'calendar' | 'collation' | 'currency' | 'numberingSystem' | 'timeZone' | 'unit';
|
||||
type Key =
|
||||
| "calendar"
|
||||
| "collation"
|
||||
| "currency"
|
||||
| "numberingSystem"
|
||||
| "timeZone"
|
||||
| "unit";
|
||||
|
||||
function supportedValuesOf(input: Key): string[];
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import type { RootState, AppDispatch } from './store'
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import type { RootState, AppDispatch } from "./store";
|
||||
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
||||
export const useAppSelector = useSelector.withTypes<RootState>()
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||
export const useAppSelector = useSelector.withTypes<RootState>();
|
||||
|
||||
@@ -133,7 +133,9 @@ main {
|
||||
|
||||
/* selected day hover */
|
||||
.react-calendar__tile {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
color 0.3s ease;
|
||||
border: none;
|
||||
background-color: white;
|
||||
width: 12px;
|
||||
|
||||
@@ -31,13 +31,13 @@ import { createSelector } from "@reduxjs/toolkit";
|
||||
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import AccessTimeIcon from "@mui/icons-material/AccessTime";
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import { userAttendee } from "../../features/User/userDataTypes";
|
||||
|
||||
const computeStartOfTheWeek = (date: Date): Date => {
|
||||
const startOfWeek = new Date(date);
|
||||
startOfWeek.setDate(date.getDate() - ((date.getDay() + 6) % 7)); // Monday
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
return startOfWeek;
|
||||
};
|
||||
|
||||
@@ -204,7 +204,6 @@ export default function CalendarApp() {
|
||||
calendarRef.current?.view.type === "timeGridWeek" ||
|
||||
calendarRef.current?.view.type === undefined
|
||||
) {
|
||||
|
||||
const startOfWeek = computeStartOfTheWeek(selected);
|
||||
|
||||
const endOfWeek = new Date(startOfWeek);
|
||||
@@ -430,7 +429,9 @@ export default function CalendarApp() {
|
||||
if (!calendars[arg.event._def.extendedProps.calId]) return;
|
||||
|
||||
const attendees = event._def.extendedProps.attendee || [];
|
||||
const isPrivate = event._def.extendedProps.class === "PRIVATE" || event._def.extendedProps.class === "CONFIDENTIAL";
|
||||
const isPrivate =
|
||||
event._def.extendedProps.class === "PRIVATE" ||
|
||||
event._def.extendedProps.class === "CONFIDENTIAL";
|
||||
let Icon = null;
|
||||
let titleStyle: React.CSSProperties = {};
|
||||
const ownerEmails = new Set(
|
||||
@@ -461,9 +462,17 @@ export default function CalendarApp() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{isPrivate && <LockIcon data-testid="lock-icon" fontSize="small" style={{ marginRight: '4px' }} />}
|
||||
{Icon && <Icon fontSize="small" style={{ marginRight: '4px' }}/>}
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{isPrivate && (
|
||||
<LockIcon
|
||||
data-testid="lock-icon"
|
||||
fontSize="small"
|
||||
style={{ marginRight: "4px" }}
|
||||
/>
|
||||
)}
|
||||
{Icon && (
|
||||
<Icon fontSize="small" style={{ marginRight: "4px" }} />
|
||||
)}
|
||||
<span style={titleStyle}>{event.title}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,6 @@ function CalendarPopover({
|
||||
const [timeZone, setTimeZone] = useState("");
|
||||
const timezones = Intl.supportedValuesOf?.("timeZone") ?? [];
|
||||
const handleSave = () => {
|
||||
|
||||
dispatch(createCalendar({ name, description, color }));
|
||||
onClose({}, "backdropClick");
|
||||
|
||||
@@ -64,7 +63,11 @@ function CalendarPopover({
|
||||
}}
|
||||
>
|
||||
<Box p={2}>
|
||||
<Typography variant="h6" gutterBottom style={{backgroundColor:color}}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
gutterBottom
|
||||
style={{ backgroundColor: color }}
|
||||
>
|
||||
Create a Calendar
|
||||
</Typography>
|
||||
<TextField
|
||||
|
||||
@@ -58,7 +58,7 @@ export function parseCalendarEvent(
|
||||
case "organizer":
|
||||
event.organizer = {
|
||||
cn: params?.cn ?? "",
|
||||
cal_address: value.replace(/^mailto:/, ""),
|
||||
cal_address: value?.replace(/^mailto:/, ""),
|
||||
};
|
||||
break;
|
||||
case "attendee":
|
||||
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
|
||||
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family:
|
||||
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
Reference in New Issue
Block a user