update adding demo app,

This commit is contained in:
louiscklaw
2025-06-04 14:46:31 +08:00
parent b78709db9b
commit dff07ddcb0
82 changed files with 3552 additions and 97 deletions

View File

@@ -9,6 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-geolocation')
implementation project(':capacitor-preferences')
}

View File

@@ -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')

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -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} />

View File

@@ -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} /> */}
</>
);
};

View File

@@ -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} />
</>
);
};

View 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',
};

View 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),
});

View 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;
}

View 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;
}

View 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;

View File

@@ -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;
}

View 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;

View 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;

View 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;

View 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;

View 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>&nbsp;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;

View 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} />&nbsp;
Add to Cart
<CreateAnimation ref={animationRef} {...mainAnimation}>
<IonIcon icon={cartOutline} size="large" style={floatStyle} />
</CreateAnimation>
</IonButton>
);
}

View 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>
);
}

View File

@@ -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 &rarr;
</IonButton>
</IonFooter>
</IonPage>
);
};

View 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>
);
}

View 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;
}

View 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>
</>
);
}

View 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} />
&nbsp;&nbsp;
<IonNote>{reviewCount} review{reviewCount > 1 && "s"}</IonNote>
</IonCol>
);
}

View File

@@ -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>
);
}

View 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,
},
];

View 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;
}

View 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;
}

View 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);

View File

@@ -0,0 +1,2 @@
export { default as FavouritesStore } from './FavouritesStore';
export { default as CartStore } from './CartStore';

View 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;
}

View File

@@ -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 (

View 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;
}

View File

@@ -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} />
{/* */}

View File

@@ -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),
});

View 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;

View 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;

View File

@@ -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>
);
};

View File

@@ -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}&#8451;
</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>
);

View File

@@ -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>
);

View 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;

View 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;
}

View 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} />&nbsp;
Add to Cart
<CreateAnimation ref={animationRef} {...mainAnimation}>
<IonIcon icon={cartOutline} size="large" style={floatStyle} />
</CreateAnimation>
</IonButton>
);
}

View 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>
);
}

View 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 &rarr;</IonButton>
</IonFooter>
</IonPage>
);
}

View 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>
);
}

View 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;
}

View 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>
</>
);
}

View 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} />
&nbsp;&nbsp;
<IonNote>{reviewCount} review{reviewCount > 1 && "s"}</IonNote>
</IonCol>
);
}

View File

@@ -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>
);
}

View 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;
}

View 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;
}

View 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);

View File

@@ -0,0 +1,2 @@
export { default as FavouritesStore } from "./FavouritesStore";
export { default as CartStore } from "./CartStore";

View 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);
};

View File

@@ -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;

View File

@@ -21,7 +21,8 @@
"useDefineForClassFields": true
},
"include": [
"src"
"src",
"src/pages/Settings/index.tsx"
],
"exclude": [
"node_modules",

View File

@@ -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"