Merge pull request #435 from linagora/feat/migration-twake-ui

Feat/migration twake UI
This commit is contained in:
lenhanphung
2026-01-08 13:50:00 +07:00
committed by GitHub
64 changed files with 782 additions and 544 deletions
Vendored
+7
View File
@@ -3,6 +3,7 @@ pipeline {
environment { environment {
DOCKER_HUB_CREDENTIAL = credentials('dockerHub') DOCKER_HUB_CREDENTIAL = credentials('dockerHub')
GITHUB_CREDENTIAL = credentials('github')
} }
tools { tools {
@@ -18,6 +19,12 @@ pipeline {
stages { stages {
stage('Compile') { stage('Compile') {
steps { steps {
sh '''
cat > .npmrc << EOF
@linagora:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_CREDENTIAL_PSW}
EOF
'''
sh 'npm install' sh 'npm install'
} }
} }
+16 -10
View File
@@ -443,11 +443,14 @@ describe("calendar Availability search", () => {
screen.getByRole("button", { name: "event.createEvent" }) screen.getByRole("button", { name: "event.createEvent" })
); );
}); });
await waitFor(() => { await waitFor(
expect(screen.getAllByText("event.createEvent")).toHaveLength(2); () => {
expect(screen.getAllByText(/New User/i)).toHaveLength(2); expect(screen.getAllByText("event.createEvent")).toHaveLength(2);
}); expect(screen.getAllByText(/New User/i)).toHaveLength(2);
}); },
{ timeout: 10000 }
);
}, 15000);
it("open window with attendees filled after temp search on enter in temp input", async () => { it("open window with attendees filled after temp search on enter in temp input", async () => {
const spy = jest const spy = jest
@@ -482,11 +485,14 @@ describe("calendar Availability search", () => {
fireEvent.keyDown(input, { key: "Enter" }); fireEvent.keyDown(input, { key: "Enter" });
}); });
await waitFor(() => { await waitFor(
expect(screen.getAllByText("event.createEvent")).toHaveLength(2); () => {
expect(screen.getAllByText(/New User/i)).toHaveLength(2); expect(screen.getAllByText("event.createEvent")).toHaveLength(2);
}); expect(screen.getAllByText(/New User/i)).toHaveLength(2);
}); },
{ timeout: 10000 }
);
}, 15000);
it("BUGFIX: can untoggle all calendar.personal", async () => { it("BUGFIX: can untoggle all calendar.personal", async () => {
await act(async () => await act(async () =>
@@ -69,10 +69,13 @@ describe("Event Error Handling", () => {
renderWithProviders(<CalendarLayout />, erroredState) renderWithProviders(<CalendarLayout />, erroredState)
); );
await waitFor(() => { await waitFor(
expect(screen.getByText("Test Event")); () => {
expect(screen.getByRole("alert")).toBeInTheDocument(); expect(screen.getByText("Test Event"));
}); expect(screen.getByRole("alert")).toBeInTheDocument();
},
{ timeout: 10000 }
);
const closeButton = screen.queryByRole("button", { name: "common.ok" }); const closeButton = screen.queryByRole("button", { name: "common.ok" });
if (closeButton) { if (closeButton) {
@@ -82,9 +85,12 @@ describe("Event Error Handling", () => {
fireEvent.click(closeButton); fireEvent.click(closeButton);
}); });
await waitFor(() => { await waitFor(
expect(screen.queryByRole("alert")).not.toBeInTheDocument(); () => {
}); expect(screen.queryByRole("alert")).not.toBeInTheDocument();
},
{ timeout: 5000 }
);
const afterCloseWarnCount = consoleWarnSpy.mock.calls.length; const afterCloseWarnCount = consoleWarnSpy.mock.calls.length;
+2
View File
@@ -199,6 +199,8 @@ describe("RepeatEvent Component", () => {
}); });
describe("Repeat Event Integration Tests", () => { describe("Repeat Event Integration Tests", () => {
// Increase timeout for all tests in this describe block
jest.setTimeout(30000);
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
jest.restoreAllMocks(); jest.restoreAllMocks();
+29 -24
View File
@@ -1,19 +1,24 @@
import { fireEvent, render, screen } from "@testing-library/react"; import { fireEvent, render, screen } from "@testing-library/react";
import React from "react"; import React from "react";
import { ResponsiveDialog } from "../../src/components/Dialog"; import { ResponsiveDialog } from "../../src/components/Dialog";
import { Button, TextField } from "@mui/material"; import { Button, TextField } from "@linagora/twake-mui";
import { TwakeMuiThemeProvider } from "@linagora/twake-mui";
describe("ResponsiveDialog", () => { describe("ResponsiveDialog", () => {
const mockOnClose = jest.fn(); const mockOnClose = jest.fn();
const mockOnExpandToggle = jest.fn(); const mockOnExpandToggle = jest.fn();
const renderWithTheme = (ui: React.ReactElement) => {
return render(<TwakeMuiThemeProvider>{ui}</TwakeMuiThemeProvider>);
};
beforeEach(() => { beforeEach(() => {
mockOnClose.mockClear(); mockOnClose.mockClear();
mockOnExpandToggle.mockClear(); mockOnExpandToggle.mockClear();
}); });
it("renders in normal mode by default", () => { it("renders in normal mode by default", () => {
render( renderWithTheme(
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test Dialog"> <ResponsiveDialog open={true} onClose={mockOnClose} title="Test Dialog">
<TextField label="Name" /> <TextField label="Name" />
</ResponsiveDialog> </ResponsiveDialog>
@@ -24,7 +29,7 @@ describe("ResponsiveDialog", () => {
}); });
it("renders title in normal mode", () => { it("renders title in normal mode", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -40,7 +45,7 @@ describe("ResponsiveDialog", () => {
}); });
it("renders back arrow in extended mode", () => { it("renders back arrow in extended mode", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -57,7 +62,7 @@ describe("ResponsiveDialog", () => {
}); });
it("calls onExpandToggle when back arrow is clicked", () => { it("calls onExpandToggle when back arrow is clicked", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -76,7 +81,7 @@ describe("ResponsiveDialog", () => {
}); });
it("renders actions when provided", () => { it("renders actions when provided", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -91,7 +96,7 @@ describe("ResponsiveDialog", () => {
}); });
it("does not render actions when not provided", () => { it("does not render actions when not provided", () => {
const { container } = render( const { container } = renderWithTheme(
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test"> <ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
<div>Content</div> <div>Content</div>
</ResponsiveDialog> </ResponsiveDialog>
@@ -102,7 +107,7 @@ describe("ResponsiveDialog", () => {
}); });
it("calls onClose when backdrop is clicked", () => { it("calls onClose when backdrop is clicked", () => {
render( renderWithTheme(
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test"> <ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
<div>Content</div> <div>Content</div>
</ResponsiveDialog> </ResponsiveDialog>
@@ -117,7 +122,7 @@ describe("ResponsiveDialog", () => {
}); });
it("applies custom normalMaxWidth", () => { it("applies custom normalMaxWidth", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -132,7 +137,7 @@ describe("ResponsiveDialog", () => {
}); });
it("wraps children in Stack component", () => { it("wraps children in Stack component", () => {
render( renderWithTheme(
<ResponsiveDialog open={true} onClose={mockOnClose} title="Test"> <ResponsiveDialog open={true} onClose={mockOnClose} title="Test">
<TextField label="Field 1" /> <TextField label="Field 1" />
<TextField label="Field 2" /> <TextField label="Field 2" />
@@ -144,7 +149,7 @@ describe("ResponsiveDialog", () => {
}); });
it("uses correct spacing in normal mode", () => { it("uses correct spacing in normal mode", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -160,7 +165,7 @@ describe("ResponsiveDialog", () => {
}); });
it("uses correct spacing in extended mode", () => { it("uses correct spacing in extended mode", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -176,7 +181,7 @@ describe("ResponsiveDialog", () => {
}); });
it("applies contentSx custom styles", () => { it("applies contentSx custom styles", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -191,7 +196,7 @@ describe("ResponsiveDialog", () => {
}); });
it("applies titleSx custom styles", () => { it("applies titleSx custom styles", () => {
const { container } = render( const { container } = renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -207,7 +212,7 @@ describe("ResponsiveDialog", () => {
}); });
it("shows dividers when dividers prop is true", () => { it("shows dividers when dividers prop is true", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -222,7 +227,7 @@ describe("ResponsiveDialog", () => {
}); });
it("does not show back arrow when onExpandToggle is not provided", () => { it("does not show back arrow when onExpandToggle is not provided", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -238,7 +243,7 @@ describe("ResponsiveDialog", () => {
}); });
it("accepts custom headerHeight", () => { it("accepts custom headerHeight", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -254,7 +259,7 @@ describe("ResponsiveDialog", () => {
}); });
it("renders with custom expandedContentMaxWidth", () => { it("renders with custom expandedContentMaxWidth", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -270,7 +275,7 @@ describe("ResponsiveDialog", () => {
}); });
it("does not render dialog content when open is false", () => { it("does not render dialog content when open is false", () => {
render( renderWithTheme(
<ResponsiveDialog open={false} onClose={mockOnClose} title="Test"> <ResponsiveDialog open={false} onClose={mockOnClose} title="Test">
<div>Test Content</div> <div>Test Content</div>
</ResponsiveDialog> </ResponsiveDialog>
@@ -280,7 +285,7 @@ describe("ResponsiveDialog", () => {
}); });
it("renders correctly in extended mode", () => { it("renders correctly in extended mode", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -297,7 +302,7 @@ describe("ResponsiveDialog", () => {
}); });
it("renders expand and close icons in normal mode when showHeaderActions is true", () => { it("renders expand and close icons in normal mode when showHeaderActions is true", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -315,7 +320,7 @@ describe("ResponsiveDialog", () => {
}); });
it("does not render header icons when showHeaderActions is false", () => { it("does not render header icons when showHeaderActions is false", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -334,7 +339,7 @@ describe("ResponsiveDialog", () => {
}); });
it("calls onClose when close icon is clicked", () => { it("calls onClose when close icon is clicked", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
@@ -353,7 +358,7 @@ describe("ResponsiveDialog", () => {
}); });
it("calls onExpandToggle when expand icon is clicked", () => { it("calls onExpandToggle when expand icon is clicked", () => {
render( renderWithTheme(
<ResponsiveDialog <ResponsiveDialog
open={true} open={true}
onClose={mockOnClose} onClose={mockOnClose}
+74 -2
View File
@@ -662,7 +662,78 @@ describe("Event Preview Display", () => {
preloadedState.calendars.list["667037022b752d0026472254/cal1"].events[ preloadedState.calendars.list["667037022b752d0026472254/cal1"].events[
"event1" "event1"
]; ];
const expectedUrl = `test/mailto/?uri=mailto:john@test.com?subject=Test Event`; const expectedUrl = `test/mailto/?uri=mailto:john@test.com?subject=Test%20Event`;
expect(mockOpen).toHaveBeenCalledWith(expectedUrl);
});
it("message button encodes special characters in event title correctly", () => {
(window as any).MAIL_SPA_URL = "test";
const mockOpen = jest.fn();
window.open = mockOpen;
const specialCharState = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
...preloadedState.calendars.list,
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"]
.events,
eventWithSpecialChars: {
uid: "eventWithSpecialChars",
title: "Meeting & Discussion? #Important",
calId: "667037022b752d0026472254/cal1",
start: day.toISOString(),
end: day.toISOString(),
organizer: { cn: "test", cal_address: "test@test.com" },
attendee: [
{
cn: "test",
cal_address: "test@test.com",
partstat: "NEEDS-ACTION",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
},
{
cn: "John",
cal_address: "john@test.com",
partstat: "NEEDS-ACTION",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
},
],
},
},
},
},
},
};
renderWithProviders(
<EventPreviewModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"eventWithSpecialChars"}
/>,
specialCharState
);
fireEvent.click(screen.getByTestId("MoreVertIcon"));
const emailButton = screen.getByRole("menuitem", {
name: "eventPreview.emailAttendees",
});
expect(emailButton).toBeInTheDocument();
fireEvent.click(emailButton);
const expectedUrl = `test/mailto/?uri=mailto:john@test.com?subject=Meeting%20%26%20Discussion%3F%20%23Important`;
expect(mockOpen).toHaveBeenCalledWith(expectedUrl); expect(mockOpen).toHaveBeenCalledWith(expectedUrl);
}); });
@@ -1957,7 +2028,8 @@ describe("Helper functions", () => {
it("stringAvatar returns correct props", () => { it("stringAvatar returns correct props", () => {
const result = stringAvatar("Alice"); const result = stringAvatar("Alice");
expect(result.children).toBe("A"); expect(result.children).toBe("A");
expect(result.style.backgroundColor).toMatch(/^#/); expect(result.color).toBeDefined();
expect(typeof result.color).toBe("string");
}); });
it("InfoRow renders text and link if url is valid", () => { it("InfoRow renders text and link if url is valid", () => {
@@ -296,7 +296,7 @@ describe("EventPreviewModal - Recurring Event Interactions", () => {
await waitFor(() => { await waitFor(() => {
expect( expect(
screen.getByText("editModeDialog.updateRecurrentEvent") screen.getByText("editModeDialog.deleteRecurrentEvent")
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
}); });
@@ -381,7 +381,7 @@ describe("EventPreviewModal - Recurring Event Interactions", () => {
}); });
expect( expect(
screen.queryByText("editModeDialog.updateRecurrentEvent") screen.queryByText("editModeDialog.deleteRecurrentEvent")
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
}); });
@@ -416,7 +416,7 @@ describe("Delete Recurring Event Instance", () => {
await waitFor(() => { await waitFor(() => {
expect( expect(
screen.getByText("editModeDialog.updateRecurrentEvent") screen.getByText("editModeDialog.deleteRecurrentEvent")
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
@@ -463,7 +463,7 @@ describe("Delete Recurring Event Instance", () => {
await waitFor(() => { await waitFor(() => {
expect( expect(
screen.getByText("editModeDialog.updateRecurrentEvent") screen.getByText("editModeDialog.deleteRecurrentEvent")
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
+25 -22
View File
@@ -4,6 +4,7 @@ import React, { PropsWithChildren } from "react";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import { I18nContext } from "twake-i18n"; import { I18nContext } from "twake-i18n";
import { TwakeMuiThemeProvider } from "@linagora/twake-mui";
import type { AppStore, RootState } from "../../src/app/store"; import type { AppStore, RootState } from "../../src/app/store";
import { setupStore } from "../../src/app/store"; import { setupStore } from "../../src/app/store";
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> { interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
@@ -21,28 +22,30 @@ export function renderWithProviders(
const Wrapper = ({ children }: PropsWithChildren) => { const Wrapper = ({ children }: PropsWithChildren) => {
return ( return (
<I18nContext.Provider <TwakeMuiThemeProvider>
value={{ <I18nContext.Provider
t: (key: string, vars?: Record<string, string>) => { value={{
if (key === "locale") { t: (key: string, vars?: Record<string, string>) => {
return "en"; if (key === "locale") {
} return "en";
if (vars) { }
const params = Object.entries(vars) if (vars) {
.map(([k, v]) => `${k}=${v}`) const params = Object.entries(vars)
.join(","); .map(([k, v]) => `${k}=${v}`)
return `${key}(${params})`; .join(",");
} return `${key}(${params})`;
return key; }
}, return key;
f: (date: Date, formatStr: string) => date.toString(), },
lang: "en", f: (date: Date, formatStr: string) => date.toString(),
}} lang: "en",
> }}
<MemoryRouter initialEntries={["/"]}> >
<Provider store={store}>{children}</Provider> <MemoryRouter initialEntries={["/"]}>
</MemoryRouter> <Provider store={store}>{children}</Provider>
</I18nContext.Provider> </MemoryRouter>
</I18nContext.Provider>
</TwakeMuiThemeProvider>
); );
}; };
+7 -1
View File
@@ -23,6 +23,7 @@ const config: Config = {
], ],
testEnvironment: "jsdom", testEnvironment: "jsdom",
testMatch: ["**/*.test.tsx"], testMatch: ["**/*.test.tsx"],
testTimeout: 15000,
transform: { transform: {
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }], "^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }],
"^.+\\.(js|jsx|mjs)$": "babel-jest", "^.+\\.(js|jsx|mjs)$": "babel-jest",
@@ -37,7 +38,11 @@ const config: Config = {
"/node_modules/(?!(preact|@fullcalendar|react-calendar|get-user-locale|memoize|mimic-function|@wojtekmaj|ky|cozy-ui)/)", "/node_modules/(?!(preact|@fullcalendar|react-calendar|get-user-locale|memoize|mimic-function|@wojtekmaj|ky|cozy-ui)/)",
], ],
moduleNameMapper: { "^preact(/(.*)|$)": "preact$1" }, moduleNameMapper: {
"^preact(/(.*)|$)": "preact$1",
"^react$": "<rootDir>/node_modules/react",
"^react-dom$": "<rootDir>/node_modules/react-dom",
},
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"], setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
}, },
{ {
@@ -55,6 +60,7 @@ const config: Config = {
], ],
testEnvironment: "node", testEnvironment: "node",
testMatch: ["**/*.test.ts"], testMatch: ["**/*.test.ts"],
testTimeout: 15000,
transform: { transform: {
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }], "^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }],
"^.+\\.(js|jsx|mjs)$": "babel-jest", "^.+\\.(js|jsx|mjs)$": "babel-jest",
+404 -272
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -12,6 +12,7 @@
"@fullcalendar/moment-timezone": "^6.1.19", "@fullcalendar/moment-timezone": "^6.1.19",
"@fullcalendar/react": "^6.1.17", "@fullcalendar/react": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.17", "@fullcalendar/timegrid": "^6.1.17",
"@linagora/twake-mui": "1.1.1",
"@mui/icons-material": "^7.1.2", "@mui/icons-material": "^7.1.2",
"@mui/material": "^7.1.2", "@mui/material": "^7.1.2",
"@mui/x-date-pickers": "^8.14.0", "@mui/x-date-pickers": "^8.14.0",
@@ -89,6 +90,7 @@
"jest-preview": "^0.3.1", "jest-preview": "^0.3.1",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"ts-jest": "^29.4.6", "ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^4.9.5" "typescript": "^4.9.5"
} }
} }
+6
View File
@@ -23,4 +23,10 @@ export default defineConfig({
}, },
minify: false, minify: false,
}, },
resolve: {
alias: {
react: require.resolve("react"),
"react-dom": require.resolve("react-dom"),
},
},
}); });
+3 -3
View File
@@ -8,7 +8,7 @@ import { Loading } from "./components/Loading/Loading";
import HandleLogin from "./features/User/HandleLogin"; import HandleLogin from "./features/User/HandleLogin";
import CalendarLayout from "./components/Calendar/CalendarLayout"; import CalendarLayout from "./components/Calendar/CalendarLayout";
import { Error } from "./components/Error/Error"; import { Error } from "./components/Error/Error";
import { CustomThemeProvider } from "./theme/ThemeProvider"; import { TwakeMuiThemeProvider } from "@linagora/twake-mui";
import { useAppDispatch, useAppSelector } from "./app/hooks"; import { useAppDispatch, useAppSelector } from "./app/hooks";
import { push } from "redux-first-history"; import { push } from "redux-first-history";
import { ErrorSnackbar } from "./components/Error/ErrorSnackbar"; import { ErrorSnackbar } from "./components/Error/ErrorSnackbar";
@@ -61,7 +61,7 @@ function App() {
}, [error, dispatch]); }, [error, dispatch]);
return ( return (
<CustomThemeProvider> <TwakeMuiThemeProvider>
<I18n <I18n
dictRequire={(lang: keyof typeof locale) => locale[lang]} dictRequire={(lang: keyof typeof locale) => locale[lang]}
lang={lang} lang={lang}
@@ -79,7 +79,7 @@ function App() {
<ErrorSnackbar error={error} type="user" /> <ErrorSnackbar error={error} type="user" />
</Suspense> </Suspense>
</I18n> </I18n>
</CustomThemeProvider> </TwakeMuiThemeProvider>
); );
} }
+12 -12
View File
@@ -1,12 +1,13 @@
import Autocomplete, { import {
AutocompleteRenderInputParams, Autocomplete,
} from "@mui/material/Autocomplete"; type AutocompleteRenderInputParams,
import Avatar from "@mui/material/Avatar"; Avatar,
import CircularProgress from "@mui/material/CircularProgress"; CircularProgress,
import ListItem from "@mui/material/ListItem"; ListItem,
import ListItemAvatar from "@mui/material/ListItemAvatar"; ListItemAvatar,
import ListItemText from "@mui/material/ListItemText"; ListItemText,
import TextField from "@mui/material/TextField"; TextField,
} from "@linagora/twake-mui";
import { import {
type ReactNode, type ReactNode,
useCallback, useCallback,
@@ -16,12 +17,11 @@ import {
} from "react"; } from "react";
import { searchUsers } from "../../features/User/userAPI"; import { searchUsers } from "../../features/User/userAPI";
import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined"; import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined";
import Chip from "@mui/material/Chip"; import { Chip, useTheme } from "@linagora/twake-mui";
import { useTheme } from "@mui/material/styles";
import { getAccessiblePair } from "../Calendar/utils/calendarColorsUtils"; import { getAccessiblePair } from "../Calendar/utils/calendarColorsUtils";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { SnackbarAlert } from "../Loading/SnackBarAlert"; import { SnackbarAlert } from "../Loading/SnackBarAlert";
import { PopperProps, PaperProps } from "@mui/material"; import { PopperProps, PaperProps } from "@linagora/twake-mui";
export interface User { export interface User {
email: string; email: string;
+1 -1
View File
@@ -9,7 +9,7 @@ import {
InputAdornment, InputAdornment,
Backdrop, Backdrop,
CircularProgress, CircularProgress,
} from "@mui/material"; } from "@linagora/twake-mui";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { import {
exportCalendar, exportCalendar,
+5 -3
View File
@@ -20,8 +20,7 @@ import { push } from "redux-first-history";
import EventPreviewModal from "../../features/Events/EventDisplayPreview"; import EventPreviewModal from "../../features/Events/EventDisplayPreview";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import { TempCalendarsInput } from "./TempCalendarsInput"; import { TempCalendarsInput } from "./TempCalendarsInput";
import Button from "@mui/material/Button"; import { Button, Box, radius } from "@linagora/twake-mui";
import { Box } from "@mui/material";
import { import {
updateSlotLabelVisibility, updateSlotLabelVisibility,
eventToFullCalendarFormat, eventToFullCalendarFormat,
@@ -36,7 +35,7 @@ import momentTimezonePlugin from "@fullcalendar/moment-timezone";
import { TimezoneSelector } from "./TimezoneSelector"; import { TimezoneSelector } from "./TimezoneSelector";
import { MiniCalendar } from "./MiniCalendar"; import { MiniCalendar } from "./MiniCalendar";
import { User } from "../Attendees/PeopleSearch"; import { User } from "../Attendees/PeopleSearch";
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@linagora/twake-mui";
import { updateDarkColor } from "./utils/calendarColorsUtils"; import { updateDarkColor } from "./utils/calendarColorsUtils";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import frLocale from "@fullcalendar/core/locales/fr"; import frLocale from "@fullcalendar/core/locales/fr";
@@ -619,6 +618,9 @@ export default function CalendarApp({
onClick={() => onClick={() =>
eventHandlers.handleDateSelect(null as unknown as DateSelectArg) eventHandlers.handleDateSelect(null as unknown as DateSelectArg)
} }
sx={{
borderRadius: radius.lg,
}}
> >
<AddIcon /> <p>{t("event.createEvent")}</p> <AddIcon /> <p>{t("event.createEvent")}</p>
</Button> </Button>
@@ -7,7 +7,7 @@ import {
TextField, TextField,
Typography, Typography,
useTheme, useTheme,
} from "@mui/material"; } from "@linagora/twake-mui";
import { useState } from "react"; import { useState } from "react";
import { HexColorPicker } from "react-colorful"; import { HexColorPicker } from "react-colorful";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { MenuItem } from "@mui/material"; import { MenuItem } from "@linagora/twake-mui";
import { Calendar } from "../../features/Calendars/CalendarTypes"; import { Calendar } from "../../features/Calendars/CalendarTypes";
import { CalendarName } from "./CalendarName"; import { CalendarName } from "./CalendarName";
+3 -3
View File
@@ -1,4 +1,4 @@
import { Button, DialogActions, Tab, Tabs } from "@mui/material"; import { Button, Tab, Tabs } from "@linagora/twake-mui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { import {
@@ -198,7 +198,7 @@ function CalendarPopover({
</Tabs> </Tabs>
} }
actions={ actions={
<DialogActions> <>
<Button <Button
variant="outlined" variant="outlined"
onClick={(e) => handleClose({}, "backdropClick")} onClick={(e) => handleClose({}, "backdropClick")}
@@ -216,7 +216,7 @@ function CalendarPopover({
? t("actions.save") ? t("actions.save")
: t("actions.create")} : t("actions.create")}
</Button> </Button>
</DialogActions> </>
} }
> >
{tab === "import" && ( {tab === "import" && (
+1 -1
View File
@@ -1,4 +1,4 @@
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@linagora/twake-mui";
import { Calendar } from "../../features/Calendars/CalendarTypes"; import { Calendar } from "../../features/Calendars/CalendarTypes";
import SquareRoundedIcon from "@mui/icons-material/SquareRounded"; import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
export function CalendarName({ calendar }: { calendar: Calendar }) { export function CalendarName({ calendar }: { calendar: Calendar }) {
+9 -7
View File
@@ -1,17 +1,19 @@
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import Avatar from "@mui/material/Avatar"; import {
import Box from "@mui/material/Box"; Avatar,
import Button from "@mui/material/Button"; Box,
import IconButton from "@mui/material/IconButton"; Button,
import { useTheme } from "@mui/material/styles"; IconButton,
import Typography from "@mui/material/Typography"; Typography,
TextField,
useTheme,
} from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { useState } from "react"; import { useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { getCalendars } from "../../features/Calendars/CalendarApi"; import { getCalendars } from "../../features/Calendars/CalendarApi";
import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice"; import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice";
import { Calendar } from "../../features/Calendars/CalendarTypes"; import { Calendar } from "../../features/Calendars/CalendarTypes";
import TextField from "@mui/material/TextField";
import { PeopleSearch, User } from "../Attendees/PeopleSearch"; import { PeopleSearch, User } from "../Attendees/PeopleSearch";
import { ResponsiveDialog } from "../Dialog"; import { ResponsiveDialog } from "../Dialog";
import { ColorPicker } from "./CalendarColorPicker"; import { ColorPicker } from "./CalendarColorPicker";
+12 -7
View File
@@ -1,18 +1,23 @@
import Accordion from "@mui/material/Accordion"; import {
import AccordionDetails from "@mui/material/AccordionDetails"; Accordion,
import AccordionSummary from "@mui/material/AccordionSummary"; AccordionDetails,
import Typography from "@mui/material/Typography"; AccordionSummary,
Typography,
IconButton,
Checkbox,
Divider,
ListItem,
Menu,
MenuItem,
} from "@linagora/twake-mui";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import { useState, useMemo, useEffect } from "react"; import { useState, useMemo, useEffect } from "react";
import CalendarPopover from "./CalendarModal"; import CalendarPopover from "./CalendarModal";
import { Calendar } from "../../features/Calendars/CalendarTypes"; import { Calendar } from "../../features/Calendars/CalendarTypes";
import MoreVertIcon from "@mui/icons-material/MoreVert"; import MoreVertIcon from "@mui/icons-material/MoreVert";
import IconButton from "@mui/material/IconButton";
import Checkbox from "@mui/material/Checkbox";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import CalendarSearch from "./CalendarSearch"; import CalendarSearch from "./CalendarSearch";
import { Divider, ListItem, Menu, MenuItem } from "@mui/material";
import { removeCalendarAsync } from "../../features/Calendars/CalendarSlice"; import { removeCalendarAsync } from "../../features/Calendars/CalendarSlice";
import { DeleteCalendarDialog } from "./DeleteCalendarDialog"; import { DeleteCalendarDialog } from "./DeleteCalendarDialog";
import { trimLongTextWithoutSpace } from "../../utils/textUtils"; import { trimLongTextWithoutSpace } from "../../utils/textUtils";
@@ -1,10 +1,12 @@
import Dialog from "@mui/material/Dialog"; import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
} from "@linagora/twake-mui";
import { Calendar } from "../../features/Calendars/CalendarTypes"; import { Calendar } from "../../features/Calendars/CalendarTypes";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
export function DeleteCalendarDialog({ export function DeleteCalendarDialog({
+1 -1
View File
@@ -9,7 +9,7 @@ import {
ToggleButton, ToggleButton,
ToggleButtonGroup, ToggleButtonGroup,
Typography, Typography,
} from "@mui/material"; } from "@linagora/twake-mui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useAppSelector } from "../../app/hooks"; import { useAppSelector } from "../../app/hooks";
import { CalendarItemList } from "./CalendarItemList"; import { CalendarItemList } from "./CalendarItemList";
+1 -1
View File
@@ -8,7 +8,7 @@ import {
ToggleButton, ToggleButton,
ToggleButtonGroup, ToggleButtonGroup,
Typography, Typography,
} from "@mui/material"; } from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useAppSelector } from "../../app/hooks"; import { useAppSelector } from "../../app/hooks";
@@ -1,4 +1,4 @@
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@linagora/twake-mui";
import { useRef } from "react"; import { useRef } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
@@ -8,7 +8,7 @@ import {
} from "../../features/Calendars/CalendarSlice"; } from "../../features/Calendars/CalendarSlice";
import { Calendar } from "../../features/Calendars/CalendarTypes"; import { Calendar } from "../../features/Calendars/CalendarTypes";
import { setView } from "../../features/Settings/SettingsSlice"; import { setView } from "../../features/Settings/SettingsSlice";
import TextField from "@mui/material/TextField"; import { TextField } from "@linagora/twake-mui";
import { User, PeopleSearch } from "../Attendees/PeopleSearch"; import { User, PeopleSearch } from "../Attendees/PeopleSearch";
import { getAccessiblePair } from "./utils/calendarColorsUtils"; import { getAccessiblePair } from "./utils/calendarColorsUtils";
+1 -1
View File
@@ -1,4 +1,4 @@
import { Button, Popover } from "@mui/material"; import { Button, Popover } from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import moment from "moment"; import moment from "moment";
import { MouseEvent, useMemo, useState } from "react"; import { MouseEvent, useMemo, useState } from "react";
@@ -1,4 +1,4 @@
import { darken, getContrastRatio, lighten, Theme } from "@mui/material"; import { darken, getContrastRatio, lighten, Theme } from "@linagora/twake-mui";
import { ThunkDispatch } from "@reduxjs/toolkit"; import { ThunkDispatch } from "@reduxjs/toolkit";
import { updateCalColor } from "../../../features/Calendars/CalendarSlice"; import { updateCalColor } from "../../../features/Calendars/CalendarSlice";
import { Calendar } from "../../../features/Calendars/CalendarTypes"; import { Calendar } from "../../../features/Calendars/CalendarTypes";
+2 -5
View File
@@ -11,7 +11,7 @@ import {
SxProps, SxProps,
Theme, Theme,
Box, Box,
} from "@mui/material"; } from "@linagora/twake-mui";
import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import OpenInFullIcon from "@mui/icons-material/OpenInFull"; import OpenInFullIcon from "@mui/icons-material/OpenInFull";
@@ -132,7 +132,7 @@ function ResponsiveDialog({
"& .MuiDialogActions-root .MuiBox-root": { "& .MuiDialogActions-root .MuiBox-root": {
maxWidth: isExpanded ? expandedContentMaxWidth : undefined, maxWidth: isExpanded ? expandedContentMaxWidth : undefined,
margin: isExpanded ? "0 auto" : undefined, margin: isExpanded ? "0 auto" : undefined,
padding: isExpanded ? "0 12px" : undefined, padding: "0",
width: isExpanded ? "100%" : undefined, width: isExpanded ? "100%" : undefined,
justifyContent: isExpanded ? "flex-end" : undefined, justifyContent: isExpanded ? "flex-end" : undefined,
}, },
@@ -140,7 +140,6 @@ function ResponsiveDialog({
const baseContentSx: SxProps<Theme> = { const baseContentSx: SxProps<Theme> = {
width: "100%", width: "100%",
padding: isExpanded ? "16px" : undefined,
}; };
const contentWrapperSx: SxProps<Theme> = { const contentWrapperSx: SxProps<Theme> = {
@@ -239,8 +238,6 @@ function ResponsiveDialog({
? (theme) => `1px solid ${theme.palette.divider}` ? (theme) => `1px solid ${theme.palette.divider}`
: undefined, : undefined,
justifyContent: actionsJustifyContent, justifyContent: actionsJustifyContent,
paddingTop: "18px",
paddingBottom: "18px",
}} }}
> >
{actions} {actions}
+8 -1
View File
@@ -1,6 +1,13 @@
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import ReplayIcon from "@mui/icons-material/Replay"; import ReplayIcon from "@mui/icons-material/Replay";
import { Box, Button, Fade, Paper, Stack, Typography } from "@mui/material"; import {
Box,
Button,
Fade,
Paper,
Stack,
Typography,
} from "@linagora/twake-mui";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { push } from "redux-first-history"; import { push } from "redux-first-history";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
+1 -3
View File
@@ -1,6 +1,4 @@
import Snackbar from "@mui/material/Snackbar"; import { Snackbar, Alert, Button } from "@linagora/twake-mui";
import Alert from "@mui/material/Alert";
import Button from "@mui/material/Button";
import { useAppDispatch } from "../../app/hooks"; import { useAppDispatch } from "../../app/hooks";
import { clearError as calendarClearError } from "../../features/Calendars/CalendarSlice"; import { clearError as calendarClearError } from "../../features/Calendars/CalendarSlice";
import { clearError as userClearError } from "../../features/User/userSlice"; import { clearError as userClearError } from "../../features/User/userSlice";
+1 -1
View File
@@ -1,4 +1,4 @@
import { Box, Button, TextField } from "@mui/material"; import { Box, Button, TextField } from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { FieldWithLabel } from "./components/FieldWithLabel"; import { FieldWithLabel } from "./components/FieldWithLabel";
import { Description as DescriptionIcon } from "@mui/icons-material"; import { Description as DescriptionIcon } from "@mui/icons-material";
+2 -1
View File
@@ -8,7 +8,7 @@ import {
FormControlLabel, FormControlLabel,
Radio, Radio,
RadioGroup, RadioGroup,
} from "@mui/material"; } from "@linagora/twake-mui";
import { CalendarEvent } from "../../features/Events/EventsTypes"; import { CalendarEvent } from "../../features/Events/EventsTypes";
import { useState } from "react"; import { useState } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
@@ -40,6 +40,7 @@ export function EditModeDialog({
<Dialog open={Boolean(type)} onClose={handleClose}> <Dialog open={Boolean(type)} onClose={handleClose}>
<DialogTitle> <DialogTitle>
{type === "edit" && t("editModeDialog.updateRecurrentEvent")} {type === "edit" && t("editModeDialog.updateRecurrentEvent")}
{type === "delete" && t("editModeDialog.deleteRecurrentEvent")}
{type === "attendance" && t("editModeDialog.updateParticipationStatus")} {type === "attendance" && t("editModeDialog.updateParticipationStatus")}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@@ -1,4 +1,4 @@
import { Card, Typography } from "@mui/material"; import { Card, Typography } from "@linagora/twake-mui";
import { useRef, useEffect } from "react"; import { useRef, useEffect } from "react";
import { EventErrorHandler } from "../../Error/EventErrorHandler"; import { EventErrorHandler } from "../../Error/EventErrorHandler";
+5 -6
View File
@@ -5,7 +5,7 @@ import {
CardContent, CardContent,
CardHeader, CardHeader,
Typography, Typography,
} from "@mui/material"; } from "@linagora/twake-mui";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { stringAvatar } from "../utils/eventUtils"; import { stringAvatar } from "../utils/eventUtils";
import { ErrorEventChip } from "./ErrorEventChip"; import { ErrorEventChip } from "./ErrorEventChip";
@@ -110,7 +110,7 @@ export function EventChip({
event._def.extendedProps.organizer?.cn ?? event._def.extendedProps.organizer?.cn ??
event._def.extendedProps.organizer?.cal_address event._def.extendedProps.organizer?.cal_address
) )
: { style: {}, children: null }; : { color: undefined, children: null };
return ( return (
<Card <Card
@@ -261,10 +261,9 @@ export function EventChip({
(window as any).displayOrgAvatar && ( (window as any).displayOrgAvatar && (
<Avatar <Avatar
children={OrganizerAvatar.children} children={OrganizerAvatar.children}
style={{ color={OrganizerAvatar.color}
...OrganizerAvatar.style, size="xs"
width: "20px", sx={{
height: "20px",
bottom: 0, bottom: 0,
right: 0, right: 0,
margin: "8px", margin: "8px",
@@ -1,7 +1,7 @@
import CancelIcon from "@mui/icons-material/Cancel"; import CancelIcon from "@mui/icons-material/Cancel";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline"; import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import LockOutlineIcon from "@mui/icons-material/LockOutline"; import LockOutlineIcon from "@mui/icons-material/LockOutline";
import { Box, getContrastRatio } from "@mui/material"; import { Box, getContrastRatio } from "@linagora/twake-mui";
import moment from "moment"; import moment from "moment";
import React, { useLayoutEffect, useState } from "react"; import React, { useLayoutEffect, useState } from "react";
import { Calendar } from "../../../features/Calendars/CalendarTypes"; import { Calendar } from "../../../features/Calendars/CalendarTypes";
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Card, Typography } from "@mui/material"; import { Card, Typography } from "@linagora/twake-mui";
export function SimpleEventChip({ title }: { title: string }) { export function SimpleEventChip({ title }: { title: string }) {
return ( return (
+1 -1
View File
@@ -1,4 +1,4 @@
import { MenuItem } from "@mui/material"; import { MenuItem } from "@linagora/twake-mui";
import { CalendarEvent } from "../../features/Events/EventsTypes"; import { CalendarEvent } from "../../features/Events/EventsTypes";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
+1 -1
View File
@@ -13,7 +13,7 @@ import {
TextField, TextField,
ToggleButtonGroup, ToggleButtonGroup,
ToggleButton, ToggleButton,
} from "@mui/material"; } from "@linagora/twake-mui";
import { import {
Description as DescriptionIcon, Description as DescriptionIcon,
Public as PublicIcon, Public as PublicIcon,
+1 -1
View File
@@ -12,7 +12,7 @@ import {
FormGroup, FormGroup,
Radio, Radio,
RadioGroup, RadioGroup,
} from "@mui/material"; } from "@linagora/twake-mui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { RepetitionObject } from "../../features/Events/EventsTypes"; import { RepetitionObject } from "../../features/Events/EventsTypes";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Box, Typography, Link } from "@mui/material"; import { Box, Typography, Link } from "@linagora/twake-mui";
type InfoRowProps = { type InfoRowProps = {
icon: React.ReactNode; icon: React.ReactNode;
@@ -1,6 +1,6 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@linagora/twake-mui";
import { TextFieldProps } from "@mui/material/TextField"; import { TextFieldProps } from "@linagora/twake-mui";
import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { DatePickerFieldProps } from "@mui/x-date-pickers/DatePicker"; import { DatePickerFieldProps } from "@mui/x-date-pickers/DatePicker";
import { TimePicker } from "@mui/x-date-pickers/TimePicker"; import { TimePicker } from "@mui/x-date-pickers/TimePicker";
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useCallback } from "react"; import React, { useState, useRef, useEffect, useCallback } from "react";
import TextField from "@mui/material/TextField"; import { TextField } from "@linagora/twake-mui";
import { import {
PickerFieldAdapter, PickerFieldAdapter,
PickerValidationScope, PickerValidationScope,
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@linagora/twake-mui";
/** /**
* Helper component for field with label * Helper component for field with label
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import TextField from "@mui/material/TextField"; import { TextField } from "@linagora/twake-mui";
import { import {
PickerFieldAdapter, PickerFieldAdapter,
PickerValidationScope, PickerValidationScope,
+3 -5
View File
@@ -1,9 +1,7 @@
import CancelIcon from "@mui/icons-material/Cancel"; import CancelIcon from "@mui/icons-material/Cancel";
import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import Avatar from "@mui/material/Avatar"; import { Avatar, Badge, Box, Typography } from "@linagora/twake-mui";
import Badge from "@mui/material/Badge"; import { stringToGradient } from "../../../utils/avatarUtils";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { ThunkDispatch } from "@reduxjs/toolkit"; import { ThunkDispatch } from "@reduxjs/toolkit";
import { import {
emptyEventsCal, emptyEventsCal,
@@ -104,7 +102,7 @@ export function stringToColor(string: string) {
export function stringAvatar(name: string) { export function stringAvatar(name: string) {
return { return {
style: { backgroundColor: stringToColor(name) }, color: stringToGradient(name),
children: name[0], children: name[0],
}; };
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import Alert, { AlertColor } from "@mui/material/Alert"; import { Alert, Snackbar } from "@linagora/twake-mui";
import Snackbar from "@mui/material/Snackbar"; import type { AlertColor } from "@linagora/twake-mui";
export function SnackbarAlert({ export function SnackbarAlert({
open, open,
+2 -2
View File
@@ -14,8 +14,8 @@ import {
Stack, Stack,
TextField, TextField,
Typography, Typography,
} from "@mui/material"; } from "@linagora/twake-mui";
import { type AutocompleteRenderInputParams } from "@mui/material/Autocomplete"; import { type AutocompleteRenderInputParams } from "@linagora/twake-mui";
import { useRef, useState, useEffect } from "react"; import { useRef, useState, useEffect } from "react";
import HighlightOffIcon from "@mui/icons-material/HighlightOff"; import HighlightOffIcon from "@mui/icons-material/HighlightOff";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
+20 -24
View File
@@ -8,7 +8,7 @@ import SettingsIcon from "@mui/icons-material/Settings";
import LogoutIcon from "@mui/icons-material/Logout"; import LogoutIcon from "@mui/icons-material/Logout";
import "./Menubar.styl"; import "./Menubar.styl";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { stringToColor } from "../Event/utils/eventUtils"; import { stringToGradient } from "../../utils/avatarUtils";
import { import {
Avatar, Avatar,
IconButton, IconButton,
@@ -22,7 +22,7 @@ import {
Typography, Typography,
Box, Box,
Divider, Divider,
} from "@mui/material"; } from "@linagora/twake-mui";
import { push } from "redux-first-history"; import { push } from "redux-first-history";
import { CalendarApi } from "@fullcalendar/core"; import { CalendarApi } from "@fullcalendar/core";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
@@ -194,7 +194,7 @@ export function Menubar({
</div> </div>
<div className="menu-items"> <div className="menu-items">
<div className="current-date-time"> <div className="current-date-time">
<Typography variant="h6" component="div"> <Typography variant="h3" component="div">
{dateLabel} {dateLabel}
</Typography> </Typography>
</div> </div>
@@ -255,16 +255,14 @@ export function Menubar({
<div className="menu-items"> <div className="menu-items">
<IconButton onClick={handleUserMenuClick}> <IconButton onClick={handleUserMenuClick}>
<Avatar <Avatar
style={{ color={stringToGradient(
backgroundColor: stringToColor( user && user.family_name
user && user.family_name ? user.family_name
? user.family_name : user && user.email
: user && user.email ? user.email
? user.email : ""
: "" )}
), size="m"
}}
sizes="large"
aria-label={t("menubar.userProfile")} aria-label={t("menubar.userProfile")}
> >
{user?.name && user?.family_name {user?.name && user?.family_name
@@ -333,17 +331,15 @@ export function Menubar({
}} }}
> >
<Avatar <Avatar
style={{ color={stringToGradient(
backgroundColor: stringToColor( user && user.family_name
user && user.family_name ? user.family_name
? user.family_name : user && user.email
: user && user.email ? user.email
? user.email : ""
: "" )}
), size="m"
marginBottom: "8px", sx={{ marginBottom: "8px" }}
}}
sizes="large"
> >
{user?.name && user?.family_name {user?.name && user?.family_name
? `${user.name[0]}${user.family_name[0]}` ? `${user.name[0]}${user.family_name[0]}`
@@ -1,4 +1,4 @@
import { Autocomplete, TextField } from "@mui/material"; import { Autocomplete, TextField } from "@linagora/twake-mui";
import { PublicOutlined as TimezoneIcon } from "@mui/icons-material"; import { PublicOutlined as TimezoneIcon } from "@mui/icons-material";
import { useMemo } from "react"; import { useMemo } from "react";
@@ -1,4 +1,4 @@
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@linagora/twake-mui";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { Calendar } from "../../Calendars/CalendarTypes"; import { Calendar } from "../../Calendars/CalendarTypes";
@@ -1,4 +1,4 @@
import { Button } from "@mui/material"; import { Button } from "@linagora/twake-mui";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { useAppDispatch } from "../../../app/hooks"; import { useAppDispatch } from "../../../app/hooks";
+34 -15
View File
@@ -13,7 +13,7 @@ import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
import RepeatIcon from "@mui/icons-material/Repeat"; import RepeatIcon from "@mui/icons-material/Repeat";
import SubjectIcon from "@mui/icons-material/Subject"; import SubjectIcon from "@mui/icons-material/Subject";
import VideocamIcon from "@mui/icons-material/Videocam"; import VideocamIcon from "@mui/icons-material/Videocam";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@linagora/twake-mui";
import EventPopover from "./EventModal"; import EventPopover from "./EventModal";
import { import {
Button, Button,
@@ -23,8 +23,8 @@ import {
Menu, Menu,
MenuItem, MenuItem,
Tooltip, Tooltip,
} from "@mui/material"; } from "@linagora/twake-mui";
import AvatarGroup from "@mui/material/AvatarGroup"; import { AvatarGroup } from "@linagora/twake-mui";
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { CalendarName } from "../../components/Calendar/CalendarName"; import { CalendarName } from "../../components/Calendar/CalendarName";
@@ -49,7 +49,6 @@ import { userAttendee } from "../User/models/attendee";
import { browserDefaultTimeZone } from "../../utils/timezone"; import { browserDefaultTimeZone } from "../../utils/timezone";
import { AttendanceValidation } from "./AttendanceValidation/AttendanceValidation"; import { AttendanceValidation } from "./AttendanceValidation/AttendanceValidation";
import { Calendar } from "../Calendars/CalendarTypes"; import { Calendar } from "../Calendars/CalendarTypes";
import { userData } from "../User/userDataTypes";
import { createEventContext } from "./createEventContext"; import { createEventContext } from "./createEventContext";
export default function EventPreviewModal({ export default function EventPreviewModal({
@@ -369,7 +368,9 @@ export default function EventPreviewModal({
`${mailSpaUrl}/mailto/?uri=mailto:${event.attendee `${mailSpaUrl}/mailto/?uri=mailto:${event.attendee
.map((a) => a.cal_address) .map((a) => a.cal_address)
.filter((mail) => mail !== user.email) .filter((mail) => mail !== user.email)
.join(",")}?subject=${event.title}` .join(
","
)}?subject=${encodeURIComponent(event.title ?? "")}`
) )
} }
> >
@@ -402,18 +403,28 @@ export default function EventPreviewModal({
eventId eventId
) )
); );
setOpenEditModePopup("edit"); setOpenEditModePopup("delete");
} else { } else {
onClose({}, "backdropClick"); onClose({}, "backdropClick");
await dispatch( try {
deleteEventAsync({ const result = await dispatch(
calId, deleteEventAsync({
eventId, calId,
eventURL: event.URL, eventId,
}) eventURL: event.URL,
); })
);
// For compatibility with tests that may not mock unwrap
if (result && typeof result.unwrap === "function") {
await result.unwrap();
}
updateTempList();
} catch (error) {
console.error("Failed to delete event:", error);
}
} }
updateTempList();
}} }}
> >
{t("eventPreview.deleteEvent")} {t("eventPreview.deleteEvent")}
@@ -616,7 +627,15 @@ export default function EventPreviewModal({
} }
text={t("eventPreview.alarmText", { text={t("eventPreview.alarmText", {
trigger: t(`event.form.notifications.${event.alarm.trigger}`), trigger: t(`event.form.notifications.${event.alarm.trigger}`),
action: t(`event.form.notifications.${event.alarm.action}`), action: (() => {
if (!event.alarm.action) return "";
const translationKey = `event.form.notifications.${event.alarm.action}`;
const translated = t(translationKey);
// If translation returns the key itself, it means translation not found, use raw value
return translated === translationKey
? event.alarm.action
: translated;
})(),
})} })}
style={{ style={{
fontSize: "16px", fontSize: "16px",
+1 -1
View File
@@ -1,5 +1,5 @@
import { CalendarApi, DateSelectArg } from "@fullcalendar/core"; import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import { Box, Button } from "@mui/material"; import { Box, Button } from "@linagora/twake-mui";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import React, { import React, {
useEffect, useEffect,
+1 -1
View File
@@ -1,4 +1,4 @@
import { Box, Button } from "@mui/material"; import { Box, Button } from "@linagora/twake-mui";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import { useEffect, useState, useMemo, useCallback, useRef } from "react"; import { useEffect, useState, useMemo, useCallback, useRef } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
+8 -2
View File
@@ -2,8 +2,14 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import RepeatIcon from "@mui/icons-material/Repeat"; import RepeatIcon from "@mui/icons-material/Repeat";
import SquareRoundedIcon from "@mui/icons-material/SquareRounded"; import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
import VideocamIcon from "@mui/icons-material/Videocam"; import VideocamIcon from "@mui/icons-material/Videocam";
import { Box, Button, IconButton, Stack, Typography } from "@mui/material"; import {
import CircularProgress from "@mui/material/CircularProgress"; Box,
Button,
IconButton,
Stack,
Typography,
CircularProgress,
} from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { useState } from "react"; import { useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
+2 -6
View File
@@ -15,7 +15,7 @@ import {
Snackbar, Snackbar,
Switch, Switch,
FormControlLabel, FormControlLabel,
} from "@mui/material"; } from "@linagora/twake-mui";
import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
// import SyncIcon from "@mui/icons-material/Sync"; // import SyncIcon from "@mui/icons-material/Sync";
@@ -418,11 +418,7 @@ export default function SettingsPage() {
)} )}
{activeSettingsSubTab === "notifications" && ( {activeSettingsSubTab === "notifications" && (
<Box className="settings-tab-content"> <Box className="settings-tab-content">
<Typography <Typography variant="h6" sx={{ mb: 3 }}>
variant="body2"
color="text.secondary"
sx={{ mb: 3 }}
>
{t("settings.notifications.deliveryMethod") || {t("settings.notifications.deliveryMethod") ||
"Delivery method"} "Delivery method"}
</Typography> </Typography>
+4 -1
View File
@@ -152,7 +152,9 @@
"-PT1D": "1 day before", "-PT1D": "1 day before",
"-PT2D": "2 days before", "-PT2D": "2 days before",
"-PT1W": "1 week before", "-PT1W": "1 week before",
"EMAIL": "email" "EMAIL": "email",
"DISPLAY": "display",
"AUDIO": "audio"
}, },
"showMeAs": "Show me as", "showMeAs": "Show me as",
"free": "Free", "free": "Free",
@@ -188,6 +190,7 @@
"editModeDialog": { "editModeDialog": {
"updateRecurrentEvent": "Update the recurrent event", "updateRecurrentEvent": "Update the recurrent event",
"updateParticipationStatus": "Update the participation status", "updateParticipationStatus": "Update the participation status",
"deleteRecurrentEvent": "Delete the recurrent event",
"thisEvent": "This event", "thisEvent": "This event",
"allEvents": "All the events" "allEvents": "All the events"
}, },
+3 -1
View File
@@ -154,7 +154,9 @@
"-PT1D": "1 jour avant", "-PT1D": "1 jour avant",
"-PT2D": "2 jours avant", "-PT2D": "2 jours avant",
"-PT1W": "1 semaine avant", "-PT1W": "1 semaine avant",
"EMAIL": "e-mail" "EMAIL": "e-mail",
"DISPLAY": "affichage",
"AUDIO": "audio"
}, },
"showMeAs": "M'afficher comme", "showMeAs": "M'afficher comme",
"free": "Libre", "free": "Libre",
+3 -1
View File
@@ -154,7 +154,9 @@
"-PT1D": "За 1 день", "-PT1D": "За 1 день",
"-PT2D": "За 2 дня", "-PT2D": "За 2 дня",
"-PT1W": "За 1 неделю", "-PT1W": "За 1 неделю",
"EMAIL": "Email" "EMAIL": "Email",
"DISPLAY": "Отображение",
"AUDIO": "Звук"
}, },
"showMeAs": "Показать как", "showMeAs": "Показать как",
"free": "Свободен", "free": "Свободен",
+3 -1
View File
@@ -152,7 +152,9 @@
"-PT1D": "1 ngày trước", "-PT1D": "1 ngày trước",
"-PT2D": "2 ngày trước", "-PT2D": "2 ngày trước",
"-PT1W": "1 tuần trước", "-PT1W": "1 tuần trước",
"EMAIL": "email" "EMAIL": "email",
"DISPLAY": "hiển thị",
"AUDIO": "âm thanh"
}, },
"showMeAs": "Hiển thị tôi là", "showMeAs": "Hiển thị tôi là",
"free": "Rảnh", "free": "Rảnh",
-19
View File
@@ -1,19 +0,0 @@
import React from "react";
import { ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material";
import { customTheme } from "./theme";
interface CustomThemeProviderProps {
children: React.ReactNode;
}
export function CustomThemeProvider({ children }: CustomThemeProviderProps) {
return (
<ThemeProvider theme={customTheme}>
<CssBaseline />
{children}
</ThemeProvider>
);
}
export { customTheme };
-38
View File
@@ -1,38 +0,0 @@
import { createTheme } from "@mui/material/styles";
// Custom theme for Twake Calendar
export const customTheme = createTheme({
palette: {
text: {
primary: "#000000",
secondary: "#717D96",
secondaryContainer: "#243B55",
},
},
typography: {
caption: {
fontSize: "0.75rem",
fontWeight: 400,
lineHeight: 1.4,
color: "#8C9CAF", // Custom secondary text color
},
},
components: {
// Custom Typography component
MuiTypography: {
styleOverrides: {
// Custom variant for event info text
caption: {
fontSize: "13px",
color: "#717D96",
},
},
},
// Custom Button component
// Custom Dialog component
// Custom DialogActions component
},
});
// Export theme type for TypeScript
export type CustomTheme = typeof customTheme;
+11
View File
@@ -0,0 +1,11 @@
import { nameToColor } from "@linagora/twake-mui";
/**
* Generate a gradient color from a string
* @param str - String to generate color from
* @returns Color name for gradient
*/
export function stringToGradient(str: string): string | undefined {
if (!str) return undefined;
return nameToColor(str);
}