diff --git a/03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab1.jsx b/03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab1.jsx
new file mode 100644
index 0000000..54ab2b9
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab1.jsx
@@ -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 (
+
+
+
+ My Weather
+
+
+ getCurrentPosition()}>
+
+
+
+
+
+ handleBackClick()}>
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+
+
+
+ Here's your location based weather
+
+
+
+
+ {currentWeather ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+export default Tab1;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab2.jsx b/03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab2.jsx
new file mode 100644
index 0000000..216544f
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab2.jsx
@@ -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 (
+
+
+
+ Search
+
+
+
+
+
+ Search
+
+
+
+
+
+ setSearch(e.target.value)}
+ />
+
+
+
+
+ Search
+
+
+
+
+
+ {currentWeather ? (
+
+ ) : (
+
Your search result will appear here
+ )}
+
+
+
+ );
+}
+
+export default Tab2;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.jsx b/03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.jsx
new file mode 100644
index 0000000..ef077a6
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.jsx
@@ -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 (
+ <>
+
+
+ balance
+
+ £
+ {formatBalance(card.balance)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Transactions
+
+
+
+ {card.transactions.length > 0 && (
+
+
+
+ {card.transactions.length > 0 &&
+ card.transactions
+ .slice(0)
+ .reverse()
+ .map((transaction, index) => )}
+
+
+
+ )}
+
+ {card.transactions.length === 0 && (
+
+
+ No transactions found
+
+
+ Transfer funds
+
+
+
+ )}
+ >
+ );
+};
+
+export default CardSlide;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.module.css b/03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.module.css
new file mode 100644
index 0000000..7ef5587
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.module.css
@@ -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;
+}
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/WeatherProperty.tsx b/03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/WeatherProperty.tsx
new file mode 100644
index 0000000..52949af
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/WeatherProperty.tsx
@@ -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 (
+
+
+
+ {!property.isIcon && (
+
+ )}
+ {property.isIcon && (
+
+ )}
+
+
+
+ {property.label}
+ {property.value}
+
+
+
+ );
+};
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/index.tsx b/03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/index.tsx
new file mode 100644
index 0000000..ceb4332
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/index.tsx
@@ -0,0 +1,48 @@
+import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
+import { WeatherProperty } from './WeatherProperty';
+
+export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
+
+
+
+
+
+ {currentWeather.location.region},{' '}
+ {currentWeather.location.country}
+
+
+
+
+

+
+
+ {currentWeather.current.condition.text}
+
+
+
+ {new Date(currentWeather.location.localtime).toDateString()}
+
+
+
+
+ {currentWeather.current.temp_c}℃
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.jsx b/03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.jsx
new file mode 100644
index 0000000..4ea47c6
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.jsx
@@ -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 (
+
+
+

+

+

+
**** **** **** {lastFourCardNumbers}
+
+
Card holder
+
{`${profile.firstname} ${profile.surname}`}
+
+
+
+
+
+
+
+
+
+

+

+
+
+
+ );
+};
+
+export default DebitCard;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.module.css b/03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.module.css
new file mode 100644
index 0000000..b2064a5
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.module.css
@@ -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);
+}
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/SkeletonDashboard/index.tsx b/03_source/mobile/src/pages/DemoBankingUi/components/SkeletonDashboard/index.tsx
new file mode 100644
index 0000000..234fb9b
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/SkeletonDashboard/index.tsx
@@ -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 = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wind
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Feels like
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Index UV
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pressure
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.jsx b/03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.jsx
new file mode 100644
index 0000000..9813971
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.jsx
@@ -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 (
+
+
+
+
+ {getContactNameInitials(name)}
+
+
+
+
+ {name}
+
+
+
+
+ {deposit ? '+' : '-'}
+ £{formatBalance(amount)}
+
+
+
+
+ );
+};
+
+export default TransactionItem;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.module.css b/03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.module.css
new file mode 100644
index 0000000..68649fd
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.module.css
@@ -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;
+}
diff --git a/03_source/mobile/src/pages/DemoBankingUi/data/AccountStore.js b/03_source/mobile/src/pages/DemoBankingUi/data/AccountStore.js
new file mode 100644
index 0000000..e7df019
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/data/AccountStore.js
@@ -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) });
+// }
diff --git a/03_source/mobile/src/pages/DemoBankingUi/data/CardStore.js b/03_source/mobile/src/pages/DemoBankingUi/data/CardStore.js
new file mode 100644
index 0000000..a50176d
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/data/CardStore.js
@@ -0,0 +1,6 @@
+import { Store } from 'pullstate';
+
+export const CardStore = new Store({
+ card_colors: ['orange', 'black', 'blue', 'purple'],
+ card_types: ['visa', 'mastercard'],
+});
diff --git a/03_source/mobile/src/pages/DemoBankingUi/data/Utils.js b/03_source/mobile/src/pages/DemoBankingUi/data/Utils.js
new file mode 100644
index 0000000..c943a9e
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/data/Utils.js
@@ -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);
+};
diff --git a/03_source/mobile/src/pages/DemoBankingUi/index.tsx b/03_source/mobile/src/pages/DemoBankingUi/index.tsx
new file mode 100644
index 0000000..31506ae
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/index.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default DemoBankingUi;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/Account.jsx b/03_source/mobile/src/pages/DemoBankingUi/pages/Account.jsx
new file mode 100644
index 0000000..3ee3118
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/Account.jsx
@@ -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 (
+
+
+
+
+
+
+
+ Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`${profile.firstname} ${profile.surname}`}
+ {cards.length} current cards
+
+
+
+
+
+
+
+ Add Card
+
+
+
+
+
+ {cards.map((card, index) => {
+ return (
+
+
+
+
+
+
+ {card.description}
+
+
+
+ £{formatBalance(card.balance)}
+
+
+
+
+ );
+ })}
+
+
+
+
+ );
+};
+
+export default Account;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/Account.module.css b/03_source/mobile/src/pages/DemoBankingUi/pages/Account.module.css
new file mode 100644
index 0000000..8e59143
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/Account.module.css
@@ -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;
+}
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/AddCard.jsx b/03_source/mobile/src/pages/DemoBankingUi/pages/AddCard.jsx
new file mode 100644
index 0000000..db360e8
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/AddCard.jsx
@@ -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 (
+
+
+
+
+
+
+
+ Add Card
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Card Type
+ setCardType(e.currentTarget.value)}>
+ {cardTypes.map((option, index) => {
+ return (
+
+ {option.toUpperCase()}
+
+ );
+ })}
+
+
+
+
+
+
+ Card Color
+ setCardColor(e.currentTarget.value)}>
+ {cardColors.map((option, index) => {
+ return (
+
+ {option.toUpperCase()}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+ Card Name
+ setCardDescription(e.currentTarget.value)}
+ />
+
+
+
+
+
+ Starting Balance
+ setCardBalance(e.currentTarget.value)} />
+
+
+
+
+
+
+
+ Card Number
+ setCardNumber(e.currentTarget.value)}
+ />
+
+
+
+
+
+
+
+ Card Expiry
+ setCardExpiry(e.currentTarget.value)} />
+
+
+
+
+
+ Card Secret
+ setCardSecret(e.currentTarget.value)} />
+
+
+
+
+
+
+
+ {!adding && (
+ <>
+
+ Add Card
+ >
+ )}
+
+ {adding && (
+ <>
+
+ Adding...
+ >
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default AddCard;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/AddTransaction.jsx b/03_source/mobile/src/pages/DemoBankingUi/pages/AddTransaction.jsx
new file mode 100644
index 0000000..8461455
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/AddTransaction.jsx
@@ -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 (
+
+
+
+
+
+
+
+ Add Transaction
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ setTransactionName(e.currentTarget.value)}
+ />
+
+
+
+
+
+ Amount
+ setTransactionAmount(e.currentTarget.value)}
+ />
+
+
+
+
+
+
+
+ Deposit?
+ setTransactionDeposit(e.currentTarget.checked)}
+ />
+
+
+
+
+
+
+
+ {!adding && (
+ <>
+
+ Add Transaction
+ >
+ )}
+
+ {adding && (
+ <>
+
+ Adding...
+ >
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default AddTransaction;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/Home.jsx b/03_source/mobile/src/pages/DemoBankingUi/pages/Home.jsx
new file mode 100644
index 0000000..98a3a43
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/Home.jsx
@@ -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 (
+
+
+
+
+ {/* */}
+
+
+
+
+
+ {pageTitle}
+
+
+ handleBackClick()}>
+
+
+ {/* */}
+
+
+
+
+
+
+
+
+
+ manageTouch(true, e)}
+ onTouchEnd={(e) => manageTouch(false, e)}
+ onSlideChange={(e) => changeSlide(e)}
+ >
+ {cards.map((card, index) => {
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.css b/03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.css
new file mode 100644
index 0000000..5d5719c
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.css
@@ -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;
+}
diff --git a/03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.scss b/03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.scss
new file mode 100644
index 0000000..0065249
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.scss
@@ -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;
+}
diff --git a/03_source/mobile/src/pages/DemoBankingUi/style.scss b/03_source/mobile/src/pages/DemoBankingUi/style.scss
new file mode 100644
index 0000000..378cae3
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoBankingUi/style.scss
@@ -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;
+ }
+}