init commit,
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
|
||||
interface AboutPopoverProps {
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
const AboutPopover: React.FC<AboutPopoverProps> = ({ dismiss }) => {
|
||||
const close = (url: string) => {
|
||||
window.open(url, '_blank');
|
||||
dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonList>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/docs')}>
|
||||
<IonLabel>Learn Ionic</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem
|
||||
button
|
||||
onClick={() => close('https://ionicframework.com/docs/react')}
|
||||
>
|
||||
<IonLabel>Documentation</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem
|
||||
button
|
||||
onClick={() => close('https://showcase.ionicframework.com')}
|
||||
>
|
||||
<IonLabel>Showcase</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem
|
||||
button
|
||||
onClick={() => close('https://github.com/ionic-team/ionic-framework')}
|
||||
>
|
||||
<IonLabel>GitHub Repo</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={dismiss}>
|
||||
<IonLabel>Support</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutPopover;
|
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
interface StateProps {
|
||||
hasSeenTutorial: boolean;
|
||||
}
|
||||
|
||||
const HomeOrTutorial: React.FC<StateProps> = ({ hasSeenTutorial }) => {
|
||||
return hasSeenTutorial ? (
|
||||
<Redirect to="/tabs/schedule" />
|
||||
) : (
|
||||
<Redirect to="/tutorial" />
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
hasSeenTutorial: state.user.hasSeenTutorial,
|
||||
}),
|
||||
component: HomeOrTutorial,
|
||||
});
|
@@ -0,0 +1,89 @@
|
||||
ion-menu ion-content {
|
||||
--padding-top: 20px;
|
||||
--padding-bottom: 20px;
|
||||
|
||||
--background: var(--ion-item-background, var(--ion-background-color, #fff));
|
||||
}
|
||||
|
||||
/* Remove background transitions for switching themes */
|
||||
ion-menu ion-item {
|
||||
--transition: none;
|
||||
}
|
||||
|
||||
ion-item.selected {
|
||||
--color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
/*
|
||||
* Material Design Menu
|
||||
*/
|
||||
ion-menu.md ion-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
ion-menu.md ion-list-header {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: min(0.875rem, 32px);
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item {
|
||||
--padding-start: 18px;
|
||||
|
||||
margin-right: 10px;
|
||||
|
||||
border-radius: 0 50px 50px 0;
|
||||
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item.selected {
|
||||
--background: rgba(var(--ion-color-primary-rgb), 0.14);
|
||||
}
|
||||
|
||||
ion-menu.md ion-item.selected ion-icon {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
ion-menu.md ion-list-header,
|
||||
ion-menu.md ion-item ion-icon {
|
||||
color: var(--ion-color-step-650, #5f6368);
|
||||
}
|
||||
|
||||
ion-menu.md ion-list:not(:last-of-type) {
|
||||
border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* iOS Menu
|
||||
*/
|
||||
ion-menu.ios ion-list-header {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
margin-bottom: 8px;
|
||||
font-size: clamp(22px, 1.375rem, 40px);
|
||||
}
|
||||
|
||||
ion-menu.ios ion-list {
|
||||
padding: 20px 0 0;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item {
|
||||
--padding-start: 16px;
|
||||
--min-height: 50px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item ion-icon {
|
||||
font-size: 24px;
|
||||
color: #73849a;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item.selected ion-icon {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
154
99_references/ionic-react-conference-app/src/components/Menu.tsx
Normal file
154
99_references/ionic-react-conference-app/src/components/Menu.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps, withRouter, useLocation } from 'react-router';
|
||||
|
||||
import {
|
||||
IonContent,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonMenu,
|
||||
IonMenuToggle,
|
||||
IonToggle,
|
||||
} from '@ionic/react';
|
||||
import {
|
||||
calendarOutline,
|
||||
hammer,
|
||||
moonOutline,
|
||||
help,
|
||||
informationCircleOutline,
|
||||
logIn,
|
||||
logOut,
|
||||
mapOutline,
|
||||
peopleOutline,
|
||||
person,
|
||||
personAdd,
|
||||
} from 'ionicons/icons';
|
||||
|
||||
import { connect } from '../data/connect';
|
||||
import { setDarkMode } from '../data/user/user.actions';
|
||||
|
||||
import './Menu.css';
|
||||
|
||||
const routes = {
|
||||
appPages: [
|
||||
{ title: 'Schedule', path: '/tabs/schedule', icon: calendarOutline },
|
||||
{ title: 'Speakers', path: '/tabs/speakers', icon: peopleOutline },
|
||||
{ title: 'Map', path: '/tabs/map', icon: mapOutline },
|
||||
{ title: 'About', path: '/tabs/about', icon: informationCircleOutline },
|
||||
],
|
||||
loggedInPages: [
|
||||
{ title: 'Account', path: '/account', icon: person },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Logout', path: '/logout', icon: logOut },
|
||||
],
|
||||
loggedOutPages: [
|
||||
{ title: 'Login', path: '/login', icon: logIn },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Signup', path: '/signup', icon: personAdd },
|
||||
],
|
||||
};
|
||||
|
||||
interface Pages {
|
||||
title: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
routerDirection?: string;
|
||||
}
|
||||
interface StateProps {
|
||||
darkMode: boolean;
|
||||
isAuthenticated: boolean;
|
||||
menuEnabled: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setDarkMode: typeof setDarkMode;
|
||||
}
|
||||
|
||||
interface MenuProps extends RouteComponentProps, StateProps, DispatchProps {}
|
||||
|
||||
const Menu: React.FC<MenuProps> = ({
|
||||
darkMode,
|
||||
history,
|
||||
isAuthenticated,
|
||||
setDarkMode,
|
||||
menuEnabled,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
|
||||
function renderlistItems(list: Pages[]) {
|
||||
return list
|
||||
.filter((route) => !!route.path)
|
||||
.map((p) => (
|
||||
<IonMenuToggle key={p.title} auto-hide="false">
|
||||
<IonItem
|
||||
detail={false}
|
||||
routerLink={p.path}
|
||||
routerDirection="none"
|
||||
className={
|
||||
location.pathname.startsWith(p.path) ? 'selected' : undefined
|
||||
}
|
||||
>
|
||||
<IonIcon slot="start" icon={p.icon} />
|
||||
<IonLabel>{p.title}</IonLabel>
|
||||
</IonItem>
|
||||
</IonMenuToggle>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<IonMenu type="overlay" disabled={!menuEnabled} contentId="main">
|
||||
<IonContent forceOverscroll={false}>
|
||||
<IonList lines="none">
|
||||
<IonListHeader>Conference</IonListHeader>
|
||||
{renderlistItems(routes.appPages)}
|
||||
</IonList>
|
||||
<IonList lines="none">
|
||||
<IonListHeader>Account</IonListHeader>
|
||||
{isAuthenticated
|
||||
? renderlistItems(routes.loggedInPages)
|
||||
: renderlistItems(routes.loggedOutPages)}
|
||||
<IonItem>
|
||||
<IonIcon
|
||||
slot="start"
|
||||
icon={moonOutline}
|
||||
aria-hidden="true"
|
||||
></IonIcon>
|
||||
<IonToggle
|
||||
checked={darkMode}
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
>
|
||||
Dark Mode
|
||||
</IonToggle>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<IonList lines="none">
|
||||
<IonListHeader>Tutorial</IonListHeader>
|
||||
<IonItem
|
||||
button
|
||||
detail={false}
|
||||
onClick={() => {
|
||||
history.push('/tutorial');
|
||||
}}
|
||||
>
|
||||
<IonIcon slot="start" icon={hammer} />
|
||||
<IonLabel>Show Tutorial</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
isAuthenticated: state.user.isLoggedin,
|
||||
menuEnabled: state.data.menuEnabled,
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setDarkMode,
|
||||
},
|
||||
component: withRouter(Menu),
|
||||
});
|
@@ -0,0 +1,22 @@
|
||||
import React, { useEffect, useContext } from 'react';
|
||||
import { IonRouterContext } from '@ionic/react';
|
||||
|
||||
interface RedirectToLoginProps {
|
||||
setIsLoggedIn: Function;
|
||||
setUsername: Function;
|
||||
}
|
||||
|
||||
const RedirectToLogin: React.FC<RedirectToLoginProps> = ({
|
||||
setIsLoggedIn,
|
||||
setUsername,
|
||||
}) => {
|
||||
const ionRouterContext = useContext(IonRouterContext);
|
||||
useEffect(() => {
|
||||
setIsLoggedIn(false);
|
||||
setUsername(undefined);
|
||||
ionRouterContext.push('/tabs/schedule');
|
||||
}, [setIsLoggedIn, setUsername]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default RedirectToLogin;
|
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
IonItemDivider,
|
||||
IonItemGroup,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonAlert,
|
||||
AlertButton,
|
||||
} from '@ionic/react';
|
||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { Schedule, Session } from '../models/Schedule';
|
||||
import SessionListItem from './SessionListItem';
|
||||
import { connect } from '../data/connect';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
schedule: Schedule;
|
||||
listType: 'all' | 'favorites';
|
||||
hide: boolean;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
favoriteSessions: number[];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
interface SessionListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const SessionList: React.FC<SessionListProps> = ({
|
||||
addFavorite,
|
||||
removeFavorite,
|
||||
favoriteSessions,
|
||||
hide,
|
||||
schedule,
|
||||
listType,
|
||||
}) => {
|
||||
const scheduleListRef = useRef<HTMLIonListElement>(null);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertHeader, setAlertHeader] = useState('');
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
const [alertButtons, setAlertButtons] = useState<(AlertButton | string)[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const handleShowAlert = useCallback(
|
||||
(header: string, message: string, buttons: AlertButton[]) => {
|
||||
setAlertHeader(header);
|
||||
setAlertMessage(message);
|
||||
setAlertButtons(buttons);
|
||||
setShowAlert(true);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (scheduleListRef.current) {
|
||||
scheduleListRef.current.closeSlidingItems();
|
||||
}
|
||||
}, [hide]);
|
||||
|
||||
if (schedule.groups.length === 0 && !hide) {
|
||||
return (
|
||||
<IonList>
|
||||
<IonListHeader>No Sessions Found</IonListHeader>
|
||||
</IonList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonList ref={scheduleListRef} style={hide ? { display: 'none' } : {}}>
|
||||
{schedule.groups.map((group, index: number) => (
|
||||
<IonItemGroup key={`group-${index}`}>
|
||||
<IonItemDivider sticky>
|
||||
<IonLabel>{group.time}</IonLabel>
|
||||
</IonItemDivider>
|
||||
{group.sessions.map((session: Session, sessionIndex: number) => (
|
||||
<SessionListItem
|
||||
onShowAlert={handleShowAlert}
|
||||
isFavorite={favoriteSessions.indexOf(session.id) > -1}
|
||||
onAddFavorite={addFavorite}
|
||||
onRemoveFavorite={removeFavorite}
|
||||
key={`group-${index}-${sessionIndex}`}
|
||||
session={session}
|
||||
listType={listType}
|
||||
/>
|
||||
))}
|
||||
</IonItemGroup>
|
||||
))}
|
||||
</IonList>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header={alertHeader}
|
||||
message={alertMessage}
|
||||
buttons={alertButtons}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
></IonAlert>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
favoriteSessions: state.data.favorites,
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
addFavorite,
|
||||
removeFavorite,
|
||||
},
|
||||
component: SessionList,
|
||||
});
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Material Design
|
||||
*/
|
||||
|
||||
.md .session-list-filter ion-toolbar ion-button {
|
||||
text-transform: capitalize;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.md .session-list-filter ion-checkbox {
|
||||
--checkbox-background-checked: transparent;
|
||||
--border-color: transparent;
|
||||
--border-color-checked: transparent;
|
||||
--checkmark-color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.md .session-list-filter ion-list {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS
|
||||
*/
|
||||
|
||||
.ios .session-list-filter ion-list-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ios .session-list-filter ion-checkbox {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getMode } from '@ionic/core';
|
||||
import {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonCheckbox,
|
||||
IonFooter,
|
||||
IonIcon,
|
||||
} from '@ionic/react';
|
||||
import {
|
||||
logoReact,
|
||||
call,
|
||||
document,
|
||||
logoIonic,
|
||||
hammer,
|
||||
restaurant,
|
||||
cog,
|
||||
colorPalette,
|
||||
construct,
|
||||
compass,
|
||||
} from 'ionicons/icons';
|
||||
|
||||
import './SessionListFilter.css';
|
||||
|
||||
import { connect } from '../data/connect';
|
||||
import { updateFilteredTracks } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
onDismissModal: () => void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
allTracks: string[];
|
||||
filteredTracks: string[];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateFilteredTracks: typeof updateFilteredTracks;
|
||||
}
|
||||
|
||||
type SessionListFilterProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionListFilter: React.FC<SessionListFilterProps> = ({
|
||||
allTracks,
|
||||
filteredTracks,
|
||||
onDismissModal,
|
||||
updateFilteredTracks,
|
||||
}) => {
|
||||
const ios = getMode() === 'ios';
|
||||
|
||||
const toggleTrackFilter = (track: string) => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
updateFilteredTracks(filteredTracks.filter((x) => x !== track));
|
||||
} else {
|
||||
updateFilteredTracks([...filteredTracks, track]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeselectAll = () => {
|
||||
updateFilteredTracks([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
updateFilteredTracks([...allTracks]);
|
||||
};
|
||||
|
||||
const iconMap: { [key: string]: any } = {
|
||||
React: logoReact,
|
||||
Documentation: document,
|
||||
Food: restaurant,
|
||||
Ionic: logoIonic,
|
||||
Tooling: hammer,
|
||||
Design: colorPalette,
|
||||
Services: cog,
|
||||
Workshop: construct,
|
||||
Navigation: compass,
|
||||
Communication: call,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonHeader translucent={true} className="session-list-filter">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
{ios && <IonButton onClick={onDismissModal}>Cancel</IonButton>}
|
||||
{!ios && <IonButton onClick={handleDeselectAll}>Reset</IonButton>}
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>Filter Sessions</IonTitle>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={onDismissModal} strong>
|
||||
Done
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className="session-list-filter">
|
||||
<IonList lines={ios ? 'inset' : 'full'}>
|
||||
<IonListHeader>Tracks</IonListHeader>
|
||||
|
||||
{allTracks.map((track) => (
|
||||
<IonItem key={track}>
|
||||
{ios && (
|
||||
<IonIcon
|
||||
slot="start"
|
||||
icon={iconMap[track]}
|
||||
color="medium"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<IonCheckbox
|
||||
onIonChange={() => toggleTrackFilter(track)}
|
||||
checked={filteredTracks.indexOf(track) !== -1}
|
||||
color="primary"
|
||||
value={track}
|
||||
>
|
||||
{track}
|
||||
</IonCheckbox>
|
||||
</IonItem>
|
||||
))}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
{ios && (
|
||||
<IonFooter>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={handleDeselectAll}>Deselect All</IonButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={handleSelectAll}>Select All</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
allTracks: state.data.allTracks,
|
||||
filteredTracks: state.data.filteredTracks,
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
updateFilteredTracks,
|
||||
},
|
||||
component: SessionListFilter,
|
||||
});
|
@@ -0,0 +1,120 @@
|
||||
import React, { useRef } from 'react';
|
||||
import {
|
||||
IonItemSliding,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonItemOptions,
|
||||
IonItemOption,
|
||||
AlertButton,
|
||||
useIonToast,
|
||||
} from '@ionic/react';
|
||||
import { Session } from '../models/Schedule';
|
||||
|
||||
interface SessionListItemProps {
|
||||
session: Session;
|
||||
listType: 'all' | 'favorites';
|
||||
onAddFavorite: (id: number) => void;
|
||||
onRemoveFavorite: (id: number) => void;
|
||||
onShowAlert: (
|
||||
header: string,
|
||||
message: string,
|
||||
buttons: AlertButton[]
|
||||
) => void;
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
const SessionListItem: React.FC<SessionListItemProps> = ({
|
||||
isFavorite,
|
||||
onAddFavorite,
|
||||
onRemoveFavorite,
|
||||
onShowAlert,
|
||||
session,
|
||||
listType,
|
||||
}) => {
|
||||
const [presentToast] = useIonToast();
|
||||
const ionItemSlidingRef = useRef<HTMLIonItemSlidingElement>(null);
|
||||
|
||||
const dismissAlert = () => {
|
||||
ionItemSlidingRef.current && ionItemSlidingRef.current.close();
|
||||
};
|
||||
|
||||
const removeFavoriteSession = (title: string) => {
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert(
|
||||
title,
|
||||
'Would you like to remove this session from your favorites?',
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
handler: dismissAlert,
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
handler: () => {
|
||||
onRemoveFavorite(session.id);
|
||||
dismissAlert();
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const addFavoriteSession = async () => {
|
||||
if (isFavorite) {
|
||||
// Prompt to remove favorite
|
||||
removeFavoriteSession('Favorite already added');
|
||||
} else {
|
||||
// Add as a favorite
|
||||
onAddFavorite(session.id);
|
||||
|
||||
// Close the open item
|
||||
ionItemSlidingRef.current && ionItemSlidingRef.current.close();
|
||||
|
||||
// Create a toast
|
||||
presentToast({
|
||||
message: `${session.name} was successfully added as a favorite.`,
|
||||
duration: 3000,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Close',
|
||||
role: 'cancel',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItemSliding
|
||||
ref={ionItemSlidingRef}
|
||||
class={'track-' + session.tracks[0].toLowerCase()}
|
||||
>
|
||||
<IonItem routerLink={`/tabs/schedule/${session.id}`}>
|
||||
<IonLabel>
|
||||
<h3>{session.name}</h3>
|
||||
<p>
|
||||
{session.timeStart} —
|
||||
{session.timeEnd}:
|
||||
{session.location}
|
||||
</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
<IonItemOptions>
|
||||
{listType === 'favorites' ? (
|
||||
<IonItemOption
|
||||
color="danger"
|
||||
onClick={() => removeFavoriteSession('Remove Favorite')}
|
||||
>
|
||||
Remove
|
||||
</IonItemOption>
|
||||
) : (
|
||||
<IonItemOption color="favorite" onClick={addFavoriteSession}>
|
||||
Favorite
|
||||
</IonItemOption>
|
||||
)}
|
||||
</IonItemOptions>
|
||||
</IonItemSliding>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SessionListItem);
|
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
IonLoading,
|
||||
IonFab,
|
||||
IonFabButton,
|
||||
IonIcon,
|
||||
IonFabList,
|
||||
} from '@ionic/react';
|
||||
import {
|
||||
shareSocial,
|
||||
logoVimeo,
|
||||
logoInstagram,
|
||||
logoTwitter,
|
||||
logoFacebook,
|
||||
} from 'ionicons/icons';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const ShareSocialFab: React.FC = () => {
|
||||
const [loadingMessage, setLoadingMessage] = useState('');
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
|
||||
const openSocial = (network: string) => {
|
||||
setLoadingMessage(`Posting to ${network}`);
|
||||
setShowLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonLoading
|
||||
isOpen={showLoading}
|
||||
message={loadingMessage}
|
||||
duration={2000}
|
||||
spinner="crescent"
|
||||
onDidDismiss={() => setShowLoading(false)}
|
||||
/>
|
||||
<IonFab slot="fixed" vertical="bottom" horizontal="end">
|
||||
<IonFabButton>
|
||||
<IonIcon icon={shareSocial} />
|
||||
</IonFabButton>
|
||||
<IonFabList side="top">
|
||||
<IonFabButton color="vimeo" onClick={() => openSocial('Vimeo')}>
|
||||
<IonIcon icon={logoVimeo} />
|
||||
</IonFabButton>
|
||||
<IonFabButton
|
||||
color="instagram"
|
||||
onClick={() => openSocial('Instagram')}
|
||||
>
|
||||
<IonIcon icon={logoInstagram} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="twitter" onClick={() => openSocial('Twitter')}>
|
||||
<IonIcon icon={logoTwitter} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="facebook" onClick={() => openSocial('Facebook')}>
|
||||
<IonIcon icon={logoFacebook} />
|
||||
</IonFabButton>
|
||||
</IonFabList>
|
||||
</IonFab>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShareSocialFab;
|
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { Session } from '../models/Schedule';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import {
|
||||
IonCard,
|
||||
IonCardHeader,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonAvatar,
|
||||
IonCardContent,
|
||||
IonList,
|
||||
} from '@ionic/react';
|
||||
|
||||
interface SpeakerItemProps {
|
||||
speaker: Speaker;
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
const SpeakerItem: React.FC<SpeakerItemProps> = ({ speaker, sessions }) => {
|
||||
return (
|
||||
<>
|
||||
<IonCard className="speaker-card">
|
||||
<IonCardHeader>
|
||||
<IonItem
|
||||
button
|
||||
detail={false}
|
||||
lines="none"
|
||||
className="speaker-item"
|
||||
routerLink={`/tabs/speakers/${speaker.id}`}
|
||||
>
|
||||
<IonAvatar slot="start">
|
||||
<img src={speaker.profilePic} alt="Speaker profile pic" />
|
||||
</IonAvatar>
|
||||
<IonLabel>
|
||||
<h2>{speaker.name}</h2>
|
||||
<p>{speaker.title}</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<IonList lines="none">
|
||||
{sessions.map((session) => (
|
||||
<IonItem
|
||||
detail={false}
|
||||
routerLink={`/tabs/speakers/sessions/${session.id}`}
|
||||
key={session.name}
|
||||
>
|
||||
<IonLabel>
|
||||
<h3>{session.name}</h3>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
))}
|
||||
<IonItem detail={false} routerLink={`/tabs/speakers/${speaker.id}`}>
|
||||
<IonLabel>
|
||||
<h3>About {speaker.name}</h3>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerItem;
|
Reference in New Issue
Block a user