Compare commits
21 Commits
develop/mo
...
develop/mo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e4b6c5e92d | ||
![]() |
03782cde24 | ||
![]() |
7cc6d939f5 | ||
![]() |
3bcb40c5ef | ||
![]() |
43a285dd2c | ||
![]() |
09a8dc539e | ||
![]() |
8f75226763 | ||
![]() |
7cfbcc1573 | ||
![]() |
5234ac06cd | ||
![]() |
aec59fb328 | ||
![]() |
7f9b4c2224 | ||
![]() |
197b006df3 | ||
![]() |
3692b2204b | ||
![]() |
3217a8d594 | ||
![]() |
d453144500 | ||
![]() |
e8d12f34e8 | ||
![]() |
56d43062c9 | ||
![]() |
c2a02cff77 | ||
![]() |
d3ef280b20 | ||
![]() |
b2e9616178 | ||
![]() |
d909805283 |
@@ -3,13 +3,5 @@
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 160,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "src/App.tsx",
|
||||
"options": {
|
||||
"printWidth": 240
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"printWidth": 100
|
||||
}
|
@@ -12,7 +12,9 @@ dependencies {
|
||||
implementation project(':capacitor-barcode-scanner')
|
||||
implementation project(':capacitor-clipboard')
|
||||
implementation project(':capacitor-geolocation')
|
||||
implementation project(':capacitor-google-maps')
|
||||
implementation project(':capacitor-preferences')
|
||||
implementation project(':capacitor-share')
|
||||
|
||||
}
|
||||
|
||||
|
@@ -11,5 +11,11 @@ project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacito
|
||||
include ':capacitor-geolocation'
|
||||
project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')
|
||||
|
||||
include ':capacitor-google-maps'
|
||||
project(':capacitor-google-maps').projectDir = new File('../node_modules/@capacitor/google-maps/android')
|
||||
|
||||
include ':capacitor-preferences'
|
||||
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||
|
||||
include ':capacitor-share'
|
||||
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
|
||||
|
@@ -14,7 +14,9 @@ def capacitor_pods
|
||||
pod 'CapacitorBarcodeScanner', :path => '../../node_modules/@capacitor/barcode-scanner'
|
||||
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
||||
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
|
||||
pod 'CapacitorGoogleMaps', :path => '../../node_modules/@capacitor/google-maps'
|
||||
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
|
@@ -11,8 +11,10 @@
|
||||
"@capacitor/clipboard": "^7.0.1",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/geolocation": "^7.1.2",
|
||||
"@capacitor/google-maps": "^7.0.2",
|
||||
"@capacitor/ios": "7.0.1",
|
||||
"@capacitor/preferences": "^7.0.0",
|
||||
"@capacitor/share": "^7.0.1",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@ionic/react": "^8.5.0",
|
||||
"@ionic/react-router": "^8.5.0",
|
||||
@@ -27,6 +29,7 @@
|
||||
"pigeon-maps": "^0.22.1",
|
||||
"pullstate": "^1",
|
||||
"react": "19.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
@@ -47,7 +50,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run dev",
|
||||
"dev": "vite --host 0.0.0.0 --cors",
|
||||
"dev": "vite --force --host 0.0.0.0 --cors",
|
||||
"ionic:serve": "vite",
|
||||
"ionic:build": "tsc && vite build",
|
||||
"build": "tsc && vite build",
|
||||
|
BIN
03_source/mobile/public/assets/DemoBankingUi/alan.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/chip.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoBankingUi/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/mastercard.png
Normal file
After Width: | Height: | Size: 26 KiB |
1
03_source/mobile/public/assets/DemoBankingUi/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/visa.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
03_source/mobile/public/assets/DemoQuizApp/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoQuizApp/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoQuizApp/main.png
Normal file
After Width: | Height: | Size: 39 KiB |
1
03_source/mobile/public/assets/DemoQuizApp/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/autumn.png
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar.jpeg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar1.png
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar2.png
Normal file
After Width: | Height: | Size: 424 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar3.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar4.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar5.png
Normal file
After Width: | Height: | Size: 264 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar6.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover1.jpeg
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover2.jpeg
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover4.jpeg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover5.jpeg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover6.jpeg
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/flower.jpeg
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/h.jpeg
Normal file
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/ocean.jpeg
Normal file
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/spring.png
Normal file
After Width: | Height: | Size: 288 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/summer.png
Normal file
After Width: | Height: | Size: 210 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/van.jpeg
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/winter.png
Normal file
After Width: | Height: | Size: 206 KiB |
@@ -65,7 +65,8 @@ import paths from './paths';
|
||||
import PrivacyAgreement from './pages/PrivacyAgreement';
|
||||
import AppRoute from './AppRoute';
|
||||
//
|
||||
import DemoReactShop from './pages/DemoReactShop';
|
||||
// TODO: resume DemoReactShop
|
||||
// import DemoReactShop from './pages/DemoReactShop';
|
||||
import DemoWeatherApp from './pages/DemoWeatherApp';
|
||||
import DemoClubHouse from './pages/DemoClubHouse';
|
||||
import DemoScoreBoard from './pages/DemoScoreBoard';
|
||||
@@ -78,6 +79,25 @@ import DemoDictionaryApp from './pages/DemoDictionaryApp';
|
||||
// demo-recipe-app
|
||||
import DemoRecipeApp from './pages/DemoRecipeApp';
|
||||
|
||||
// DemoSlidingProfile
|
||||
import DemoSlidingProfile from './pages/DemoSlidingProfile';
|
||||
|
||||
// DemoQuizApp
|
||||
import DemoQuizApp from './pages/DemoQuizApp';
|
||||
import DemoBlogPostUi from './pages/DemoBlogPostUi';
|
||||
import DemoReactTravelApp from './pages/DemoReactTravelApp';
|
||||
import DemoPinterestFloatingTabBar from './pages/DemoPinterestFloatingTabBar';
|
||||
import DemoRestaurantFinder from './pages/DemoRestaurantFinder';
|
||||
import DemoReactOverlayHooks from './pages/DemoReactOverlayHooks';
|
||||
import DemoReactSwitchTabs from './pages/DemoReactSwitchTabs';
|
||||
import DemoReactPollApp from './pages/DemoReactPollApp';
|
||||
import DemoReactWhatsAppClone from './pages/DemoReactWhatsAppClone';
|
||||
import Demo2FaExample from './pages/Demo2FaExample';
|
||||
import DemoAccordionTutorial from './pages/DemoAccordionTutorial';
|
||||
import DemoBankingUi from './pages/DemoBankingUi';
|
||||
import DemoCapacitorGoogleMapsTutorial from './pages/DemoCapacitorGoogleMapsTutorial';
|
||||
import DemoColorTutorial from './pages/DemoColorTutorial';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
const App: React.FC = () => {
|
||||
@@ -132,19 +152,100 @@ const IonicApp: React.FC<IonicAppProps> = ({
|
||||
|
||||
<AppRoute />
|
||||
|
||||
{/* */}
|
||||
<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={paths.DEMO_ACCORDION_TUTORIAL} render={() => <DemoAccordionTutorial />} />
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
|
||||
<Route path={paths.DEMO_BANKING_UI} render={() => <DemoBankingUi />} />
|
||||
<Route
|
||||
path={paths.DEMO_CAPACITOR_GOOGLE_MAPS_TUTORIAL}
|
||||
render={() => <DemoCapacitorGoogleMapsTutorial />}
|
||||
/>
|
||||
|
||||
<Route path={paths.DEMO_COLOR_TUTORIAL} render={() => <DemoColorTutorial />} />
|
||||
|
||||
{/*
|
||||
<Route path={paths.DEMO_ECOMMERCE_EXAMPLE} render={() => <DemoEcommerceExample />} />
|
||||
<Route path={paths.DEMO_FACEBOOK_CLONE} render={() => <DemoFacebookClone />} />
|
||||
<Route path={paths.DEMO_FAST_FOOD_APP} render={() => <DemoFastFoodApp />} />
|
||||
<Route path={paths.DEMO_FLOATING_TABS} render={() => <DemoFloatingTabs />} />
|
||||
<Route path={paths.DEMO_INSTAGRAM_CLONE} render={() => <DemoInstagramClone />} />
|
||||
<Route path={paths.DEMO_KANBAN_BOARD} render={() => <DemoKanbanBoard />} />
|
||||
<Route path={paths.DEMO_ORDERING_APP} render={() => <DemoOrderingApp />} />
|
||||
<Route path={paths.DEMO_PROFILE_EXAMPLE} render={() => <DemoProfileExample />} />
|
||||
<Route path={paths.DEMO_PULLSTATE_TUTORIAL} render={() => <DemoPullstateTutorial />} />
|
||||
<Route path={paths.DEMO_REACT_ADD_TO_CART} render={() => <DemoReactAddToCart />} />
|
||||
<Route path={paths.DEMO_REACT_CALCULATOR} render={() => <DemoReactCalculator />} />
|
||||
<Route path={paths.DEMO_REACT_DRAWING_CANVAS} render={() => <DemoReactDrawingCanvas />} />
|
||||
<Route path={paths.DEMO_REACT_HOOK_FORM_EXAMPLE} render={() => <DemoReactHookFormExample />} />
|
||||
<Route path={paths.DEMO_REACT_ITEM_LIST} render={() => <DemoReactItemList />} />
|
||||
<Route path={paths.DEMO_REACT_LIFECYCLES} render={() => <DemoReactLifecycles />} />
|
||||
<Route path={paths.DEMO_REACT_LOGIN} render={() => <DemoReactLogin />} />
|
||||
<Route path={paths.DEMO_REACT_MARVEL_APP} render={() => <DemoReactMarvelApp />} />
|
||||
<Route path={paths.DEMO_REACT_MOVIE_APP_WITH_ALGOLIA} render(() => <DemoReactMovieAppWithAlgolia />} />
|
||||
<Route path={paths.DEMO_REACT_NOTES} render={() => <DemoReactNotes />} />
|
||||
<Route path={paths.DEMO_REACT_ONBOARDING_UI} render={() => <DemoReactOnboardingUI />} />
|
||||
<Route path={paths.DEMO_REACT_PROFILE_DASHBOARD_UI} render(() => <DemoReactProfileDashboardUI />} />
|
||||
<Route path={paths.DEMO_REACT_QR_CODE} render={() => <DemoReactQRCode />} />
|
||||
<Route path={paths.DEMO_REACT_QUOTES} render(() => <DemoReactQuotes />} />
|
||||
<Route path={paths.DEMO_REACT_SHOP_UI} render(() => <DemoReactShopUI />} />
|
||||
<Route path={paths.DEMO_REACT_TABS_MENUS_CUSTOM} render(() => <DemoReactTabsMenusCustom />} />
|
||||
<Route path={paths.DEMO_REACT_THEME_SWITCHER} render(() => <DemoReactThemeSwitcher />} />
|
||||
<Route path={paths.DEMO_REACT_WHATSAPP_CLONE} render(() => <DemoReactWhatsAppClone />} />
|
||||
<Route path={paths.DEMO_SKELETON_TEXT} render(() => <DemoSkeletonText />} />
|
||||
<Route path={paths.DEMO_STICKY_BOTTOM_SHEET_EXAMPLE} render(() => <DemoStickyBottomSheetExample />} />
|
||||
<Route path={paths.DEMO_STORAGE_EXAMPLE} render(() => <DemoStorageExample />} />
|
||||
<Route path={paths.DEMO_SWIPERJS_TUTORIAL} render(() => <DemoSwiperjsTutorial />} />
|
||||
<Route path={paths.DEMO_WEATHER_APP_UI} render(() => <DemoWeatherAppUI />} />
|
||||
*/}
|
||||
|
||||
<Route path={paths.DEMO_2FA_EXAMPLE} render={() => <Demo2FaExample />} />
|
||||
|
||||
{/* have problemx` */}
|
||||
{/* <Route path={paths.DEMO_REACT_WHATSAPP_CLONE} render={() => <DemoReactWhatsAppClone />} /> */}
|
||||
|
||||
<Route path={paths.DEMO_REACT_POLL_APP} render={() => <DemoReactPollApp />} />
|
||||
|
||||
<Route path={paths.DEMO_BLOG_POST_UI} render={() => <DemoBlogPostUi />} />
|
||||
<Route path={paths.DEMO_CLUB_HOUSE} render={() => <DemoClubHouse />} />
|
||||
<Route path={paths.DEMO_DICTIONARY_APP} render={() => <DemoDictionaryApp />} />
|
||||
<Route
|
||||
path={paths.DEMO_PINTEREST_FLOATING_TAB_BAR}
|
||||
render={() => <DemoPinterestFloatingTabBar />}
|
||||
/>
|
||||
<Route path={paths.DEMO_QR_SCANNER} render={() => <DemoQrScanner />} />
|
||||
<Route path={paths.DEMO_QUIZ_APP} render={() => <DemoQuizApp />} />
|
||||
<Route path={paths.DEMO_QUOTE_APP} render={() => <DemoQuoteApp />} />
|
||||
<Route path={paths.DEMO_REACT_OVERLAY_HOOKS} render={() => <DemoReactOverlayHooks />} />
|
||||
<Route path={paths.DEMO_REACT_POLL_APP} render={() => <DemoReactPollApp />} />
|
||||
|
||||
{/* TODO: resume DemoReactShop */}
|
||||
{/* <Route path={paths.DEMO_REACT_SHOP} render={() => <DemoReactShop />} /> */}
|
||||
|
||||
<Route path={paths.DEMO_REACT_SWITCH_TABS} render={() => <DemoReactSwitchTabs />} />
|
||||
<Route path={paths.DEMO_REACT_TRAVEL_APP} render={() => <DemoReactTravelApp />} />
|
||||
<Route path={paths.DEMO_RECIPE_APP} render={() => <DemoRecipeApp />} />
|
||||
<Route path={paths.DEMO_RESTAURANT_FINDER} render={() => <DemoRestaurantFinder />} />
|
||||
<Route path={paths.DEMO_SCORE_BOARD} render={() => <DemoScoreBoard />} />
|
||||
<Route path={paths.DEMO_SHOP_APP_UI} render={() => <DemoShopAppUi />} />
|
||||
<Route path={paths.DEMO_SLIDING_PROFILE} render={() => <DemoSlidingProfile />} />
|
||||
|
||||
<Route path="/account" component={Account} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/mylogin" component={MyLogin} />
|
||||
@@ -153,7 +254,6 @@ const IonicApp: React.FC<IonicAppProps> = ({
|
||||
<Route path="/support" component={Support} />
|
||||
<Route path="/tutorial" component={Tutorial} />
|
||||
|
||||
{/* */}
|
||||
<Route
|
||||
path="/logout"
|
||||
render={() => {
|
||||
|
96
03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab1.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
};
|
||||
|
||||
// const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>My Weather</IonTitle>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => getCurrentPosition()}>
|
||||
<IonIcon icon={refreshOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Dashboard</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-margin-start ion-margin-end ion-justify-content-center ion-text-center">
|
||||
<IonCol size="12">
|
||||
<h4>Here's your location based weather</h4>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-1.5rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<SkeletonDashboard />
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab1;
|
81
03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab2.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
||||
const performSearch = async () => {
|
||||
getAddress(search);
|
||||
};
|
||||
|
||||
const getAddress = async (city) => {
|
||||
const response = await fetch(
|
||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.current && data.location) {
|
||||
setCurrentWeather(data);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
|
||||
<IonCol size="7">
|
||||
<IonSearchbar
|
||||
placeholder="Try 'London'"
|
||||
animated
|
||||
value={search}
|
||||
onIonChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="5">
|
||||
<IonButton
|
||||
expand="block"
|
||||
className="ion-margin-start ion-margin-end"
|
||||
onClick={performSearch}
|
||||
>
|
||||
Search
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-0.8rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<h3 className="ion-text-center">Your search result will appear here</h3>
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,62 @@
|
||||
import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => {
|
||||
const [property, setProperty] = useState(false);
|
||||
|
||||
const properties = {
|
||||
wind: {
|
||||
isIcon: false,
|
||||
icon: '/assets/WeatherDemo/wind.png',
|
||||
alt: 'wind',
|
||||
label: 'Wind',
|
||||
value: `${currentWeather.current.wind_mph}mph`,
|
||||
},
|
||||
feelsLike: {
|
||||
isIcon: true,
|
||||
icon: thermometerOutline,
|
||||
alt: 'feels like',
|
||||
label: 'Feels like',
|
||||
value: `${currentWeather.current.feelslike_c}°C`,
|
||||
},
|
||||
indexUV: {
|
||||
isIcon: true,
|
||||
icon: sunnyOutline,
|
||||
alt: 'index uv',
|
||||
label: 'Index UV',
|
||||
value: currentWeather.current.uv,
|
||||
},
|
||||
pressure: {
|
||||
isIcon: true,
|
||||
icon: pulseOutline,
|
||||
alt: 'pressure',
|
||||
label: 'Pressure',
|
||||
value: `${currentWeather.current.pressure_mb} mbar`,
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setProperty(properties[type]);
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
{!property.isIcon && (
|
||||
<img alt={property.alt} src={property.icon} height="32" width="32" />
|
||||
)}
|
||||
{property.isIcon && (
|
||||
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
|
||||
)}
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>{property.label}</IonCardSubtitle>
|
||||
<IonNote>{property.value}</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
|
||||
import { WeatherProperty } from './WeatherProperty';
|
||||
|
||||
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
{currentWeather.location.region},{' '}
|
||||
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<img
|
||||
alt="condition"
|
||||
src={currentWeather.current.condition.icon.replace('//', 'https://')}
|
||||
/>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
{currentWeather.current.temp_c}℃
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<WeatherProperty type="wind" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="pressure" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonGrid,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonRow,
|
||||
IonSkeletonText,
|
||||
IonText,
|
||||
IonThumbnail,
|
||||
} from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
|
||||
export const SkeletonDashboard = () => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<IonThumbnail>
|
||||
<IonSkeletonText animated style={{ width: '2rem', height: '2rem' }} />
|
||||
</IonThumbnail>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
<IonSkeletonText animated style={{ height: '3rem', width: '30%', textAlign: 'center' }} />
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<img alt="wind" src="/assets/WeatherDemo/wind.png" height="32" width="32" />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Wind</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={thermometerOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Feels like</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={sunnyOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Index UV</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={pulseOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Pressure</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
40
03_source/mobile/src/pages/Demo2FaExample/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function Demo2FaExample() {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/demo-weather-app/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-weather-app/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-weather-app" to="/demo-weather-app/tab1" />
|
||||
</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 Demo2FaExample;
|
103
03_source/mobile/src/pages/Demo2FaExample/style.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
#about-page {
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
|
||||
}
|
||||
|
||||
.about-header .austin {
|
||||
background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
|
||||
}
|
||||
|
||||
.about-header .chicago {
|
||||
background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
|
||||
}
|
||||
|
||||
.about-header .seattle {
|
||||
background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: relative;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
z-index: 2; // display rounded border above header image
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#date-input-popover {
|
||||
--offset-y: -var(--ion-safe-area-bottom);
|
||||
|
||||
--max-width: 90%;
|
||||
--width: 336px;
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
};
|
||||
|
||||
// const router = useIonRouter();
|
||||
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;
|
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
||||
const performSearch = async () => {
|
||||
getAddress(search);
|
||||
};
|
||||
|
||||
const getAddress = async (city) => {
|
||||
const response = await fetch(
|
||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.current && data.location) {
|
||||
setCurrentWeather(data);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
|
||||
<IonCol size="7">
|
||||
<IonSearchbar
|
||||
placeholder="Try 'London'"
|
||||
animated
|
||||
value={search}
|
||||
onIonChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="5">
|
||||
<IonButton
|
||||
expand="block"
|
||||
className="ion-margin-start ion-margin-end"
|
||||
onClick={performSearch}
|
||||
>
|
||||
Search
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-0.8rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<h3 className="ion-text-center">Your search result will appear here</h3>
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,29 @@
|
||||
import { IonAccordion, IonAccordionGroup, IonIcon, IonItem, IonLabel, IonList } from '@ionic/react';
|
||||
import { topics } from '../data';
|
||||
|
||||
export const Accordion = () => {
|
||||
return (
|
||||
<IonAccordionGroup>
|
||||
{topics.map((topic, index) => {
|
||||
return (
|
||||
<IonAccordion key={`accordion_${index}`} value={topic.header.toLowerCase()}>
|
||||
<IonItem slot="header">
|
||||
<IonIcon icon={topic.icon} color={topic.color} />
|
||||
<IonLabel className="ion-padding-start">{topic.header}</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
{topic.options.map((option, index2) => {
|
||||
return (
|
||||
<IonItem key={`option_${index}_${index2}`} routerLink={`/topics/${option.label}`}>
|
||||
<IonLabel>{option.label}</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
);
|
||||
})}
|
||||
</IonAccordionGroup>
|
||||
);
|
||||
};
|
@@ -0,0 +1,30 @@
|
||||
import { IonAccordion, IonAccordionGroup, IonIcon, IonItem, IonLabel, IonList } from '@ionic/react';
|
||||
import { topics } from '../data';
|
||||
import React from 'react';
|
||||
|
||||
export const Accordion: React.FC = () => {
|
||||
return (
|
||||
<IonAccordionGroup>
|
||||
{topics.map((topic: any, index: number) => {
|
||||
return (
|
||||
<IonAccordion key={`accordion_${index}`} value={topic.header.toLowerCase()}>
|
||||
<IonItem slot="header">
|
||||
<IonIcon icon={topic.icon} color={topic.color} />
|
||||
<IonLabel className="ion-padding-start">{topic.header}</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
{topic.options.map((option: any, index2: number) => {
|
||||
return (
|
||||
<IonItem key={`option_${index}_${index2}`} routerLink={`/topics/${option.label}`}>
|
||||
<IonLabel>{option.label}</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
);
|
||||
})}
|
||||
</IonAccordionGroup>
|
||||
);
|
||||
};
|
@@ -0,0 +1,62 @@
|
||||
import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => {
|
||||
const [property, setProperty] = useState(false);
|
||||
|
||||
const properties = {
|
||||
wind: {
|
||||
isIcon: false,
|
||||
icon: '/assets/WeatherDemo/wind.png',
|
||||
alt: 'wind',
|
||||
label: 'Wind',
|
||||
value: `${currentWeather.current.wind_mph}mph`,
|
||||
},
|
||||
feelsLike: {
|
||||
isIcon: true,
|
||||
icon: thermometerOutline,
|
||||
alt: 'feels like',
|
||||
label: 'Feels like',
|
||||
value: `${currentWeather.current.feelslike_c}°C`,
|
||||
},
|
||||
indexUV: {
|
||||
isIcon: true,
|
||||
icon: sunnyOutline,
|
||||
alt: 'index uv',
|
||||
label: 'Index UV',
|
||||
value: currentWeather.current.uv,
|
||||
},
|
||||
pressure: {
|
||||
isIcon: true,
|
||||
icon: pulseOutline,
|
||||
alt: 'pressure',
|
||||
label: 'Pressure',
|
||||
value: `${currentWeather.current.pressure_mb} mbar`,
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setProperty(properties[type]);
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
{!property.isIcon && (
|
||||
<img alt={property.alt} src={property.icon} height="32" width="32" />
|
||||
)}
|
||||
{property.isIcon && (
|
||||
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
|
||||
)}
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>{property.label}</IonCardSubtitle>
|
||||
<IonNote>{property.value}</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
|
||||
import { WeatherProperty } from './WeatherProperty';
|
||||
|
||||
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
{currentWeather.location.region},{' '}
|
||||
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<img
|
||||
alt="condition"
|
||||
src={currentWeather.current.condition.icon.replace('//', 'https://')}
|
||||
/>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
{currentWeather.current.temp_c}℃
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<WeatherProperty type="wind" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="pressure" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonGrid,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonRow,
|
||||
IonSkeletonText,
|
||||
IonText,
|
||||
IonThumbnail,
|
||||
} from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
|
||||
export const SkeletonDashboard = () => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<IonThumbnail>
|
||||
<IonSkeletonText animated style={{ width: '2rem', height: '2rem' }} />
|
||||
</IonThumbnail>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
<IonSkeletonText animated style={{ height: '3rem', width: '30%', textAlign: 'center' }} />
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<img alt="wind" src="/assets/WeatherDemo/wind.png" height="32" width="32" />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Wind</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={thermometerOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Feels like</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={sunnyOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Index UV</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
<IonIcon icon={pulseOutline} color="medium" style={{ fontSize: '2rem' }} />
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>Pressure</IonCardSubtitle>
|
||||
<IonNote>
|
||||
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
|
||||
</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
120
03_source/mobile/src/pages/DemoAccordionTutorial/data.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import { bicycleOutline, fastFoodOutline, filmOutline, gameControllerOutline, libraryOutline } from 'ionicons/icons';
|
||||
|
||||
export const topics = [
|
||||
{
|
||||
header: 'Attractions',
|
||||
color: 'primary',
|
||||
icon: filmOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Cinema',
|
||||
},
|
||||
{
|
||||
label: 'Bowling Alley',
|
||||
},
|
||||
{
|
||||
label: 'Crazy Golf',
|
||||
},
|
||||
{
|
||||
label: 'Theme Park',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Dining',
|
||||
color: 'success',
|
||||
icon: fastFoodOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Breakfast & Brunch',
|
||||
},
|
||||
{
|
||||
label: 'New American',
|
||||
},
|
||||
{
|
||||
label: 'Sushi Bars',
|
||||
},
|
||||
{
|
||||
label: 'Filipino Food',
|
||||
},
|
||||
{
|
||||
label: 'Asian Fusion',
|
||||
},
|
||||
{
|
||||
label: 'Ramen Houses',
|
||||
},
|
||||
{
|
||||
label: 'Dinner Venues',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Gaming',
|
||||
color: 'warning',
|
||||
icon: gameControllerOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Xbox',
|
||||
},
|
||||
{
|
||||
label: 'Playstation',
|
||||
},
|
||||
{
|
||||
label: 'Nintendo Switch',
|
||||
},
|
||||
{
|
||||
label: 'PC',
|
||||
},
|
||||
{
|
||||
label: 'Mobile',
|
||||
},
|
||||
{
|
||||
label: 'Dreamcast',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Exercise',
|
||||
color: 'secondary',
|
||||
icon: bicycleOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Yoga',
|
||||
},
|
||||
{
|
||||
label: 'Pilates',
|
||||
},
|
||||
{
|
||||
label: 'Weight Training',
|
||||
},
|
||||
{
|
||||
label: 'Cardio',
|
||||
},
|
||||
{
|
||||
label: 'Zumba',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Education',
|
||||
color: 'danger',
|
||||
icon: libraryOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'School',
|
||||
},
|
||||
{
|
||||
label: 'High School',
|
||||
},
|
||||
{
|
||||
label: 'University Bachelors',
|
||||
},
|
||||
{
|
||||
label: 'University Masters',
|
||||
},
|
||||
{
|
||||
label: 'University pHD',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
126
03_source/mobile/src/pages/DemoAccordionTutorial/data.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
bicycleOutline,
|
||||
fastFoodOutline,
|
||||
filmOutline,
|
||||
gameControllerOutline,
|
||||
libraryOutline,
|
||||
} from 'ionicons/icons';
|
||||
|
||||
export const topics: any = [
|
||||
{
|
||||
header: 'Attractions',
|
||||
color: 'primary',
|
||||
icon: filmOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Cinema',
|
||||
},
|
||||
{
|
||||
label: 'Bowling Alley',
|
||||
},
|
||||
{
|
||||
label: 'Crazy Golf',
|
||||
},
|
||||
{
|
||||
label: 'Theme Park',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Dining',
|
||||
color: 'success',
|
||||
icon: fastFoodOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Breakfast & Brunch',
|
||||
},
|
||||
{
|
||||
label: 'New American',
|
||||
},
|
||||
{
|
||||
label: 'Sushi Bars',
|
||||
},
|
||||
{
|
||||
label: 'Filipino Food',
|
||||
},
|
||||
{
|
||||
label: 'Asian Fusion',
|
||||
},
|
||||
{
|
||||
label: 'Ramen Houses',
|
||||
},
|
||||
{
|
||||
label: 'Dinner Venues',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Gaming',
|
||||
color: 'warning',
|
||||
icon: gameControllerOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Xbox',
|
||||
},
|
||||
{
|
||||
label: 'Playstation',
|
||||
},
|
||||
{
|
||||
label: 'Nintendo Switch',
|
||||
},
|
||||
{
|
||||
label: 'PC',
|
||||
},
|
||||
{
|
||||
label: 'Mobile',
|
||||
},
|
||||
{
|
||||
label: 'Dreamcast',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Exercise',
|
||||
color: 'secondary',
|
||||
icon: bicycleOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'Yoga',
|
||||
},
|
||||
{
|
||||
label: 'Pilates',
|
||||
},
|
||||
{
|
||||
label: 'Weight Training',
|
||||
},
|
||||
{
|
||||
label: 'Cardio',
|
||||
},
|
||||
{
|
||||
label: 'Zumba',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: 'Education',
|
||||
color: 'danger',
|
||||
icon: libraryOutline,
|
||||
options: [
|
||||
{
|
||||
label: 'School',
|
||||
},
|
||||
{
|
||||
label: 'High School',
|
||||
},
|
||||
{
|
||||
label: 'University Bachelors',
|
||||
},
|
||||
{
|
||||
label: 'University Masters',
|
||||
},
|
||||
{
|
||||
label: 'University pHD',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
56
03_source/mobile/src/pages/DemoAccordionTutorial/index.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
// import Tab1 from './AppPages/Tab1';
|
||||
// import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import Topic from './pages/Topic';
|
||||
import Home from './pages/Home';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoAccordionTutorial() {
|
||||
return (
|
||||
<IonTabs className="demo-accordion-tutorial">
|
||||
<IonRouterOutlet>
|
||||
{/*
|
||||
<Route exact path="/demo-accordion-tutorial/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/demo-accordion-tutorial/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
*/}
|
||||
|
||||
<Route exact path="/demo-accordion-tutorial/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-accordion-tutorial/topics/:topic">
|
||||
<Topic />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-accordion-tutorial" to="/demo-accordion-tutorial/home" />
|
||||
|
||||
{/* <Redirect exact path="/demo-accordion-tutorial" to="/demo-accordion-tutorial/tab1" /> */}
|
||||
</IonRouterOutlet>
|
||||
|
||||
{/*
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/demo-accordion-tutorial/tab1">
|
||||
<IonIcon icon={cloudOutline} />
|
||||
<IonLabel>Dashboard</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/demo-accordion-tutorial/tab2">
|
||||
<IonIcon icon={searchOutline} />
|
||||
<IonLabel>Search</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
*/}
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoAccordionTutorial;
|
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
IonAccordion,
|
||||
IonAccordionGroup,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { Accordion } from '../components/Accordion';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = () => {
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Accordion</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">Accordion</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<Accordion />
|
||||
|
||||
{/* <IonAccordionGroup>
|
||||
<IonAccordion>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Languages</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>English</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Spanish</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Italian</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
|
||||
<IonAccordion>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Languages 2</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>English</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Spanish</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Italian</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup> */}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
IonAccordion,
|
||||
IonAccordionGroup,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { Accordion } from '../components/Accordion';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Accordion</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">Accordion</IonTitle>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<Accordion />
|
||||
|
||||
{/* <IonAccordionGroup>
|
||||
<IonAccordion>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Languages</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>English</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Spanish</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Italian</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
|
||||
<IonAccordion>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Languages 2</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<IonList slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>English</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Spanish</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Italian</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup> */}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,39 @@
|
||||
import { IonBackButton, IonButtons, IonCol, IonContent, IonGrid, IonHeader, IonLabel, IonPage, IonRow, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
const Topic = () => {
|
||||
|
||||
const { topic } = useParams();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Topics" />
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>{ topic }</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">{ topic }</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonLabel>This is the page for the topic: { topic }.</IonLabel>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Topic;
|
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
const Topic = () => {
|
||||
const { topic } = useParams<{ topic: string }>();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Topics" />
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>{topic}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">{topic}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid>
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonLabel>This is the page for the topic: {topic}.</IonLabel>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Topic;
|
237
03_source/mobile/src/pages/DemoAccordionTutorial/style.scss
Normal file
@@ -0,0 +1,237 @@
|
||||
.demo-accordion-tutorial {
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
* {
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** 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: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 245, 248;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
* Dark Colors
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
body {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66, 140, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--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: #222428;
|
||||
--ion-color-light-rgb: 34, 36, 40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
--ion-item-background: #000000;
|
||||
|
||||
--ion-card-background: #1c1c1d;
|
||||
}
|
||||
|
||||
.ios ion-modal {
|
||||
--ion-background-color: var(--ion-color-step-100);
|
||||
--ion-toolbar-background: var(--ion-color-step-150);
|
||||
--ion-toolbar-border-color: var(--ion-color-step-250);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
|
||||
--ion-card-background: #1e1e1e;
|
||||
}
|
||||
}
|
||||
}
|
96
03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab1.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
};
|
||||
|
||||
// const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>My Weather</IonTitle>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => getCurrentPosition()}>
|
||||
<IonIcon icon={refreshOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Dashboard</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-margin-start ion-margin-end ion-justify-content-center ion-text-center">
|
||||
<IonCol size="12">
|
||||
<h4>Here's your location based weather</h4>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-1.5rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<SkeletonDashboard />
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab1;
|
81
03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab2.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
||||
const performSearch = async () => {
|
||||
getAddress(search);
|
||||
};
|
||||
|
||||
const getAddress = async (city) => {
|
||||
const response = await fetch(
|
||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.current && data.location) {
|
||||
setCurrentWeather(data);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
|
||||
<IonCol size="7">
|
||||
<IonSearchbar
|
||||
placeholder="Try 'London'"
|
||||
animated
|
||||
value={search}
|
||||
onIonChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="5">
|
||||
<IonButton
|
||||
expand="block"
|
||||
className="ion-margin-start ion-margin-end"
|
||||
onClick={performSearch}
|
||||
>
|
||||
Search
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-0.8rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<h3 className="ion-text-center">Your search result will appear here</h3>
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,75 @@
|
||||
import { IonButton, IonCardSubtitle, IonCol, IonIcon, IonList, IonRow } from '@ionic/react';
|
||||
import DebitCard from './DebitCard';
|
||||
|
||||
import styles from './CardSlide.module.css';
|
||||
import TransactionItem from './TransactionItem';
|
||||
import { addOutline, arrowRedoOutline } from 'ionicons/icons';
|
||||
import { formatBalance } from '../data/Utils';
|
||||
|
||||
const CardSlide = (props) => {
|
||||
const { index, card, profile } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonRow className="ion-text-center">
|
||||
<IonCol size="12">
|
||||
<IonCardSubtitle color="medium">balance</IonCardSubtitle>
|
||||
<IonCardSubtitle id={`slide_${index}_balance`} className={` ${styles.balance} animate__animated`}>
|
||||
<span className={styles.poundSign}>£</span>
|
||||
{formatBalance(card.balance)}
|
||||
<IonButton
|
||||
className={styles.addButton}
|
||||
size="small"
|
||||
style={{ '--background': card.color, '--background-focused': card.color, '--background-hover': card.color, '--background-activated': card.color }}
|
||||
routerLink={`/add-transaction/${card.id}`}
|
||||
>
|
||||
<IonIcon icon={addOutline} />
|
||||
</IonButton>
|
||||
</IonCardSubtitle>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
<IonRow id={`card_${index}_container`} className="animate__animated ion-text-center ion-justify-content-center">
|
||||
<IonCol size="12">
|
||||
<DebitCard key={index} {...card} profile={profile} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className={styles.heading}>
|
||||
<IonCol size="12">
|
||||
<h6>Transactions</h6>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
{card.transactions.length > 0 && (
|
||||
<IonRow id={`slide_${index}_transactions`} className="animate__animated">
|
||||
<IonCol size="12">
|
||||
<IonList className={styles.transactionList}>
|
||||
{card.transactions.length > 0 &&
|
||||
card.transactions
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map((transaction, index) => <TransactionItem key={`card_transaction_${index}`} {...transaction} color={card.color} />)}
|
||||
</IonList>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
)}
|
||||
|
||||
{card.transactions.length === 0 && (
|
||||
<IonRow id={`slide_${index}_transactions`} className="animate__animated">
|
||||
<IonCol size="12">
|
||||
<h5>No transactions found</h5>
|
||||
<IonButton
|
||||
style={{ '--background': card.color, '--background-focused': card.color, '--background-hover': card.color, '--background-activated': card.color }}
|
||||
routerLink={`/add-transaction/${card.id}`}
|
||||
>
|
||||
<IonIcon icon={arrowRedoOutline} />
|
||||
Transfer funds
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardSlide;
|
@@ -0,0 +1,51 @@
|
||||
.customSlide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.transactionList {
|
||||
overflow: scroll;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.balance {
|
||||
font-weight: 300;
|
||||
font-size: 1.5rem;
|
||||
color: black;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.poundSign {
|
||||
font-weight: 800;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.heading h6 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: left !important;
|
||||
float: left !important;
|
||||
text-align: left !important;
|
||||
color: rgb(124, 124, 124);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.heading {
|
||||
width: 83%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
--border-radius: 500px !important;
|
||||
width: fit-content !important;
|
||||
margin-top: 0.45rem;
|
||||
margin-left: 1rem;
|
||||
opacity: 0.6;
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => {
|
||||
const [property, setProperty] = useState(false);
|
||||
|
||||
const properties = {
|
||||
wind: {
|
||||
isIcon: false,
|
||||
icon: '/assets/WeatherDemo/wind.png',
|
||||
alt: 'wind',
|
||||
label: 'Wind',
|
||||
value: `${currentWeather.current.wind_mph}mph`,
|
||||
},
|
||||
feelsLike: {
|
||||
isIcon: true,
|
||||
icon: thermometerOutline,
|
||||
alt: 'feels like',
|
||||
label: 'Feels like',
|
||||
value: `${currentWeather.current.feelslike_c}°C`,
|
||||
},
|
||||
indexUV: {
|
||||
isIcon: true,
|
||||
icon: sunnyOutline,
|
||||
alt: 'index uv',
|
||||
label: 'Index UV',
|
||||
value: currentWeather.current.uv,
|
||||
},
|
||||
pressure: {
|
||||
isIcon: true,
|
||||
icon: pulseOutline,
|
||||
alt: 'pressure',
|
||||
label: 'Pressure',
|
||||
value: `${currentWeather.current.pressure_mb} mbar`,
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setProperty(properties[type]);
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
{!property.isIcon && (
|
||||
<img alt={property.alt} src={property.icon} height="32" width="32" />
|
||||
)}
|
||||
{property.isIcon && (
|
||||
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
|
||||
)}
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>{property.label}</IonCardSubtitle>
|
||||
<IonNote>{property.value}</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
|
||||
import { WeatherProperty } from './WeatherProperty';
|
||||
|
||||
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
{currentWeather.location.region},{' '}
|
||||
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<img
|
||||
alt="condition"
|
||||
src={currentWeather.current.condition.icon.replace('//', 'https://')}
|
||||
/>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
{currentWeather.current.temp_c}℃
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<WeatherProperty type="wind" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="pressure" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './DebitCard.module.css';
|
||||
|
||||
const DebitCard = (props) => {
|
||||
const { type, number, profile, expiry, secret, color } = props;
|
||||
const [lastFourCardNumbers, setLastFourCardNumbers] = useState('****');
|
||||
|
||||
const cardClass = `card_${color}`;
|
||||
const cardTypeLogo = type === 'visa' ? '/visa.png' : '/mastercard.png';
|
||||
|
||||
useEffect(() => {
|
||||
var lastFourNumbers = number ? number.substr(number.length - 4) : '1234';
|
||||
setLastFourCardNumbers(lastFourNumbers);
|
||||
}, [number]);
|
||||
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={`${styles.card__front} ${styles.card__part} ${styles[cardClass]}`}>
|
||||
<img className={`${styles.card__front_chip} ${styles.card__square}`} src="/chip.png" alt="1" />
|
||||
<img className={`${styles.card__front_square} ${styles.card__square}`} src="/ionicwhite.png" alt="1" />
|
||||
<img className={`${styles.card__front_logo} ${styles.card__logo}`} src={cardTypeLogo} alt="2" />
|
||||
<p className={styles.card_number}>**** **** **** {lastFourCardNumbers}</p>
|
||||
<div className={styles.card__space_75}>
|
||||
<span className={styles.card__label}>Card holder</span>
|
||||
<p className={styles.card__info}>{`${profile.firstname} ${profile.surname}`}</p>
|
||||
</div>
|
||||
<div className={styles.card__space_25}>
|
||||
<span className={styles.card__label}>Expires</span>
|
||||
<p className={styles.card__info}>{expiry}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`${styles.card__back} ${styles.card__part} ${styles[cardClass]}`}>
|
||||
<div className={styles.card__black_line}></div>
|
||||
<div className={styles.card__back_content}>
|
||||
<div className={styles.card__secret}>
|
||||
<p className={styles.card__secret__last}>{secret}</p>
|
||||
</div>
|
||||
|
||||
<img className={`${styles.card__back_square} ${styles.card__square}`} src="/ionicwhite.png" alt="3" />
|
||||
<img className={`${styles.card__back_logo} ${styles.card__logo}`} src={cardTypeLogo} alt="5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebitCard;
|
@@ -0,0 +1,188 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Space+Mono:400,400i,700,700i');
|
||||
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
font-family: 'Space Mono', monospace;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
color: #162969;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 320px;
|
||||
height: 190px;
|
||||
-webkit-perspective: 600px;
|
||||
-moz-perspective: 600px;
|
||||
perspective: 600px;
|
||||
}
|
||||
|
||||
.card__part {
|
||||
box-shadow: 1px 1px #aaa3a3;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
width: 320px;
|
||||
height: 190px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 8px;
|
||||
|
||||
-webkit-transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
-moz-transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
-ms-transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
-o-transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
-webkit-transform-style: preserve-3d;
|
||||
-moz-transform-style: preserve-3d;
|
||||
-webkit-backface-visibility: hidden;
|
||||
-moz-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.card_orange {
|
||||
background: linear-gradient(to right bottom, #fd696b, #fa616e, #f65871, #c74261, #d62158);
|
||||
}
|
||||
|
||||
.card_blue {
|
||||
background: linear-gradient(to right bottom, #699dfd, #61b5fa, #58aff6, #4b86c9, #2151d6);
|
||||
}
|
||||
|
||||
.card_black {
|
||||
background: linear-gradient(to right bottom, #292929, #363636, #555555, #444444, #0f0f0f);
|
||||
}
|
||||
|
||||
.card_purple {
|
||||
background: linear-gradient(to right bottom, #7a43df, #644897, #8964cf, #633cac, #512c96);
|
||||
}
|
||||
|
||||
.card__front {
|
||||
padding: 18px;
|
||||
-webkit-transform: rotateY(0);
|
||||
-moz-transform: rotateY(0);
|
||||
}
|
||||
|
||||
.card__back {
|
||||
padding: 18px 0;
|
||||
-webkit-transform: rotateY(-180deg);
|
||||
-moz-transform: rotateY(-180deg);
|
||||
}
|
||||
|
||||
.card__black_line {
|
||||
margin-top: 5px;
|
||||
height: 38px;
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
.card__logo {
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
.card__front_chip {
|
||||
left: 1.2rem;
|
||||
height: 1.5rem !important;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.card__front_logo {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
}
|
||||
|
||||
.card__square {
|
||||
border-radius: 5px;
|
||||
height: 30px !important;
|
||||
}
|
||||
|
||||
.card_number {
|
||||
display: block;
|
||||
width: 100%;
|
||||
word-spacing: 4px;
|
||||
font-size: 20px;
|
||||
letter-spacing: 2px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card__space_75 {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.card__space_25 {
|
||||
width: 25%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.card__label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.card__info {
|
||||
margin-bottom: 0;
|
||||
margin-top: 5px;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
color: #fff;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card__back_content {
|
||||
padding: 15px 15px 0;
|
||||
}
|
||||
.card__secret__last {
|
||||
color: #303030;
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.card__secret {
|
||||
padding: 5px 12px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card__secret:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -3px;
|
||||
height: calc(100% + 6px);
|
||||
width: calc(100% - 42px);
|
||||
border-radius: 4px;
|
||||
background: repeating-linear-gradient(45deg, #ededed, #ededed 5px, #f9f9f9 5px, #f9f9f9 10px);
|
||||
}
|
||||
|
||||
.card__back_logo {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.card__back_square {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
.card:hover .card__front {
|
||||
-webkit-transform: rotateY(180deg);
|
||||
-moz-transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.card:hover .card__back {
|
||||
-webkit-transform: rotateY(0deg);
|
||||
-moz-transform: rotateY(0deg);
|
||||
}
|
@@ -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>
|
||||
);
|
@@ -0,0 +1,56 @@
|
||||
import { IonAvatar, IonItem, IonLabel } from '@ionic/react';
|
||||
import { formatBalance } from '../data/Utils';
|
||||
import styles from './TransactionItem.module.css';
|
||||
|
||||
const TransactionItem = (props) => {
|
||||
const { name, amount, deposit, color } = props;
|
||||
|
||||
const getContactNameInitials = (contactName) => {
|
||||
var nameInitials = '';
|
||||
|
||||
if (contactName && contactName !== '' && contactName !== undefined) {
|
||||
const nameParts = contactName && contactName.split(' ');
|
||||
|
||||
if (nameParts) {
|
||||
if (nameParts[0].charAt(0).match(/^[a-z]+$/i)) {
|
||||
nameInitials += nameParts[0].charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
if (nameParts[1]) {
|
||||
if (nameParts[1].charAt(0).match(/^[a-z]+$/i)) {
|
||||
nameInitials += nameParts[1].charAt(0).toUpperCase();
|
||||
}
|
||||
} else {
|
||||
nameInitials += nameParts[0].charAt(1).toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nameInitials;
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItem lines="full" detail={false} className={`item-text-wrap ion-text-wrap ${styles.transactionItem}`}>
|
||||
<div className={styles.transactionItemContent}>
|
||||
<IonAvatar slot="start">
|
||||
<div style={{ borderColor: 'grey', color: 'grey' }} className={styles.avatarImage}>
|
||||
{getContactNameInitials(name)}
|
||||
</div>
|
||||
</IonAvatar>
|
||||
|
||||
<IonLabel className={`ion-text-wrap ${styles.transactionContent}`}>
|
||||
<h2>{name}</h2>
|
||||
</IonLabel>
|
||||
|
||||
<IonLabel className={`ion-text-wrap ${styles.transactionContent}`}>
|
||||
<h4 className={deposit ? styles.green : styles.red}>
|
||||
{deposit ? '+' : '-'}
|
||||
£{formatBalance(amount)}
|
||||
</h4>
|
||||
</IonLabel>
|
||||
</div>
|
||||
</IonItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionItem;
|
@@ -0,0 +1,48 @@
|
||||
.avatarImage {
|
||||
/* background-color: var(--ion-color); */
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 500px;
|
||||
color: black;
|
||||
font-size: 1.3rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
padding: 0.5rem !important;
|
||||
border: 2px solid rgb(44, 44, 44);
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.transactionItem {
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.transactionItemContent {
|
||||
padding-left: 3rem;
|
||||
padding-right: 2rem;
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
margin-top: -0.2rem;
|
||||
margin-bottom: -0.2rem;
|
||||
}
|
||||
|
||||
.transactionContent {
|
||||
padding: 1rem;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: rgb(0, 165, 0);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
119
03_source/mobile/src/pages/DemoBankingUi/data/AccountStore.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Store } from 'pullstate';
|
||||
|
||||
export const AccountStore = new Store({
|
||||
profile: {
|
||||
firstname: 'Alan',
|
||||
surname: 'Montgomery',
|
||||
avatar: '/assets/DemoBankingUi/alan.jpg',
|
||||
},
|
||||
cards: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'visa',
|
||||
description: 'Current Account',
|
||||
number: '4859 2390 5635 7347',
|
||||
expiry: '11/22',
|
||||
secret: '483',
|
||||
color: 'orange',
|
||||
balance: '38.21',
|
||||
transactions: [
|
||||
{
|
||||
name: 'Joe Bloggs',
|
||||
amount: '2.50',
|
||||
deposit: true,
|
||||
},
|
||||
{
|
||||
name: 'Ocean Pratt',
|
||||
amount: '12.99',
|
||||
deposit: true,
|
||||
},
|
||||
{
|
||||
name: 'Eugene Piper',
|
||||
amount: '74.99',
|
||||
deposit: false,
|
||||
},
|
||||
{
|
||||
name: 'Emeli Potts',
|
||||
amount: '4.20',
|
||||
deposit: false,
|
||||
},
|
||||
{
|
||||
name: 'Asia Wells',
|
||||
amount: '12.73',
|
||||
deposit: true,
|
||||
},
|
||||
{
|
||||
name: 'Awais Brook',
|
||||
amount: '17.10',
|
||||
deposit: false,
|
||||
},
|
||||
{
|
||||
name: 'Coen Haas',
|
||||
amount: '9.99',
|
||||
deposit: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'visa',
|
||||
description: 'Savings',
|
||||
number: '7349 1284 6790 4587',
|
||||
expiry: '05/23',
|
||||
secret: '590',
|
||||
color: 'blue',
|
||||
balance: '120.90',
|
||||
transactions: [
|
||||
{
|
||||
name: 'Joe Bloggs',
|
||||
amount: '120.90',
|
||||
deposit: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'visa',
|
||||
description: 'House Fund',
|
||||
number: '6783 5692 4475 6682',
|
||||
expiry: '01/24',
|
||||
secret: '321',
|
||||
color: 'purple',
|
||||
balance: '0',
|
||||
transactions: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const addCardToAccount = (newCard) => {
|
||||
AccountStore.update((s) => {
|
||||
s.cards = [...s.cards, newCard];
|
||||
});
|
||||
};
|
||||
|
||||
export const addTransactionToCard = (newTransaction, cardID) => {
|
||||
AccountStore.update((s) => {
|
||||
s.cards.find((c, index) =>
|
||||
parseInt(c.id) === parseInt(cardID) ? (s.cards[index].transactions = [...s.cards[index].transactions, newTransaction]) : false
|
||||
);
|
||||
});
|
||||
|
||||
if (newTransaction.deposit) {
|
||||
AccountStore.update((s) => {
|
||||
s.cards.find((c, index) =>
|
||||
parseInt(c.id) === parseInt(cardID) ? (s.cards[index].balance = parseFloat(s.cards[index].balance) + parseFloat(newTransaction.amount)) : false
|
||||
);
|
||||
});
|
||||
} else {
|
||||
AccountStore.update((s) => {
|
||||
s.cards.find((c, index) =>
|
||||
parseInt(c.id) === parseInt(cardID) ? (s.cards[index].balance = parseFloat(s.cards[index].balance) - parseFloat(newTransaction.amount)) : false
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// export const removeFromCart = productIndex => {
|
||||
|
||||
// AccountStore.update(s => { s.product_ids.splice(productIndex, 1) });
|
||||
// }
|
@@ -0,0 +1,6 @@
|
||||
import { Store } from 'pullstate';
|
||||
|
||||
export const CardStore = new Store({
|
||||
card_colors: ['orange', 'black', 'blue', 'purple'],
|
||||
card_types: ['visa', 'mastercard'],
|
||||
});
|
9
03_source/mobile/src/pages/DemoBankingUi/data/Utils.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const formatBalance = (balance) => {
|
||||
var formatter = new Intl.NumberFormat('en-GB', {
|
||||
// style: 'currency',
|
||||
currency: 'GBP',
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
|
||||
return formatter.format(balance);
|
||||
};
|
42
03_source/mobile/src/pages/DemoBankingUi/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Tab1 from './AppPages/Tab1';
|
||||
import Tab2 from './AppPages/Tab2';
|
||||
|
||||
import Home from './pages/Home.jsx';
|
||||
import Account from './pages/Account';
|
||||
import AddCard from './pages/AddCard';
|
||||
import AddTransaction from './pages/AddTransaction';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoBankingUi() {
|
||||
return (
|
||||
<IonTabs className="demo-banking-ui">
|
||||
<IonRouterOutlet>
|
||||
<Route path="/demo-banking-ui/home" exact={true}>
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route path="/demo-banking-ui/account" exact={true}>
|
||||
<Account />
|
||||
</Route>
|
||||
|
||||
<Route path="/demo-banking-ui/account/add-card" exact={true}>
|
||||
<AddCard />
|
||||
</Route>
|
||||
|
||||
<Route path="/demo-banking-ui/add-transaction/:card_id" exact={true}>
|
||||
<AddTransaction />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-banking-ui" to="/demo-banking-ui/home" />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoBankingUi;
|
95
03_source/mobile/src/pages/DemoBankingUi/pages/Account.jsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import styles from './Account.module.css';
|
||||
import { AccountStore } from '../data/AccountStore';
|
||||
import { addOutline, logOutOutline } from 'ionicons/icons';
|
||||
import { formatBalance } from '../data/Utils';
|
||||
|
||||
const Account = () => {
|
||||
const cards = AccountStore.useState((s) => s.cards);
|
||||
const profile = AccountStore.useState((s) => s.profile);
|
||||
|
||||
return (
|
||||
<IonPage className={styles.accountPage}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton color="dark" />
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>Account</IonTitle>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton>
|
||||
<IonIcon color="dark" icon={logOutOutline} />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow className="ion-text-center ion-justify-content-center">
|
||||
<IonCol size="4" className="animate__animated animate__fadeInTopLeft animate__faster">
|
||||
<img src={profile.avatar} className={styles.avatar} alt="account avatar" />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className={`ion-no-margin ion-text-center ion-justify-content-center ${styles.profileDetails}`}>
|
||||
<IonCol size="12">
|
||||
<h5>{`${profile.firstname} ${profile.surname}`}</h5>
|
||||
<h6>{cards.length} current cards</h6>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-text-center">
|
||||
<IonCol size="12">
|
||||
<IonButton color="primary" routerLink="/account/add-card" routerDirection="forward">
|
||||
<IonIcon icon={addOutline} />
|
||||
Add Card
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
{cards.map((card, index) => {
|
||||
return (
|
||||
<IonRow key={`smallCard_${index}`} className="animate__animated animate__fadeInLeft animate__faster">
|
||||
<IonCol size="12">
|
||||
<IonItem className={styles.cardItem} detail={false} lines="none">
|
||||
<div className={styles.smallCard} style={{ backgroundColor: card.color }}></div>
|
||||
|
||||
<IonLabel className={`ion-text-left ${styles.cardDescription}`}>
|
||||
<h4>{card.description}</h4>
|
||||
</IonLabel>
|
||||
|
||||
<IonLabel className="ion-text-right">
|
||||
<h4>£{formatBalance(card.balance)}</h4>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Account;
|
@@ -0,0 +1,54 @@
|
||||
.accountPage ion-toolbar {
|
||||
--border-style: none;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 500px;
|
||||
border: 3px solid var(--ion-color-primary);
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.profileDetails {
|
||||
}
|
||||
|
||||
.profileDetails h6,
|
||||
.profileDetails h5 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.profileDetails h6 {
|
||||
color: var(--ion-color-medium);
|
||||
}
|
||||
|
||||
.cards {
|
||||
/* margin-top: */
|
||||
}
|
||||
|
||||
.smallCard {
|
||||
width: 15%;
|
||||
height: 80%;
|
||||
border-radius: 5px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.cardDescription {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.cardDescription h4 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.cardItem {
|
||||
--background: rgb(246, 246, 246);
|
||||
--border-radius: 5px;
|
||||
--padding-start: 1rem;
|
||||
--padding-end: 1rem;
|
||||
--padding-top: 0.5rem;
|
||||
--padding-bottom: 0.5rem;
|
||||
}
|
209
03_source/mobile/src/pages/DemoBankingUi/pages/AddCard.jsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonInput,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import styles from './Account.module.css';
|
||||
import DebitCard from '../components/DebitCard';
|
||||
import { AccountStore, addCardToAccount } from '../data/AccountStore';
|
||||
import { CardStore } from '../data/CardStore';
|
||||
import { addOutline, timerOutline } from 'ionicons/icons';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
const AddCard = () => {
|
||||
const cards = AccountStore.useState((s) => s.cards);
|
||||
const cardTypes = CardStore.useState((s) => s.card_types);
|
||||
const cardColors = CardStore.useState((s) => s.card_colors);
|
||||
const profile = AccountStore.useState((s) => s.profile);
|
||||
|
||||
const [cardType, setCardType] = useState(cardTypes[0]);
|
||||
const [cardColor, setCardColor] = useState(cardColors[0]);
|
||||
const [cardDescription, setCardDescription] = useState('');
|
||||
const [cardNumber, setCardNumber] = useState('1234 1234 1234 1234');
|
||||
const [cardSecret, setCardSecret] = useState('123');
|
||||
const [cardExpiry, setCardExpiry] = useState('01/22');
|
||||
const [cardBalance, setCardBalance] = useState(0);
|
||||
|
||||
const history = useHistory();
|
||||
const [adding, setAdding] = useState(false);
|
||||
|
||||
const addCard = async () => {
|
||||
setAdding(true);
|
||||
|
||||
const newCard = {
|
||||
id: cards.length + 1,
|
||||
type: cardType,
|
||||
color: cardColor,
|
||||
description: cardDescription,
|
||||
number: cardNumber,
|
||||
secret: cardSecret,
|
||||
expiry: cardExpiry,
|
||||
balance: cardBalance,
|
||||
transactions: [
|
||||
{
|
||||
name: 'Starting Balance',
|
||||
amount: cardBalance,
|
||||
deposit: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await addCardToAccount(newCard);
|
||||
|
||||
setTimeout(() => {
|
||||
setAdding(false);
|
||||
history.goBack();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage className={styles.accountPage}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton color="dark" />
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>Add Card</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow className="animate__animated animate__fadeInTopLeft animate__faster ion-justify-content-center ion-text-center">
|
||||
<IonCol size="12" className="ion-justify-content-center ion-text-center">
|
||||
<DebitCard color={cardColor} type={cardType} expiry={cardExpiry} number={cardNumber} secret={cardSecret} profile={profile} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-padding-top">
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Card Type</IonLabel>
|
||||
<IonSelect placeholder="Select type" value={cardType} onIonChange={(e) => setCardType(e.currentTarget.value)}>
|
||||
{cardTypes.map((option, index) => {
|
||||
return (
|
||||
<IonSelectOption key={index} value={option}>
|
||||
{option.toUpperCase()}
|
||||
</IonSelectOption>
|
||||
);
|
||||
})}
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Card Color</IonLabel>
|
||||
<IonSelect placeholder="Select color" value={cardColor} onIonChange={(e) => setCardColor(e.currentTarget.value)}>
|
||||
{cardColors.map((option, index) => {
|
||||
return (
|
||||
<IonSelectOption key={index} value={option}>
|
||||
{option.toUpperCase()}
|
||||
</IonSelectOption>
|
||||
);
|
||||
})}
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Card Name</IonLabel>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputmode="text"
|
||||
placeholder="Card name"
|
||||
value={cardDescription}
|
||||
onIonChange={(e) => setCardDescription(e.currentTarget.value)}
|
||||
/>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Starting Balance</IonLabel>
|
||||
<IonInput type="text" inputmode="text" placeholder="0" value={cardBalance} onIonChange={(e) => setCardBalance(e.currentTarget.value)} />
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Card Number</IonLabel>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputmode="text"
|
||||
placeholder="**** **** **** ****"
|
||||
value={cardNumber}
|
||||
onIonChange={(e) => setCardNumber(e.currentTarget.value)}
|
||||
/>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Card Expiry</IonLabel>
|
||||
<IonInput type="text" inputmode="text" placeholder="01/22" value={cardExpiry} onIonChange={(e) => setCardExpiry(e.currentTarget.value)} />
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Card Secret</IonLabel>
|
||||
<IonInput type="text" inputmode="text" placeholder="123" value={cardSecret} onIonChange={(e) => setCardSecret(e.currentTarget.value)} />
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
style={{ '--background': cardColor, '--background-focused': cardColor, '--background-hover': cardColor, '--background-activated': cardColor }}
|
||||
expand="block"
|
||||
disabled={adding}
|
||||
onClick={addCard}
|
||||
>
|
||||
{!adding && (
|
||||
<>
|
||||
<IonIcon icon={addOutline} />
|
||||
Add Card
|
||||
</>
|
||||
)}
|
||||
|
||||
{adding && (
|
||||
<>
|
||||
<IonIcon icon={timerOutline} />
|
||||
Adding...
|
||||
</>
|
||||
)}
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCard;
|
@@ -0,0 +1,165 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonInput,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonTitle,
|
||||
IonToggle,
|
||||
IonToolbar,
|
||||
useIonViewWillEnter,
|
||||
} from '@ionic/react';
|
||||
import styles from './Account.module.css';
|
||||
import DebitCard from '../components/DebitCard';
|
||||
import { AccountStore, addCardToAccount, addTransactionToCard } from '../data/AccountStore';
|
||||
import { CardStore } from '../data/CardStore';
|
||||
import { addOutline, timerOutline } from 'ionicons/icons';
|
||||
import { useHistory, useParams } from 'react-router';
|
||||
|
||||
const AddTransaction = () => {
|
||||
const cards = AccountStore.useState((s) => s.cards);
|
||||
const profile = AccountStore.useState((s) => s.profile);
|
||||
|
||||
const [cardID, setCardID] = useState(false);
|
||||
const [card, setCard] = useState({});
|
||||
const [transactionName, setTransactionName] = useState('Test Transaction');
|
||||
const [transactionAmount, setTransactionAmount] = useState(0);
|
||||
const [transactionDeposit, setTransactionDeposit] = useState(false);
|
||||
|
||||
const history = useHistory();
|
||||
const params = useParams();
|
||||
const [adding, setAdding] = useState(false);
|
||||
|
||||
useIonViewWillEnter(() => {
|
||||
const tempCardID = params.card_id;
|
||||
const tempCard = cards.filter((c) => parseInt(c.id) === parseInt(tempCardID))[0];
|
||||
setCardID(tempCardID);
|
||||
setCard(tempCard);
|
||||
});
|
||||
|
||||
const addTransaction = async () => {
|
||||
setAdding(true);
|
||||
|
||||
const newTransaction = {
|
||||
name: transactionName,
|
||||
amount: transactionAmount,
|
||||
deposit: transactionDeposit,
|
||||
};
|
||||
|
||||
await addTransactionToCard(newTransaction, cardID);
|
||||
|
||||
setTimeout(() => {
|
||||
setAdding(false);
|
||||
history.goBack();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage className={styles.accountPage}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton color="dark" />
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>Add Transaction</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<IonRow className="animate__animated animate__fadeInTopLeft animate__faster ion-justify-content-center ion-text-center">
|
||||
<IonCol size="12" className="ion-justify-content-center ion-text-center">
|
||||
<DebitCard {...card} profile={profile} />
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Name</IonLabel>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputmode="text"
|
||||
placeholder="Transaction name"
|
||||
value={transactionName}
|
||||
onIonChange={(e) => setTransactionName(e.currentTarget.value)}
|
||||
/>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="6">
|
||||
<IonItem lines="full">
|
||||
<IonLabel position="floating">Amount</IonLabel>
|
||||
<IonInput
|
||||
type="text"
|
||||
inputmode="text"
|
||||
placeholder="0"
|
||||
value={transactionAmount}
|
||||
onIonChange={(e) => setTransactionAmount(e.currentTarget.value)}
|
||||
/>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonItem lines="full">
|
||||
<IonLabel>Deposit?</IonLabel>
|
||||
<IonToggle
|
||||
style={{ '--background-checked': card.color }}
|
||||
slot="end"
|
||||
value={transactionDeposit}
|
||||
onIonChange={(e) => setTransactionDeposit(e.currentTarget.checked)}
|
||||
/>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonButton
|
||||
style={{
|
||||
'--background': card.color,
|
||||
'--background-focused': card.color,
|
||||
'--background-hover': card.color,
|
||||
'--background-activated': card.color,
|
||||
}}
|
||||
expand="block"
|
||||
disabled={adding}
|
||||
onClick={addTransaction}
|
||||
>
|
||||
{!adding && (
|
||||
<>
|
||||
<IonIcon icon={addOutline} />
|
||||
Add Transaction
|
||||
</>
|
||||
)}
|
||||
|
||||
{adding && (
|
||||
<>
|
||||
<IonIcon icon={timerOutline} />
|
||||
Adding...
|
||||
</>
|
||||
)}
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTransaction;
|
116
03_source/mobile/src/pages/DemoBankingUi/pages/Home.jsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { IonButton, IonButtons, IonContent, IonGrid, IonHeader, IonIcon, IonPage, IonTitle, IonToolbar, useIonRouter, useIonViewDidEnter } from '@ionic/react';
|
||||
import styles from './Home.module.css';
|
||||
import { AccountStore } from '../data/AccountStore';
|
||||
import CardSlide from '../components/CardSlide';
|
||||
import { chevronBackOutline, searchOutline } from 'ionicons/icons';
|
||||
|
||||
// Import Swiper React components
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
|
||||
// Import Swiper styles
|
||||
// import 'swiper/swiper.scss';
|
||||
import 'swiper/css';
|
||||
|
||||
import stylesS from './Home.module.scss';
|
||||
|
||||
const Home = () => {
|
||||
const cards = AccountStore.useState((s) => s.cards);
|
||||
const profile = AccountStore.useState((s) => s.profile);
|
||||
|
||||
const [pageTitle, setPageTitle] = useState(cards[0].description);
|
||||
const [mainColor, setMainColor] = useState(cards[0].color);
|
||||
const [slideSpace, setSlideSpace] = useState(10);
|
||||
|
||||
const slidesRef = useRef();
|
||||
|
||||
useIonViewDidEnter(() => {
|
||||
setSlideSpace(0);
|
||||
});
|
||||
|
||||
const changeSlide = async (e) => {
|
||||
const swiper = e;
|
||||
const swiperIndex = swiper.activeIndex;
|
||||
|
||||
setPageTitle(cards[swiperIndex].description);
|
||||
setMainColor(cards[swiperIndex].color);
|
||||
|
||||
document.getElementById(`slide_${swiperIndex}_balance`).classList.add('animate__headShake');
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById(`slide_${swiperIndex}_balance`).classList.remove('animate__headShake');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const manageTouch = async (touched, e) => {
|
||||
const swiper = e;
|
||||
const swiperIndex = swiper.activeIndex;
|
||||
|
||||
if (touched) {
|
||||
document.getElementById(`slide_${swiperIndex}_transactions`).classList.add('animate__fadeOut');
|
||||
} else {
|
||||
document.getElementById(`slide_${swiperIndex}_transactions`).classList.remove('animate__fadeOut');
|
||||
document.getElementById(`slide_${swiperIndex}_transactions`).classList.add('animate__fadeIn');
|
||||
}
|
||||
};
|
||||
|
||||
const router = useIonRouter();
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage className={stylesS.homePage}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
{/* */}
|
||||
<IonButton
|
||||
//
|
||||
routerLink="/demo-banking-ui/account"
|
||||
className={stylesS.toolbarAvatar}
|
||||
>
|
||||
<img alt="toolbar avatar" className={stylesS.toolbarAvatarImage} src={profile.avatar} />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
|
||||
<IonTitle>{pageTitle}</IonTitle>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => handleBackClick()}>
|
||||
<IonIcon icon={chevronBackOutline} color="primary" />
|
||||
</IonButton>
|
||||
{/* */}
|
||||
<IonButton>
|
||||
<IonIcon color="light" icon={searchOutline} style={{ backgroundColor: mainColor, borderRadius: '500px', padding: '0.2rem' }} />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonGrid>
|
||||
<Swiper
|
||||
spaceBetween={slideSpace}
|
||||
ref={slidesRef}
|
||||
slidesPerView={1}
|
||||
className={stylesS.cardsContainer}
|
||||
onTouchStart={(e) => manageTouch(true, e)}
|
||||
onTouchEnd={(e) => manageTouch(false, e)}
|
||||
onSlideChange={(e) => changeSlide(e)}
|
||||
>
|
||||
{cards.map((card, index) => {
|
||||
return (
|
||||
<SwiperSlide key={`slide_${index}`} id={`slide_${index}`} className={stylesS.customSlide}>
|
||||
<CardSlide key={index} card={card} profile={profile} index={index} />
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,38 @@
|
||||
.homePage ion-toolbar {
|
||||
--border-style: none;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.customSlide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.transactionList {
|
||||
overflow: scroll;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.balance {
|
||||
font-weight: 300;
|
||||
font-size: 1.5rem;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.poundSign {
|
||||
font-weight: 800;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.toolbarAvatarImage {
|
||||
border-radius: 500px;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.helloworld {
|
||||
background-color: gold;
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
.homePage ion-toolbar {
|
||||
--border-style: none;
|
||||
--padding-top: 1rem;
|
||||
--padding-bottom: 1rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.customSlide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.transactionList {
|
||||
overflow: scroll;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.balance {
|
||||
font-weight: 300;
|
||||
font-size: 1.5rem;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.poundSign {
|
||||
font-weight: 800;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.toolbarAvatarImage {
|
||||
border-radius: 500px;
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.helloworld {
|
||||
background-color: cyan;
|
||||
}
|
139
03_source/mobile/src/pages/DemoBankingUi/style.scss
Normal file
@@ -0,0 +1,139 @@
|
||||
.demo-banking-ui {
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
* {
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** 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: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 245, 248;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--padding-start: 0;
|
||||
--inner-padding-end: 0;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
ion-item h2 {
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ion-item p {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
ion-item .date {
|
||||
float: right;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
ion-item ion-icon {
|
||||
color: #c9c9ca;
|
||||
}
|
||||
|
||||
ion-item ion-note {
|
||||
font-size: 15px;
|
||||
margin-right: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ion-item ion-note.md {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 50%;
|
||||
align-self: start;
|
||||
margin: 16px 10px 16px 16px;
|
||||
}
|
||||
|
||||
.dot-unread {
|
||||
background: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
ion-footer ion-title {
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
.view-post-footer {
|
||||
|
||||
background-color: white;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
IonBackButton,
|
||||
IonBadge,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonFooter,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonText,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { bookmarkOutline, shareOutline } from 'ionicons/icons';
|
||||
import { useParams } from 'react-router';
|
||||
import { blogPosts } from '../localData';
|
||||
import './BlogPost.css';
|
||||
|
||||
const BlogPost = () => {
|
||||
const { id } = useParams();
|
||||
const post = blogPosts.filter((post) => parseInt(post.id) === parseInt(id))[0];
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Blog</IonTitle>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Blog Posts" />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<img src={post.image} alt="post header" />
|
||||
|
||||
<IonGrid className="ion-padding-start ion-padding-end">
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<img src={post.authorImage} className="post-author-avatar" alt="post author" />
|
||||
<IonCardSubtitle className="ion-no-margin ion-no-padding ion-margin-start">
|
||||
{post.author}
|
||||
</IonCardSubtitle>
|
||||
</IonRow>
|
||||
<IonNote>{post.date}</IonNote>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonCardTitle className="post-title">{post.title}</IonCardTitle>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<IonText color="medium">{post.content}</IonText>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter className="view-post-footer">
|
||||
<IonRow className="post-footer ion-align-self-center ion-justify-content-between">
|
||||
<div>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={shareOutline} />
|
||||
</IonButton>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={bookmarkOutline} />
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IonBadge color="primary" className="post-category">
|
||||
{post.category}
|
||||
</IonBadge>
|
||||
</div>
|
||||
</IonRow>
|
||||
</IonFooter>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogPost;
|
51
03_source/mobile/src/pages/DemoBlogPostUi/AppPages/Home.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { Post } from '../components/Post';
|
||||
import { blogPosts } from '../localData';
|
||||
import { chevronBackOutline } from 'ionicons/icons';
|
||||
|
||||
const Home = () => {
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleBackClick() {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Ionic Blog</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">Ionic Blog</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
{blogPosts.map((post, index) => (
|
||||
<Post post={post} key={`post_${index}`} />
|
||||
))}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
@@ -0,0 +1,37 @@
|
||||
.post-author-avatar {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
border-radius: 500px;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.post-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
width: 100%;
|
||||
border-top: 2px solid rgb(245, 245, 245);
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.post-category {
|
||||
margin-top: 1.1rem;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
IonBadge,
|
||||
IonButton,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCardHeader,
|
||||
IonCardSubtitle,
|
||||
IonCardTitle,
|
||||
IonIcon,
|
||||
IonNote,
|
||||
IonRow,
|
||||
} from '@ionic/react';
|
||||
import { bookmarkOutline, shareOutline } from 'ionicons/icons';
|
||||
|
||||
import './Post.css';
|
||||
|
||||
export const Post = ({ post }) => {
|
||||
return (
|
||||
<IonCard routerLink={`/demo-blog-post-ui/post/${post.id}`}>
|
||||
<img src={post.image} alt="main post" className="post-image" />
|
||||
|
||||
<IonCardHeader>
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<IonRow className="ion-align-items-center ion-justify-content-between">
|
||||
<img src={post.authorImage} className="post-author-avatar" alt="post author" />
|
||||
<IonCardSubtitle className="ion-no-margin ion-no-padding ion-margin-start">
|
||||
{post.author}
|
||||
</IonCardSubtitle>
|
||||
</IonRow>
|
||||
<IonNote>{post.date}</IonNote>
|
||||
</IonRow>
|
||||
<IonCardTitle className="post-title">{post.title}</IonCardTitle>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent>
|
||||
<p className="post-content">{post.content}</p>
|
||||
|
||||
<IonRow className="post-footer ion-align-self-center ion-justify-content-between">
|
||||
<div>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={shareOutline} />
|
||||
</IonButton>
|
||||
<IonButton fill="clear" color="primary">
|
||||
<IonIcon icon={bookmarkOutline} />
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IonBadge color="primary" className="post-category">
|
||||
{post.category}
|
||||
</IonBadge>
|
||||
</div>
|
||||
</IonRow>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
);
|
||||
};
|
27
03_source/mobile/src/pages/DemoBlogPostUi/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
|
||||
|
||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
import Home from './AppPages/Home';
|
||||
import BlogPost from './AppPages/BlogPost';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
function DemoBlogPostUi() {
|
||||
return (
|
||||
<IonRouterOutlet className="demo-blog-post-ui">
|
||||
<Route exact path="/demo-blog-post-ui/home">
|
||||
<Home />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/demo-blog-post-ui/post/:id">
|
||||
<BlogPost />
|
||||
</Route>
|
||||
|
||||
<Redirect exact path="/demo-blog-post-ui" to="/demo-blog-post-ui/home" />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}
|
||||
|
||||
export default DemoBlogPostUi;
|
98
03_source/mobile/src/pages/DemoBlogPostUi/localData/index.js
Normal file
@@ -0,0 +1,98 @@
|
||||
export const blogPosts = [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "How to Convince Your Boss to Choose Ionic",
|
||||
"title_link": "https://ionicframework.com/blog/convince-boss-choose-ionic-app-development/",
|
||||
"date": "August 3, 2021",
|
||||
"author": "By Kim Maida",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/kim-maida-150x150.jpg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/how-to-convince-your-boss_image_1aug2021.png",
|
||||
"content": "Greetings, friend! You’re a web developer, team lead, or engineering manager who has discovered that Ionic products are awesome. They have helped you build cross-platform applications quickly, made the app development process enjoyable, and solved important mobile development problems. You can see that Ionic would be extremely beneficial in your daily job, but are wondering how to convince your boss to endorse the adoption of new software. In a nutshell:"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Ioniconf 2021 Conference Recap",
|
||||
"title_link": "https://ionicframework.com/blog/ioniconf-2021-conference-recap/",
|
||||
"date": "July 29, 2021",
|
||||
"author": "By Mike Hartington",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2018/08/mike-headshot-2-smaller-150x150.png",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/06/og-imgx2.png",
|
||||
"content": "And with that, Ioniconf 2021 has concluded! Ioniconf, our online conference for Ionic developers and the wider web development community, featured twelve expert Ionic speakers and was attended by many thousands of Ionic community members. We’re thrilled by the community’s reception to the event and are already looking forward to our next event taking place in September. Read on for a recap and links to all recorded talks."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Announcing Identity Vault 5.0",
|
||||
"title_link": "https://ionicframework.com/blog/announcing-identity-vault-5-0/",
|
||||
"date": "July 28, 2021",
|
||||
"author": "By Dallas James",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/dallas-james-150x150.jpg",
|
||||
"category": "PRODUCT",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/iv-5-feature-image.png",
|
||||
"content": "Today I’m excited to announce Identity Vault 5.0, the newest version of Ionic’s mobile biometrics solution. Featuring the latest in native security best practices, Identity Vault improves frontend security in any Ionic app by making it easy to add secure biometric authentication in minutes."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Building with Stencil: Clock Component",
|
||||
"title_link": "https://ionicframework.com/blog/building-with-stencil-clock-component/",
|
||||
"date": "July 22, 2021",
|
||||
"author": "By Kevin Hoyt",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/2520666-150x150.jpg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": "https://ionicframework.com/blog//blog/category/announcements",
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/Image-from-iOS.png",
|
||||
"content": "I have not seen a clock in a web-based user interface in a long time. This makes sense — they are pretty redundant these days. You have a clock on your watch, on your mobile device, and on your desktop, and those are just the digital versions available at a glance. Nonetheless, the process of building a clock can reveal a lot about how a platform works."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Building with Stencil: Calendar Component",
|
||||
"title_link": "https://ionicframework.com/blog/building-with-stencil-calendar-component/",
|
||||
"date": "July 19, 2021",
|
||||
"author": "By Kevin Hoyt",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2021/07/2520666-150x150.jpg",
|
||||
"category": "TUTORIALS",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/ionic-blog-post-image_first-look-01.png",
|
||||
"content": "Take a look at the month view of a calendar and you will see several rows of numbers. The numbers themselves, increasing in value one after the other, are arranged in columns. HTML and CSS provide us with a number of tools to display content in rows and columns. Making a calendar component should be easy, right? Right?"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Introducing the New Overlay Hooks for Ionic React",
|
||||
"title_link": "https://ionicframework.com/blog/introducing-the-new-overlay-hooks-for-ionic-react/",
|
||||
"date": "July 14, 2021",
|
||||
"author": "By Ely Lucas",
|
||||
"authorImage": "https://secure.gravatar.com/avatar/45ad19965b4bde97e9f4396ea01ed184?s=32&r=g",
|
||||
"category": "ENGINEERING",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/react-overlay-hooks-feature-image.png",
|
||||
"content": "Hello Friends! We know everyone is excited about the new features in Ionic Framework 6.0 beta, but that doesn’t mean we’re done with V5! In Ionic React 5.6, we packaged up a new set of hooks for controlling our overlay components that we think you might like. What is an overlay you ask? It’s the term we give components that display over your current content, such as alerts, modals, toasts, etc."
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "The Future of Stencil: Expanded Team, New Software Platform, and More",
|
||||
"title_link": "https://ionicframework.com/blog/the-future-of-stencil-expanded-team-new-software-platform-and-more/",
|
||||
"date": "July 7, 2021",
|
||||
"author": "By Nick Hyatt",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2018/11/Nick-Hyatt-Headshot-150x150.jpeg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/07/stencil-future-feature-image.png",
|
||||
"content": "Today I’m excited to share some news about Stencil, Ionic’s open source toolchain that generates small, fast, and 100% standards-based Web Components that run in every browser. As you might have noticed, we’ve been actively increasing our investments across the entire Ionic App Platform, including the recent launch of Capacitor 3.0, Ionic Portals, tons of Appflow improvements, and the upcoming Ionic Framework v6."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"title": "Announcing the Ionic Framework v6 Beta",
|
||||
"title_link": "https://ionicframework.com/blog/announcing-the-ionic-framework-v6-beta/",
|
||||
"date": "June 29, 2021",
|
||||
"author": "By Liam DeBeasi",
|
||||
"authorImage": "https://ionicframework.com/blog/wp-content/uploads/2020/01/ZNK4lRAJ_400x400-150x150.jpg",
|
||||
"category": "ANNOUNCEMENTS",
|
||||
"category_link": null,
|
||||
"image": "https://ionicframework.com/blog/wp-content/uploads/2021/06/framework6-feature-image.png",
|
||||
"content": "Earlier this week I had the privilege of giving the Ionic Framework Update at Ioniconf 2021 where we announced the Ionic Framework v6 beta. Ionic Framework has come far from its roots as an AngularJS-only UI library to a truly cross-platform framework for building Web Native applications. As we look to the future of Ionic Framework, let’s talk about some of the improvements coming in Framework v6 and how you can get access to these improvements today."
|
||||
}
|
||||
];
|
79
03_source/mobile/src/pages/DemoBlogPostUi/style.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
.demo-blog-post-ui {
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
* {
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** 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: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 245, 248;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
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);
|
||||
};
|
||||
|
||||
// const router = useIonRouter();
|
||||
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;
|
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonSearchbar,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { CurrentWeather } from '../components/CurrentWeather';
|
||||
|
||||
function Tab2() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [currentWeather, setCurrentWeather] = useState(false);
|
||||
|
||||
const performSearch = async () => {
|
||||
getAddress(search);
|
||||
};
|
||||
|
||||
const getAddress = async (city) => {
|
||||
const response = await fetch(
|
||||
`https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.current && data.location) {
|
||||
setCurrentWeather(data);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Search</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
|
||||
<IonCol size="7">
|
||||
<IonSearchbar
|
||||
placeholder="Try 'London'"
|
||||
animated
|
||||
value={search}
|
||||
onIonChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="5">
|
||||
<IonButton
|
||||
expand="block"
|
||||
className="ion-margin-start ion-margin-end"
|
||||
onClick={performSearch}
|
||||
>
|
||||
Search
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
|
||||
<div style={{ marginTop: '-0.8rem' }}>
|
||||
{currentWeather ? (
|
||||
<CurrentWeather currentWeather={currentWeather} />
|
||||
) : (
|
||||
<h3 className="ion-text-center">Your search result will appear here</h3>
|
||||
)}
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tab2;
|
@@ -0,0 +1,62 @@
|
||||
import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react';
|
||||
import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => {
|
||||
const [property, setProperty] = useState(false);
|
||||
|
||||
const properties = {
|
||||
wind: {
|
||||
isIcon: false,
|
||||
icon: '/assets/WeatherDemo/wind.png',
|
||||
alt: 'wind',
|
||||
label: 'Wind',
|
||||
value: `${currentWeather.current.wind_mph}mph`,
|
||||
},
|
||||
feelsLike: {
|
||||
isIcon: true,
|
||||
icon: thermometerOutline,
|
||||
alt: 'feels like',
|
||||
label: 'Feels like',
|
||||
value: `${currentWeather.current.feelslike_c}°C`,
|
||||
},
|
||||
indexUV: {
|
||||
isIcon: true,
|
||||
icon: sunnyOutline,
|
||||
alt: 'index uv',
|
||||
label: 'Index UV',
|
||||
value: currentWeather.current.uv,
|
||||
},
|
||||
pressure: {
|
||||
isIcon: true,
|
||||
icon: pulseOutline,
|
||||
alt: 'pressure',
|
||||
label: 'Pressure',
|
||||
value: `${currentWeather.current.pressure_mb} mbar`,
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setProperty(properties[type]);
|
||||
}, [type]);
|
||||
|
||||
return (
|
||||
<IonCol size="6">
|
||||
<IonRow className="ion-justify-content-center ion-align-items-center">
|
||||
<IonCol size="3">
|
||||
{!property.isIcon && (
|
||||
<img alt={property.alt} src={property.icon} height="32" width="32" />
|
||||
)}
|
||||
{property.isIcon && (
|
||||
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
|
||||
)}
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="9">
|
||||
<IonCardSubtitle>{property.label}</IonCardSubtitle>
|
||||
<IonNote>{property.value}</IonNote>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
@@ -0,0 +1,48 @@
|
||||
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
|
||||
import { WeatherProperty } from './WeatherProperty';
|
||||
|
||||
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
|
||||
<IonGrid>
|
||||
<IonCard>
|
||||
<IonCardContent className="ion-text-center">
|
||||
<IonText color="primary">
|
||||
<h1>
|
||||
{currentWeather.location.region},{' '}
|
||||
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
|
||||
</h1>
|
||||
</IonText>
|
||||
|
||||
<div className="ion-margin-top">
|
||||
<img
|
||||
alt="condition"
|
||||
src={currentWeather.current.condition.icon.replace('//', 'https://')}
|
||||
/>
|
||||
|
||||
<IonText color="dark">
|
||||
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
|
||||
</IonText>
|
||||
|
||||
<IonText color="medium">
|
||||
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
|
||||
</IonText>
|
||||
</div>
|
||||
|
||||
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
|
||||
{currentWeather.current.temp_c}℃
|
||||
</IonCardTitle>
|
||||
|
||||
<IonGrid className="ion-margin-top">
|
||||
<IonRow>
|
||||
<WeatherProperty type="wind" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
|
||||
<IonRow className="ion-margin-top">
|
||||
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
|
||||
<WeatherProperty type="pressure" currentWeather={currentWeather} />
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonCardContent>
|
||||
</IonCard>
|
||||
</IonGrid>
|
||||
);
|
@@ -0,0 +1,24 @@
|
||||
.container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container a {
|
||||
text-decoration: none;
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
import './ExploreContainer.css';
|
||||
|
||||
interface ContainerProps { }
|
||||
|
||||
const ExploreContainer: React.FC<ContainerProps> = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<strong>Ready to create an app?</strong>
|
||||
<p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExploreContainer;
|