init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
import { Component } from 'react';
/**
* A React Component for adding a meeting URL to an existing calendar meeting.
*
* @augments Component
*/
class AddMeetingUrlButton extends Component<void> {
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
// Not yet implemented.
return null;
}
}
export default AddMeetingUrlButton;

View File

@@ -0,0 +1,101 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createCalendarClickedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IStore } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconPlus } from '../../base/icons/svg';
import Tooltip from '../../base/tooltip/components/Tooltip';
import { updateCalendarEvent } from '../actions.web';
/**
* The type of the React {@code Component} props of {@link AddMeetingUrlButton}.
*/
interface IProps extends WithTranslation {
/**
* The calendar ID associated with the calendar event.
*/
calendarId: string;
/**
* Invoked to add a meeting URL to a calendar event.
*/
dispatch: IStore['dispatch'];
/**
* The ID of the calendar event that will have a meeting URL added on click.
*/
eventId: string;
}
/**
* A React Component for adding a meeting URL to an existing calendar event.
*
* @augments Component
*/
class AddMeetingUrlButton extends Component<IProps> {
/**
* Initializes a new {@code AddMeetingUrlButton} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onClick = this._onClick.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
return (
<Tooltip content = { this.props.t('calendarSync.addMeetingURL') }>
<div
className = 'button add-button'
onClick = { this._onClick }
onKeyPress = { this._onKeyPress }
role = 'button'>
<Icon src = { IconPlus } />
</div>
</Tooltip>
);
}
/**
* Dispatches an action to adding a meeting URL to a calendar event.
*
* @returns {void}
*/
_onClick() {
const { calendarId, dispatch, eventId } = this.props;
sendAnalytics(createCalendarClickedEvent('add.url'));
dispatch(updateCalendarEvent(eventId, calendarId));
}
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onClick();
}
}
}
export default translate(connect()(AddMeetingUrlButton));

View File

@@ -0,0 +1,143 @@
import React from 'react';
import { WithTranslation } from 'react-i18next';
import {
Text,
TouchableOpacity,
View,
ViewStyle
} from 'react-native';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import AbstractPage from '../../base/react/components/AbstractPage';
import { openSettings } from '../../mobile/permissions/functions';
import { refreshCalendar } from '../actions.native';
import CalendarListContent from './CalendarListContent.native';
import styles from './styles';
/**
* The type of the React {@code Component} props of {@link CalendarList}.
*/
interface IProps extends WithTranslation {
/**
* The current state of the calendar access permission.
*/
_authorization?: string;
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean;
}
/**
* Component to display a list of events from the (mobile) user's calendar.
*/
class CalendarList extends AbstractPage<IProps> {
/**
* Initializes a new {@code CalendarList} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._getRenderListEmptyComponent
= this._getRenderListEmptyComponent.bind(this);
}
/**
* Public API method for {@code Component}s rendered in
* {@link AbstractPagedList}. When invoked, refreshes the calendar entries
* in the app.
*
* @param {Function} dispatch - The Redux dispatch function.
* @param {boolean} isInteractive - If true this refresh was caused by
* direct user interaction, false otherwise.
* @public
* @returns {void}
*/
static refresh(dispatch: IStore['dispatch'], isInteractive: boolean) {
dispatch(refreshCalendar(false, isInteractive));
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
const { disabled } = this.props;
return (
CalendarListContent
? <View
style = {
(disabled
? styles.calendarSyncDisabled
: styles.calendarSync) as ViewStyle }>
<CalendarListContent
disabled = { disabled }
listEmptyComponent
= { this._getRenderListEmptyComponent() } />
</View>
: null
);
}
/**
* Returns a list empty component if a custom one has to be rendered instead
* of the default one in the {@link NavigateSectionList}.
*
* @private
* @returns {?React$Component}
*/
_getRenderListEmptyComponent() {
const { _authorization, t } = this.props;
// If we don't provide a list specific renderListEmptyComponent, then
// the default empty component of the NavigateSectionList will be
// rendered, which (atm) is a simple "Pull to refresh" message.
if (_authorization !== 'denied') {
return <></>;
}
return (
<View style = { styles.noPermissionMessageView as ViewStyle }>
<Text style = { styles.noPermissionMessageText as ViewStyle }>
{ t('calendarSync.permissionMessage') }
</Text>
<TouchableOpacity
onPress = { openSettings }
style = { styles.noPermissionMessageButton as ViewStyle } >
<Text style = { styles.noPermissionMessageButtonText as ViewStyle }>
{ t('calendarSync.permissionButton') }
</Text>
</TouchableOpacity>
</View>
);
}
}
/**
* Maps redux state to component props.
*
* @param {Object} state - The redux state.
* @returns {{
* _authorization: ?string,
* _eventList: Array<Object>
* }}
*/
function _mapStateToProps(state: IReduxState) {
const { authorization } = state['features/calendar-sync'];
return {
_authorization: authorization
};
}
export default translate(connect(_mapStateToProps)(CalendarList));

View File

@@ -0,0 +1,269 @@
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createCalendarClickedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { IReduxState, IStore } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconCalendar } from '../../base/icons/svg';
import AbstractPage from '../../base/react/components/AbstractPage';
import Spinner from '../../base/ui/components/web/Spinner';
import { openSettingsDialog } from '../../settings/actions.web';
import { SETTINGS_TABS } from '../../settings/constants';
import { refreshCalendar } from '../actions.web';
import { ERRORS } from '../constants';
import CalendarListContent from './CalendarListContent.web';
/**
* The type of the React {@code Component} props of {@link CalendarList}.
*/
interface IProps extends WithTranslation {
/**
* The error object containing details about any error that has occurred
* while interacting with calendar integration.
*/
_calendarError?: { error: string; };
/**
* Whether or not a calendar may be connected for fetching calendar events.
*/
_hasIntegrationSelected: boolean;
/**
* Whether or not events have been fetched from a calendar.
*/
_hasLoadedEvents: boolean;
/**
* Indicates if the list is disabled or not.
*/
disabled?: boolean;
/**
* The Redux dispatch function.
*/
dispatch: IStore['dispatch'];
}
/**
* Component to display a list of events from the user's calendar.
*/
class CalendarList extends AbstractPage<IProps> {
/**
* Initializes a new {@code CalendarList} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._getRenderListEmptyComponent
= this._getRenderListEmptyComponent.bind(this);
this._onOpenSettings = this._onOpenSettings.bind(this);
this._onKeyPressOpenSettings = this._onKeyPressOpenSettings.bind(this);
this._onRefreshEvents = this._onRefreshEvents.bind(this);
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
const { disabled } = this.props;
return (
CalendarListContent
? <CalendarListContent
disabled = { Boolean(disabled) }
listEmptyComponent
= { this._getRenderListEmptyComponent() } />
: null
);
}
/**
* Returns a component for showing the error message related to calendar
* sync.
*
* @private
* @returns {React$Component}
*/
_getErrorMessage() {
const { _calendarError = { error: undefined }, t } = this.props;
let errorMessageKey = 'calendarSync.error.generic';
let showRefreshButton = true;
let showSettingsButton = true;
if (_calendarError.error === ERRORS.GOOGLE_APP_MISCONFIGURED) {
errorMessageKey = 'calendarSync.error.appConfiguration';
showRefreshButton = false;
showSettingsButton = false;
} else if (_calendarError.error === ERRORS.AUTH_FAILED) {
errorMessageKey = 'calendarSync.error.notSignedIn';
showRefreshButton = false;
}
return (
<div className = 'meetings-list-empty'>
<p className = 'description'>
{ t(errorMessageKey) }
</p>
<div className = 'calendar-action-buttons'>
{ showSettingsButton
&& <div
className = 'button'
onClick = { this._onOpenSettings }>
{ t('calendarSync.permissionButton') }
</div>
}
{ showRefreshButton
&& <div
className = 'button'
onClick = { this._onRefreshEvents }>
{ t('calendarSync.refresh') }
</div>
}
</div>
</div>
);
}
/**
* Returns a list empty component if a custom one has to be rendered instead
* of the default one in the {@link NavigateSectionList}.
*
* @private
* @returns {React$Component}
*/
_getRenderListEmptyComponent() {
const {
_calendarError,
_hasIntegrationSelected,
_hasLoadedEvents,
t
} = this.props;
if (_calendarError) {
return this._getErrorMessage();
} else if (_hasIntegrationSelected && _hasLoadedEvents) {
return (
<div className = 'meetings-list-empty'>
<p className = 'description'>
{ t('calendarSync.noEvents') }
</p>
<div
className = 'button'
onClick = { this._onRefreshEvents }>
{ t('calendarSync.refresh') }
</div>
</div>
);
} else if (_hasIntegrationSelected && !_hasLoadedEvents) {
return (
<div className = 'meetings-list-empty'>
<Spinner />
</div>
);
}
return (
<div className = 'meetings-list-empty'>
<div className = 'meetings-list-empty-image'>
<img
alt = { t('welcomepage.logo.calendar') }
src = './images/calendar.svg' />
</div>
<div className = 'description'>
{ t('welcomepage.connectCalendarText', {
app: interfaceConfig.APP_NAME,
provider: interfaceConfig.PROVIDER_NAME
}) }
</div>
<div
className = 'meetings-list-empty-button'
onClick = { this._onOpenSettings }
onKeyPress = { this._onKeyPressOpenSettings }
role = 'button'
tabIndex = { 0 }>
<Icon
className = 'meetings-list-empty-icon'
src = { IconCalendar } />
<span>{ t('welcomepage.connectCalendarButton') }</span>
</div>
</div>
);
}
/**
* Opens {@code SettingsDialog}.
*
* @private
* @returns {void}
*/
_onOpenSettings() {
sendAnalytics(createCalendarClickedEvent('connect'));
this.props.dispatch(openSettingsDialog(SETTINGS_TABS.CALENDAR));
}
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onKeyPressOpenSettings(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onOpenSettings();
}
}
/**
* Gets an updated list of calendar events.
*
* @private
* @returns {void}
*/
_onRefreshEvents() {
this.props.dispatch(refreshCalendar(true));
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code CalendarList} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _calendarError: Object,
* _hasIntegrationSelected: boolean,
* _hasLoadedEvents: boolean
* }}
*/
function _mapStateToProps(state: IReduxState) {
const {
error,
events,
integrationType,
isLoadingEvents
} = state['features/calendar-sync'];
return {
_calendarError: error,
_hasIntegrationSelected: Boolean(integrationType),
_hasLoadedEvents: Boolean(events) || !isLoadingEvents
};
}
export default translate(connect(_mapStateToProps)(CalendarList));

View File

@@ -0,0 +1,249 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createCalendarClickedEvent, createCalendarSelectedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { appNavigate } from '../../app/actions.native';
import { IReduxState, IStore } from '../../app/types';
import { getLocalizedDateFormatter } from '../../base/i18n/dateUtil';
import { translate } from '../../base/i18n/functions';
import NavigateSectionList from '../../base/react/components/native/NavigateSectionList';
import { openUpdateCalendarEventDialog, refreshCalendar } from '../actions.native';
/**
* The type of the React {@code Component} props of
* {@link CalendarListContent}.
*/
interface IProps extends WithTranslation {
/**
* The calendar event list.
*/
_eventList: Array<any>;
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean;
/**
* The Redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
*
*/
listEmptyComponent: React.ReactElement<any>;
}
/**
* Component to display a list of events from a connected calendar.
*/
class CalendarListContent extends Component<IProps> {
/**
* Default values for the component's props.
*/
static defaultProps = {
_eventList: []
};
/**
* Initializes a new {@code CalendarListContent} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onPress = this._onPress.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this._onSecondaryAction = this._onSecondaryAction.bind(this);
this._toDateString = this._toDateString.bind(this);
this._toDisplayableItem = this._toDisplayableItem.bind(this);
this._toDisplayableList = this._toDisplayableList.bind(this);
this._toTimeString = this._toTimeString.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after this component is mounted.
*
* @inheritdoc
* @returns {void}
*/
override componentDidMount() {
sendAnalytics(createCalendarSelectedEvent());
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
const { disabled, listEmptyComponent } = this.props;
return (
<NavigateSectionList
disabled = { disabled }
onPress = { this._onPress }
onRefresh = { this._onRefresh }
onSecondaryAction = { this._onSecondaryAction }
renderListEmptyComponent
= { listEmptyComponent }
sections = { this._toDisplayableList() } />
);
}
/**
* Handles the list's navigate action.
*
* @private
* @param {string} url - The url string to navigate to.
* @param {string} analyticsEventName - Тhe name of the analytics event
* associated with this action.
* @returns {void}
*/
_onPress(url: string, analyticsEventName = 'meeting.tile') {
sendAnalytics(createCalendarClickedEvent(analyticsEventName));
this.props.dispatch(appNavigate(url));
}
/**
* Callback to execute when the list is doing a pull-to-refresh.
*
* @private
* @returns {void}
*/
_onRefresh() {
this.props.dispatch(refreshCalendar(true));
}
/**
* Handles the list's secondary action.
*
* @private
* @param {string} id - The ID of the item on which the secondary action was
* performed.
* @returns {void}
*/
_onSecondaryAction(id: string) {
this.props.dispatch(openUpdateCalendarEventDialog(id));
}
/**
* Generates a date string for a given event.
*
* @param {Object} event - The event.
* @private
* @returns {string}
*/
_toDateString(event: any) {
const startDateTime
= getLocalizedDateFormatter(event.startDate).format('MMM Do, YYYY');
return `${startDateTime}`;
}
/**
* Creates a displayable object from an event.
*
* @param {Object} event - The calendar event.
* @private
* @returns {Object}
*/
_toDisplayableItem(event: any) {
return {
id: event.id,
key: `${event.id}-${event.startDate}`,
lines: [
event.url,
this._toTimeString(event)
],
title: event.title,
url: event.url
};
}
/**
* Transforms the event list to a displayable list with sections.
*
* @private
* @returns {Array<Object>}
*/
_toDisplayableList() {
const { _eventList, t } = this.props;
const now = new Date();
const { createSection } = NavigateSectionList;
const TODAY_SECTION = 'today';
const sectionMap = new Map();
for (const event of _eventList) {
const displayableEvent = this._toDisplayableItem(event);
const startDate = new Date(event.startDate).getDate();
if (startDate === now.getDate()) {
let todaySection = sectionMap.get(TODAY_SECTION);
if (!todaySection) {
todaySection
= createSection(t('calendarSync.today'), TODAY_SECTION);
sectionMap.set(TODAY_SECTION, todaySection);
}
todaySection.data.push(displayableEvent);
} else if (sectionMap.has(startDate)) {
const section = sectionMap.get(startDate);
if (section) {
section.data.push(displayableEvent);
}
} else {
const newSection
= createSection(this._toDateString(event), startDate);
sectionMap.set(startDate, newSection);
newSection.data.push(displayableEvent);
}
}
return Array.from(sectionMap.values());
}
/**
* Generates a time (interval) string for a given event.
*
* @param {Object} event - The event.
* @private
* @returns {string}
*/
_toTimeString(event: any) {
const startDateTime
= getLocalizedDateFormatter(event.startDate).format('lll');
const endTime
= getLocalizedDateFormatter(event.endDate).format('LT');
return `${startDateTime} - ${endTime}`;
}
}
/**
* Maps redux state to component props.
*
* @param {Object} state - The redux state.
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
return {
_eventList: state['features/calendar-sync'].events
};
}
export default translate(connect(_mapStateToProps)(CalendarListContent));

View File

@@ -0,0 +1,163 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createCalendarClickedEvent, createCalendarSelectedEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions';
import { appNavigate } from '../../app/actions.web';
import { IReduxState, IStore } from '../../app/types';
import MeetingsList from '../../base/react/components/web/MeetingsList';
import AddMeetingUrlButton from './AddMeetingUrlButton.web';
import JoinButton from './JoinButton.web';
/**
* The type of the React {@code Component} props of
* {@link CalendarListContent}.
*/
interface IProps {
/**
* The calendar event list.
*/
_eventList: Array<Object>;
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean;
/**
* The Redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
*
*/
listEmptyComponent: React.ReactNode;
}
/**
* Component to display a list of events from a connected calendar.
*/
class CalendarListContent extends Component<IProps> {
/**
* Default values for the component's props.
*/
static defaultProps = {
_eventList: []
};
/**
* Initializes a new {@code CalendarListContent} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onJoinPress = this._onJoinPress.bind(this);
this._onPress = this._onPress.bind(this);
this._toDisplayableItem = this._toDisplayableItem.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after this component is mounted.
*
* @inheritdoc
* @returns {void}
*/
override componentDidMount() {
sendAnalytics(createCalendarSelectedEvent());
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
const { disabled, listEmptyComponent } = this.props;
const { _eventList = [] } = this.props;
const meetings = _eventList.map(this._toDisplayableItem);
return (
<MeetingsList
disabled = { disabled }
listEmptyComponent = { listEmptyComponent }
meetings = { meetings }
onPress = { this._onPress } />
);
}
/**
* Handles the list's navigate action.
*
* @private
* @param {Object} event - The click event.
* @param {string} url - The url string to navigate to.
* @returns {void}
*/
_onJoinPress(event: React.KeyboardEvent, url: string) {
event.stopPropagation();
this._onPress(url, 'meeting.join');
}
/**
* Handles the list's navigate action.
*
* @private
* @param {string} url - The url string to navigate to.
* @param {string} analyticsEventName - Тhe name of the analytics event
* associated with this action.
* @returns {void}
*/
_onPress(url: string, analyticsEventName = 'meeting.tile') {
sendAnalytics(createCalendarClickedEvent(analyticsEventName));
this.props.dispatch(appNavigate(url));
}
/**
* Creates a displayable object from an event.
*
* @param {Object} event - The calendar event.
* @private
* @returns {Object}
*/
_toDisplayableItem(event: any) {
return {
elementAfter: event.url
? <JoinButton
onPress = { this._onJoinPress }
url = { event.url } />
: (<AddMeetingUrlButton
calendarId = { event.calendarId }
eventId = { event.id } />),
date: event.startDate,
time: [ event.startDate, event.endDate ],
description: event.url,
title: event.title,
url: event.url
};
}
}
/**
* Maps redux state to component props.
*
* @param {Object} state - The redux state.
* @returns {{
* _eventList: Array<Object>
* }}
*/
function _mapStateToProps(state: IReduxState) {
return {
_eventList: state['features/calendar-sync'].events
};
}
export default connect(_mapStateToProps)(CalendarListContent);

View File

@@ -0,0 +1,21 @@
import { Component } from 'react';
/**
* A React Component for joining an existing calendar meeting.
*
* @augments Component
*/
class JoinButton extends Component<void> {
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
// Not yet implemented.
return null;
}
}
export default JoinButton;

View File

@@ -0,0 +1,96 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconPlus } from '../../base/icons/svg';
import Tooltip from '../../base/tooltip/components/Tooltip';
/**
* The type of the React {@code Component} props of {@link JoinButton}.
*/
interface IProps extends WithTranslation {
/**
* The function called when the button is pressed.
*/
onPress: Function;
/**
* The meeting URL associated with the {@link JoinButton} instance.
*/
url: string;
}
/**
* A React Component for joining an existing calendar meeting.
*
* @augments Component
*/
class JoinButton extends Component<IProps> {
/**
* Initializes a new {@code JoinButton} instance.
*
* @param {*} props - The read-only properties with which the new instance
* is to be initialized.
*/
constructor(props: IProps) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onClick = this._onClick.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
override render() {
const { t } = this.props;
return (
<Tooltip
content = { t('calendarSync.joinTooltip') }>
<div
className = 'button join-button'
onClick = { this._onClick }
onKeyPress = { this._onKeyPress }
role = 'button'>
<Icon
size = '14'
src = { IconPlus } />
</div>
</Tooltip>
);
}
/**
* Callback invoked when the component is clicked.
*
* @param {Object} event - The DOM click event.
* @private
* @returns {void}
*/
_onClick(event?: React.MouseEvent) {
this.props.onPress(event, this.props.url);
}
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onKeyPress(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
this._onClick();
}
}
}
export default translate(JoinButton);

View File

@@ -0,0 +1,52 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../base/i18n/functions';
/**
* The type of the React {@code Component} props of
* {@link MicrosoftSignInButton}.
*/
interface IProps extends WithTranslation {
/**
* The callback to invoke when {@code MicrosoftSignInButton} is clicked.
*/
onClick: (e?: React.MouseEvent) => void;
/**
* The text to display within {@code MicrosoftSignInButton}.
*/
text: string;
}
/**
* A React Component showing a button to sign in with Microsoft.
*
* @augments Component
*/
class MicrosoftSignInButton extends Component<IProps> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
override render() {
return (
<div
className = 'microsoft-sign-in'
onClick = { this.props.onClick }>
<img
alt = { this.props.t('welcomepage.logo.microsoftLogo') }
className = 'microsoft-logo'
src = 'images/microsoftLogo.svg' />
<div className = 'microsoft-cta'>
{ this.props.text }
</div>
</div>
);
}
}
export default translate(MicrosoftSignInButton);

View File

@@ -0,0 +1,65 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IStore } from '../../app/types';
import ConfirmDialog from '../../base/dialog/components/native/ConfirmDialog';
import { translate } from '../../base/i18n/functions';
import { updateCalendarEvent } from '../actions';
interface IProps extends WithTranslation {
/**
* The Redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
* The ID of the event to be updated.
*/
eventId: string;
}
/**
* Component for the add Jitsi link confirm dialog.
*/
class UpdateCalendarEventDialog extends Component<IProps> {
/**
* Initializes a new {@code UpdateCalendarEventDialog} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
override render() {
return (
<ConfirmDialog
descriptionKey = 'calendarSync.confirmAddLink'
onSubmit = { this._onSubmit } />
);
}
/**
* Callback for the confirm button.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
this.props.dispatch(updateCalendarEvent(this.props.eventId));
return true;
}
}
export default translate(connect()(UpdateCalendarEventDialog));

View File

@@ -0,0 +1,182 @@
import { ColorPalette } from '../../base/styles/components/styles/ColorPalette';
import { createStyleSheet } from '../../base/styles/functions.any';
import BaseTheme from '../../base/ui/components/BaseTheme';
const NOTIFICATION_SIZE = 55;
/**
* The styles of the React {@code Component}s of the feature meeting-list i.e.
* {@code CalendarList}.
*/
export default createStyleSheet({
/**
* Button style of the open settings button.
*/
noPermissionMessageButton: {
backgroundColor: ColorPalette.blue,
borderColor: ColorPalette.blue,
borderRadius: 4,
borderWidth: 1,
height: 30,
justifyContent: 'center',
margin: 15,
paddingHorizontal: 20
},
/**
* Text style of the open settings button.
*/
noPermissionMessageButtonText: {
color: ColorPalette.white
},
/**
* Text style of the no permission message.
*/
noPermissionMessageText: {
backgroundColor: 'transparent',
color: 'rgba(255, 255, 255, 0.6)',
textAlign: 'center'
},
/**
* Top level view of the no permission message.
*/
noPermissionMessageView: {
alignItems: 'center',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 20
},
/**
* The top level container of the notification.
*/
notificationContainer: {
alignSelf: 'flex-start',
flexDirection: 'row',
justifyContent: 'center',
overflow: 'hidden',
position: 'absolute'
},
/**
* Additional style for the container when the notification is displayed
* on the side (narrow view).
*/
notificationContainerSide: {
top: 100
},
/**
* Additional style for the container when the notification is displayed
* on the top (wide view).
*/
notificationContainerTop: {
justifyContent: 'center',
left: 0,
right: 0,
top: 0
},
/**
* The top level container of the notification.
*/
notificationContent: {
alignSelf: 'flex-start',
flexDirection: 'row',
height: NOTIFICATION_SIZE,
justifyContent: 'center',
paddingHorizontal: 10
},
/**
* Color for upcoming meeting notification.
*/
notificationContentNext: {
backgroundColor: '#eeb231'
},
/**
* Color for already ongoing meeting notifications.
*/
notificationContentPast: {
backgroundColor: 'red'
},
/**
* Additional style for the content when the notification is displayed
* on the side (narrow view).
*/
notificationContentSide: {
borderBottomRightRadius: NOTIFICATION_SIZE,
borderTopRightRadius: NOTIFICATION_SIZE
},
/**
* Additional style for the content when the notification is displayed
* on the top (wide view).
*/
notificationContentTop: {
borderBottomLeftRadius: NOTIFICATION_SIZE / 2,
borderBottomRightRadius: NOTIFICATION_SIZE / 2,
paddingHorizontal: 20
},
/**
* The icon of the notification.
*/
notificationIcon: {
color: 'white',
fontSize: '1.5rem'
},
/**
* The container that contains the icon.
*/
notificationIconContainer: {
alignItems: 'center',
flexDirection: 'row',
height: NOTIFICATION_SIZE,
justifyContent: 'center'
},
/**
* A single line of text of the notification.
*/
notificationText: {
color: 'white',
fontSize: '0.875rem'
},
/**
* The container for all the lines if the notification.
*/
notificationTextContainer: {
flexDirection: 'column',
height: NOTIFICATION_SIZE,
justifyContent: 'center'
},
/**
* The touchable component.
*/
touchableView: {
flexDirection: 'row'
},
calendarSync: {
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1,
overflow: 'hidden'
},
calendarSyncDisabled: {
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1,
opacity: 0.8,
overflow: 'hidden'
}
});