update adding demo app,
@@ -9,6 +9,7 @@ android {
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-geolocation')
|
||||
implementation project(':capacitor-preferences')
|
||||
|
||||
}
|
||||
|
@@ -2,5 +2,8 @@
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||
|
||||
include ':capacitor-geolocation'
|
||||
project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')
|
||||
|
||||
include ':capacitor-preferences'
|
||||
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||
|
@@ -11,6 +11,7 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
|
||||
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||
end
|
||||
|
||||
|
19
03_source/mobile/package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@capacitor/android": "7.0.1",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/geolocation": "^7.1.2",
|
||||
"@capacitor/ios": "7.0.1",
|
||||
"@capacitor/preferences": "^7.0.0",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
@@ -432,6 +433,18 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/geolocation": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/geolocation/-/geolocation-7.1.2.tgz",
|
||||
"integrity": "sha512-J++OuOpn6Bjweo7SZ+jwI/dhVF9DNw6Wx0UHgQ4qfrATqmNKGNZ/BUljGhXiKJceSx2GIhfrYS7BYqo3uWibgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor/synapse": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@capacitor/core": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/ios": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-7.0.1.tgz",
|
||||
@@ -450,6 +463,12 @@
|
||||
"@capacitor/core": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/synapse": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/synapse/-/synapse-1.0.2.tgz",
|
||||
"integrity": "sha512-ynq39s4D2rhk+aVLWKfKfMCz9SHPKijL9tq8aFL5dG7ik7/+PvBHmg9cPHbqdvFEUSMmaGzL6cIjzkOruW7vGA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"dependencies": {
|
||||
"@capacitor/android": "7.0.1",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/geolocation": "^7.1.2",
|
||||
"@capacitor/ios": "7.0.1",
|
||||
"@capacitor/preferences": "^7.0.0",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
@@ -20,6 +21,7 @@
|
||||
"date-fns": "^2.25.0",
|
||||
"ionicons": "^7.1.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"pullstate": "^2.0.0-pre.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
|
BIN
03_source/mobile/public/assets/WeatherDemo/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/WeatherDemo/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/WeatherDemo/map.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
1
03_source/mobile/public/assets/WeatherDemo/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/WeatherDemo/temp.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
03_source/mobile/public/assets/WeatherDemo/temp2.png
Normal file
After Width: | Height: | Size: 971 B |
BIN
03_source/mobile/public/assets/WeatherDemo/wind.png
Normal file
After Width: | Height: | Size: 896 B |
BIN
03_source/mobile/public/assets/react-shop/beds.jpeg
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
03_source/mobile/public/assets/react-shop/coats3.jpeg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
03_source/mobile/public/assets/react-shop/coffee_table.jpeg
Normal file
After Width: | Height: | Size: 389 KiB |
BIN
03_source/mobile/public/assets/react-shop/dresses3.jpeg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
03_source/mobile/public/assets/react-shop/formal_shirts2.jpeg
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
03_source/mobile/public/assets/react-shop/home.jpeg
Normal file
After Width: | Height: | Size: 105 KiB |
BIN
03_source/mobile/public/assets/react-shop/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/react-shop/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/react-shop/jeans.jpeg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
03_source/mobile/public/assets/react-shop/makeup2.jpeg
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
03_source/mobile/public/assets/react-shop/men.jpeg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
03_source/mobile/public/assets/react-shop/office.jpeg
Normal file
After Width: | Height: | Size: 82 KiB |
1
03_source/mobile/public/assets/react-shop/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/react-shop/sportswear2.jpeg
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
03_source/mobile/public/assets/react-shop/women.jpeg
Normal file
After Width: | Height: | Size: 47 KiB |
@@ -64,6 +64,8 @@ import ServiceAgreement from './pages/ServiceAgreement';
|
||||
import paths from './paths';
|
||||
import PrivacyAgreement from './pages/PrivacyAgreement';
|
||||
import AppRoute from './AppRoute';
|
||||
import DemoReactShop from './pages/DemoReactShop';
|
||||
import DemoWeatherApp from './pages/WeatherDemo';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
@@ -121,6 +123,8 @@ const IonicApp: React.FC<IonicAppProps> = ({
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs" render={() => <MainTabs />} />
|
||||
<Route path={paths.DEMO_REACT_SHOP} render={() => <DemoReactShop />} />
|
||||
<Route path={paths.DEMO_WEATHER_APP} render={() => <DemoWeatherApp />} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/account" component={Account} />
|
||||
|
@@ -7,14 +7,12 @@ import NotImplemented from './pages/NotImplemented';
|
||||
import EventDetail from './pages/EventDetail';
|
||||
import MemberProfile from './pages/MemberProfile';
|
||||
import paths from './paths';
|
||||
import Helloworld from './pages/Helloworld';
|
||||
import Settings from './pages/Settings';
|
||||
import ChangeLanguage from './pages/ChangeLanguage';
|
||||
import ServiceAgreement from './pages/ServiceAgreement';
|
||||
import PrivacyAgreement from './pages/PrivacyAgreement';
|
||||
// import OrderDetails from './pages/OrderDetail';
|
||||
import OrderDetail from './pages/OrderDetail';
|
||||
import SpeakerDetail from './pages/SpeakerDetail';
|
||||
|
||||
const AppRoute: React.FC = () => {
|
||||
return (
|
||||
@@ -34,6 +32,9 @@ const AppRoute: React.FC = () => {
|
||||
<Route exact={true} path={paths.CHANGE_LANGUAGE} component={ChangeLanguage} />
|
||||
<Route exact={true} path={paths.SERVICE_AGREEMENT} component={ServiceAgreement} />
|
||||
<Route exact={true} path={paths.PRIVACY_AGREEMENT} component={PrivacyAgreement} />
|
||||
|
||||
{/* TODO: review DemoReactShop to fix */}
|
||||
{/* <Route path={paths.DEMO_REACT_SHOP} render={() => <DemoReactShop />} exact={true} /> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -10,6 +10,9 @@ import Favourites from './pages/Favourites';
|
||||
import MyProfile from './pages/MyProfile';
|
||||
import EventList from './pages/EventList';
|
||||
import Helloworld from './pages/Helloworld';
|
||||
// import WeatherDemo from './pages/WeatherDemo/Tab1';
|
||||
import DemoList from './pages/DemoList';
|
||||
// import DemoReactShop from './pages/DemoReactShop';
|
||||
|
||||
const TabAppRoute: React.FC = () => {
|
||||
return (
|
||||
@@ -33,6 +36,12 @@ const TabAppRoute: React.FC = () => {
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.PROFILE} render={() => <MyProfile />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs/demo-list" render={() => <DemoList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs/helloworld" render={() => <Helloworld />} exact={true} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
13
03_source/mobile/src/pages/DemoList/TestContent.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export const TestContent = {
|
||||
eventDate: format(new Date(), 'yyyy-MM-dd'),
|
||||
title: 'helloworld',
|
||||
price: 123,
|
||||
currency: 'HKD',
|
||||
duration_m: 480,
|
||||
ageBottom: 12,
|
||||
ageTop: 48,
|
||||
location: 'Hong Kong Island',
|
||||
avatar: 'https://www.ionics.io/img/ionic-logo.png',
|
||||
};
|
271
03_source/mobile/src/pages/DemoList/index.tsx
Normal file
@@ -0,0 +1,271 @@
|
||||
// REQ0054/user-setting
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Provides functionality view user profile
|
||||
//
|
||||
// RULES:
|
||||
// - T.B.A.
|
||||
//
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonButtons,
|
||||
IonMenuButton,
|
||||
IonGrid,
|
||||
IonRow,
|
||||
IonCol,
|
||||
useIonRouter,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonPopover,
|
||||
IonAvatar,
|
||||
IonImg,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonModal,
|
||||
IonSearchbar,
|
||||
useIonModal,
|
||||
IonInput,
|
||||
IonNote,
|
||||
IonText,
|
||||
} from '@ionic/react';
|
||||
import SpeakerItem from '../../components/SpeakerItem';
|
||||
import { Speaker } from '../../models/Speaker';
|
||||
import { Session } from '../../models/Schedule';
|
||||
import { connect } from '../../data/connect';
|
||||
import * as selectors from '../../data/selectors';
|
||||
import '../SpeakerList.scss';
|
||||
import { getEvents } from '../../api/getEvents';
|
||||
import { format } from 'date-fns';
|
||||
import { Event } from './types';
|
||||
import {
|
||||
alertCircleOutline,
|
||||
alertOutline,
|
||||
cart,
|
||||
chatbubbleOutline,
|
||||
chevronBackOutline,
|
||||
chevronForward,
|
||||
chevronForwardOutline,
|
||||
createOutline,
|
||||
documentTextOutline,
|
||||
gift,
|
||||
giftOutline,
|
||||
heart,
|
||||
languageOutline,
|
||||
listCircle,
|
||||
menuOutline,
|
||||
settingsOutline,
|
||||
shareSocialOutline,
|
||||
sunny,
|
||||
trashOutline,
|
||||
} from 'ionicons/icons';
|
||||
import AboutPopover from '../../components/AboutPopover';
|
||||
import { OverlayEventDetail } from '@ionic/react/dist/types/components/react-component-lib/interfaces';
|
||||
import paths from '../../paths';
|
||||
import { logoutUser, setAccessToken, setIsLoggedIn } from '../../data/user/user.actions';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
interface StateProps {
|
||||
speakers: Speaker[];
|
||||
speakerSessions: { [key: string]: Session[] };
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
logoutUser: typeof logoutUser;
|
||||
setAccessToken: typeof setAccessToken;
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
}
|
||||
|
||||
interface SettingsProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const SettingsPage: React.FC<SettingsProps> = ({
|
||||
speakers,
|
||||
speakerSessions,
|
||||
logoutUser,
|
||||
setAccessToken,
|
||||
setIsLoggedIn,
|
||||
}) => {
|
||||
const [events, setEvents] = useState<Event[] | []>([]);
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
const router = useIonRouter();
|
||||
|
||||
useEffect(() => {
|
||||
getEvents().then(({ data }) => {
|
||||
console.log({ data });
|
||||
setEvents(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
function handleBackButtonClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
function handleLanguageClick() {
|
||||
router.push(paths.CHANGE_LANGUAGE);
|
||||
}
|
||||
|
||||
function handleNotImplementedClick() {
|
||||
router.push(paths.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
function handleDemoPageClick() {
|
||||
router.push(paths.DEMO_PAGE);
|
||||
}
|
||||
|
||||
function handleServiceAgreementClick() {
|
||||
router.push(paths.SERVICE_AGREEMENT);
|
||||
}
|
||||
|
||||
function handlePrivacyAgreementClick() {
|
||||
router.push(paths.PRIVACY_AGREEMENT);
|
||||
}
|
||||
|
||||
const [showLogoutConfirmModal, setShowLogoutConfirmModal] = useState<boolean>(false);
|
||||
function handleConfirmLogoutClick() {
|
||||
setShowLogoutConfirmModal(true);
|
||||
}
|
||||
|
||||
function handleLogoutClick() {
|
||||
setAccessToken();
|
||||
setIsLoggedIn(false);
|
||||
|
||||
router.push('/tabs', 'forward', 'replace');
|
||||
|
||||
setShowLogoutConfirmModal(false);
|
||||
}
|
||||
function handleLogoutCancel() {
|
||||
setShowLogoutConfirmModal(false);
|
||||
}
|
||||
|
||||
function handleDemoWeatherApp() {
|
||||
router.push(paths.DEMO_WEATHER_APP);
|
||||
}
|
||||
|
||||
function handleDemoReactShopClick() {
|
||||
router.push(paths.DEMO_REACT_SHOP);
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader translucent={true} className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
{/* <IonMenuButton /> */}
|
||||
<IonButton shape="round" onClick={() => handleBackButtonClick()}>
|
||||
<IonIcon slot="icon-only" icon={chevronBackOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-start' }}>
|
||||
<IonIcon icon={settingsOutline} size="large"></IonIcon>
|
||||
<IonTitle>Setting</IonTitle>
|
||||
</div>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen={true}>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Setting</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => handleDemoWeatherApp()}>
|
||||
<IonIcon slot="start" icon={sunny} size="large"></IonIcon>
|
||||
<IonLabel>Weather App</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<IonList inset={false}>
|
||||
<IonItem button={true} onClick={() => handleDemoReactShopClick()}>
|
||||
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
|
||||
<IonLabel>Demo React Shop</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline}></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
{/* REQ0058/logout */}
|
||||
<IonModal
|
||||
isOpen={showLogoutConfirmModal}
|
||||
initialBreakpoint={0.5}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
>
|
||||
<IonContent
|
||||
className="ion-padding"
|
||||
style={{
|
||||
'--background': 'pink',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
}}
|
||||
>
|
||||
<div style={{ marginTop: '1rem', width: '50px', height: '50px' }}>
|
||||
<IonIcon icon={alertCircleOutline} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontWeight: '1rem',
|
||||
fontSize: '1.5rem',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontWeight: '1rem',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
>
|
||||
Unable to receive notifications after logging out
|
||||
</div>
|
||||
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<IonButton size="large" fill="outline" shape="round" onClick={handleLogoutCancel}>
|
||||
Cancel
|
||||
</IonButton>
|
||||
<IonButton size="large" shape="round" onClick={handleLogoutClick}>
|
||||
Logout
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state),
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
logoutUser,
|
||||
setAccessToken,
|
||||
setIsLoggedIn,
|
||||
},
|
||||
component: React.memo(SettingsPage),
|
||||
});
|
103
03_source/mobile/src/pages/DemoList/style.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
#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/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/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;
|
||||
}
|
14
03_source/mobile/src/pages/DemoList/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface Event {
|
||||
eventDate: Date;
|
||||
joinMembers: undefined;
|
||||
title: string;
|
||||
price: number;
|
||||
currency: string;
|
||||
duration_m: number;
|
||||
ageBottom: number;
|
||||
ageTop: number;
|
||||
location: string;
|
||||
avatar: string;
|
||||
//
|
||||
id: string;
|
||||
}
|
89
03_source/mobile/src/pages/DemoReactShop/Categories.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRouterLink,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import {
|
||||
checkmarkOutline,
|
||||
chevronBackOutline,
|
||||
chevronDownCircleOutline,
|
||||
closeOutline,
|
||||
heart,
|
||||
languageOutline,
|
||||
menuOutline,
|
||||
} from 'ionicons/icons';
|
||||
import { capitalize, productInfo } from '../utils';
|
||||
|
||||
const Categories = () => {
|
||||
const categories = Object.keys(productInfo);
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start" onClick={handleBackClick}>
|
||||
<IonButton shape="round" id="open-modal" expand="block">
|
||||
<IonIcon slot="icon-only" icon={chevronBackOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Ionic Shop</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large" className="page-title">
|
||||
<IonLabel>ionic</IonLabel>
|
||||
<IonNote>shop</IonNote>
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow>
|
||||
{categories.map((category, idx) => (
|
||||
<IonRouterLink key={idx} routerLink={`/categories/${category.toLowerCase()}`}>
|
||||
<div style={{ display: 'flex', color: 'white' }}>
|
||||
<img src={productInfo[category].coverImage} alt="cover" />
|
||||
<p
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'absolute',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||
width: '50%',
|
||||
paddingTop: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
margin: '0 auto',
|
||||
fontSize: '2rem',
|
||||
}}
|
||||
>
|
||||
{capitalize(category)}
|
||||
</p>
|
||||
</div>
|
||||
</IonRouterLink>
|
||||
|
||||
// <IonButton key={c} routerLink={`/categories/${c.toLowerCase()}`}>{capitalize(c)}</IonButton>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Categories;
|
@@ -0,0 +1,18 @@
|
||||
.categoryContainer {
|
||||
display: flex;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.categoryContainer p {
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
width: 50%;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
margin: 0 auto;
|
||||
font-size: 2rem;
|
||||
}
|
68
03_source/mobile/src/pages/DemoReactShop/Category.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRouterLink,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { chevronBack } from 'ionicons/icons';
|
||||
import { useParams } from 'react-router';
|
||||
import { capitalize, productInfo } from '../utils';
|
||||
|
||||
import styles from './Categories.module.scss';
|
||||
|
||||
const Category = () => {
|
||||
const router = useIonRouter();
|
||||
const { category } = useParams();
|
||||
const productTypes = Object.keys(productInfo[category].productTypes);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton className="custom-back" onClick={() => router.goBack()}>
|
||||
<IonIcon icon={chevronBack} />
|
||||
<IonLabel>Back</IonLabel>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle>{category}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large" className="page-title">
|
||||
<IonNote>shop</IonNote>
|
||||
<IonLabel>{category}</IonLabel>
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow>
|
||||
{productTypes.map((product) => (
|
||||
<IonRouterLink
|
||||
key={`${category}_${product}`}
|
||||
routerLink={`/categories/${category}/${product.toLowerCase().replaceAll(' ', '_')}`}
|
||||
>
|
||||
<div className={styles.categoryContainer}>
|
||||
<img src={productInfo[category].productTypes[product].coverImage} alt="cover" />
|
||||
<p>{capitalize(product)}</p>
|
||||
</div>
|
||||
</IonRouterLink>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Category;
|
102
03_source/mobile/src/pages/DemoReactShop/Favourites.jsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonImg,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonText,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonModal,
|
||||
} from '@ionic/react';
|
||||
import { heartOutline } from 'ionicons/icons';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { useState } from 'react';
|
||||
import { ProductModal } from '../components/ProductModal';
|
||||
import { FavouritesStore } from '../store';
|
||||
import { getFavourites } from '../store/Selectors';
|
||||
|
||||
const Favourites = () => {
|
||||
const favourites = useStoreState(FavouritesStore, getFavourites);
|
||||
|
||||
const [selectedProduct, setSelectedProduct] = useState([]);
|
||||
const [presentProductModal, dismissProductModal] = useIonModal(ProductModal, {
|
||||
dismiss: () => dismissProductModal(),
|
||||
product: selectedProduct,
|
||||
});
|
||||
|
||||
const handleProductModal = (product) => {
|
||||
setSelectedProduct(product);
|
||||
presentProductModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Favourites</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Favourites</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid className="animate__animated">
|
||||
<IonRow>
|
||||
{favourites.map((product, index) => {
|
||||
if (
|
||||
product.image !== null &&
|
||||
product.image !== '' &&
|
||||
!product.image.includes('Placeholder')
|
||||
) {
|
||||
return (
|
||||
<IonCol
|
||||
onClick={() => handleProductModal(product)}
|
||||
key={index}
|
||||
size="6"
|
||||
sizeXs="6"
|
||||
sizeSm="3"
|
||||
sizeMd="3"
|
||||
sizeXl="2"
|
||||
>
|
||||
<IonImg src={product.image} style={{ marginBottom: '0.25rem' }} />
|
||||
<IonLabel>
|
||||
<h3>{product.title}</h3>
|
||||
<p>{product.price}</p>
|
||||
</IonLabel>
|
||||
</IonCol>
|
||||
);
|
||||
} else return null;
|
||||
})}
|
||||
</IonRow>
|
||||
|
||||
{favourites.length === 0 && (
|
||||
<IonRow className="ion-text-center ion-justify-content-center">
|
||||
<IonCol size="10">
|
||||
<IonText color="dark">
|
||||
<h1>No favourites yet</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<h3>
|
||||
Add some by clicking the <IonIcon icon={heartOutline} color="danger" /> icon on
|
||||
a product
|
||||
</h3>
|
||||
</IonText>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
)}
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Favourites;
|
37
03_source/mobile/src/pages/DemoReactShop/Helloworld.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// REQ0041/home_discover_event_tab
|
||||
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
} from '@ionic/react';
|
||||
import { menuOutline } from 'ionicons/icons';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import './style.scss';
|
||||
|
||||
const Helloworld: React.FC = () => {
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader translucent={true} className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="end">
|
||||
{/* <IonMenuButton /> */}
|
||||
<IonButton shape="round" id="events-open-modal" expand="block">
|
||||
<IonIcon slot="icon-only" icon={menuOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Discover Events</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen={true}>Helloworld</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Helloworld;
|
81
03_source/mobile/src/pages/DemoReactShop/MainTabs.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
// REQ0116/main-tab
|
||||
|
||||
import React from 'react';
|
||||
import { IonTabs, IonRouterOutlet, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { calendar, location, informationCircle, people } from 'ionicons/icons';
|
||||
import SchedulePage from '../SchedulePage';
|
||||
import SpeakerList from '../SpeakerList';
|
||||
import SpeakerDetail from '../SpeakerDetail';
|
||||
import SessionDetail from '../SessionDetail';
|
||||
import MapView from '../MapView';
|
||||
import About from '../About';
|
||||
import EventList from '../EventList';
|
||||
import MembersNearByList from '../MembersNearByList';
|
||||
import OrderList from '../OrderList';
|
||||
import MyProfile from '../MyProfile';
|
||||
import MessageList from '../MessageList';
|
||||
import paths from '../../paths';
|
||||
import Favourites from '../Favourites';
|
||||
import TabAppRoute from '../../TabAppRoute';
|
||||
|
||||
interface MainTabsProps {}
|
||||
|
||||
const MainTabs: React.FC<MainTabsProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
{/* REQ0117/default-route */}
|
||||
<Redirect exact path="/tabs" to="/tabs/events" />
|
||||
{/*
|
||||
Using the render method prop cuts down the number of renders your components will have due to route changes.
|
||||
Use the component prop when your component depends on the RouterComponentProps passed in automatically.
|
||||
*/}
|
||||
<Route path="/tabs/schedule" render={() => <SchedulePage />} exact={true} />
|
||||
<Route path="/tabs/speakers" render={() => <SpeakerList />} exact={true} />
|
||||
<Route path="/tabs/speakers/:id" component={SpeakerDetail} exact={true} />
|
||||
|
||||
<Route path="/tabs/schedule/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/speakers/sessions/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/map" render={() => <MapView />} exact={true} />
|
||||
|
||||
<Route path="/tabs/about" render={() => <About />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<TabAppRoute />
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
{/*
|
||||
<IonTabButton tab="speakers" href={'/tabs/speakers'}>
|
||||
<IonIcon icon={calendar} />
|
||||
<IonLabel>Speakers</IonLabel>
|
||||
</IonTabButton>
|
||||
*/}
|
||||
|
||||
<IonTabButton tab="events" href={paths.EVENT_LIST}>
|
||||
<IonIcon icon={calendar} />
|
||||
<IonLabel>Event</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="nearby" href={paths.NEARBY_LIST}>
|
||||
<IonIcon icon={people} />
|
||||
<IonLabel>Nearby</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="orders" href={paths.ORDERS_LIST}>
|
||||
<IonIcon icon={location} />
|
||||
<IonLabel>Order</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="message" href={paths.MESSAGE_LIST}>
|
||||
<IonIcon icon={informationCircle} />
|
||||
<IonLabel>Message</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="my_profile" href={paths.PROFILE}>
|
||||
<IonIcon icon={informationCircle} />
|
||||
<IonLabel>Profile</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainTabs;
|
211
03_source/mobile/src/pages/DemoReactShop/ProductType.jsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import {
|
||||
IonBreadcrumb,
|
||||
IonBreadcrumbs,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonImg,
|
||||
IonLabel,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonModal,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { chevronBack, filter } from 'ionicons/icons';
|
||||
import { useRef } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { FilterModal } from '../components/FilterModal';
|
||||
import { ProductModal } from '../components/ProductModal';
|
||||
import { capitalize, productInfo } from '../utils';
|
||||
|
||||
const ProductType = () => {
|
||||
const router = useIonRouter();
|
||||
const { category, type } = useParams();
|
||||
const productsRef = useRef();
|
||||
|
||||
const [products, setProducts] = useState([]);
|
||||
const [filteredProducts, setFilteredProducts] = useState([]);
|
||||
const [filterCriteria, setFilterCriteria] = useState('None');
|
||||
|
||||
const filters = productInfo[category].productTypes[type].filters;
|
||||
const searchPlaceholder = productInfo[category].productTypes[type].searchPlaceholder;
|
||||
|
||||
const [selectedProduct, setSelectedProduct] = useState([]);
|
||||
const [presentProductModal, dismissProductModal] = useIonModal(ProductModal, {
|
||||
dismiss: () => dismissProductModal(),
|
||||
category,
|
||||
type,
|
||||
product: selectedProduct,
|
||||
});
|
||||
|
||||
const handleProductModal = (product) => {
|
||||
setSelectedProduct(product);
|
||||
presentProductModal();
|
||||
};
|
||||
|
||||
const [present, dismiss] = useIonModal(FilterModal, {
|
||||
dismiss: () => dismiss(),
|
||||
|
||||
filterCriteria,
|
||||
setFilterCriteria,
|
||||
productsRef,
|
||||
filters,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const getProducts = async () => {
|
||||
const response = await fetch(`/data/${category}/${type}.json`);
|
||||
const data = await response.json();
|
||||
setProducts(data);
|
||||
setFilteredProducts(data);
|
||||
};
|
||||
|
||||
getProducts();
|
||||
}, [category, type]);
|
||||
|
||||
const openModal = () => {
|
||||
present({
|
||||
breakpoints: [0, 0.25],
|
||||
initialBreakpoint: 0.25,
|
||||
backdropBreakpoint: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const performSearch = (e) => {
|
||||
const searchCriteria = e.target.value.toLowerCase();
|
||||
let tempFilteredProducts = [...products];
|
||||
|
||||
if (searchCriteria !== '') {
|
||||
tempFilteredProducts = tempFilteredProducts.filter((product) =>
|
||||
product.title.toLowerCase().includes(searchCriteria)
|
||||
);
|
||||
setFilteredProducts(tempFilteredProducts);
|
||||
} else {
|
||||
setFilteredProducts(products);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton className="custom-back" onClick={() => router.goBack()}>
|
||||
<IonIcon icon={chevronBack} />
|
||||
<IonLabel>Back</IonLabel>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle>{capitalize(type)}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large" className="page-title">
|
||||
<IonNote>shop</IonNote>
|
||||
<IonLabel>{category}</IonLabel>
|
||||
</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-align-items-center ion-text-center ion-justify-content-between">
|
||||
<IonCol size="10">
|
||||
<IonBreadcrumbs>
|
||||
<IonBreadcrumb active={false} color="medium">
|
||||
{capitalize(category)}
|
||||
</IonBreadcrumb>
|
||||
<IonBreadcrumb
|
||||
separator={false}
|
||||
color={filterCriteria !== 'None' && 'medium'}
|
||||
active={filterCriteria === 'None' ? true : false}
|
||||
>
|
||||
{capitalize(type)}
|
||||
</IonBreadcrumb>
|
||||
{filterCriteria !== 'None' && (
|
||||
<IonBreadcrumb color="dark" active={true}>
|
||||
<IonIcon slot="start" icon={filter} />
|
||||
{filterCriteria}
|
||||
</IonBreadcrumb>
|
||||
)}
|
||||
</IonBreadcrumbs>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="2" className="ion-text-right">
|
||||
<div
|
||||
onClick={openModal}
|
||||
style={{
|
||||
display: 'flex',
|
||||
color: '#828282',
|
||||
float: 'right',
|
||||
padding: '0.5rem',
|
||||
backgroundColor: '#F4F5F8',
|
||||
marginRight: '0.5rem',
|
||||
width: 'fit-content',
|
||||
}}
|
||||
>
|
||||
<IonIcon icon={filter} />
|
||||
<IonLabel> Filter</IonLabel>
|
||||
</div>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonSearchbar
|
||||
color="light"
|
||||
animated={true}
|
||||
style={{ '--border-radius': 'none' }}
|
||||
placeholder={`Try '${searchPlaceholder}'`}
|
||||
onIonChange={(e) => performSearch(e)}
|
||||
/>
|
||||
|
||||
<IonGrid ref={productsRef} className="animate__animated">
|
||||
<IonRow>
|
||||
{filteredProducts.map((product, index) => {
|
||||
if (
|
||||
product.image !== null &&
|
||||
product.image !== '' &&
|
||||
!product.image.includes('Placeholder')
|
||||
) {
|
||||
return (
|
||||
<IonCol
|
||||
onClick={() => handleProductModal(product)}
|
||||
key={index}
|
||||
size="6"
|
||||
sizeXs="6"
|
||||
sizeSm="3"
|
||||
sizeMd="3"
|
||||
sizeXl="2"
|
||||
style={{
|
||||
display:
|
||||
(filterCriteria !== 'None' &&
|
||||
product.title.toLowerCase().includes(filterCriteria.toLowerCase())) ||
|
||||
filterCriteria === 'None'
|
||||
? 'block'
|
||||
: 'none',
|
||||
}}
|
||||
>
|
||||
<IonImg src={product.image} style={{ marginBottom: '0.25rem' }} />
|
||||
<IonLabel>
|
||||
<h3>{product.title}</h3>
|
||||
<p>{product.price}</p>
|
||||
</IonLabel>
|
||||
</IonCol>
|
||||
);
|
||||
} else return null;
|
||||
})}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductType;
|
@@ -0,0 +1,58 @@
|
||||
import { CreateAnimation, IonButton, IonIcon } from "@ionic/react";
|
||||
import { cartOutline } from "ionicons/icons";
|
||||
import { useRef, useState } from "react";
|
||||
import { addToCart } from "../store/CartStore";
|
||||
|
||||
export const AddToCartButton = ({product}) => {
|
||||
|
||||
const animationRef = useRef();
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
||||
const floatStyle = {
|
||||
|
||||
display: hidden ? "none" : "",
|
||||
position: "absolute"
|
||||
};
|
||||
|
||||
const floatGrowAnimation = {
|
||||
|
||||
property: "transform",
|
||||
fromValue: "translateY(0) scale(1)",
|
||||
toValue: "translateY(-55px) scale(1.5)"
|
||||
};
|
||||
|
||||
const colorAnimation = {
|
||||
|
||||
property: "color",
|
||||
fromValue: "green",
|
||||
toValue: "green"
|
||||
};
|
||||
|
||||
const mainAnimation = {
|
||||
|
||||
duration: 1500,
|
||||
iterations: "1",
|
||||
fromTo: [ floatGrowAnimation, colorAnimation ],
|
||||
easing: "cubic-bezier(0.25, 0.7, 0.25, 0.7)"
|
||||
};
|
||||
|
||||
const handleAddToCart = async product => {
|
||||
|
||||
setHidden(false);
|
||||
await animationRef.current.animation.play();
|
||||
setHidden(true);
|
||||
addToCart(product);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonButton color="dark" expand="full" onClick={() => handleAddToCart(product)}>
|
||||
<IonIcon icon={cartOutline} />
|
||||
Add to Cart
|
||||
|
||||
<CreateAnimation ref={animationRef} {...mainAnimation}>
|
||||
<IonIcon icon={cartOutline} size="large" style={floatStyle} />
|
||||
</CreateAnimation>
|
||||
</IonButton>
|
||||
);
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
import { IonBreadcrumb, IonBreadcrumbs, IonIcon } from "@ionic/react";
|
||||
import { fastFoodOutline } from "ionicons/icons";
|
||||
import { useState } from "react";
|
||||
|
||||
export const Breadcrumbs = () => {
|
||||
|
||||
const [maxItems, setMaxItems] = useState(2);
|
||||
|
||||
const handleClick = () => {
|
||||
|
||||
setMaxItems(undefined);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonBreadcrumbs maxItems={maxItems} onIonCollapsedClick={handleClick}>
|
||||
<IonBreadcrumb color="medium">Page 1</IonBreadcrumb>
|
||||
<IonBreadcrumb color="medium">Page 2</IonBreadcrumb>
|
||||
<IonBreadcrumb color="medium">Page 3</IonBreadcrumb>
|
||||
<IonBreadcrumb>Page 4</IonBreadcrumb>
|
||||
</IonBreadcrumbs>
|
||||
);
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CartStore } from '../store';
|
||||
import { addToCart } from '../store/CartStore';
|
||||
import { getCart } from '../store/Selectors';
|
||||
|
||||
const {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonButtons,
|
||||
IonIcon,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonRow,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonText,
|
||||
IonThumbnail,
|
||||
IonFooter,
|
||||
IonCol,
|
||||
IonButton,
|
||||
IonItemSliding,
|
||||
IonItemOptions,
|
||||
IonItemOption,
|
||||
} = require('@ionic/react');
|
||||
const { close } = require('ionicons/icons');
|
||||
|
||||
export const CartModal = (props) => {
|
||||
const cart = useStoreState(CartStore, getCart);
|
||||
const [totalPrice, setTotalPrice] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let total = 0;
|
||||
cart.forEach((item) => (total += parseInt(item.price.replace('£', ''))));
|
||||
setTotalPrice(total);
|
||||
}, [cart]);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Cart</IonTitle>
|
||||
<IonButtons slot="end" onClick={props.close}>
|
||||
<IonIcon icon={close} size="large" />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonGrid>
|
||||
<IonRow style={{ borderBottom: '1px solid black' }} className="ion-margin-bottom">
|
||||
<IonItem lines="none">
|
||||
<IonLabel>
|
||||
<h1>{cart.length} products in your cart</h1>
|
||||
<IonText color="medium">
|
||||
<h2>Review products and checkout</h2>
|
||||
</IonText>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
|
||||
{cart.map((item, index) => (
|
||||
<IonItemSliding>
|
||||
<IonItem
|
||||
key={index}
|
||||
lines="none"
|
||||
className="ion-padding-end"
|
||||
style={{ paddingTop: '0.75rem', paddingBottom: '0.75rem' }}
|
||||
>
|
||||
<IonThumbnail>
|
||||
<img src={item.image} alt="item" />
|
||||
</IonThumbnail>
|
||||
<IonLabel className="ion-padding-start ion-text-wrap">
|
||||
<h2>{item.title}</h2>
|
||||
<p>{item.price}</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonItemOptions side="end">
|
||||
<IonItemOption color="danger" onClick={() => addToCart(item)}>
|
||||
Remove
|
||||
</IonItemOption>
|
||||
</IonItemOptions>
|
||||
</IonItemSliding>
|
||||
))}
|
||||
</IonContent>
|
||||
|
||||
<IonFooter
|
||||
className="ion-padding-bottom ion-padding-start ion-padding-end"
|
||||
style={{ paddingBottom: '3rem' }}
|
||||
>
|
||||
<IonRow className="ion-justify-content-between">
|
||||
<IonCol size="8">
|
||||
<h1>Total</h1>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4">
|
||||
<h1>£{totalPrice.toFixed(2)}</h1>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
<IonButton expand="block" color="dark">
|
||||
Checkout →
|
||||
</IonButton>
|
||||
</IonFooter>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
@@ -0,0 +1,36 @@
|
||||
import { IonButton, IonCol, IonContent, IonGrid, IonHeader, IonRow, IonTitle, IonToolbar } from "@ionic/react";
|
||||
|
||||
export const FilterModal = ({productsRef, filterCriteria, setFilterCriteria, dismiss, filters}) => {
|
||||
|
||||
const filterProducts = async filter => {
|
||||
|
||||
await productsRef.current.classList.add("animate__fadeOutLeft");
|
||||
|
||||
setTimeout(() => {
|
||||
productsRef.current.classList.remove("animate__fadeOutLeft");
|
||||
productsRef.current.classList.add("animate__fadeInRight");
|
||||
setFilterCriteria(filter);
|
||||
}, 500);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonContent>
|
||||
<IonHeader>
|
||||
<IonToolbar color="none" style={{"--border-style": "none"}}>
|
||||
<IonTitle className="ion-margin-top">Filter</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
{filters.map(f => (
|
||||
<IonCol key={f} size="3">
|
||||
<IonButton expand="full" color={filterCriteria === f ? "dark" : "light"} onClick={() => filterProducts(f)}>{f}</IonButton>
|
||||
</IonCol>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
);
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
ion-card {
|
||||
margin: 0;
|
||||
/* margin-top: var(--ion-safe-area-top); */
|
||||
z-index: -1;
|
||||
|
||||
border-radius: 0px;
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: none;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
@supports not (aspect-ratio: 1 / 1) {
|
||||
ion-card::before {
|
||||
float: left;
|
||||
padding-top: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
ion-card::after {
|
||||
display: block;
|
||||
content: '';
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
ion-card-header {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
/* background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.7) 100%); */
|
||||
background: rgba(0, 0, 0, 0.5)
|
||||
}
|
||||
|
||||
ion-card-title,
|
||||
ion-card-subtitle {
|
||||
color: white;
|
||||
}
|
||||
|
||||
ion-card-header ion-card-title {
|
||||
|
||||
margin: 0 0 6px 0;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
ion-card-header ion-card-subtitle {
|
||||
|
||||
text-transform: none;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ion-card-content {
|
||||
height: calc(60px + var(--ion-safe-area-top));
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 100%);
|
||||
}
|
||||
|
||||
#close-button {
|
||||
position: fixed;
|
||||
|
||||
top: max(var(--ion-safe-area-top), 16px);
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
#fave-button {
|
||||
position: fixed;
|
||||
|
||||
top: max(var(--ion-safe-area-top), 16px);
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
#product-view-buttons {
|
||||
|
||||
z-index: 10;
|
||||
background: linear-gradient(360deg, rgba(0, 0, 0, 0) 0%, rgba(82, 82, 82, 0.9) 100%) !important;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.sticky-bottom {
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
import { IonButton, IonButtons, IonCard, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCol, IonContent, IonFooter, IonIcon, IonLabel, IonNote, IonRow, IonText, IonToolbar } from "@ionic/react";
|
||||
import { closeCircle, heart, heartOutline } from "ionicons/icons";
|
||||
import { useStoreState } from "pullstate";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { checkFavourites } from "../store/Selectors";
|
||||
import { addToFavourites } from "../store/FavouritesStore";
|
||||
import { FavouritesStore } from "../store";
|
||||
|
||||
import "./ProductModal.css";
|
||||
import { ProductReviews } from "./ProductReviews";
|
||||
import { ProductSpecificationsAccordion } from "./ProductSpecificationsAccordion";
|
||||
import { AddToCartButton } from "./AddToCartButton";
|
||||
|
||||
export const ProductModal = props => {
|
||||
|
||||
const { dismiss, category = false, product } = props;
|
||||
const isFavourite = useStoreState(FavouritesStore, checkFavourites(product));
|
||||
const contentRef = useRef(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonContent ref={contentRef}>
|
||||
<IonButtons id="product-view-buttons">
|
||||
<IonButton color="light" onClick={dismiss} id="close-button">
|
||||
<IonIcon icon={closeCircle} size="large" />
|
||||
</IonButton>
|
||||
|
||||
<IonButton color="danger" onClick={() => addToFavourites(product, category)} id="fave-button">
|
||||
<IonIcon icon={isFavourite ? heart : heartOutline} size="large" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonCard style={{backgroundImage: `url('${product.image}')`}}>
|
||||
<IonCardHeader>
|
||||
<IonCardTitle>{product.title}</IonCardTitle>
|
||||
<IonCardSubtitle>{product.price}</IonCardSubtitle>
|
||||
</IonCardHeader>
|
||||
</IonCard>
|
||||
|
||||
<div className="ion-padding">
|
||||
|
||||
<IonRow className="ion-align-items-center">
|
||||
<IonCol>
|
||||
<IonText size="large" className="page-title">
|
||||
<IonNote>shop</IonNote>
|
||||
<IonLabel>{category ? category : "Favourite"}</IonLabel>
|
||||
</IonText>
|
||||
</IonCol>
|
||||
|
||||
<ProductReviews reviews={product.reviews} />
|
||||
</IonRow>
|
||||
<h2>Product Description</h2>
|
||||
<IonText>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam elit felis, molestie id venenatis at, commodo ac tortor. Pellentesque tempus aliquet purus, sed vulputate elit tempus ut.</IonText>
|
||||
|
||||
<h2>Product Specifications</h2>
|
||||
<ProductSpecificationsAccordion contentRef={contentRef} type={category} />
|
||||
</div>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter collapse="fade">
|
||||
<IonToolbar>
|
||||
<IonRow className="ion-justify-content-between ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonButton expand="full" color="light">{product.price}</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="8" className="ion-text-right">
|
||||
<AddToCartButton product={product} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
import { IonCol, IonIcon, IonNote } from "@ionic/react";
|
||||
import { star } from "ionicons/icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { randomCount } from "../utils";
|
||||
|
||||
export const ProductReviews = () => {
|
||||
|
||||
// This count could come from the product (if real data was fed)
|
||||
const [reviewCount, setReviewCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
setReviewCount(randomCount());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IonCol className="ion-text-right">
|
||||
<IonIcon color="warning" icon={star} />
|
||||
|
||||
<IonNote>{reviewCount} review{reviewCount > 1 && "s"}</IonNote>
|
||||
</IonCol>
|
||||
);
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
import { IonAccordion, IonAccordionGroup, IonItem, IonLabel, IonList, IonNote } from "@ionic/react";
|
||||
import { useRef } from "react";
|
||||
import { productSpecs } from "../utils";
|
||||
|
||||
export const ProductSpecificationsAccordion = ({type, contentRef}) => {
|
||||
|
||||
const accordionGroupRef = useRef(null);
|
||||
|
||||
const log = () => {
|
||||
|
||||
const selectedAccordion = accordionGroupRef.current.value;
|
||||
|
||||
if (selectedAccordion) {
|
||||
|
||||
setTimeout(() => contentRef.current.scrollToBottom(400), 200);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<IonAccordionGroup ref={accordionGroupRef} onIonChange={log}>
|
||||
{Object.keys(productSpecs).map((spec, index) => {
|
||||
|
||||
const {header, options, wrapText = false, noteColor = false} = productSpecs[spec];
|
||||
|
||||
return (
|
||||
|
||||
<IonAccordion key={`accordion_${header}_${index}`}>
|
||||
<IonItem slot="header" className="ion-no-padding">
|
||||
<IonLabel>{header}</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
|
||||
<IonList slot="content" className="ion-no-padding">
|
||||
{options.map((option, index2) => {
|
||||
|
||||
const {label, value} = option;
|
||||
|
||||
return (
|
||||
|
||||
<IonItem key={`accordion_${header}_${option}_${index2}`} className="ion-no-padding">
|
||||
<IonLabel>
|
||||
<h3>{label}</h3>
|
||||
</IonLabel>
|
||||
<IonLabel className={wrapText && "ion-text-wrap"}>
|
||||
<IonNote color={noteColor ? (value ? "success" : "danger") : "medium"}>
|
||||
{noteColor ? (value ? "In stock" : "Out of stock") : value}
|
||||
</IonNote>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
);
|
||||
})}
|
||||
</IonAccordionGroup>
|
||||
);
|
||||
}
|
129
03_source/mobile/src/pages/DemoReactShop/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
// REQ0116/main-tab
|
||||
|
||||
import React, { useRef, useState } from 'react';
|
||||
import {
|
||||
IonTabs,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonModal,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { calendar, location, informationCircle, people } from 'ionicons/icons';
|
||||
import SchedulePage from '../SchedulePage';
|
||||
import SpeakerList from '../SpeakerList';
|
||||
import SpeakerDetail from '../SpeakerDetail';
|
||||
import SessionDetail from '../SessionDetail';
|
||||
import MapView from '../MapView';
|
||||
import About from '../About';
|
||||
import paths from '../../paths';
|
||||
import TabAppRoute from '../../TabAppRoute';
|
||||
import { CartStore } from './store';
|
||||
import { getCartCount } from './store/Selectors';
|
||||
import { CartModal } from './components/CartModal';
|
||||
|
||||
interface MainTabsProps {}
|
||||
|
||||
const DemoReactShop: React.FC<MainTabsProps> = () => {
|
||||
const cartCount = useStoreState(CartStore, getCartCount);
|
||||
const [selected, setSelected] = useState('tab0');
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
const handleClick = (tab) => {
|
||||
tab === 'tabCart' ? setOpen(true) : setSelected(tab);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet ref={ref}>
|
||||
{ReactShopPages.map((page, index) => (
|
||||
<Route
|
||||
key={`route_${index}`}
|
||||
exact={true}
|
||||
path={`/demo-react-shop${page.href}`}
|
||||
component={page.component}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/*
|
||||
<Route exact path="/demo-react-shop">
|
||||
<Redirect to={ReactShopPages.filter((p) => p.default)[0].href} />
|
||||
</Route>
|
||||
*/}
|
||||
<Redirect exact path="/demo-react-shop" to="/demo-react-shop/categories" />
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
|
||||
<IonTabBar slot="bottom">
|
||||
{ReactShopPages.map((page, index) => {
|
||||
const isSelected = selected === `tab${index}`;
|
||||
|
||||
if (page.isTab) {
|
||||
return (
|
||||
<IonTabButton
|
||||
key={`tab${index}`}
|
||||
tab={`tab${index}`}
|
||||
href={`/demo-react-shop${page.href}`}
|
||||
>
|
||||
<IonIcon icon={page.icon} />
|
||||
{isSelected && <div className="tab-dot" />}
|
||||
</IonTabButton>
|
||||
);
|
||||
} else return null;
|
||||
})}
|
||||
|
||||
<IonTabButton tab="tabCart">
|
||||
<IonIcon icon={cartOutline} />
|
||||
<div className="cart-count">{cartCount}</div>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
|
||||
{/* <IonModal presentingElement={ref.current} isOpen={open} onDidDismiss={() => setOpen(false)}> */}
|
||||
{/* <CartModal close={() => setOpen(false)} /> */}
|
||||
{/* </IonModal> */}
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoReactShop;
|
||||
|
||||
import { cartOutline, heartOutline, homeOutline, shirtOutline } from 'ionicons/icons';
|
||||
|
||||
import Categories from './Categories';
|
||||
import Favourites from './Favourites';
|
||||
import ProductType from './ProductType';
|
||||
import Category from './Category';
|
||||
import { useStoreState } from 'pullstate';
|
||||
|
||||
export const ReactShopPages = [
|
||||
{
|
||||
href: '/categories',
|
||||
icon: shirtOutline,
|
||||
component: Categories,
|
||||
default: true,
|
||||
isTab: true,
|
||||
},
|
||||
{
|
||||
href: '/categories/:category/:type',
|
||||
component: ProductType,
|
||||
default: false,
|
||||
isTab: false,
|
||||
},
|
||||
{
|
||||
href: '/categories/:category',
|
||||
icon: shirtOutline,
|
||||
component: Category,
|
||||
default: true,
|
||||
isTab: false,
|
||||
},
|
||||
{
|
||||
href: '/favourites',
|
||||
icon: heartOutline,
|
||||
component: Favourites,
|
||||
default: false,
|
||||
isTab: true,
|
||||
},
|
||||
];
|
27
03_source/mobile/src/pages/DemoReactShop/store/CartStore.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
const CartStore = new Store({
|
||||
|
||||
cart: []
|
||||
});
|
||||
|
||||
export default CartStore;
|
||||
|
||||
export const addToCart = product => {
|
||||
|
||||
const currentCart = CartStore.getRawState().cart;
|
||||
const added = !currentCart.includes(product);
|
||||
|
||||
CartStore.update(s => {
|
||||
|
||||
if (currentCart.includes(product)) {
|
||||
|
||||
s.cart = currentCart.filter(current => current !== product);
|
||||
} else {
|
||||
|
||||
s.cart = [ ...s.cart, product ];
|
||||
}
|
||||
});
|
||||
|
||||
return added;
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
const FavouritesStore = new Store({
|
||||
|
||||
favourites: []
|
||||
});
|
||||
|
||||
export default FavouritesStore;
|
||||
|
||||
export const checkIfFavourite = product => {
|
||||
|
||||
const currentFavourites = FavouritesStore.getRawState().favourites;
|
||||
const isFavourite = currentFavourites.includes(product);
|
||||
|
||||
return isFavourite;
|
||||
}
|
||||
|
||||
export const addToFavourites = (product, category) => {
|
||||
|
||||
const currentFavourites = FavouritesStore.getRawState().favourites;
|
||||
const added = !currentFavourites.includes(product);
|
||||
|
||||
FavouritesStore.update(s => {
|
||||
|
||||
if (!added) {
|
||||
|
||||
s.favourites = currentFavourites.filter(current => current !== product);
|
||||
} else {
|
||||
|
||||
s.favourites = [ ...s.favourites, product ];
|
||||
}
|
||||
});
|
||||
|
||||
return added;
|
||||
}
|
10
03_source/mobile/src/pages/DemoReactShop/store/Selectors.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const getState = (state) => state;
|
||||
|
||||
// General getters
|
||||
export const getFavourites = createSelector(getState, (state) => state.favourites);
|
||||
export const checkFavourites = (product) =>
|
||||
createSelector(getState, (state) => state.favourites.includes(product));
|
||||
export const getCart = createSelector(getState, (state) => state.cart);
|
||||
export const getCartCount = createSelector(getState, (state) => state.cart.length);
|
2
03_source/mobile/src/pages/DemoReactShop/store/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as FavouritesStore } from './FavouritesStore';
|
||||
export { default as CartStore } from './CartStore';
|
103
03_source/mobile/src/pages/DemoReactShop/style.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
#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/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/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;
|
||||
}
|
@@ -1,8 +1,18 @@
|
||||
// REQ0041/home_discover_event_tab
|
||||
|
||||
import { IonPage, IonHeader, IonToolbar, IonButtons, IonButton, IonIcon, IonTitle, IonContent } from '@ionic/react';
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
} from '@ionic/react';
|
||||
import { menuOutline } from 'ionicons/icons';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import './style.scss';
|
||||
|
||||
const Helloworld: React.FC = () => {
|
||||
return (
|
||||
|
103
03_source/mobile/src/pages/Helloworld/style.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
#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/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/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;
|
||||
}
|
@@ -38,6 +38,7 @@ const MainTabs: React.FC<MainTabsProps> = () => {
|
||||
<Route path="/tabs/schedule/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/speakers/sessions/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/map" render={() => <MapView />} exact={true} />
|
||||
|
||||
<Route path="/tabs/about" render={() => <About />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
|
@@ -52,6 +52,8 @@ import {
|
||||
chevronForwardOutline,
|
||||
createOutline,
|
||||
documentTextOutline,
|
||||
gift,
|
||||
giftOutline,
|
||||
heart,
|
||||
languageOutline,
|
||||
listCircle,
|
||||
@@ -78,9 +80,9 @@ interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
}
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
interface SettingsProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const EventList: React.FC<SpeakerListProps> = ({
|
||||
const SettingsPage: React.FC<SettingsProps> = ({
|
||||
speakers,
|
||||
speakerSessions,
|
||||
logoutUser,
|
||||
@@ -113,6 +115,10 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
router.push(paths.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
function handleDemoPageClick() {
|
||||
router.push(paths.DEMO_PAGE);
|
||||
}
|
||||
|
||||
function handleServiceAgreementClick() {
|
||||
router.push(paths.SERVICE_AGREEMENT);
|
||||
}
|
||||
@@ -196,6 +202,12 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
<IonLabel>Delete Account</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline} slot="end"></IonIcon>
|
||||
</IonItem>
|
||||
|
||||
<IonItem button={true} onClick={handleDemoPageClick}>
|
||||
<IonIcon color="success" slot="start" icon={gift} size="large"></IonIcon>
|
||||
<IonLabel>Demo pages</IonLabel>
|
||||
<IonIcon icon={chevronForwardOutline} slot="end"></IonIcon>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<div style={{ margin: '3rem 0.5rem' }}>
|
||||
@@ -251,7 +263,13 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
Unable to receive notifications after logging out
|
||||
</div>
|
||||
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<IonButton size="large" fill="outline" shape="round" onClick={handleLogoutCancel}>
|
||||
Cancel
|
||||
</IonButton>
|
||||
@@ -276,5 +294,5 @@ export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
setAccessToken,
|
||||
setIsLoggedIn,
|
||||
},
|
||||
component: React.memo(EventList),
|
||||
component: React.memo(SettingsPage),
|
||||
});
|
||||
|
95
03_source/mobile/src/pages/WeatherDemo/AppPages/Tab1.jsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SkeletonDashboard } from '../components/SkeletonDashboard';
|
||||
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab1() {
|
||||
const router = useIonRouter();
|
||||
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getCurrentPosition();
|
||||
}, []);
|
||||
|
||||
const getCurrentPosition = async () => {
|
||||
setCurrentWeather(false);
|
||||
const coordinates = await Geolocation.getCurrentPosition();
|
||||
getAddress(coordinates.coords);
|
||||
};
|
||||
|
||||
const getAddress = async (coords) => {
|
||||
const query = `${coords.latitude},${coords.longitude}`;
|
||||
const response = await fetch(
|
||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${query}`
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
setCurrentWeather(data);
|
||||
};
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>My Weather</IonTitle>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => getCurrentPosition()}>
|
||||
<IonIcon icon={refreshOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Dashboard</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-margin-start ion-margin-end ion-justify-content-center ion-text-center">
|
||||
<IonCol size="12">
|
||||
<h4>Here's your location based weather</h4>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-1.5rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<SkeletonDashboard />
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab1;
|
81
03_source/mobile/src/pages/WeatherDemo/AppPages/Tab2.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
||||
const performSearch = async () => {
|
||||
getAddress(search);
|
||||
};
|
||||
|
||||
const getAddress = async (city) => {
|
||||
const response = await fetch(
|
||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.current && data.location) {
|
||||
setCurrentWeather(data);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
|
||||
<IonCol size="7">
|
||||
<IonSearchbar
|
||||
placeholder="Try 'London'"
|
||||
animated
|
||||
value={search}
|
||||
onIonChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="5">
|
||||
<IonButton
|
||||
expand="block"
|
||||
className="ion-margin-start ion-margin-end"
|
||||
onClick={performSearch}
|
||||
>
|
||||
Search
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-0.8rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<h3 className="ion-text-center">Your search result will appear here</h3>
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,62 @@
|
||||
import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => {
|
||||
const [property, setProperty] = useState(false);
|
||||
|
||||
const properties = {
|
||||
wind: {
|
||||
isIcon: false,
|
||||
icon: '/assets/WeatherDemo/wind.png',
|
||||
alt: 'wind',
|
||||
label: 'Wind',
|
||||
value: `${currentWeather.current.wind_mph}mph`,
|
||||
},
|
||||
feelsLike: {
|
||||
isIcon: true,
|
||||
icon: thermometerOutline,
|
||||
alt: 'feels like',
|
||||
label: 'Feels like',
|
||||
value: `${currentWeather.current.feelslike_c}°C`,
|
||||
},
|
||||
indexUV: {
|
||||
isIcon: true,
|
||||
icon: sunnyOutline,
|
||||
alt: 'index uv',
|
||||
label: 'Index UV',
|
||||
value: currentWeather.current.uv,
|
||||
},
|
||||
pressure: {
|
||||
isIcon: true,
|
||||
icon: pulseOutline,
|
||||
alt: 'pressure',
|
||||
label: 'Pressure',
|
||||
value: `${currentWeather.current.pressure_mb} mbar`,
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setProperty(properties[type]);
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
{!property.isIcon && (
|
||||
<img alt={property.alt} src={property.icon} height="32" width="32" />
|
||||
)}
|
||||
{property.isIcon && (
|
||||
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
|
||||
)}
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>{property.label}</IonCardSubtitle>
|
||||
<IonNote>{property.value}</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
|
||||
import { WeatherProperty } from './WeatherProperty';
|
||||
|
||||
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
{currentWeather.location.region},{' '}
|
||||
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<img
|
||||
alt="condition"
|
||||
src={currentWeather.current.condition.icon.replace('//', 'https://')}
|
||||
/>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
{currentWeather.current.temp_c}℃
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<WeatherProperty type="wind" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="pressure" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonGrid,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonRow,
|
||||
IonSkeletonText,
|
||||
IonText,
|
||||
IonThumbnail,
|
||||
} from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
|
||||
export const SkeletonDashboard = () => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<IonThumbnail>
|
||||
<IonSkeletonText animated style={{ width: '2rem', height: '2rem' }} />
|
||||
</IonThumbnail>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
<IonSkeletonText animated style={{ height: '3rem', width: '30%', textAlign: 'center' }} />
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<img alt="wind" src="/assets/WeatherDemo/wind.png" height="32" width="32" />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Wind</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={thermometerOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Feels like</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={sunnyOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Index UV</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={pulseOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Pressure</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
59
03_source/mobile/src/pages/WeatherDemo/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonRow,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SkeletonDashboard } from './components/SkeletonDashboard';
|
||||
import { chevronBack, cloudOutline, refreshOutline, searchOutline } from 'ionicons/icons';
|
||||
import { CurrentWeather } from './components/CurrentWeather';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
|
||||
const DemoWeatherApp = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app">
|
||||
<Redirect to="/demo-weather-app/tab1" />
|
||||
</Route>
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-weather-app/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-weather-app/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoWeatherApp;
|
103
03_source/mobile/src/pages/WeatherDemo/style.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
#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;
|
||||
}
|
58
03_source/mobile/src/pages/components/AddToCartButton.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { CreateAnimation, IonButton, IonIcon } from "@ionic/react";
|
||||
import { cartOutline } from "ionicons/icons";
|
||||
import { useRef, useState } from "react";
|
||||
import { addToCart } from "../store/CartStore";
|
||||
|
||||
export const AddToCartButton = ({product}) => {
|
||||
|
||||
const animationRef = useRef();
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
||||
const floatStyle = {
|
||||
|
||||
display: hidden ? "none" : "",
|
||||
position: "absolute"
|
||||
};
|
||||
|
||||
const floatGrowAnimation = {
|
||||
|
||||
property: "transform",
|
||||
fromValue: "translateY(0) scale(1)",
|
||||
toValue: "translateY(-55px) scale(1.5)"
|
||||
};
|
||||
|
||||
const colorAnimation = {
|
||||
|
||||
property: "color",
|
||||
fromValue: "green",
|
||||
toValue: "green"
|
||||
};
|
||||
|
||||
const mainAnimation = {
|
||||
|
||||
duration: 1500,
|
||||
iterations: "1",
|
||||
fromTo: [ floatGrowAnimation, colorAnimation ],
|
||||
easing: "cubic-bezier(0.25, 0.7, 0.25, 0.7)"
|
||||
};
|
||||
|
||||
const handleAddToCart = async product => {
|
||||
|
||||
setHidden(false);
|
||||
await animationRef.current.animation.play();
|
||||
setHidden(true);
|
||||
addToCart(product);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonButton color="dark" expand="full" onClick={() => handleAddToCart(product)}>
|
||||
<IonIcon icon={cartOutline} />
|
||||
Add to Cart
|
||||
|
||||
<CreateAnimation ref={animationRef} {...mainAnimation}>
|
||||
<IonIcon icon={cartOutline} size="large" style={floatStyle} />
|
||||
</CreateAnimation>
|
||||
</IonButton>
|
||||
);
|
||||
}
|
23
03_source/mobile/src/pages/components/Breadcrumbs.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IonBreadcrumb, IonBreadcrumbs, IonIcon } from "@ionic/react";
|
||||
import { fastFoodOutline } from "ionicons/icons";
|
||||
import { useState } from "react";
|
||||
|
||||
export const Breadcrumbs = () => {
|
||||
|
||||
const [maxItems, setMaxItems] = useState(2);
|
||||
|
||||
const handleClick = () => {
|
||||
|
||||
setMaxItems(undefined);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonBreadcrumbs maxItems={maxItems} onIonCollapsedClick={handleClick}>
|
||||
<IonBreadcrumb color="medium">Page 1</IonBreadcrumb>
|
||||
<IonBreadcrumb color="medium">Page 2</IonBreadcrumb>
|
||||
<IonBreadcrumb color="medium">Page 3</IonBreadcrumb>
|
||||
<IonBreadcrumb>Page 4</IonBreadcrumb>
|
||||
</IonBreadcrumbs>
|
||||
);
|
||||
}
|
82
03_source/mobile/src/pages/components/CartModal.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useStoreState } from "pullstate";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CartStore } from "../store";
|
||||
import { addToCart } from "../store/CartStore";
|
||||
import { getCart } from "../store/Selectors";
|
||||
|
||||
const { IonPage, IonHeader, IonToolbar, IonTitle, IonButtons, IonIcon, IonContent, IonGrid, IonRow, IonItem, IonLabel, IonText, IonThumbnail, IonFooter, IonCol, IonButton, IonItemSliding, IonItemOptions, IonItemOption } = require("@ionic/react");
|
||||
const { close } = require("ionicons/icons");
|
||||
|
||||
export const CartModal = props => {
|
||||
|
||||
const cart = useStoreState(CartStore, getCart);
|
||||
const [totalPrice, setTotalPrice] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let total = 0;
|
||||
cart.forEach(item => total += parseInt(item.price.replace("£", "")));
|
||||
setTotalPrice(total);
|
||||
}, [cart]);
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Cart</IonTitle>
|
||||
<IonButtons slot="end" onClick={props.close}>
|
||||
<IonIcon icon={close} size="large" />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<IonGrid>
|
||||
<IonRow style={{borderBottom: "1px solid black"}} className="ion-margin-bottom">
|
||||
<IonItem lines="none">
|
||||
<IonLabel>
|
||||
<h1>{cart.length} products in your cart</h1>
|
||||
<IonText color="medium">
|
||||
<h2>Review products and checkout</h2>
|
||||
</IonText>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
|
||||
{cart.map((item, index) => (
|
||||
<IonItemSliding>
|
||||
<IonItem key={index} lines="none" className="ion-padding-end" style={{paddingTop: "0.75rem", paddingBottom: "0.75rem"}}>
|
||||
<IonThumbnail>
|
||||
<img src={item.image} alt="item" />
|
||||
</IonThumbnail>
|
||||
<IonLabel className="ion-padding-start ion-text-wrap">
|
||||
<h2>{item.title}</h2>
|
||||
<p>{item.price}</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonItemOptions side="end">
|
||||
<IonItemOption color="danger" onClick={() => addToCart(item)}>
|
||||
Remove
|
||||
</IonItemOption>
|
||||
</IonItemOptions>
|
||||
</IonItemSliding>
|
||||
))}
|
||||
</IonContent>
|
||||
|
||||
<IonFooter className="ion-padding-bottom ion-padding-start ion-padding-end" style={{paddingBottom: "3rem"}}>
|
||||
<IonRow className="ion-justify-content-between">
|
||||
<IonCol size="8">
|
||||
<h1>Total</h1>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4">
|
||||
<h1>£{totalPrice.toFixed(2)}</h1>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
<IonButton expand="block" color="dark">Checkout →</IonButton>
|
||||
</IonFooter>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
36
03_source/mobile/src/pages/components/FilterModal.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { IonButton, IonCol, IonContent, IonGrid, IonHeader, IonRow, IonTitle, IonToolbar } from "@ionic/react";
|
||||
|
||||
export const FilterModal = ({productsRef, filterCriteria, setFilterCriteria, dismiss, filters}) => {
|
||||
|
||||
const filterProducts = async filter => {
|
||||
|
||||
await productsRef.current.classList.add("animate__fadeOutLeft");
|
||||
|
||||
setTimeout(() => {
|
||||
productsRef.current.classList.remove("animate__fadeOutLeft");
|
||||
productsRef.current.classList.add("animate__fadeInRight");
|
||||
setFilterCriteria(filter);
|
||||
}, 500);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<IonContent>
|
||||
<IonHeader>
|
||||
<IonToolbar color="none" style={{"--border-style": "none"}}>
|
||||
<IonTitle className="ion-margin-top">Filter</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
{filters.map(f => (
|
||||
<IonCol key={f} size="3">
|
||||
<IonButton expand="full" color={filterCriteria === f ? "dark" : "light"} onClick={() => filterProducts(f)}>{f}</IonButton>
|
||||
</IonCol>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
);
|
||||
}
|
88
03_source/mobile/src/pages/components/ProductModal.css
Normal file
@@ -0,0 +1,88 @@
|
||||
ion-card {
|
||||
margin: 0;
|
||||
/* margin-top: var(--ion-safe-area-top); */
|
||||
z-index: -1;
|
||||
|
||||
border-radius: 0px;
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: none;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
@supports not (aspect-ratio: 1 / 1) {
|
||||
ion-card::before {
|
||||
float: left;
|
||||
padding-top: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
ion-card::after {
|
||||
display: block;
|
||||
content: '';
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
ion-card-header {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
/* background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.7) 100%); */
|
||||
background: rgba(0, 0, 0, 0.5)
|
||||
}
|
||||
|
||||
ion-card-title,
|
||||
ion-card-subtitle {
|
||||
color: white;
|
||||
}
|
||||
|
||||
ion-card-header ion-card-title {
|
||||
|
||||
margin: 0 0 6px 0;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
ion-card-header ion-card-subtitle {
|
||||
|
||||
text-transform: none;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ion-card-content {
|
||||
height: calc(60px + var(--ion-safe-area-top));
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 100%);
|
||||
}
|
||||
|
||||
#close-button {
|
||||
position: fixed;
|
||||
|
||||
top: max(var(--ion-safe-area-top), 16px);
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
#fave-button {
|
||||
position: fixed;
|
||||
|
||||
top: max(var(--ion-safe-area-top), 16px);
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
#product-view-buttons {
|
||||
|
||||
z-index: 10;
|
||||
background: linear-gradient(360deg, rgba(0, 0, 0, 0) 0%, rgba(82, 82, 82, 0.9) 100%) !important;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.sticky-bottom {
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
76
03_source/mobile/src/pages/components/ProductModal.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { IonButton, IonButtons, IonCard, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCol, IonContent, IonFooter, IonIcon, IonLabel, IonNote, IonRow, IonText, IonToolbar } from "@ionic/react";
|
||||
import { closeCircle, heart, heartOutline } from "ionicons/icons";
|
||||
import { useStoreState } from "pullstate";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { checkFavourites } from "../store/Selectors";
|
||||
import { addToFavourites } from "../store/FavouritesStore";
|
||||
import { FavouritesStore } from "../store";
|
||||
|
||||
import "./ProductModal.css";
|
||||
import { ProductReviews } from "./ProductReviews";
|
||||
import { ProductSpecificationsAccordion } from "./ProductSpecificationsAccordion";
|
||||
import { AddToCartButton } from "./AddToCartButton";
|
||||
|
||||
export const ProductModal = props => {
|
||||
|
||||
const { dismiss, category = false, product } = props;
|
||||
const isFavourite = useStoreState(FavouritesStore, checkFavourites(product));
|
||||
const contentRef = useRef(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonContent ref={contentRef}>
|
||||
<IonButtons id="product-view-buttons">
|
||||
<IonButton color="light" onClick={dismiss} id="close-button">
|
||||
<IonIcon icon={closeCircle} size="large" />
|
||||
</IonButton>
|
||||
|
||||
<IonButton color="danger" onClick={() => addToFavourites(product, category)} id="fave-button">
|
||||
<IonIcon icon={isFavourite ? heart : heartOutline} size="large" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonCard style={{backgroundImage: `url('${product.image}')`}}>
|
||||
<IonCardHeader>
|
||||
<IonCardTitle>{product.title}</IonCardTitle>
|
||||
<IonCardSubtitle>{product.price}</IonCardSubtitle>
|
||||
</IonCardHeader>
|
||||
</IonCard>
|
||||
|
||||
<div className="ion-padding">
|
||||
|
||||
<IonRow className="ion-align-items-center">
|
||||
<IonCol>
|
||||
<IonText size="large" className="page-title">
|
||||
<IonNote>shop</IonNote>
|
||||
<IonLabel>{category ? category : "Favourite"}</IonLabel>
|
||||
</IonText>
|
||||
</IonCol>
|
||||
|
||||
<ProductReviews reviews={product.reviews} />
|
||||
</IonRow>
|
||||
<h2>Product Description</h2>
|
||||
<IonText>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam elit felis, molestie id venenatis at, commodo ac tortor. Pellentesque tempus aliquet purus, sed vulputate elit tempus ut.</IonText>
|
||||
|
||||
<h2>Product Specifications</h2>
|
||||
<ProductSpecificationsAccordion contentRef={contentRef} type={category} />
|
||||
</div>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter collapse="fade">
|
||||
<IonToolbar>
|
||||
<IonRow className="ion-justify-content-between ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonButton expand="full" color="light">{product.price}</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="8" className="ion-text-right">
|
||||
<AddToCartButton product={product} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
</>
|
||||
);
|
||||
}
|
23
03_source/mobile/src/pages/components/ProductReviews.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IonCol, IonIcon, IonNote } from "@ionic/react";
|
||||
import { star } from "ionicons/icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { randomCount } from "../utils";
|
||||
|
||||
export const ProductReviews = () => {
|
||||
|
||||
// This count could come from the product (if real data was fed)
|
||||
const [reviewCount, setReviewCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
setReviewCount(randomCount());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IonCol className="ion-text-right">
|
||||
<IonIcon color="warning" icon={star} />
|
||||
|
||||
<IonNote>{reviewCount} review{reviewCount > 1 && "s"}</IonNote>
|
||||
</IonCol>
|
||||
);
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
import { IonAccordion, IonAccordionGroup, IonItem, IonLabel, IonList, IonNote } from "@ionic/react";
|
||||
import { useRef } from "react";
|
||||
import { productSpecs } from "../utils";
|
||||
|
||||
export const ProductSpecificationsAccordion = ({type, contentRef}) => {
|
||||
|
||||
const accordionGroupRef = useRef(null);
|
||||
|
||||
const log = () => {
|
||||
|
||||
const selectedAccordion = accordionGroupRef.current.value;
|
||||
|
||||
if (selectedAccordion) {
|
||||
|
||||
setTimeout(() => contentRef.current.scrollToBottom(400), 200);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<IonAccordionGroup ref={accordionGroupRef} onIonChange={log}>
|
||||
{Object.keys(productSpecs).map((spec, index) => {
|
||||
|
||||
const {header, options, wrapText = false, noteColor = false} = productSpecs[spec];
|
||||
|
||||
return (
|
||||
|
||||
<IonAccordion key={`accordion_${header}_${index}`}>
|
||||
<IonItem slot="header" className="ion-no-padding">
|
||||
<IonLabel>{header}</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
|
||||
<IonList slot="content" className="ion-no-padding">
|
||||
{options.map((option, index2) => {
|
||||
|
||||
const {label, value} = option;
|
||||
|
||||
return (
|
||||
|
||||
<IonItem key={`accordion_${header}_${option}_${index2}`} className="ion-no-padding">
|
||||
<IonLabel>
|
||||
<h3>{label}</h3>
|
||||
</IonLabel>
|
||||
<IonLabel className={wrapText && "ion-text-wrap"}>
|
||||
<IonNote color={noteColor ? (value ? "success" : "danger") : "medium"}>
|
||||
{noteColor ? (value ? "In stock" : "Out of stock") : value}
|
||||
</IonNote>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
);
|
||||
})}
|
||||
</IonAccordionGroup>
|
||||
);
|
||||
}
|
27
03_source/mobile/src/pages/store/CartStore.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
const CartStore = new Store({
|
||||
|
||||
cart: []
|
||||
});
|
||||
|
||||
export default CartStore;
|
||||
|
||||
export const addToCart = product => {
|
||||
|
||||
const currentCart = CartStore.getRawState().cart;
|
||||
const added = !currentCart.includes(product);
|
||||
|
||||
CartStore.update(s => {
|
||||
|
||||
if (currentCart.includes(product)) {
|
||||
|
||||
s.cart = currentCart.filter(current => current !== product);
|
||||
} else {
|
||||
|
||||
s.cart = [ ...s.cart, product ];
|
||||
}
|
||||
});
|
||||
|
||||
return added;
|
||||
}
|
35
03_source/mobile/src/pages/store/FavouritesStore.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Store } from "pullstate";
|
||||
|
||||
const FavouritesStore = new Store({
|
||||
|
||||
favourites: []
|
||||
});
|
||||
|
||||
export default FavouritesStore;
|
||||
|
||||
export const checkIfFavourite = product => {
|
||||
|
||||
const currentFavourites = FavouritesStore.getRawState().favourites;
|
||||
const isFavourite = currentFavourites.includes(product);
|
||||
|
||||
return isFavourite;
|
||||
}
|
||||
|
||||
export const addToFavourites = (product, category) => {
|
||||
|
||||
const currentFavourites = FavouritesStore.getRawState().favourites;
|
||||
const added = !currentFavourites.includes(product);
|
||||
|
||||
FavouritesStore.update(s => {
|
||||
|
||||
if (!added) {
|
||||
|
||||
s.favourites = currentFavourites.filter(current => current !== product);
|
||||
} else {
|
||||
|
||||
s.favourites = [ ...s.favourites, product ];
|
||||
}
|
||||
});
|
||||
|
||||
return added;
|
||||
}
|
9
03_source/mobile/src/pages/store/Selectors.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const getState = state => state;
|
||||
|
||||
// General getters
|
||||
export const getFavourites = createSelector(getState, state => state.favourites);
|
||||
export const checkFavourites = product => createSelector(getState, state => state.favourites.includes(product));
|
||||
export const getCart = createSelector(getState, state => state.cart);
|
||||
export const getCartCount = createSelector(getState, state => state.cart.length);
|
2
03_source/mobile/src/pages/store/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as FavouritesStore } from "./FavouritesStore";
|
||||
export { default as CartStore } from "./CartStore";
|
143
03_source/mobile/src/pages/utils/index.js
Normal file
@@ -0,0 +1,143 @@
|
||||
export const capitalize = (s) => s && (s[0].toUpperCase() + s.slice(1)).replaceAll('_', ' ');
|
||||
|
||||
export const productInfo = {
|
||||
men: {
|
||||
coverImage: '/assets/react-shop/men.jpeg',
|
||||
productTypes: {
|
||||
formal_shirts: {
|
||||
coverImage: '/assets/react-shop/formal_shirts2.jpeg',
|
||||
filters: ['None', 'Regular', 'Slim', 'Stretch'],
|
||||
searchPlaceholder: 'Single Cuff',
|
||||
},
|
||||
sportswear: {
|
||||
coverImage: '/assets/react-shop/sportswear2.jpeg',
|
||||
filters: ['None', 'Trainers', 'Joggers', 'Shorts', 'Hoodie'],
|
||||
searchPlaceholder: 'Nike',
|
||||
},
|
||||
coats: {
|
||||
coverImage: '/assets/react-shop/coats3.jpeg',
|
||||
filters: ['None', 'Funnel', 'Hooded', 'Barbour', 'Collar'],
|
||||
searchPlaceholder: 'Bomber',
|
||||
},
|
||||
},
|
||||
},
|
||||
women: {
|
||||
coverImage: '/assets/react-shop/women.jpeg',
|
||||
productTypes: {
|
||||
jeans: {
|
||||
coverImage: '/assets/react-shop/jeans.jpeg',
|
||||
filters: ['None', 'Skinny', 'Slim', 'Boot Cut', 'Flare'],
|
||||
searchPlaceholder: 'Skinny',
|
||||
},
|
||||
dresses: {
|
||||
coverImage: '/assets/react-shop/dresses3.jpeg',
|
||||
filters: ['None', 'Short', 'Maxi', 'Long', 'Regular'],
|
||||
searchPlaceholder: 'Long Sleeve',
|
||||
},
|
||||
makeup: {
|
||||
coverImage: '/assets/react-shop/makeup2.jpeg',
|
||||
filters: ['None', 'Mascara', 'Lip Gloss', 'Foundation', 'Blush'],
|
||||
searchPlaceholder: 'Brush Set',
|
||||
},
|
||||
},
|
||||
},
|
||||
home: {
|
||||
coverImage: '/assets/react-shop/home.jpeg',
|
||||
productTypes: {
|
||||
beds: {
|
||||
coverImage: '/assets/react-shop/beds.jpeg',
|
||||
filters: ['None', 'Metal', 'Ottoman', 'Storage', 'Wooden'],
|
||||
searchPlaceholder: 'Upholstered',
|
||||
},
|
||||
office: {
|
||||
coverImage: '/assets/react-shop/office.jpeg',
|
||||
filters: ['None', 'Desk', 'Chair', 'Lamp', 'Shelf'],
|
||||
searchPlaceholder: 'Space Saving',
|
||||
},
|
||||
coffee_tables: {
|
||||
coverImage: '/assets/react-shop/coffee_table.jpeg',
|
||||
filters: ['None', 'Wood', 'Glass', 'Round', 'Storage'],
|
||||
searchPlaceholder: 'Oak Effect',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const productSpecs = {
|
||||
dimensions: {
|
||||
header: 'Dimensions',
|
||||
options: [
|
||||
{
|
||||
label: 'Height',
|
||||
value: '100cm',
|
||||
},
|
||||
{
|
||||
label: 'Width',
|
||||
value: '130cm',
|
||||
},
|
||||
{
|
||||
label: 'Depth',
|
||||
value: '150cm',
|
||||
},
|
||||
],
|
||||
},
|
||||
shipping: {
|
||||
header: 'Shipping',
|
||||
options: [
|
||||
{
|
||||
label: 'UK',
|
||||
value: '£4.99',
|
||||
},
|
||||
{
|
||||
label: 'USA',
|
||||
value: '£6.99',
|
||||
},
|
||||
{
|
||||
label: 'Gloal',
|
||||
value: '£9.99',
|
||||
},
|
||||
],
|
||||
},
|
||||
colors: {
|
||||
header: 'Colors',
|
||||
noteColor: true,
|
||||
options: [
|
||||
{
|
||||
label: 'Red',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'Blue',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: 'Brown',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
sizes: {
|
||||
header: 'Sizes',
|
||||
wrapText: true,
|
||||
options: [
|
||||
{
|
||||
label: 'Large',
|
||||
value: 'Check size guide for details',
|
||||
},
|
||||
{
|
||||
label: 'Width',
|
||||
value: 'Check size guide for details',
|
||||
},
|
||||
{
|
||||
label: 'Depth',
|
||||
value: 'Check size guide for details',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const randomCount = () => {
|
||||
const max = 273;
|
||||
const min = 23;
|
||||
return Math.floor(Math.random() * (max - min) + min).toFixed(0);
|
||||
};
|
@@ -21,5 +21,9 @@ const paths = {
|
||||
PROFILE: '/tabs/my_profile',
|
||||
//
|
||||
SIGN_IN: '/mylogin',
|
||||
//
|
||||
DEMO_PAGE: '/tabs/demo-list',
|
||||
DEMO_WEATHER_APP: '/demo-weather-app',
|
||||
DEMO_REACT_SHOP: '/demo-react-shop',
|
||||
};
|
||||
export default paths;
|
||||
|
@@ -21,7 +21,8 @@
|
||||
"useDefineForClassFields": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
"src",
|
||||
"src/pages/Settings/index.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
@@ -24,7 +24,7 @@
|
||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz"
|
||||
integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.26.10":
|
||||
"@babel/core@^7.26.10":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz"
|
||||
integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==
|
||||
@@ -203,13 +203,20 @@
|
||||
tslib "^2.8.1"
|
||||
xml2js "^0.6.2"
|
||||
|
||||
"@capacitor/core@^7.0.0", "@capacitor/core@>=7.0.0":
|
||||
"@capacitor/core@^7.0.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/@capacitor/core/-/core-7.2.0.tgz"
|
||||
integrity sha512-2zCnA6RJeZ9ec4470o8QMZEQTWpekw9FNoqm5TLc10jeCrhvHVI8MPgxdZVc3mOdFlyieYu4AS1fNxSqbS57Pw==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@capacitor/geolocation@^7.1.2":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.npmjs.org/@capacitor/geolocation/-/geolocation-7.1.2.tgz"
|
||||
integrity sha512-J++OuOpn6Bjweo7SZ+jwI/dhVF9DNw6Wx0UHgQ4qfrATqmNKGNZ/BUljGhXiKJceSx2GIhfrYS7BYqo3uWibgQ==
|
||||
dependencies:
|
||||
"@capacitor/synapse" "^1.0.1"
|
||||
|
||||
"@capacitor/ios@7.0.1":
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmjs.org/@capacitor/ios/-/ios-7.0.1.tgz"
|
||||
@@ -220,11 +227,136 @@
|
||||
resolved "https://registry.npmjs.org/@capacitor/preferences/-/preferences-7.0.1.tgz"
|
||||
integrity sha512-XF9jOHzvoIBZLwZr/EX6aVaUO1d8Mx7TwBLQS33pYHOliCW5knT5KUkFOXNNYxh9qqODYesee9xuQIKNJpQBag==
|
||||
|
||||
"@capacitor/synapse@^1.0.1":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/@capacitor/synapse/-/synapse-1.0.2.tgz"
|
||||
integrity sha512-ynq39s4D2rhk+aVLWKfKfMCz9SHPKijL9tq8aFL5dG7ik7/+PvBHmg9cPHbqdvFEUSMmaGzL6cIjzkOruW7vGA==
|
||||
|
||||
"@esbuild/aix-ppc64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz#830d6476cbbca0c005136af07303646b419f1162"
|
||||
integrity sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==
|
||||
|
||||
"@esbuild/android-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz#d11d4fc299224e729e2190cacadbcc00e7a9fd67"
|
||||
integrity sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==
|
||||
|
||||
"@esbuild/android-arm@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.4.tgz#5660bd25080553dd2a28438f2a401a29959bd9b1"
|
||||
integrity sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==
|
||||
|
||||
"@esbuild/android-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.4.tgz#18ddde705bf984e8cd9efec54e199ac18bc7bee1"
|
||||
integrity sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==
|
||||
|
||||
"@esbuild/darwin-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz#b0b7fb55db8fc6f5de5a0207ae986eb9c4766e67"
|
||||
integrity sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==
|
||||
|
||||
"@esbuild/darwin-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz#e6813fdeba0bba356cb350a4b80543fbe66bf26f"
|
||||
integrity sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz#dc11a73d3ccdc308567b908b43c6698e850759be"
|
||||
integrity sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==
|
||||
|
||||
"@esbuild/freebsd-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz#91da08db8bd1bff5f31924c57a81dab26e93a143"
|
||||
integrity sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==
|
||||
|
||||
"@esbuild/linux-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz#efc15e45c945a082708f9a9f73bfa8d4db49728a"
|
||||
integrity sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==
|
||||
|
||||
"@esbuild/linux-arm@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz#9b93c3e54ac49a2ede6f906e705d5d906f6db9e8"
|
||||
integrity sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==
|
||||
|
||||
"@esbuild/linux-ia32@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz#be8ef2c3e1d99fca2d25c416b297d00360623596"
|
||||
integrity sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==
|
||||
|
||||
"@esbuild/linux-loong64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz#b0840a2707c3fc02eec288d3f9defa3827cd7a87"
|
||||
integrity sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==
|
||||
|
||||
"@esbuild/linux-mips64el@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz#2a198e5a458c9f0e75881a4e63d26ba0cf9df39f"
|
||||
integrity sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==
|
||||
|
||||
"@esbuild/linux-ppc64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz#64f4ae0b923d7dd72fb860b9b22edb42007cf8f5"
|
||||
integrity sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==
|
||||
|
||||
"@esbuild/linux-riscv64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz#fb2844b11fdddd39e29d291c7cf80f99b0d5158d"
|
||||
integrity sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==
|
||||
|
||||
"@esbuild/linux-s390x@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz#1466876e0aa3560c7673e63fdebc8278707bc750"
|
||||
integrity sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==
|
||||
|
||||
"@esbuild/linux-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz"
|
||||
integrity sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==
|
||||
|
||||
"@esbuild/netbsd-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz#02e483fbcbe3f18f0b02612a941b77be76c111a4"
|
||||
integrity sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==
|
||||
|
||||
"@esbuild/netbsd-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz#ec401fb0b1ed0ac01d978564c5fc8634ed1dc2ed"
|
||||
integrity sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==
|
||||
|
||||
"@esbuild/openbsd-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz#f272c2f41cfea1d91b93d487a51b5c5ca7a8c8c4"
|
||||
integrity sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==
|
||||
|
||||
"@esbuild/openbsd-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz#2e25950bc10fa9db1e5c868e3d50c44f7c150fd7"
|
||||
integrity sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==
|
||||
|
||||
"@esbuild/sunos-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz#cd596fa65a67b3b7adc5ecd52d9f5733832e1abd"
|
||||
integrity sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==
|
||||
|
||||
"@esbuild/win32-arm64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz#b4dbcb57b21eeaf8331e424c3999b89d8951dc88"
|
||||
integrity sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==
|
||||
|
||||
"@esbuild/win32-ia32@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz#410842e5d66d4ece1757634e297a87635eb82f7a"
|
||||
integrity sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==
|
||||
|
||||
"@esbuild/win32-x64@0.25.4":
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz#0b17ec8a70b2385827d52314c1253160a0b9bacc"
|
||||
integrity sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==
|
||||
|
||||
"@hookform/resolvers@^4.1.3":
|
||||
version "4.1.3"
|
||||
resolved "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-4.1.3.tgz"
|
||||
@@ -258,7 +390,7 @@
|
||||
"@ionic/react" "8.5.7"
|
||||
tslib "*"
|
||||
|
||||
"@ionic/react@^8.5.0", "@ionic/react@8.5.7":
|
||||
"@ionic/react@8.5.7", "@ionic/react@^8.5.0":
|
||||
version "8.5.7"
|
||||
resolved "https://registry.npmjs.org/@ionic/react/-/react-8.5.7.tgz"
|
||||
integrity sha512-AgX4iu6SfuBhNgYr0H+K3oGsp7ESkCsnaqZdHRO2+GtKTmo4akMrFPihGj4LrZB/IaYwcvYQR/bPWHuZGJYsnw==
|
||||
@@ -275,7 +407,7 @@
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-fs@^3.1.7", "@ionic/utils-fs@3.1.7":
|
||||
"@ionic/utils-fs@3.1.7", "@ionic/utils-fs@^3.1.7":
|
||||
version "3.1.7"
|
||||
resolved "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz"
|
||||
integrity sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==
|
||||
@@ -327,7 +459,7 @@
|
||||
debug "^4.0.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@ionic/utils-terminal@^2.3.4", "@ionic/utils-terminal@^2.3.5", "@ionic/utils-terminal@2.3.5":
|
||||
"@ionic/utils-terminal@2.3.5", "@ionic/utils-terminal@^2.3.4", "@ionic/utils-terminal@^2.3.5":
|
||||
version "2.3.5"
|
||||
resolved "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz"
|
||||
integrity sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==
|
||||
@@ -412,6 +544,46 @@
|
||||
dependencies:
|
||||
"@types/mdx" "^2.0.0"
|
||||
|
||||
"@parcel/watcher-android-arm64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1"
|
||||
integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==
|
||||
|
||||
"@parcel/watcher-darwin-arm64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67"
|
||||
integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
|
||||
|
||||
"@parcel/watcher-darwin-x64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8"
|
||||
integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==
|
||||
|
||||
"@parcel/watcher-freebsd-x64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b"
|
||||
integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==
|
||||
|
||||
"@parcel/watcher-linux-arm-glibc@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1"
|
||||
integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==
|
||||
|
||||
"@parcel/watcher-linux-arm-musl@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e"
|
||||
integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==
|
||||
|
||||
"@parcel/watcher-linux-arm64-glibc@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30"
|
||||
integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==
|
||||
|
||||
"@parcel/watcher-linux-arm64-musl@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2"
|
||||
integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==
|
||||
|
||||
"@parcel/watcher-linux-x64-glibc@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz"
|
||||
@@ -422,6 +594,21 @@
|
||||
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz"
|
||||
integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==
|
||||
|
||||
"@parcel/watcher-win32-arm64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243"
|
||||
integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==
|
||||
|
||||
"@parcel/watcher-win32-ia32@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6"
|
||||
integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==
|
||||
|
||||
"@parcel/watcher-win32-x64@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947"
|
||||
integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==
|
||||
|
||||
"@parcel/watcher@^2.4.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
|
||||
@@ -456,6 +643,81 @@
|
||||
resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz"
|
||||
integrity sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz#f39f09f60d4a562de727c960d7b202a2cf797424"
|
||||
integrity sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz#d19af7e23760717f1d879d4ca3d2cd247742dff2"
|
||||
integrity sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz#1c3a2fbf205d80641728e05f4a56c909e95218b7"
|
||||
integrity sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz#aa66d2ba1a25e609500e13bef06dc0e71cc0c0d4"
|
||||
integrity sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==
|
||||
|
||||
"@rollup/rollup-freebsd-arm64@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz#df10a7b6316a0ef1028c6ca71a081124c537e30d"
|
||||
integrity sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==
|
||||
|
||||
"@rollup/rollup-freebsd-x64@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz#a3fdce8a05e95b068cbcb46e4df5185e407d0c35"
|
||||
integrity sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz#49f766c55383bd0498014a9d76924348c2f3890c"
|
||||
integrity sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz#1d4d7d32fc557e17d52e1857817381ea365e2959"
|
||||
integrity sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz#f4fc317268441e9589edad3be8f62b6c03009bc1"
|
||||
integrity sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz#63a1f1b0671cb17822dabae827fef0e443aebeb7"
|
||||
integrity sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz#c659b01cc6c0730b547571fc3973e1e955369f98"
|
||||
integrity sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz#612e746f9ad7e58480f964d65e0d6c3f4aae69a8"
|
||||
integrity sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz#4610dbd1dcfbbae32fbc10c20ae7387acb31110c"
|
||||
integrity sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz#054911fab40dc83fafc21e470193c058108f19d8"
|
||||
integrity sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz#98896eca8012547c7f04bd07eaa6896825f9e1a5"
|
||||
integrity sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz"
|
||||
@@ -466,6 +728,21 @@
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz"
|
||||
integrity sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz#7eeada98444e580674de6989284e4baacd48ea65"
|
||||
integrity sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz#516c4b54f80587b4a390aaf4940b40870271d35d"
|
||||
integrity sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.41.1":
|
||||
version "4.41.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz#848f99b0d9936d92221bb6070baeff4db6947a30"
|
||||
integrity sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==
|
||||
|
||||
"@sheerun/mutationobserver-shim@^0.3.2":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz"
|
||||
@@ -476,7 +753,7 @@
|
||||
resolved "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz"
|
||||
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
|
||||
|
||||
"@stencil/core@^4.0.3", "@stencil/core@4.20.0":
|
||||
"@stencil/core@4.20.0", "@stencil/core@^4.0.3":
|
||||
version "4.20.0"
|
||||
resolved "https://registry.npmjs.org/@stencil/core/-/core-4.20.0.tgz"
|
||||
integrity sha512-WPrTHFngvN081RY+dJPneKQLwnOFD60OMCOQGmmSHfCW0f4ujPMzzhwWU1gcSwXPWXz5O+8cBiiCaxAbJU7kAg==
|
||||
@@ -550,7 +827,7 @@
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@1.0.7":
|
||||
"@types/estree@*", "@types/estree@1.0.7", "@types/estree@^1.0.0":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz"
|
||||
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
|
||||
@@ -648,7 +925,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz"
|
||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||
|
||||
"@types/node@*", "@types/node@^18.0.0 || ^20.0.0 || >=22.0.0":
|
||||
"@types/node@*":
|
||||
version "22.15.21"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz"
|
||||
integrity sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==
|
||||
@@ -687,7 +964,7 @@
|
||||
"@types/history" "^4.7.11"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^18.2.25 || ^19", "@types/react@^19.0.0", "@types/react@>=16", "@types/react@>=18", "@types/react@19.0.10":
|
||||
"@types/react@*", "@types/react@19.0.10":
|
||||
version "19.0.10"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz"
|
||||
integrity sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==
|
||||
@@ -890,7 +1167,7 @@ braces@^3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.24.0, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.24.0:
|
||||
version "4.24.5"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz"
|
||||
integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==
|
||||
@@ -923,6 +1200,11 @@ ccount@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz"
|
||||
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
|
||||
|
||||
chalk@5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz"
|
||||
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz"
|
||||
@@ -931,11 +1213,6 @@ chalk@^3.0.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz"
|
||||
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
|
||||
|
||||
character-entities-html4@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz"
|
||||
@@ -997,16 +1274,16 @@ color-convert@^2.0.1:
|
||||
dependencies:
|
||||
color-name "~1.1.4"
|
||||
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
colorette@^2.0.20:
|
||||
version "2.0.20"
|
||||
resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz"
|
||||
@@ -1024,16 +1301,16 @@ comma-separated-tokens@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz"
|
||||
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
||||
|
||||
commander@^12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz"
|
||||
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
|
||||
|
||||
commander@11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz"
|
||||
integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==
|
||||
|
||||
commander@^12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz"
|
||||
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
|
||||
|
||||
convert-source-map@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
|
||||
@@ -1087,13 +1364,6 @@ date-fns@^2.25.0:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
|
||||
debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.4.0:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
|
||||
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
@@ -1101,6 +1371,13 @@ debug@4.3.4:
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.4.0:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
|
||||
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
decode-named-character-reference@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz"
|
||||
@@ -1364,6 +1641,11 @@ fs-minipass@^2.0.0:
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
|
||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||
@@ -1516,6 +1798,11 @@ hyphenate-style-name@^1.0.3:
|
||||
resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz"
|
||||
integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==
|
||||
|
||||
immer@^9.0.16:
|
||||
version "9.0.21"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
|
||||
integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
|
||||
|
||||
immutable@^5.0.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz"
|
||||
@@ -1678,7 +1965,7 @@ kleur@^4.1.5:
|
||||
resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz"
|
||||
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
|
||||
|
||||
leaflet@^1.9.0, leaflet@^1.9.4:
|
||||
leaflet@^1.9.4:
|
||||
version "1.9.4"
|
||||
resolved "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz"
|
||||
integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
|
||||
@@ -2065,7 +2352,7 @@ micromark@^4.0.0:
|
||||
micromark-util-symbol "^2.0.0"
|
||||
micromark-util-types "^2.0.0"
|
||||
|
||||
micromatch@^4.0.5, micromatch@4.0.5:
|
||||
micromatch@4.0.5, micromatch@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz"
|
||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||
@@ -2132,16 +2419,16 @@ mkdirp@^1.0.3:
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
nano-css@^5.6.2:
|
||||
version "5.6.2"
|
||||
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz"
|
||||
@@ -2281,7 +2568,7 @@ picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
"picomatch@^3 || ^4", picomatch@^4.0.2:
|
||||
picomatch@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz"
|
||||
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
|
||||
@@ -2361,14 +2648,22 @@ proxy-from-env@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
react-dom@*, "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react-dom@^19.0.0, react-dom@>=16.8.6, react-dom@19.0.0:
|
||||
pullstate@^2.0.0-pre.0:
|
||||
version "2.0.0-pre.0"
|
||||
resolved "https://registry.yarnpkg.com/pullstate/-/pullstate-2.0.0-pre.0.tgz#30dd108112b520ded57c35bdb10929314466da40"
|
||||
integrity sha512-lvMKp36NYcl1fVsm7v36CRokEEJUW2j8IlwvOMqbYeFe/shIscDoT+boW1CXFMy08EG8KO1X9pyOFCxXjMh4jw==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.3"
|
||||
immer "^9.0.16"
|
||||
|
||||
react-dom@19.0.0:
|
||||
version "19.0.0"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz"
|
||||
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
|
||||
dependencies:
|
||||
scheduler "^0.25.0"
|
||||
|
||||
react-hook-form@^7.0.0, react-hook-form@^7.55.0:
|
||||
react-hook-form@^7.55.0:
|
||||
version "7.57.0"
|
||||
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz"
|
||||
integrity sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==
|
||||
@@ -2415,7 +2710,7 @@ react-refresh@^0.17.0:
|
||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz"
|
||||
integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==
|
||||
|
||||
react-router-dom@^5.0.1, react-router-dom@^5.3.4:
|
||||
react-router-dom@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz"
|
||||
integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==
|
||||
@@ -2428,7 +2723,7 @@ react-router-dom@^5.0.1, react-router-dom@^5.3.4:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@^5.0.1, react-router@^5.3.4, react-router@5.3.4:
|
||||
react-router@5.3.4, react-router@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz"
|
||||
integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
|
||||
@@ -2473,7 +2768,7 @@ react-use@^17.6.0:
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
react@*, "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18.0 || ^19", react@^19.0.0, react@>=15, react@>=16, react@>=16.8.6, react@>=18, react@19.0.0:
|
||||
react@19.0.0:
|
||||
version "19.0.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.0.0.tgz"
|
||||
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
|
||||
@@ -2499,11 +2794,6 @@ redux@^4.0.0:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
redux@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
|
||||
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
|
||||
|
||||
remark-parse@^11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz"
|
||||
@@ -2602,7 +2892,7 @@ safe-buffer@~5.2.0:
|
||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
sass@*, sass@^1.85.1:
|
||||
sass@^1.85.1:
|
||||
version "1.89.0"
|
||||
resolved "https://registry.npmjs.org/sass/-/sass-1.89.0.tgz"
|
||||
integrity sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==
|
||||
@@ -2613,7 +2903,7 @@ sass@*, sass@^1.85.1:
|
||||
optionalDependencies:
|
||||
"@parcel/watcher" "^2.4.1"
|
||||
|
||||
sax@>=0.6.0, sax@1.1.4:
|
||||
sax@1.1.4, sax@>=0.6.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz"
|
||||
integrity sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==
|
||||
@@ -2687,21 +2977,21 @@ slice-ansi@^5.0.0:
|
||||
ansi-styles "^6.0.0"
|
||||
is-fullwidth-code-point "^4.0.0"
|
||||
|
||||
source-map-js@^1.2.1, "source-map-js@>=0.6.2 <2.0.0":
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz"
|
||||
integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
space-separated-tokens@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
|
||||
@@ -2746,13 +3036,6 @@ stacktrace-js@^2.0.2:
|
||||
stack-generator "^2.0.5"
|
||||
stacktrace-gps "^3.0.4"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string-argv@0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz"
|
||||
@@ -2776,7 +3059,7 @@ string-width@^4.1.0:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.0:
|
||||
string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
@@ -2785,14 +3068,12 @@ string-width@^5.0.0:
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
eastasianwidth "^0.2.0"
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
stringify-entities@^4.0.0:
|
||||
version "4.0.4"
|
||||
@@ -3055,7 +3336,7 @@ vfile@^6.0.0:
|
||||
"@types/unist" "^3.0.0"
|
||||
vfile-message "^4.0.0"
|
||||
|
||||
"vite@^4.2.0 || ^5.0.0 || ^6.0.0", vite@^6.2.0:
|
||||
vite@^6.2.0:
|
||||
version "6.3.5"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz"
|
||||
integrity sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==
|
||||
@@ -3099,16 +3380,7 @@ wrap-ansi@^7.0.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.0.1:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
|
||||
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
|
||||
dependencies:
|
||||
ansi-styles "^6.1.0"
|
||||
string-width "^5.0.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
|
||||
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
|
||||
@@ -3145,11 +3417,6 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^2.4.2:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz"
|
||||
integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==
|
||||
|
||||
yaml@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz"
|
||||
|