Compare commits

...

7 Commits

241 changed files with 96964 additions and 7117 deletions

View File

@@ -1,6 +1,15 @@
{
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
"printWidth": 160,
"overrides": [
{
"files": "src/App.tsx",
"options": {
"printWidth": 240
}
}
]
}

View File

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

View File

@@ -2,5 +2,14 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-barcode-scanner'
project(':capacitor-barcode-scanner').projectDir = new File('../node_modules/@capacitor/barcode-scanner/android')
include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android')
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

@@ -1,9 +1,9 @@
#!/usr/bin/env bash
while true; do
npm i -D
yarn -D
npm run dev
yarn run dev
echo "restarting..."
sleep 1

View File

@@ -11,6 +11,9 @@ 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 'CapacitorBarcodeScanner', :path => '../../node_modules/@capacitor/barcode-scanner'
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
end

File diff suppressed because it is too large Load Diff

View File

@@ -7,24 +7,33 @@
"private": true,
"dependencies": {
"@capacitor/android": "7.0.1",
"@capacitor/barcode-scanner": "^2.0.3",
"@capacitor/clipboard": "^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",
"@ionic/react": "^8.5.0",
"@ionic/react-router": "^8.5.0",
"@mdx-js/react": "^3.1.0",
"@react-hook/window-size": "^3.1.1",
"@types/leaflet": "^1.9.17",
"@types/react-redux": "^7.1.34",
"axios": "^1.9.0",
"date-fns": "^2.25.0",
"ionicons": "^7.1.2",
"leaflet": "^1.9.4",
"pigeon-maps": "^0.22.1",
"pullstate": "^1",
"react": "19.0.0",
"react-confetti": "^6.4.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.55.0",
"react-leaflet": "^5.0.0",
"react-markdown": "^10.1.0",
"react-qr-code": "^2.0.15",
"react-qr-reader": "^3.0.0-beta-1",
"react-redux": "^9.2.0",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
@@ -32,7 +41,8 @@
"react-use": "^17.6.0",
"reselect": "^4.0.0",
"sass": "^1.85.1",
"swiper": "^9.1.1",
"swiper": "^11.2.8",
"use-sound": "^5.0.0",
"zod": "^3.24.2"
},
"scripts": {
@@ -64,7 +74,7 @@
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.3.4",
"lint-staged": "^13.2.0",
"prettier": "^2.8.6",
"prettier": "^3.5.3",
"typescript": "^5.8.2",
"vite": "^6.2.0"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 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: 23 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: 42 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: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 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: 16 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: 682 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 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: 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,19 @@ 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/DemoWeatherApp';
import DemoClubHouse from './pages/DemoClubHouse';
import DemoScoreBoard from './pages/DemoScoreBoard';
import DemoQuoteApp from './pages/DemoQuoteApp';
import DemoQrScanner from './pages/DemoQrScanner';
// DemoShopAppUi
import DemoShopAppUi from './pages/DemoShopAppUi';
// DemoDictionaryApp
import DemoDictionaryApp from './pages/DemoDictionaryApp';
// demo-recipe-app
import DemoRecipeApp from './pages/DemoRecipeApp';
setupIonicReact();
@@ -121,6 +134,15 @@ 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={paths.DEMO_CLUB_HOUSE} render={() => <DemoClubHouse />} />
<Route path={paths.DEMO_SCORE_BOARD} render={() => <DemoScoreBoard />} />
<Route path={paths.DEMO_QUOTE_APP} render={() => <DemoQuoteApp />} />
<Route path={paths.DEMO_QR_SCANNER} render={() => <DemoQrScanner />} />
<Route path={paths.DEMO_SHOP_APP_UI} render={() => <DemoShopAppUi />} />
<Route path={paths.DEMO_DICTIONARY_APP} render={() => <DemoDictionaryApp />} />
<Route path={paths.DEMO_RECIPE_APP} render={() => <DemoRecipeApp />} />
{/* */}
<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 (

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

@@ -15,13 +15,18 @@ import { endpoints } from '../../pages/MyLogin/endpoints';
export const loadUserData = () => async (dispatch: React.Dispatch<any>) => {
dispatch(setLoading(true));
const data = await getUserData();
dispatch(setData(data));
dispatch(setLoading(false));
};
export const setLoading = (isLoading: boolean) =>
({ type: 'set-user-loading', isLoading } as const);
({
type: 'set-user-loading',
isLoading,
} as const);
export const setData = (data: Partial<UserState>) =>
({
@@ -30,6 +35,7 @@ export const setData = (data: Partial<UserState>) =>
} as const);
export const logoutUser = () => async (dispatch: React.Dispatch<any>) => {
//
await setIsLoggedInData(false);
dispatch(setUsername());
};
@@ -44,6 +50,8 @@ export const setIsLoggedIn = (loggedIn: boolean) => async (dispatch: React.Dispa
export const setUsername = (username?: string) => async (dispatch: React.Dispatch<any>) => {
await setUsernameData(username);
console.log('setUsername triggered');
return {
type: 'set-username',
username,
@@ -52,6 +60,7 @@ export const setUsername = (username?: string) => async (dispatch: React.Dispatc
export const setAccessToken = (token?: string) => async (dispatch: React.Dispatch<any>) => {
await setAccessTokenData(token);
return {
type: 'set-access-token',
token,
@@ -120,5 +129,4 @@ export type UserActions =
| ActionType<typeof setHasSeenTutorial>
| ActionType<typeof setDarkMode>
| ActionType<typeof setAccessToken>
// | ActionType<typeof setSession>
| ActionType<typeof checkUserSession>;

View File

@@ -15,11 +15,9 @@ export function userReducer(state: UserState, action: UserActions): UserState {
return { ...state, darkMode: action.darkMode };
case 'set-is-loggedin':
return { ...state, isLoggedin: action.loggedIn };
case 'set-access-token':
return { ...state, token: action.token };
case 'check-user-session':
return { ...state, isSessionValid: action.sessionValid };
// case 'set-active-session':
// return { ...state, session: action.session };
// case 'set-access-token':
// return { ...state, token: action.token };
}
}

View File

@@ -1,9 +1,9 @@
export interface UserState {
isLoggedin: boolean;
username?: string;
darkMode: boolean;
hasSeenTutorial: boolean;
loading: boolean;
username?: string;
hasSeenTutorial: boolean;
darkMode: boolean;
isLoggedin: boolean;
isSessionValid: boolean;
session?: any;
token?: string;

View File

@@ -0,0 +1,12 @@
.extra-padding {
padding-right: 1.3rem !important;
padding-left: 1.3rem !important;
}
.title {
font-weight: 600;
font-size: 1.2rem;
margin-bottom: 0.5rem;
}

View File

@@ -0,0 +1,90 @@
import {
IonButton,
IonButtons,
IonCol,
IonContent,
IonGrid,
IonHeader,
IonIcon,
IonPage,
IonRow,
IonText,
IonTitle,
IonToolbar,
useIonRouter,
} from '@ionic/react';
import { chevronBack, personOutline } from 'ionicons/icons';
import { useStoreState } from 'pullstate';
import { TalkStore } from '../store';
import { getTalks } from '../store/Selectors';
import './Tab1.css';
import { TalkCard } from '../components/TalkCard';
import { useRef } from 'react';
const Tab1 = () => {
const pageRef = useRef();
const talks = useStoreState(TalkStore, getTalks);
const router = useIonRouter();
function handleBackClick() {
router.goBack();
}
return (
<IonPage ref={pageRef}>
<IonHeader>
<IonToolbar>
<IonTitle>All Rooms</IonTitle>
<IonButtons slot="end">
<IonButton>
<IonIcon icon={personOutline} />
</IonButton>
</IonButtons>
<IonButtons slot="start">
<IonButton onclick={handleBackClick}>
<IonIcon icon={chevronBack} />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonGrid className="ion-padding-start ion-padding-end extra-padding ion-padding-bottom ion-margin-bottom">
<IonRow>
<IonCol size="12">
<IonText color="dark">
<p className="title">Upcoming</p>
</IonText>
</IonCol>
</IonRow>
<IonRow>
<IonCol size="12">
<TalkCard upcoming={true} talk={talks[0]} pageRef={pageRef} />
</IonCol>
</IonRow>
<IonRow>
<IonCol size="12">
<IonText color="dark">
<p className="title">Happening Now</p>
</IonText>
</IonCol>
</IonRow>
<IonRow>
<IonCol size="12">
{talks.map((talk, talkIndex) => {
return talkIndex > 0 && <TalkCard key={talkIndex} talk={talk} pageRef={pageRef} />;
})}
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonPage>
);
};
export default Tab1;

View File

@@ -0,0 +1,23 @@
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
import './Tab2.css';
const Tab2 = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>TO DO :)</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">TO DO :)</IonTitle>
</IonToolbar>
</IonHeader>
</IonContent>
</IonPage>
);
};
export default Tab2;

View File

@@ -0,0 +1,23 @@
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
import './Tab3.css';
const Tab3 = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>TO DO :)</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">TO DO :)</IonTitle>
</IonToolbar>
</IonHeader>
</IonContent>
</IonPage>
);
};
export default Tab3;

View File

@@ -0,0 +1,100 @@
import { IonCardSubtitle, IonIcon, IonModal, IonNote, IonRow, useIonModal } from "@ionic/react";
import { bulb, micOutline, personOutline } from "ionicons/icons";
import { useStoreState } from "pullstate";
import { useEffect, useState } from "react";
import { CategoryStore } from '../store';
import { getPeople } from "../store/PeopleStore";
import { getCategory } from '../store/Selectors';
import styles from "./TalkCard.module.css";
import { TalkModal } from "./TalkModal";
export const TalkCard = ({ upcoming = false, talk, pageRef }) => {
const talkCategory = useStoreState(CategoryStore, getCategory(talk.category_id));
const [ speakers, setSpeakers ] = useState([]);
const [ showModal, setShowModal ] = useState(false);
useEffect(() => {
setSpeakers(getPeople(talk.speakers));
}, [ talk ]);
// const [ present, dismiss ] = useIonModal(TalkModal, {
// dismiss: () => dismiss(),
// talk,
// speakers,
// category: talkCategory
// });
// const handleShowTalk = () => {
// console.log("in here");
// present({
// // presentingElement: pageRef.current
// });
// }
return (
<>
<div className={ `${ styles.talkCard } ${ upcoming && styles.upcomingCard }` } onClick={ () => setShowModal(true) }>
<div className={ styles.cardTitle }>
<IonIcon color={ upcoming ? "primary" : "white" } icon={ bulb } />
<IonCardSubtitle color={ upcoming ? "light" : "primary" }>{ talkCategory.name } talks</IonCardSubtitle>
</div>
<div className={ styles.talkTitle }>
<h3>{ talk.title }</h3>
</div>
{ !upcoming &&
<IonRow className={ styles.talkSpeakers }>
{ speakers.map((speaker, index) => {
return (
<div key={ `speaker_${ index }` } className={ styles.talkSpeaker }>
<img src={ speaker.image } alt="avatar" />
</div>
);
})}
</IonRow>
}
{ upcoming &&
<div className={ styles.talkDate }>
<IonNote color="secondary">{ talk.time }</IonNote>
<div className={ styles.detailCount }>
<IonIcon icon={ micOutline } color="light " />
<span>{ talk.speakers } Speakers</span>
</div>
</div>
}
{ !upcoming &&
<div className={ styles.talkDetails }>
<div className={ styles.detailCount }>
<IonIcon icon={ micOutline } color="primary" />
<span>{ talk.speakers } Speakers</span>
</div>
<div className={ styles.detailCount }>
<IonIcon icon={ personOutline } color="primary" />
<span>{ talk.audience } Audience</span>
</div>
</div>
}
</div>
<IonModal isOpen={ showModal } onDidDismiss={ () => setShowModal(false) } presentingElement={ pageRef.current }>
<TalkModal dismiss={ () => setShowModal(false) } speakers={ speakers } talk={ talk } category={ talkCategory } />
</IonModal>
</>
);
}

View File

@@ -0,0 +1,105 @@
.talkCard {
display: flex;
flex-direction: column;
border-radius: 10px;
padding: 2rem;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
background-color: white;
}
.talkCard:not(:first-child) {
margin-top: 2rem;
}
.upcomingCard {
background-color: var(--ion-color-primary);
color: white;
}
.cardTitle {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
color: white;
}
.cardTitle ion-icon {
border-radius: 500px;
padding: 0.2rem;
margin-right: 0.75rem;
background-color: var(--ion-color-primary);
margin-top: -0.2rem;
}
.upcomingCard .cardTitle ion-icon {
background-color: white;
}
.talkTitle h3 {
font-size: 1.3rem !important;
font-weight: 600;
}
.talkDate {
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
margin-top: 0.5rem;
}
.talkSpeakers {
display: flex;
flex-direction: row;
margin-top: 0.5rem;
}
.talkSpeaker {
display: flex;
align-items: center;
justify-content: center;
height: 3.5rem;
width: 3.5rem;
border-radius: 12px;
margin-right: 0.2rem;
background-color: var(--ion-color-primary);
}
.talkSpeaker img {
height: 3rem;
width: 3rem;
}
.talkDetails {
display: flex;
justify-content: space-between;
margin-top: 1.5rem;
}
.detailCount {
display: flex;
align-content: center;
align-items: center;
font-weight: 500;
font-size: 0.9rem;
}
.detailCount ion-icon {
font-size: 1.2rem;
margin-right: 0.3rem;
}

View File

@@ -0,0 +1,98 @@
import { IonButton, IonButtons, IonCardSubtitle, IonCol, IonContent, IonGrid, IonHeader, IonIcon, IonPage, IonRow, IonTitle, IonToolbar } from "@ionic/react";
import { bulb, exitOutline, micOutline, personOutline } from "ionicons/icons";
import { useStoreState } from "pullstate";
import { PeopleStore } from "../store";
import { getAllPeople } from "../store/Selectors";
import styles from "./TalkModal.module.css";
export const TalkModal = ({ dismiss, talk, category, speakers }) => {
const people = useStoreState(PeopleStore, getAllPeople);
return (
<IonPage className="talk-modal">
<IonHeader>
<IonToolbar>
<IonTitle>Talk Room</IonTitle>
<IonButtons slot="end">
<IonButton color="primary" onClick={ dismiss }>
<IonIcon icon={ exitOutline } />
{/* Leave Room */}
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent className={ styles.modal }>
<IonGrid className="ion-padding-start ion-padding-end ion-margin-start ion-margin-end">
<IonRow>
<IonCol size="12">
<div className={ styles.cardTitle }>
<IonIcon color="white" icon={ bulb } />
<IonCardSubtitle color="primary">{ category.name } talks</IonCardSubtitle>
</div>
</IonCol>
</IonRow>
<IonRow className={ styles.talkTitle }>
<IonCol size="12">
<h1>{ talk.title }</h1>
</IonCol>
</IonRow>
<IonRow>
<IonCol size="12">
<div className={ styles.detailCount }>
<IonIcon icon={ micOutline } color="primary" />
<span>{ talk.speakers } Speakers</span>
</div>
</IonCol>
</IonRow>
<IonRow className={ styles.talkSpeakers }>
{ speakers.map((speaker, index) => {
return (
<IonCol className={ styles.speakerContainer }>
<div key={ `speaker_${ index }` } className={ `${ styles.talkSpeaker } ${ index === 0 && styles.activeSpeaker }` }>
<img src={ speaker.image } alt="avatar" />
</div>
<p>{ speaker.name.split(" ")[0] }</p>
</IonCol>
);
})}
</IonRow>
<IonRow>
<IonCol size="12">
<div className={ styles.detailCount }>
<IonIcon icon={ personOutline } color="primary" />
<span>{ talk.audience } Audience</span>
</div>
</IonCol>
</IonRow>
<IonRow className={ styles.talkSpeakers }>
{ [ ...Array(talk.audience)].map((audience, index) => {
return (
<IonCol size="3" className={ `${ styles.speakerContainer } ${ styles.audienceContainer }` }>
<div key={ `speaker_${ index }` } className={ styles.talkSpeaker }>
<img src={ people[Math.floor(Math.random() * 30)].image } alt="avatar" />
</div>
<p>{ people[Math.floor(Math.random() * 30)].name.split(" ")[0] }</p>
</IonCol>
);
})}
</IonRow>
</IonGrid>
</IonContent>
</IonPage>
);
}

View File

@@ -0,0 +1,150 @@
.talkCard {
display: flex;
flex-direction: column;
border-radius: 10px;
padding: 2rem;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
background-color: white;
}
.talkCard:not(:first-child) {
margin-top: 2rem;
}
.upcomingCard {
background-color: var(--ion-color-primary);
color: white;
}
.cardTitle {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
color: white;
}
.cardTitle ion-icon {
border-radius: 500px;
padding: 0.2rem;
margin-right: 0.75rem;
background-color: var(--ion-color-primary);
margin-top: -0.2rem;
}
.upcomingCard .cardTitle ion-icon {
background-color: white;
}
.talkTitle {
margin-top: -1rem;
}
.talkTitle h3 {
font-size: 1.3rem !important;
font-weight: 600;
}
.talkDate {
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
margin-top: 0.5rem;
}
.talkSpeakers {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 0.5rem;
margin-bottom: 1.5rem;
}
.speakerContainer {
display: flex;
flex-direction: column;
align-content: center;
align-items: center;
justify-content: space-between;
}
.audienceContainer {
margin-bottom: 1rem;
/* margin: 0.5rem; */
}
.talkSpeaker {
display: flex;
align-items: center;
justify-content: center;
height: 3.5rem;
width: 3.5rem;
border-radius: 12px;
margin-right: 0.2rem;
background-color: var(--ion-color-primary);
}
.audienceContainer .talkSpeaker {
background-color: #f2efe5;
border: 2px solid #dfd9c7;
}
.talkSpeaker img {
height: 3rem;
width: 3rem;
}
.speakerContainer p {
margin: 0;
padding: 0;
margin-top: 0.2rem;
font-weight: 500;
font-size: 0.9rem;
}
.talkDetails {
display: flex;
justify-content: space-between;
margin-top: 1.5rem;
}
.detailCount {
display: flex;
align-content: center;
align-items: center;
font-weight: 600;
font-size: 0.9rem;
margin-top: 1rem;
margin-bottom: 0.3rem;
}
.detailCount ion-icon {
font-size: 1.2rem;
margin-right: 0.3rem;
margin-left: -0.4rem;
}
.activeSpeaker {
border: 3px solid rgb(255, 187, 0);
}

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,116 @@
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 './components1/SkeletonDashboard';
import {
addCircle,
addCircleOutline,
chevronBack,
cloudOutline,
home,
homeOutline,
notifications,
notificationsOutline,
person,
personOutline,
refreshOutline,
search,
searchOutline,
} from 'ionicons/icons';
import { CurrentWeather } from './components1/CurrentWeather';
import { IonReactRouter } from '@ionic/react-router';
import { Route, Redirect } from 'react-router';
import Tab1 from './AppPages/Tab1';
import Tab2 from './AppPages/Tab2';
import Tab3 from './AppPages/Tab3';
const DemoClubHouse = () => {
const tabs = [
{
name: 'Home',
url: '/demo-club-house/home',
activeIcon: home,
icon: homeOutline,
component: Tab1,
},
{
name: 'Search',
url: '/search',
activeIcon: search,
icon: searchOutline,
component: Tab2,
},
{
name: 'Add',
url: '/add',
activeIcon: addCircle,
icon: addCircleOutline,
component: Tab3,
},
{
name: 'Account',
url: '/account',
activeIcon: person,
icon: personOutline,
component: Tab3,
},
{
name: 'Notifications',
url: '/notifications',
activeIcon: notifications,
icon: notificationsOutline,
component: Tab3,
},
];
const [activeTab, setActiveTab] = useState(tabs[0].name);
return (
<IonTabs onIonTabsDidChange={(e) => setActiveTab(e.detail.tab)}>
<IonRouterOutlet>
{tabs.map((tab, index) => {
return (
<Route key={index} exact path={tab.url}>
<tab.component />
</Route>
);
})}
<Route exact path="/demo-club-house">
<Redirect to="/demo-club-house/home" />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
{tabs.map((tab, barIndex) => {
const active = tab.name === activeTab;
return (
<IonTabButton key={`tab_${barIndex}`} tab={tab.name} href={tab.url}>
<IonIcon icon={active ? tab.activeIcon : tab.icon} />
</IonTabButton>
);
})}
</IonTabBar>
</IonTabs>
);
};
export default DemoClubHouse;

View File

@@ -0,0 +1,25 @@
import { Store } from "pullstate";
const CategoryStore = new Store({
categories: [
{
id: 1,
name: "Design"
},
{
id: 2,
name: "Javascript"
},
{
id: 3,
name: "Mobile"
},
{
id: 4,
name: "Business"
}
]
});
export default CategoryStore;

View File

@@ -0,0 +1,209 @@
import { Store } from "pullstate";
const PeopleStore = new Store({
people: [
{
name: "Andrew Bennet",
image: "/avatars/Avatar-1.png"
},
{
name: "Elizabeth Moore",
image: "/avatars/Avatar-2.png"
},
{
name: "Oscar Clarke",
image: "/avatars/Avatar-3.png"
},
{
name: "Sandra Simpson",
image: "/avatars/Avatar-4.png"
},
{
name: "Sophia Price",
image: "/avatars/Avatar-5.png"
},
{
name: "Jasmine Ruiz",
image: "/avatars/Avatar-6.png"
},
{
name: "Adriana Bonny",
image: "/avatars/Avatar-7.png"
},
{
name: "Maya Watson",
image: "/avatars/Avatar-8.png"
},
{
name: "Tatum Porter",
image: "/avatars/Avatar-9.png"
},
{
name: "Jackson Watts",
image: "/avatars/Avatar-10.png"
},
{
name: "Lana Cooper",
image: "/avatars/Avatar-11.png"
},
{
name: "Mateo Hoffman",
image: "/avatars/Avatar-12.png"
},
{
name: "Harper James",
image: "/avatars/Avatar-13.png"
},
{
name: "Edgar Douglas",
image: "/avatars/Avatar-14.png"
},
{
name: "Lilly Hale",
image: "/avatars/Avatar-15.png"
},
{
name: "Jade Williams",
image: "/avatars/Avatar-16.png"
},
{
name: "Cayden Long",
image: "/avatars/Avatar-17.png"
},
{
name: "Millie Klein",
image: "/avatars/Avatar-18.png"
},
{
name: "Heidi Toffer",
image: "/avatars/Avatar-19.png"
},
{
name: "Alaya Bailey",
image: "/avatars/Avatar-20.png"
},
{
name: "Laura Diaz",
image: "/avatars/Avatar-21.png"
},
{
name: "Alina Gomez",
image: "/avatars/Avatar-22.png"
},
{
name: "Rachel Tiffin",
image: "/avatars/Avatar-23.png"
},
{
name: "Liz Faxty",
image: "/avatars/Avatar-24.png"
},
{
name: "Sarah Goodman",
image: "/avatars/Avatar-25.png"
},
{
name: "Melissa Bengin",
image: "/avatars/Avatar-26.png"
},
{
name: "Stephanie Morter",
image: "/avatars/Avatar-27.png"
},
{
name: "Rebecca Slims",
image: "/avatars/Avatar-28.png"
},
{
name: "Arielle May",
image: "/avatars/Avatar-29.png"
},
{
name: "Jack Boppes",
image: "/avatars/Avatar-30.png"
},
{
name: "Christina Rankin",
image: "/avatars/Avatar-31.png"
},
{
name: "Ronan Murf",
image: "/avatars/Avatar-32.png"
},
{
name: "Daniel Jackson",
image: "/avatars/Avatar-33.png"
},
{
name: "Richard Bales",
image: "/avatars/Avatar-34.png"
},
{
name: "Harmony Martin",
image: "/avatars/Avatar-35.png"
},
{
name: "Chris Huges",
image: "/avatars/Avatar-36.png"
}
]
});
export default PeopleStore;
export const getPeople = amount => {
let tempPeople = [ ...PeopleStore.getRawState().people ];
return tempPeople.sort(() => Math.random() - Math.random()).slice(0, amount);
}
// export const markActiveAsDone = () => {
// PeopleStore.update(state => {
// const scoreboardIndex = state.scoreboards.findIndex(scoreboard => scoreboard.active === true);
// state.scoreboards[scoreboardIndex].done = true;
// });
// }
// export const addScoreboard = (players, details) => {
// PeopleStore.update(s => { s.scoreboards = s.scoreboards.map(scoreboard => scoreboard.active = false) });
// PeopleStore.update(state => {
// state.scoreboards.forEach((scoreboard, index) => {
// state.scoreboards[index].active = false;
// });
// });
// const newScoreboard = {
// id: Date.now(),
// title: details.title,
// players: [ ...players ],
// active: true
// };
// const playersToSave = players.filter(p => p.saved === true);
// PeopleStore.update(s => { s.scoreboards = [ ...s.scoreboards, newScoreboard ] });
// PeopleStore.update(s => { s.players = [ ...s.players, ...playersToSave ] });
// }
// export const addScoreToPlayer = (scoreboardId, playerIndex) => {
// PeopleStore.update(state => {
// const scoreboardIndex = state.scoreboards.findIndex(scoreboard => scoreboard.id === parseInt(scoreboardId));
// state.scoreboards[scoreboardIndex].players[playerIndex].score += 1;
// state.scoreboards[scoreboardIndex].players.sort((a, b) => {
// if (a.score > b.score) return -1
// return a.score < b.score ? 1 : 0
// });
// });
// }

View File

@@ -0,0 +1,13 @@
import { createSelector } from 'reselect';
const getState = state => state;
// General getters
export const getAllPeople = createSelector(getState, state => state.people);
export const getCategories = createSelector(getState, state => state.categories);
export const getTalks = createSelector(getState, state => state.talks);
// Specific getters
export const getCategoryTalks = categoryId => createSelector(getState, state => state.talks.filter(talk => parseInt(talk.category_id) === parseInt(categoryId))[0]);
export const getTalk = id => createSelector(getState, state => state.talks.filter(talk => parseInt(talk.id) === parseInt(id))[0]);
export const getCategory = id => createSelector(getState, state => state.categories.filter(category => parseInt(category.id) === parseInt(id))[0]);

View File

@@ -0,0 +1,111 @@
import { Store } from "pullstate";
const TalkStore = new Store({
talks: [
{
id: 1,
title: "The future of design systems",
date: "29th Oct 2021",
time: "5:00PM",
speakers: 3,
audience: 14,
category_id: 1
},
{
id: 2,
title: "Lets talk about ReactJS",
date: "13th Nov 2021",
time: "2:00PM",
speakers: 4,
audience: 239,
category_id: 2
},
{
id: 3,
title: "How Ionic can transform mobile development",
date: "21st Nov 2021",
time: "7:30PM",
speakers: 2,
audience: 371,
category_id: 3
},
{
id: 4,
title: "Using capacitor to access native features",
date: "25th Nov 2021",
time: "4:15PM",
speakers: 2,
audience: 587,
category_id: 3
},
{
id: 5,
title: "Does SASS give you an advantage?",
date: "29th Nov 2021",
time: "6:00PM",
speakers: 4,
audience: 97,
category_id: 1
},
{
id: 6,
title: "Building a startup from the ground up",
date: "1st Dec 2021",
time: "9:00PM",
speakers: 4,
audience: 316,
category_id: 4
},
{
id: 7,
title: "How we went from 9-5 to my own boss",
date: "12th Dec 2021",
time: "1:00PM",
speakers: 2,
audience: 33,
category_id: 4
},
{
id: 8,
title: "Features of the beast, Angular",
date: "19th Dec 2021",
time: "3:30PM",
speakers: 3,
audience: 114,
category_id: 2
}
]
});
export default TalkStore;
export const addTalk = details => {
const newTalk = {
id: Date.now(),
title: details.title,
date: details.date,
time: details.time,
speakers: details.speakers,
audience: Math.random(900),
category_id: details.category_id
};
TalkStore.update(s => { s.talks = [ ...s.talks, newTalk ] });
}
// export const addScoreToPlayer = (scoreboardId, playerIndex) => {
// PeopleStore.update(state => {
// const scoreboardIndex = state.scoreboards.findIndex(scoreboard => scoreboard.id === parseInt(scoreboardId));
// state.scoreboards[scoreboardIndex].players[playerIndex].score += 1;
// state.scoreboards[scoreboardIndex].players.sort((a, b) => {
// if (a.score > b.score) return -1
// return a.score < b.score ? 1 : 0
// });
// });
// }

View File

@@ -0,0 +1,3 @@
export { default as PeopleStore } from "./PeopleStore";
export { default as TalkStore } from "./TalkStore";
export { default as CategoryStore } from "./CategoryStore";

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,114 @@
import {
IonButton,
IonButtons,
IonCard,
IonCardContent,
IonCardSubtitle,
IonCardTitle,
IonCol,
IonContent,
IonGrid,
IonHeader,
IonIcon,
IonPage,
IonRow,
IonTitle,
IonToolbar,
useIonRouter,
} from '@ionic/react';
import { bookOutline, chevronBackOutline, heart, search } from 'ionicons/icons';
import { useStoreState } from 'pullstate';
import { useRef } from 'react';
import { WordStore } from '../store';
import { getFavourites, getSearchCount } from '../store/Selectors';
const Tab1 = () => {
const router = useIonRouter();
const pageRef = useRef();
const favourites = useStoreState(WordStore, getFavourites);
const searchCount = useStoreState(WordStore, getSearchCount);
function handleBackClick() {
router.goBack();
}
return (
<IonPage ref={pageRef}>
<IonHeader>
<IonToolbar>
<IonTitle>Dashboard</IonTitle>
<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>
<IonButtons slot="start">
<IonButton onClick={() => handleBackClick()}>
<IonIcon icon={chevronBackOutline} color="primary" />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonGrid>
<IonRow className={`animate__animated animate__faster animate__fadeIn`}>
<IonCol size="12">
<IonCard>
<IonCardContent>
<IonIcon icon={bookOutline} color="primary" style={{ fontSize: '2rem' }} />
<IonCardTitle>Ionic Dictionary App</IonCardTitle>
<p>Based on the English language</p>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
<IonRow className={`animate__animated animate__faster animate__fadeIn`}>
<IonCol size="12">
<IonCard>
<IonCardContent>
<IonCardTitle>Did you know?</IonCardTitle>
<p>There are 171, 146 words in the English language!</p>
<IonButton expand="block" className="ion-margin-top" routerLink="/search">
Search now &rarr;
</IonButton>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
<IonRow className={`animate__animated animate__faster animate__fadeIn`}>
<IonCol size="6">
<IonCard routerLink="/favourites">
<IonCardContent className="ion-text-center">
<IonIcon icon={heart} color="primary" />
<IonCardTitle>{favourites.length}</IonCardTitle>
<IonCardSubtitle>Favourites</IonCardSubtitle>
</IonCardContent>
</IonCard>
</IonCol>
<IonCol size="6">
<IonCard routerLink="/favourites">
<IonCardContent className="ion-text-center">
<IonIcon icon={search} color="primary" />
<IonCardTitle>{searchCount}</IonCardTitle>
<IonCardSubtitle>Searches</IonCardSubtitle>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonPage>
);
};
export default Tab1;

View File

@@ -0,0 +1,83 @@
import {
IonButton,
IonCol,
IonContent,
IonGrid,
IonHeader,
IonPage,
IonRow,
IonSearchbar,
IonTitle,
IonToolbar,
} from '@ionic/react';
import { useState, useRef } from 'react';
import { NoSearch } from '../components/NoSearch';
import { NoResultsWordCard, WordCard } from '../components/WordCard';
import { WordStore } from '../store';
import { searchWord } from '../utils';
const Tab2 = () => {
const pageRef = useRef();
const [searchTerm, setSearchTerm] = useState('');
const [searchResult, setSearchResult] = useState(false);
const [animatedClass, setAnimatedClass] = useState('');
const performSearch = async () => {
setAnimatedClass('animate__slideOutRight');
const result = searchTerm !== '' ? await searchWord(searchTerm) : undefined;
setTimeout(() => setSearchResult(result === undefined ? 'none' : result), 250);
setTimeout(() => setAnimatedClass('animate__slideInLeft'), 250);
WordStore.update((s) => {
s.searchCount++;
});
};
return (
<IonPage ref={pageRef}>
<IonHeader>
<IonToolbar>
<IonTitle>Search</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Search</IonTitle>
</IonToolbar>
</IonHeader>
<IonGrid>
<IonRow className="ion-align-items-center">
<IonCol size="9">
<IonSearchbar
animated
value={searchTerm}
onIonChange={(e) => setSearchTerm(e.target.value)}
/>
</IonCol>
<IonCol size="3">
<IonButton color="primary" onClick={performSearch}>
Search
</IonButton>
</IonCol>
</IonRow>
{searchResult && searchResult !== 'none' && (
<WordCard word={searchResult} animatedClass={animatedClass} pageRef={pageRef} />
)}
{searchResult && searchResult === 'none' && (
<NoResultsWordCard word={searchResult} animatedClass={animatedClass} />
)}
{!searchResult && <NoSearch />}
</IonGrid>
</IonContent>
</IonPage>
);
};
export default Tab2;

View File

@@ -0,0 +1,45 @@
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
import { useStoreState } from 'pullstate';
import { useRef, useState } from 'react';
import { NoFavourites } from '../components/NoFavourites';
import { WordCard } from '../components/WordCard';
import { WordStore } from '../store';
import { getFavourites } from '../store/Selectors';
const Tab3 = () => {
const pageRef = useRef();
const favourites = useStoreState(WordStore, getFavourites);
const [animatedClass, setAnimatedClass] = useState('animate__slideInLeft');
return (
<IonPage ref={pageRef}>
<IonHeader>
<IonToolbar>
<IonTitle>Favourites</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Favourites</IonTitle>
</IonToolbar>
</IonHeader>
{favourites.map((favourite, index) => {
return (
<WordCard
key={index}
word={favourite}
animatedClass={animatedClass}
pageRef={pageRef}
/>
);
})}
{favourites.length < 1 && <NoFavourites />}
</IonContent>
</IonPage>
);
};
export default Tab3;

View File

@@ -0,0 +1,22 @@
import { IonCol, IonLabel, IonRow } from '@ionic/react';
export const NoFavourites = () => (
<IonRow className="ion-text-center ion-justify-content-center ion-margin-top ion-padding-top">
<IonCol size="10" className="ion-padding-top ion-margin-top">
<IonLabel className="ion-padding-top ion-margin-top">
<h2>You don't have any favourites yet!</h2>
<p>
Any time you see the heart icon, press it to add the related word to your favourites and
quickly access it from here.
</p>
<lottie-player
src="https://assets9.lottiefiles.com/packages/lf20_LK2KVy.json"
background="transparent"
speed="1"
loop
autoplay
></lottie-player>
</IonLabel>
</IonCol>
</IonRow>
);

View File

@@ -0,0 +1,23 @@
import { IonCol, IonLabel, IonRow } from '@ionic/react';
export const NoSearch = () => (
<IonRow className="ion-text-center ion-justify-content-center ion-margin-top">
<IonCol size="10">
<IonLabel>
<h2>Search for a word in the English language</h2>
<p>
This app will give you word meaninigs, phonetics, origin and also an audio clip so you can
hear what it sounds like.
</p>
<lottie-player
src="https://assets7.lottiefiles.com/packages/lf20_n2m0isqh.json"
mode="bounce"
background="transparent"
speed="0.8"
loop
autoplay
></lottie-player>
</IonLabel>
</IonCol>
</IonRow>
);

View File

@@ -0,0 +1,129 @@
import {
IonBadge,
IonButton,
IonCard,
IonCardContent,
IonCardSubtitle,
IonCardTitle,
IonCol,
IonIcon,
IonNote,
IonRow,
useIonModal,
} from '@ionic/react';
import { checkmarkCircleOutline, chevronForward, closeCircleOutline } from 'ionicons/icons';
import WordModal from './WordModal';
export const WordCard = ({ word, animatedClass, pageRef }) => {
const closeModal = () => {
hideModal();
};
const openModal = () => {
showModal({
presentingElement: pageRef.current,
onDidDismiss: hideModal,
});
};
const [showModal, hideModal] = useIonModal(WordModal, {
dismiss: closeModal,
word,
});
return (
<IonRow className={`animate__animated animate__faster ${animatedClass}`}>
<IonCol size="12">
<IonCard>
<IonCardContent>
<IonCardTitle>{word.word}</IonCardTitle>
<div className="ion-padding-bottom ion-padding-top">
{word.meanings &&
word.meanings.map((meaning, index) => {
return (
<span key={index}>
<IonBadge color="primary">{meaning.partOfSpeech}</IonBadge>
&nbsp;
</span>
);
})}
</div>
<IonNote color="white">{word.origin}</IonNote>
<IonRow className="ion-padding-top ion-align-items-center ion-justify-content-center ion-text-center">
<IonCol size="4">
<IonCardTitle>{word.meanings.length}</IonCardTitle>
<IonCardSubtitle>meanings</IonCardSubtitle>
</IonCol>
<IonCol size="4">
<IonCardTitle>{word.phonetics.length}</IonCardTitle>
<IonCardSubtitle>phonetics</IonCardSubtitle>
</IonCol>
<IonCol size="4">
<IonCardTitle>
<IonIcon
icon={
word.phonetics[0] && word.phonetics[0].audio
? checkmarkCircleOutline
: closeCircleOutline
}
/>
</IonCardTitle>
<IonCardSubtitle>audio</IonCardSubtitle>
</IonCol>
</IonRow>
<IonRow>
<IonCol size="12">
<IonButton color="primary" expand="block" onClick={openModal}>
View&nbsp;
<IonIcon icon={chevronForward} />
</IonButton>
</IonCol>
</IonRow>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
);
};
export const NoResultsWordCard = ({ word, animatedClass }) => {
return (
<IonRow className={`animate__animated animate__faster ${animatedClass}`}>
<IonCol size="12">
<IonCard>
<IonCardContent>
<IonCardTitle>Whoops...</IonCardTitle>
<div className="ion-padding-bottom ion-padding-top">
<IonBadge color="primary">no results</IonBadge>&nbsp;
<IonBadge color="primary">found</IonBadge>
</div>
<IonNote color="white">
No results have been found for your search criteria! Please try another word.
</IonNote>
<IonRow className="ion-padding-top ion-align-items-center ion-justify-content-center ion-text-center">
<IonCol size="4">
<IonCardTitle>0</IonCardTitle>
<IonCardSubtitle>meanings</IonCardSubtitle>
</IonCol>
<IonCol size="4">
<IonCardTitle>0</IonCardTitle>
<IonCardSubtitle>phonetics</IonCardSubtitle>
</IonCol>
<IonCol size="4">
<IonCardTitle>
<IonIcon icon={closeCircleOutline} />
</IonCardTitle>
<IonCardSubtitle>audio</IonCardSubtitle>
</IonCol>
</IonRow>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
);
};

View File

@@ -0,0 +1,10 @@
import { IonText } from "@ionic/react";
export const WordCardHeading = ({ text }) => (
<div style={{ marginTop: "-1.5rem" }}>
<IonText color="light">
<h2 className="ion-padding-start">{ text }</h2>
</IonText>
</div>
);

View File

@@ -0,0 +1,18 @@
import { IonBadge } from '@ionic/react';
export const WordMeaning = ({ meaning, index }) => (
<div className={index > 0 ? 'ion-padding-top' : ''}>
<IonBadge key={index} color="primary">
{meaning.partOfSpeech}
</IonBadge>
<br />
{meaning.definitions.map((definition, index2) => {
return (
<p key={`definition_${index2}`} className={index2 > 0 ? 'ion-padding-top' : ''}>
{index2 + 1}.&nbsp;
{definition.definition}
</p>
);
})}
</div>
);

View File

@@ -0,0 +1,122 @@
import {
IonBadge,
IonButton,
IonButtons,
IonCard,
IonCardContent,
IonCardHeader,
IonCardSubtitle,
IonCardTitle,
IonCol,
IonContent,
IonGrid,
IonHeader,
IonIcon,
IonNote,
IonPage,
IonRow,
IonText,
IonTitle,
IonToolbar,
} from '@ionic/react';
import { heart, heartOutline, play } from 'ionicons/icons';
import { useStoreState } from 'pullstate';
import { WordStore } from '../store';
import { getFavourites } from '../store/Selectors';
import { addToFavourites } from '../store/WordStore';
import { WordCardHeading } from './WordCardHeading';
import { WordMeaning } from './WordMeaning';
const WordModal = ({ dismiss, word }) => {
const favourites = useStoreState(WordStore, getFavourites);
const isFavourite = favourites.includes(word);
const audio = word.phonetics[0] ? word.phonetics[0].audio : false;
const playAudio = () => {
const audioElement = new Audio(`https:${audio}`);
audioElement.play();
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonButton onClick={() => addToFavourites(word)}>
<IonIcon icon={isFavourite ? heart : heartOutline} />
</IonButton>
</IonButtons>
<IonTitle>View Word</IonTitle>
<IonButtons slot="end">
<IonButton onClick={dismiss}>Close</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonGrid>
<IonRow className="animate__animated animate__faster animate__slideInUp">
<IonCol size="12">
<IonCard>
<IonCardContent>
<IonCardTitle>{word.word}</IonCardTitle>
<div className="ion-padding-bottom ion-padding-top">
{word.meanings &&
word.meanings.map((meaning, index) => {
return (
<span key={`meaning_${index}`}>
<IonBadge key={index} color="primary">
{meaning.partOfSpeech}
</IonBadge>
&nbsp;
</span>
);
})}
</div>
<IonNote color="white">{word.origin}</IonNote>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
{audio && (
<IonRow className="animate__animated animate__faster animate__slideInUp">
<IonCol size="12">
<WordCardHeading text="Audio Clip" />
<IonCard>
<IonCardContent>
<IonRow>
<IonCol size="12">
<IonButton color="primary" expand="block" onClick={playAudio}>
<IonIcon icon={play} />
</IonButton>
</IonCol>
</IonRow>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
)}
<IonRow className="animate__animated animate__faster animate__slideInUp">
<IonCol size="12">
<WordCardHeading text="Meanings" />
<IonCard>
<IonCardContent>
{word.meanings &&
word.meanings.map((meaning, index) => {
return <WordMeaning key={index} index={index} meaning={meaning} />;
})}
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonPage>
);
};
export default WordModal;

View File

@@ -0,0 +1,44 @@
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { cloudOutline, heart, search, searchOutline, statsChart } from 'ionicons/icons';
import { Route, Redirect } from 'react-router';
import Tab1 from './AppPages/Tab1';
import Tab2 from './AppPages/Tab2';
import Tab3 from './AppPages/Tab3';
import './style.scss';
function DemoDictionaryApp() {
return (
<IonTabs className="demo-dictionary-app">
<IonRouterOutlet>
<Route exact path="/demo-dictionary-app/dashboard">
<Tab1 />
</Route>
<Route exact path="/demo-dictionary-app/search">
<Tab2 />
</Route>
<Route path="/demo-dictionary-app/favourites">
<Tab3 />
</Route>
<Redirect exact path="/demo-dictionary-app" to="/demo-dictionary-app/dashboard" />
</IonRouterOutlet>
{/* */}
<IonTabBar slot="bottom">
<IonTabButton tab="dashboard" href="/demo-dictionary-app/dashboard">
<IonIcon icon={statsChart} />
</IonTabButton>
<IonTabButton tab="search" href="/demo-dictionary-app/search">
<IonIcon icon={search} />
</IonTabButton>
<IonTabButton tab="favourites" href="/demo-dictionary-app/favourites">
<IonIcon icon={heart} />
</IonTabButton>
</IonTabBar>
</IonTabs>
);
}
export default DemoDictionaryApp;

View File

@@ -0,0 +1,8 @@
import { createSelector } from 'reselect';
const getState = state => state;
// General getters
export const getFavourites = createSelector(getState, state => state.favourites);
export const getPopularWords = createSelector(getState, state => state.popularWords);
export const getSearchCount = createSelector(getState, state => state.searchCount);

View File

@@ -0,0 +1,29 @@
import { Store } from "pullstate";
const WordStore = new Store({
favourites: [],
popularWords: [],
searchCount: 0
});
export default WordStore;
export const addToFavourites = (passedWord) => {
const currentFavourites = WordStore.getRawState().favourites;
const added = !currentFavourites.includes(passedWord);
WordStore.update(s => {
if (currentFavourites.includes(passedWord)) {
s.favourites = currentFavourites.filter(word => word !== passedWord);
} else {
s.favourites = [ ...s.favourites, passedWord ];
}
});
return added;
}

View File

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

View File

@@ -0,0 +1,240 @@
/* Ionic Variables and Theming. For more info, please see:
http://ionicframework.com/docs/theming/ */
/** Ionic CSS Variables **/
.demo-dictionary-app {
* {
/** primary **/
--ion-color-primary: #953cd0;
--ion-color-primary-rgb: 149, 60, 208;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #8335b7;
--ion-color-primary-tint: #a050d5;
/** secondary **/
--ion-color-secondary: #3dc2ff;
--ion-color-secondary-rgb: 61, 194, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #36abe0;
--ion-color-secondary-tint: #50c8ff;
/** tertiary **/
--ion-color-tertiary: #5260ff;
--ion-color-tertiary-rgb: 82, 96, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #4854e0;
--ion-color-tertiary-tint: #6370ff;
/** success **/
--ion-color-success: #2dd36f;
--ion-color-success-rgb: 45, 211, 111;
--ion-color-success-contrast: #ffffff;
--ion-color-success-contrast-rgb: 255, 255, 255;
--ion-color-success-shade: #28ba62;
--ion-color-success-tint: #42d77d;
/** warning **/
--ion-color-warning: #ffc409;
--ion-color-warning-rgb: 255, 196, 9;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0ac08;
--ion-color-warning-tint: #ffca22;
/** danger **/
--ion-color-danger: #eb445a;
--ion-color-danger-rgb: 235, 68, 90;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #cf3c4f;
--ion-color-danger-tint: #ed576b;
/** dark **/
--ion-color-dark: #222428;
--ion-color-dark-rgb: 34, 36, 40;
--ion-color-dark-contrast: #ffffff;
--ion-color-dark-contrast-rgb: 255, 255, 255;
--ion-color-dark-shade: #1e2023;
--ion-color-dark-tint: #383a3e;
/** medium **/
--ion-color-medium: #92949c;
--ion-color-medium-rgb: 146, 148, 156;
--ion-color-medium-contrast: #ffffff;
--ion-color-medium-contrast-rgb: 255, 255, 255;
--ion-color-medium-shade: #808289;
--ion-color-medium-tint: #9d9fa6;
/** light **/
--ion-color-light: #ffffff;
--ion-color-light-rgb: 255, 255, 255;
--ion-color-light-contrast: #000000;
--ion-color-light-contrast-rgb: 0, 0, 0;
--ion-color-light-shade: #e0e0e0;
--ion-color-light-tint: #ffffff;
--ion-background-color: #1e1b27 !important;
--ion-tab-bar-color-selected: #953cd0;
--ion-tab-bar-color: #412f6e;
--ion-text-color: white;
--ion-tab-bar-background: #191620;
--ion-toolbar-background: #191620 !important;
--ion-item-background: #000000 !important;
--ion-card-background: #272333 !important;
--ion-modal-background: #272333 !important;
}
ion-tab-bar {
--border-style: none;
border: none;
}
/*
* Dark Colors
* -------------------------------------------
*/
ion-modal {
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
overflow-y: scroll;
}
body {
overflow: hidden !important;
--ion-color-primary: #953cd0;
--ion-color-primary-rgb: 149, 60, 208;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #8335b7;
--ion-color-primary-tint: #a050d5;
--ion-color-secondary: #50c8ff;
--ion-color-secondary-rgb: 80, 200, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #46b0e0;
--ion-color-secondary-tint: #62ceff;
--ion-color-tertiary: #6a64ff;
--ion-color-tertiary-rgb: 106, 100, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #5d58e0;
--ion-color-tertiary-tint: #7974ff;
--ion-color-success: #2fdf75;
--ion-color-success-rgb: 47, 223, 117;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #29c467;
--ion-color-success-tint: #44e283;
--ion-color-warning: #ffd534;
--ion-color-warning-rgb: 255, 213, 52;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0bb2e;
--ion-color-warning-tint: #ffd948;
--ion-color-danger: #ff4961;
--ion-color-danger-rgb: 255, 73, 97;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
--ion-color-dark: #f4f5f8;
--ion-color-dark-rgb: 244, 245, 248;
--ion-color-dark-contrast: #000000;
--ion-color-dark-contrast-rgb: 0, 0, 0;
--ion-color-dark-shade: #d7d8da;
--ion-color-dark-tint: #f5f6f9;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152, 154, 162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0, 0, 0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #ffffff;
--ion-color-light-rgb: 255, 255, 255;
--ion-color-light-contrast: #000000;
--ion-color-light-contrast-rgb: 0, 0, 0;
--ion-color-light-shade: #e0e0e0;
--ion-color-light-tint: #ffffff;
}
/*
* iOS Dark Theme
* -------------------------------------------
*/
.ios body {
/* --ion-background-color: #000000; */
/* --ion-background-color-rgb: 0,0,0; */
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-color-step-50: #0d0d0d;
--ion-color-step-100: #1a1a1a;
--ion-color-step-150: #262626;
--ion-color-step-200: #333333;
--ion-color-step-250: #404040;
--ion-color-step-300: #4d4d4d;
--ion-color-step-350: #595959;
--ion-color-step-400: #666666;
--ion-color-step-450: #737373;
--ion-color-step-500: #808080;
--ion-color-step-550: #8c8c8c;
--ion-color-step-600: #999999;
--ion-color-step-650: #a6a6a6;
--ion-color-step-700: #b3b3b3;
--ion-color-step-750: #bfbfbf;
--ion-color-step-800: #cccccc;
--ion-color-step-850: #d9d9d9;
--ion-color-step-900: #e6e6e6;
--ion-color-step-950: #f2f2f2;
}
/*
* Material Design Dark Theme
* -------------------------------------------
*/
.md body {
/* --ion-background-color: #121212; */
/* --ion-background-color-rgb: 18,18,18; */
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-border-color: #222222;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
--ion-color-step-150: #363636;
--ion-color-step-200: #414141;
--ion-color-step-250: #4d4d4d;
--ion-color-step-300: #595959;
--ion-color-step-350: #656565;
--ion-color-step-400: #717171;
--ion-color-step-450: #7d7d7d;
--ion-color-step-500: #898989;
--ion-color-step-550: #949494;
--ion-color-step-600: #a0a0a0;
--ion-color-step-650: #acacac;
--ion-color-step-700: #b8b8b8;
--ion-color-step-750: #c4c4c4;
--ion-color-step-800: #d0d0d0;
--ion-color-step-850: #dbdbdb;
--ion-color-step-900: #e7e7e7;
--ion-color-step-950: #f3f3f3;
}
}

View File

@@ -0,0 +1,21 @@
import { WordStore } from './store';
const API_URL = 'https://api.dictionaryapi.dev/api/v2/entries/en/';
export const searchWord = async (word, returnOne = true) => {
const response = await fetch(`${API_URL}${word.toLowerCase()}`);
const data = await response.json();
return returnOne ? data[0] : data;
};
export const fetchPopularWords = async () => {
const words = ['mobile', 'applications', 'ionic', 'framework'];
words.forEach(async (word) => {
const wordData = await searchWord(word, false);
WordStore.update((s) => {
s.popularWords = [...s.popularWords, wordData[0]];
});
});
};

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,346 @@
// 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,
apps,
appsOutline,
car,
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>
<IonList inset={false}>
<IonItem
button={true}
onClick={() => {
router.push(paths.DEMO_CLUB_HOUSE, 'forward');
}}
>
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
<IonLabel>Demo Club house</IonLabel>
<IonIcon icon={chevronForwardOutline}></IonIcon>
</IonItem>
</IonList>
<IonList inset={false}>
<IonItem
button={true}
onClick={() => {
router.push(paths.DEMO_SCORE_BOARD, 'forward');
}}
>
<IonIcon slot="start" icon={car} size="large"></IonIcon>
<IonLabel>
Demo Score Board <br />
(IonCard problem)
</IonLabel>
<IonIcon icon={chevronForwardOutline}></IonIcon>
</IonItem>
</IonList>
{/* */}
<IonList inset={false}>
<IonItem button={true} onClick={() => router.push(paths.DEMO_QUOTE_APP, 'forward')}>
<IonIcon slot="start" icon={car} size="large"></IonIcon>
<IonLabel>Demo Quote App</IonLabel>
<IonIcon icon={chevronForwardOutline}></IonIcon>
</IonItem>
</IonList>
<IonList inset={false}>
<IonItem button={true} onClick={() => router.push(paths.DEMO_QR_SCANNER, 'forward')}>
<IonIcon slot="start" icon={car} size="large"></IonIcon>
<IonLabel>Demo Qr scanner</IonLabel>
<IonIcon icon={chevronForwardOutline}></IonIcon>
</IonItem>
</IonList>
<IonList inset={false}>
<IonItem button={true} onClick={() => router.push(paths.DEMO_SHOP_APP_UI, 'forward')}>
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
<IonLabel>Demo Shop App UI</IonLabel>
<IonIcon icon={chevronForwardOutline}></IonIcon>
</IonItem>
</IonList>
<IonList inset={false}>
<IonItem button={true} onClick={() => router.push(paths.DEMO_DICTIONARY_APP, 'forward')}>
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
<IonLabel>Demo Dictionary App</IonLabel>
<IonIcon icon={chevronForwardOutline}></IonIcon>
</IonItem>
</IonList>
{/* */}
<IonList inset={false}>
<IonItem button={true} onClick={() => router.push(paths.DEMO_RECIPE_APP, 'forward')}>
<IonIcon slot="start" icon={cart} size="large"></IonIcon>
<IonLabel>Demo Recipe App</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,144 @@
import {
getPlatforms,
IonButton,
IonButtons,
IonCol,
IonContent,
IonGrid,
IonHeader,
IonIcon,
IonPage,
IonRow,
IonTitle,
IonToolbar,
useIonModal,
useIonRouter,
} from '@ionic/react';
import { Geolocation } from '@capacitor/geolocation';
import { useEffect, useRef, useState } from 'react';
import { SkeletonDashboard } from '../components/SkeletonDashboard';
import { chevronBackOutline, refreshOutline } from 'ionicons/icons';
import { CurrentWeather } from '../components/CurrentWeather';
import { useStoreState } from 'pullstate';
import { QRStore } from '../store';
import { getCodes } from '../store/Selectors';
import useSound from 'use-sound';
import openSound from '../sounds/open.wav';
import { QRCodeScannedModal } from '../components/QRCodeScannedModal';
import { QRWebModal } from '../components/QRWebModal';
import { NoQRCodes } from '../components/NoQRCodes';
import { CustomFab } from '../components/CustomFab.jsx';
function Tab1() {
const pageRef = useRef(null);
const codes = useStoreState(QRStore, getCodes);
const [play] = useSound(openSound);
const [QRData, setQRData] = useState(false);
const handleScan = (data) => {
if (data) {
setQRData(data);
play();
handleSuccess(data);
}
};
const handleError = (err) => {
console.error(err);
};
const start = async () => {
const platforms = getPlatforms();
const isWeb =
platforms.includes('desktop') || platforms.includes('mobileweb') || platforms.includes('pwa');
if (!isWeb) {
// const data = await BarcodeScanner.scan();
// if (data) {
// handleSuccess(data);
// }
const result = await CapacitorBarcodeScanner.scanBarcode({
hint: CapacitorBarcodeScannerTypeHint.ALL,
scanInstructions: 'Please scan a barcode',
scanButton: true,
scanText: 'Scan',
cameraDirection: CapacitorBarcodeScannerCameraDirection.BACK,
scanOrientation: CapacitorBarcodeScannerScanOrientation.ADAPTIVE,
android: {
scanningLibrary: CapacitorBarcodeScannerAndroidScanningLibrary.ZXING,
},
});
handleSuccess(result.ScanResult);
} else {
presentWebModal({
presentingElement: pageRef.current,
});
}
};
const handleSuccess = (data) => {
setQRData(data);
console.log(data);
dismissWebModal();
play();
present({
presentingElement: pageRef.current,
});
};
const [present, dismiss] = useIonModal(QRCodeScannedModal, {
dismiss: () => dismiss(),
code: QRData,
set: () => setQRData(),
scan: () => start(),
});
const [presentWebModal, dismissWebModal] = useIonModal(QRWebModal, {
dismiss: () => dismissWebModal(),
set: () => setQRData(),
scan: handleScan,
error: handleError,
});
const router = useIonRouter();
function handleBackButtonClick() {
router.goBack();
}
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>QR Codes</IonTitle>
<IonButton
slot="start"
fill="clear"
shape="round"
onClick={() => handleBackButtonClick()}
>
<IonIcon slot="icon-only" icon={chevronBackOutline}></IonIcon>
</IonButton>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">QR Codes</IonTitle>
</IonToolbar>
</IonHeader>
<IonGrid>
{codes.length < 1 && <NoQRCodes />}
{codes.length > 0 && <QRCodeList codes={codes} pageRef={pageRef} />}
</IonGrid>
<CustomFab start={start} />
</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,27 @@
import { IonFab, IonFabButton, IonFabList, IonIcon } from '@ionic/react';
import { addOutline, cameraOutline, qrCodeOutline } from 'ionicons/icons';
export const CustomFab = ({ start }) => {
return (
<IonFab
vertical="bottom"
horizontal="end"
slot="fixed"
className="ion-padding-bottom ion-padding-end"
>
<IonFabButton>
<IonIcon icon={qrCodeOutline} />
</IonFabButton>
<IonFabList side="top" className="ion-padding-bottom">
<IonFabButton color="primary" onClick={start}>
<IonIcon icon={cameraOutline} />
</IonFabButton>
<IonFabButton color="primary" routerLink="/demo-qr-scanner/manual">
<IonIcon icon={addOutline} />
</IonFabButton>
</IonFabList>
</IonFab>
);
};

View File

@@ -0,0 +1,15 @@
import { IonCol, IonRow, IonText } from '@ionic/react';
export const NoQRCodes = () => (
<IonRow className="ion-text-center ion-justify-content-center">
<IonCol size="9">
<h3>It looks like you don't have any QR codes stored.</h3>
<img src="/assets/icon2.png" alt="icon" />
<p>
Click the <IonText color="primary">button</IonText> in the bottom right to scan a code or
generate a code.
</p>
</IonCol>
</IonRow>
);

View File

@@ -0,0 +1,106 @@
import {
IonButton,
IonButtons,
IonCard,
IonCardContent,
IonCardHeader,
IonCardTitle,
IonCol,
IonContent,
IonGrid,
IonHeader,
IonIcon,
IonNote,
IonPage,
IonRow,
IonTitle,
IonToolbar,
useIonToast,
} from '@ionic/react';
import QRCode from 'react-qr-code';
import { addQRCode } from '../store/QRStore';
import useSound from 'use-sound';
import closeSound from '../sounds/close.wav';
import { reloadOutline } from 'ionicons/icons';
export const QRCodeScannedModal = ({ dismiss, code, set, scan }) => {
const [play] = useSound(closeSound);
const [showToast] = useIonToast();
const handleDismiss = () => {
dismiss();
play();
};
const handleScanAgain = () => {
handleDismiss();
setTimeout(() => {
scan();
}, 10);
};
const handleAdd = async () => {
addQRCode(code.text ? code.text : code, true);
showToast({
header: 'Success!',
message: 'QR Code stored successfully.',
duration: 3000,
color: 'primary',
});
handleDismiss();
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>View QR Code</IonTitle>
<IonButtons slot="end">
<IonButton onClick={handleDismiss}>Close</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent>
<IonGrid className="ion-padding-top ion-margin-top">
<IonRow className="ion-justify-content-center ion-text-center animate__animated animate__lightSpeedInLeft animate__faster">
<IonCol size="12">
<QRCode value={code.text ? code.text : code} />
</IonCol>
</IonRow>
<IonRow>
<IonCol size="12">
<IonCard>
<IonCardHeader>
<IonCardTitle>QR Code data</IonCardTitle>
<IonNote>This is what the code represents</IonNote>
</IonCardHeader>
<IonCardContent>
<p>{code.text ? code.text : code}</p>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
<IonRow>
<IonCol size="6">
<IonButton expand="block" fill="outline" onClick={handleScanAgain}>
<IonIcon icon={reloadOutline} />
&nbsp; Scan again
</IonButton>
</IonCol>
<IonCol size="6">
<IonButton expand="block" onClick={handleAdd}>
Store &rarr;
</IonButton>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
</IonPage>
);
};

Some files were not shown because too many files have changed in this diff Show More