update demo-react-movie-app-with-algolia,
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import { IonCardSubtitle, IonCardTitle, IonCol, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonRow } from "@ionic/react";
|
||||||
|
import { connectInfiniteHits } from "react-instantsearch-core";
|
||||||
|
import CustomSearchHit from "./CustomSearchHit";
|
||||||
|
|
||||||
|
const CustomInfiniteHits = ({ hits, hasMore, refineNext }) => {
|
||||||
|
|
||||||
|
const getMore = (e, refine) => {
|
||||||
|
|
||||||
|
refine();
|
||||||
|
e.target.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="ais-InfiniteHits">
|
||||||
|
<IonList className="ais-InfiniteHits-list">
|
||||||
|
<div className="ais-Hits__root">
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
{ hits.map(hit => <CustomSearchHit key={ hit.objectID } hit={ hit } />) }
|
||||||
|
|
||||||
|
{ hits.length < 1 &&
|
||||||
|
<IonCol size="12" className="ion-text-center ion-padding-top ion-margin-top">
|
||||||
|
<IonCardTitle>No results found</IonCardTitle>
|
||||||
|
<IonCardSubtitle>Try something else</IonCardSubtitle>
|
||||||
|
</IonCol>
|
||||||
|
}
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonInfiniteScroll threshold="100px" onIonInfinite={ e => getMore(e, refineNext) }>
|
||||||
|
<IonInfiniteScrollContent loadingSpinner="bubbles" loadingText="Getting more movies..." />
|
||||||
|
</IonInfiniteScroll>
|
||||||
|
</div>
|
||||||
|
</IonList>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connectInfiniteHits(CustomInfiniteHits);
|
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCol,
|
||||||
|
IonInfiniteScroll,
|
||||||
|
IonInfiniteScrollContent,
|
||||||
|
IonList,
|
||||||
|
IonRow,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { connectInfiniteHits } from 'react-instantsearch-core';
|
||||||
|
import CustomSearchHit from './CustomSearchHit';
|
||||||
|
|
||||||
|
const CustomInfiniteHits = ({ hits, hasMore, refineNext }): React.JSX.Element => {
|
||||||
|
const getMore = (e, refine) => {
|
||||||
|
refine();
|
||||||
|
e.target.complete();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ais-InfiniteHits">
|
||||||
|
<IonList className="ais-InfiniteHits-list">
|
||||||
|
<div className="ais-Hits__root">
|
||||||
|
<IonRow>
|
||||||
|
{hits.map((hit) => (
|
||||||
|
<CustomSearchHit key={hit.objectID} hit={hit} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{hits.length < 1 && (
|
||||||
|
<IonCol size="12" className="ion-text-center ion-padding-top ion-margin-top">
|
||||||
|
<IonCardTitle>No results found</IonCardTitle>
|
||||||
|
<IonCardSubtitle>Try something else</IonCardSubtitle>
|
||||||
|
</IonCol>
|
||||||
|
)}
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonInfiniteScroll threshold="100px" onIonInfinite={(e) => getMore(e, refineNext)}>
|
||||||
|
<IonInfiniteScrollContent
|
||||||
|
loadingSpinner="bubbles"
|
||||||
|
loadingText="Getting more movies..."
|
||||||
|
/>
|
||||||
|
</IonInfiniteScroll>
|
||||||
|
</div>
|
||||||
|
</IonList>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connectInfiniteHits(CustomInfiniteHits);
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { IonCol, IonRouterLink } from "@ionic/react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import styles from "../pages/Movies.module.scss";
|
||||||
|
|
||||||
|
const CustomSearchHit = ({ hit }) => {
|
||||||
|
|
||||||
|
// Any movies without an image, lets just exclude them
|
||||||
|
// So the UI looks nicer.
|
||||||
|
// When I scraped the data, I put a placeholder URL image in
|
||||||
|
// But after more thought, it looks cleaner without including these.
|
||||||
|
if (hit && hit.image !== "https://critics.io/img/movies/poster-placeholder.png" && hit.backdrop_path !== null) {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<IonCol size="6" className={ styles.movie }>
|
||||||
|
<Link to={{ pathname: `/movie/${ hit.objectID }`, state: { movie: hit } }} className="non-link">
|
||||||
|
<div className={ styles.movieInfo }>
|
||||||
|
<img src={ hit.image } alt="movie poster" />
|
||||||
|
<h2>{ hit.title }</h2>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</IonCol>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSearchHit;
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { IonCol, IonRouterLink } from '@ionic/react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styles from '../pages/Movies.module.scss';
|
||||||
|
|
||||||
|
const CustomSearchHit = ({ hit }): React.JSX.Element | null => {
|
||||||
|
// Any movies without an image, lets just exclude them
|
||||||
|
// So the UI looks nicer.
|
||||||
|
// When I scraped the data, I put a placeholder URL image in
|
||||||
|
// But after more thought, it looks cleaner without including these.
|
||||||
|
if (
|
||||||
|
hit &&
|
||||||
|
hit.image !== 'https://critics.io/img/movies/poster-placeholder.png' &&
|
||||||
|
hit.backdrop_path !== null
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<IonCol size="6" className={styles.movie}>
|
||||||
|
<Link
|
||||||
|
to={{ pathname: `/movie/${hit.objectID}`, state: { movie: hit } }}
|
||||||
|
className="non-link"
|
||||||
|
>
|
||||||
|
<div className={styles.movieInfo}>
|
||||||
|
<img src={hit.image} alt="movie poster" />
|
||||||
|
<h2>{hit.title}</h2>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</IonCol>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomSearchHit;
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IonSearchbar } from "@ionic/react";
|
||||||
|
import { searchCircleOutline } from "ionicons/icons";
|
||||||
|
import { connectSearchBox } from "react-instantsearch-core";
|
||||||
|
|
||||||
|
const CustomSearchBox = ({ currentRefinement, refine }) => (
|
||||||
|
<IonSearchbar animated={ true } onIonCancel={ () => refine('') } value={ currentRefinement } onIonChange={ event => refine(event.currentTarget.value) } onKeyUp={ event => refine(event.currentTarget.value) } placeholder="Try 'Avengers'" icon={ searchCircleOutline } slot="end" />
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connectSearchBox(CustomSearchBox);
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { IonSearchbar } from '@ionic/react';
|
||||||
|
import { searchCircleOutline } from 'ionicons/icons';
|
||||||
|
import { connectSearchBox } from 'react-instantsearch-core';
|
||||||
|
|
||||||
|
const CustomSearchBox = ({ currentRefinement, refine }): React.JSX.Element => (
|
||||||
|
<IonSearchbar
|
||||||
|
animated={true}
|
||||||
|
onIonCancel={() => refine('')}
|
||||||
|
value={currentRefinement}
|
||||||
|
onIonChange={(event) => refine(event.currentTarget.value)}
|
||||||
|
onKeyUp={(event) => refine(event.currentTarget.value)}
|
||||||
|
placeholder="Try 'Avengers'"
|
||||||
|
icon={searchCircleOutline}
|
||||||
|
slot="end"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connectSearchBox(CustomSearchBox);
|
@@ -6,7 +6,8 @@ import { Route, Redirect } from 'react-router';
|
|||||||
import Tab1 from './AppPages/Tab1';
|
import Tab1 from './AppPages/Tab1';
|
||||||
import Tab2 from './AppPages/Tab2';
|
import Tab2 from './AppPages/Tab2';
|
||||||
|
|
||||||
import './style.scss';
|
// import './style.scss';
|
||||||
|
import './theme/variables.scss';
|
||||||
|
|
||||||
function DemoReactMovieAppWithAlgolia() {
|
function DemoReactMovieAppWithAlgolia() {
|
||||||
return (
|
return (
|
||||||
@@ -19,7 +20,11 @@ function DemoReactMovieAppWithAlgolia() {
|
|||||||
<Tab2 />
|
<Tab2 />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Redirect exact path="/demo-react-movie-app-with-algolia" to="/demo-react-movie-app-with-algolia/tab1" />
|
<Redirect
|
||||||
|
exact
|
||||||
|
path="/demo-react-movie-app-with-algolia"
|
||||||
|
to="/demo-react-movie-app-with-algolia/tab1"
|
||||||
|
/>
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
|
|
||||||
{/* */}
|
{/* */}
|
||||||
|
@@ -0,0 +1,86 @@
|
|||||||
|
import { IonBackButton, IonBadge, IonButton, IonCardSubtitle, IonCardTitle, IonCol, IonContent, IonGrid, IonHeader, IonPage, IonRow } from '@ionic/react';
|
||||||
|
import styles from './Movie.module.scss';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { arrowBack } from 'ionicons/icons';
|
||||||
|
import { useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
|
const Movie = () => {
|
||||||
|
|
||||||
|
const params = useParams();
|
||||||
|
const location = useLocation();
|
||||||
|
const [ movie, setMovie ] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (location.state.movie) {
|
||||||
|
|
||||||
|
const backdropPath = `https://image.tmdb.org/t/p/w500${ location.state.movie.backdrop_path }`;
|
||||||
|
location.state.movie.cover_image = backdropPath;
|
||||||
|
|
||||||
|
setMovie(location.state.movie);
|
||||||
|
}
|
||||||
|
}, [ params ]);
|
||||||
|
|
||||||
|
const parseDate = dateToParse => {
|
||||||
|
|
||||||
|
const dateParts = dateToParse.split("-");
|
||||||
|
return `${ dateParts[2] }-${ dateParts[1] }-${ dateParts[0] }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage className={ styles.page }>
|
||||||
|
<IonHeader>
|
||||||
|
<img src={ movie.cover_image } alt="movie cover" />
|
||||||
|
<IonBackButton color="light" className={ styles.backButton } text=" Back to search" icon={ arrowBack } />
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow>
|
||||||
|
<IonCol size="5">
|
||||||
|
<img src={ movie.image } />
|
||||||
|
</IonCol>
|
||||||
|
|
||||||
|
<IonCol size="7" className={ styles.movieInfo }>
|
||||||
|
<IonCardSubtitle>Title</IonCardSubtitle>
|
||||||
|
<IonCardTitle>{ movie.title }</IonCardTitle>
|
||||||
|
|
||||||
|
<IonRow className="ion-justify-content-center ion-margin-top">
|
||||||
|
<IonCol size="6">
|
||||||
|
<IonCardSubtitle>Popularity</IonCardSubtitle>
|
||||||
|
<IonBadge className={ styles.infoBadge }>{ movie.popularity ? movie.popularity : "Unknown" }</IonBadge>
|
||||||
|
</IonCol>
|
||||||
|
|
||||||
|
<IonCol size="6">
|
||||||
|
<IonCardSubtitle>Release Date</IonCardSubtitle>
|
||||||
|
<IonBadge className={ styles.infoBadge }>{ movie.release_date ? parseDate(movie.release_date) : "Unknown" }</IonBadge>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Search for Youtube Trailer</IonCardSubtitle>
|
||||||
|
<a href={ `https://www.youtube.com/results?search_query=${ movie.title } trailer` } rel="noopener" target="_new">
|
||||||
|
<IonButton>
|
||||||
|
Search now
|
||||||
|
</IonButton>
|
||||||
|
</a>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className={ styles.movieOverview }>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Overview</IonCardSubtitle>
|
||||||
|
<p>{ movie.overview ? movie.overview : `No overview to show unfortunately. Try doing a google search for '${ movie.title }.` }</p>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Movie;
|
@@ -0,0 +1,54 @@
|
|||||||
|
.page {
|
||||||
|
|
||||||
|
ion-header {
|
||||||
|
|
||||||
|
background-color: #373b5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
|
||||||
|
--border-style: none;
|
||||||
|
--background: #373b5e;
|
||||||
|
--color: white;
|
||||||
|
--min-height: 8rem;
|
||||||
|
--stripe: #373b5e;
|
||||||
|
--bg: #3e4368;
|
||||||
|
--background: transparent;
|
||||||
|
background: linear-gradient(135deg, var(--bg) 25%, transparent 25%) -50px 0,
|
||||||
|
linear-gradient(225deg, var(--bg) 25%, transparent 25%) -50px 0,
|
||||||
|
linear-gradient(315deg, var(--bg) 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, var(--bg) 25%, transparent 25%);
|
||||||
|
background-size: 100px 100px;
|
||||||
|
background-color: var(--stripe);
|
||||||
|
|
||||||
|
ion-title {
|
||||||
|
|
||||||
|
--color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movieOverview {
|
||||||
|
|
||||||
|
background-color:#373b5e;
|
||||||
|
padding: 2rem;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
ion-card-subtitle {
|
||||||
|
|
||||||
|
--color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backButton {
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoBadge {
|
||||||
|
|
||||||
|
background-color:rgba(44, 53, 105, 0.8);
|
||||||
|
}
|
@@ -0,0 +1,109 @@
|
|||||||
|
import {
|
||||||
|
IonBackButton,
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import styles from './Movie.module.scss';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { arrowBack } from 'ionicons/icons';
|
||||||
|
import { useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
|
const Movie = (): React.JSX.Element => {
|
||||||
|
const params = useParams();
|
||||||
|
const location = useLocation();
|
||||||
|
const [movie, setMovie] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.state.movie) {
|
||||||
|
const backdropPath = `https://image.tmdb.org/t/p/w500${location.state.movie.backdrop_path}`;
|
||||||
|
location.state.movie.cover_image = backdropPath;
|
||||||
|
|
||||||
|
setMovie(location.state.movie);
|
||||||
|
}
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
const parseDate = (dateToParse) => {
|
||||||
|
const dateParts = dateToParse.split('-');
|
||||||
|
return `${dateParts[2]}-${dateParts[1]}-${dateParts[0]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage className={styles.page}>
|
||||||
|
<IonHeader>
|
||||||
|
<img src={movie.cover_image} alt="movie cover" />
|
||||||
|
<IonBackButton
|
||||||
|
color="light"
|
||||||
|
className={styles.backButton}
|
||||||
|
text=" Back to search"
|
||||||
|
icon={arrowBack}
|
||||||
|
/>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow>
|
||||||
|
<IonCol size="5">
|
||||||
|
<img src={movie.image} />
|
||||||
|
</IonCol>
|
||||||
|
|
||||||
|
<IonCol size="7" className={styles.movieInfo}>
|
||||||
|
<IonCardSubtitle>Title</IonCardSubtitle>
|
||||||
|
<IonCardTitle>{movie.title}</IonCardTitle>
|
||||||
|
|
||||||
|
<IonRow className="ion-justify-content-center ion-margin-top">
|
||||||
|
<IonCol size="6">
|
||||||
|
<IonCardSubtitle>Popularity</IonCardSubtitle>
|
||||||
|
<IonBadge className={styles.infoBadge}>
|
||||||
|
{movie.popularity ? movie.popularity : 'Unknown'}
|
||||||
|
</IonBadge>
|
||||||
|
</IonCol>
|
||||||
|
|
||||||
|
<IonCol size="6">
|
||||||
|
<IonCardSubtitle>Release Date</IonCardSubtitle>
|
||||||
|
<IonBadge className={styles.infoBadge}>
|
||||||
|
{movie.release_date ? parseDate(movie.release_date) : 'Unknown'}
|
||||||
|
</IonBadge>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Search for Youtube Trailer</IonCardSubtitle>
|
||||||
|
<a
|
||||||
|
href={`https://www.youtube.com/results?search_query=${movie.title} trailer`}
|
||||||
|
rel="noopener"
|
||||||
|
target="_new"
|
||||||
|
>
|
||||||
|
<IonButton>Search now</IonButton>
|
||||||
|
</a>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className={styles.movieOverview}>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Overview</IonCardSubtitle>
|
||||||
|
<p>
|
||||||
|
{movie.overview
|
||||||
|
? movie.overview
|
||||||
|
: `No overview to show unfortunately. Try doing a google search for '${movie.title}.`}
|
||||||
|
</p>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Movie;
|
@@ -0,0 +1,94 @@
|
|||||||
|
import { IonButton, IonCardSubtitle, IonCardTitle, IonContent, IonHeader, IonIcon, IonPage, IonToolbar } from '@ionic/react';
|
||||||
|
import styles from './Movies.module.scss';
|
||||||
|
|
||||||
|
// Algolia imports
|
||||||
|
import algoliasearch from 'algoliasearch/lite';
|
||||||
|
import { InstantSearch } from 'react-instantsearch-dom';
|
||||||
|
|
||||||
|
// Custom Algolia UI
|
||||||
|
import CustomSearchbox from '../components/CustomSearchbox';
|
||||||
|
import CustomInfiniteHits from '../components/CustomInfiniteHits';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { addOutline } from 'ionicons/icons';
|
||||||
|
|
||||||
|
const searchClient = algoliasearch('QZKBD6VPU7', 'db21b77f5f3bf4d4cbde385b7f33c60d');
|
||||||
|
|
||||||
|
const Movies = props => {
|
||||||
|
|
||||||
|
// PWA functionality for a custom add to homescreen
|
||||||
|
// This allows us to display a custom button based on service worker
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const buttInstall = document.getElementById('buttInstall');
|
||||||
|
window.addEventListener('beforeinstallprompt', (event) => {
|
||||||
|
|
||||||
|
console.log('👍', 'beforeinstallprompt', event);
|
||||||
|
|
||||||
|
// Save the event so it can be triggered later.
|
||||||
|
window.deferredPrompt = event;
|
||||||
|
buttInstall.classList.toggle('hidden', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('appinstalled', (event) => {
|
||||||
|
|
||||||
|
console.log('👍', 'appinstalled', event);
|
||||||
|
// Clear the deferredPrompt so it can be garbage collected
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addToHomeScreen = async () => {
|
||||||
|
|
||||||
|
const buttInstall = document.getElementById('buttInstall');
|
||||||
|
|
||||||
|
console.log('👍', 'buttInstall-clicked');
|
||||||
|
const promptEvent = window.deferredPrompt;
|
||||||
|
|
||||||
|
if (!promptEvent) {
|
||||||
|
|
||||||
|
// The deferred prompt isn't available.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the install prompt.
|
||||||
|
promptEvent.prompt();
|
||||||
|
|
||||||
|
// Log the result
|
||||||
|
const result = await promptEvent.userChoice;
|
||||||
|
console.log('👍', 'userChoice', result);
|
||||||
|
|
||||||
|
// Reset the deferred prompt variable, since
|
||||||
|
// prompt() can only be called once.
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
|
||||||
|
// Hide the install button.
|
||||||
|
buttInstall.classList.toggle('hidden', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage className={ styles.page }>
|
||||||
|
|
||||||
|
<InstantSearch searchClient={ searchClient } indexName="dev_movies">
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<div className={ styles.searchContainer }>
|
||||||
|
<IonCardTitle>Movie List</IonCardTitle>
|
||||||
|
<IonCardSubtitle>with algolia search</IonCardSubtitle>
|
||||||
|
<CustomSearchbox />
|
||||||
|
|
||||||
|
<IonButton id="buttInstall" color="success" fill="solid" size="small" className="hidden add-button" onClick={ () => addToHomeScreen() }>
|
||||||
|
<IonIcon icon={ addOutline } /> Install App
|
||||||
|
</IonButton>
|
||||||
|
</div>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<CustomInfiniteHits />
|
||||||
|
</IonContent>
|
||||||
|
</InstantSearch>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Movies;
|
@@ -0,0 +1,133 @@
|
|||||||
|
.page {
|
||||||
|
|
||||||
|
ion-header {
|
||||||
|
|
||||||
|
background-color: #373b5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
|
||||||
|
--border-style: none;
|
||||||
|
--background: #373b5e;
|
||||||
|
--color: white;
|
||||||
|
--min-height: 8rem;
|
||||||
|
--stripe: #373b5e;
|
||||||
|
--bg: #3e4368;
|
||||||
|
--background: transparent;
|
||||||
|
background: linear-gradient(135deg, var(--bg) 25%, transparent 25%) -50px 0,
|
||||||
|
linear-gradient(225deg, var(--bg) 25%, transparent 25%) -50px 0,
|
||||||
|
linear-gradient(315deg, var(--bg) 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, var(--bg) 25%, transparent 25%);
|
||||||
|
background-size: 100px 100px;
|
||||||
|
background-color: var(--stripe);
|
||||||
|
|
||||||
|
ion-button {
|
||||||
|
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchContainer {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
ion-card-title,
|
||||||
|
ion-card-subtitle {
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-card-title {
|
||||||
|
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-card-subtitle {
|
||||||
|
|
||||||
|
color: rgb(189, 189, 189);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-searchbar {
|
||||||
|
|
||||||
|
--border-radius: 10px;
|
||||||
|
--background: white;
|
||||||
|
--color: black;
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
--background: #2a2d44;
|
||||||
|
--color: white;
|
||||||
|
--icon-color: white;
|
||||||
|
--clear-button-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-searchbar {
|
||||||
|
|
||||||
|
input{
|
||||||
|
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-list {
|
||||||
|
|
||||||
|
background-color: #e7edfb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.movie {
|
||||||
|
|
||||||
|
.movieInfo {
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
img {
|
||||||
|
|
||||||
|
height: 75vmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: red;
|
||||||
|
width: 95%;
|
||||||
|
background-color: rgba(44, 53, 105, 0.8);
|
||||||
|
color: white;
|
||||||
|
font-size: 4vmin;
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 4.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
max-height: 4.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-button {
|
||||||
|
|
||||||
|
--background: #5a55ca;
|
||||||
|
--background-focused: #6f6bbb;
|
||||||
|
--background-activated: #6f6bbb;
|
||||||
|
--padding-top: 1rem;
|
||||||
|
--padding-bottom: 1rem;
|
||||||
|
--padding-start: 0.75rem;
|
||||||
|
--padding-end: 0.75rem;
|
||||||
|
margin-top: -0.2rem;
|
||||||
|
}
|
@@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
IonButton,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardTitle,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonPage,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import styles from './Movies.module.scss';
|
||||||
|
|
||||||
|
// Algolia imports
|
||||||
|
import algoliasearch from 'algoliasearch/lite';
|
||||||
|
import { InstantSearch } from 'react-instantsearch-dom';
|
||||||
|
|
||||||
|
// Custom Algolia UI
|
||||||
|
import CustomSearchbox from '../components/CustomSearchbox';
|
||||||
|
import CustomInfiniteHits from '../components/CustomInfiniteHits';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { addOutline } from 'ionicons/icons';
|
||||||
|
|
||||||
|
const searchClient = algoliasearch('QZKBD6VPU7', 'db21b77f5f3bf4d4cbde385b7f33c60d');
|
||||||
|
|
||||||
|
const Movies = (props: any): React.JSX.Element => {
|
||||||
|
// PWA functionality for a custom add to homescreen
|
||||||
|
// This allows us to display a custom button based on service worker
|
||||||
|
useEffect(() => {
|
||||||
|
const buttInstall = document.getElementById('buttInstall');
|
||||||
|
window.addEventListener('beforeinstallprompt', (event) => {
|
||||||
|
console.log('👍', 'beforeinstallprompt', event);
|
||||||
|
|
||||||
|
// Save the event so it can be triggered later.
|
||||||
|
window.deferredPrompt = event;
|
||||||
|
buttInstall.classList.toggle('hidden', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('appinstalled', (event) => {
|
||||||
|
console.log('👍', 'appinstalled', event);
|
||||||
|
// Clear the deferredPrompt so it can be garbage collected
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addToHomeScreen = async () => {
|
||||||
|
const buttInstall = document.getElementById('buttInstall');
|
||||||
|
|
||||||
|
console.log('👍', 'buttInstall-clicked');
|
||||||
|
const promptEvent = window.deferredPrompt;
|
||||||
|
|
||||||
|
if (!promptEvent) {
|
||||||
|
// The deferred prompt isn't available.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the install prompt.
|
||||||
|
promptEvent.prompt();
|
||||||
|
|
||||||
|
// Log the result
|
||||||
|
const result = await promptEvent.userChoice;
|
||||||
|
console.log('👍', 'userChoice', result);
|
||||||
|
|
||||||
|
// Reset the deferred prompt variable, since
|
||||||
|
// prompt() can only be called once.
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
|
||||||
|
// Hide the install button.
|
||||||
|
buttInstall.classList.toggle('hidden', true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage className={styles.page}>
|
||||||
|
<InstantSearch searchClient={searchClient} indexName="dev_movies">
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<div className={styles.searchContainer}>
|
||||||
|
<IonCardTitle>Movie List</IonCardTitle>
|
||||||
|
<IonCardSubtitle>with algolia search</IonCardSubtitle>
|
||||||
|
<CustomSearchbox />
|
||||||
|
|
||||||
|
<IonButton
|
||||||
|
id="buttInstall"
|
||||||
|
color="success"
|
||||||
|
fill="solid"
|
||||||
|
size="small"
|
||||||
|
className="hidden add-button"
|
||||||
|
onClick={() => addToHomeScreen()}
|
||||||
|
>
|
||||||
|
<IonIcon icon={addOutline} />
|
||||||
|
Install App
|
||||||
|
</IonButton>
|
||||||
|
</div>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<CustomInfiniteHits />
|
||||||
|
</IonContent>
|
||||||
|
</InstantSearch>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Movies;
|
@@ -1,103 +0,0 @@
|
|||||||
#about-page {
|
|
||||||
ion-toolbar {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
--background: transparent;
|
|
||||||
--color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-toolbar ion-back-button,
|
|
||||||
ion-toolbar ion-button,
|
|
||||||
ion-toolbar ion-menu-button {
|
|
||||||
--color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-header {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-header .about-image {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
transition: opacity 500ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-header .madison {
|
|
||||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-header .austin {
|
|
||||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-header .chicago {
|
|
||||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-header .seattle {
|
|
||||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-info {
|
|
||||||
position: relative;
|
|
||||||
margin-top: -10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: var(--ion-background-color, #fff);
|
|
||||||
z-index: 2; // display rounded border above header image
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-info h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-info ion-list {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-info p {
|
|
||||||
line-height: 130%;
|
|
||||||
|
|
||||||
color: var(--ion-color-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-info ion-icon {
|
|
||||||
margin-inline-end: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* iOS Only
|
|
||||||
*/
|
|
||||||
|
|
||||||
.ios .about-info {
|
|
||||||
--ion-padding: 19px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ios .about-info h3 {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#date-input-popover {
|
|
||||||
--offset-y: -var(--ion-safe-area-bottom);
|
|
||||||
|
|
||||||
--max-width: 90%;
|
|
||||||
--width: 336px;
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user