From d3ef280b202b0e30b11dac6c7e1679e95af1d082 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Thu, 5 Jun 2025 13:51:25 +0800 Subject: [PATCH] update, --- .../src/pages/DemoBankingUi/AppPages/Tab1.jsx | 96 ++++++++ .../src/pages/DemoBankingUi/AppPages/Tab2.jsx | 81 +++++++ .../DemoBankingUi/components/CardSlide.jsx | 75 +++++++ .../components/CardSlide.module.css | 51 +++++ .../CurrentWeather/WeatherProperty.tsx | 62 ++++++ .../components/CurrentWeather/index.tsx | 48 ++++ .../DemoBankingUi/components/DebitCard.jsx | 48 ++++ .../components/DebitCard.module.css | 188 ++++++++++++++++ .../components/SkeletonDashboard/index.tsx | 117 ++++++++++ .../components/TransactionItem.jsx | 56 +++++ .../components/TransactionItem.module.css | 48 ++++ .../pages/DemoBankingUi/data/AccountStore.js | 119 ++++++++++ .../src/pages/DemoBankingUi/data/CardStore.js | 6 + .../src/pages/DemoBankingUi/data/Utils.js | 9 + .../mobile/src/pages/DemoBankingUi/index.tsx | 42 ++++ .../src/pages/DemoBankingUi/pages/Account.jsx | 95 ++++++++ .../DemoBankingUi/pages/Account.module.css | 54 +++++ .../src/pages/DemoBankingUi/pages/AddCard.jsx | 209 ++++++++++++++++++ .../DemoBankingUi/pages/AddTransaction.jsx | 165 ++++++++++++++ .../src/pages/DemoBankingUi/pages/Home.jsx | 116 ++++++++++ .../pages/DemoBankingUi/pages/Home.module.css | 38 ++++ .../DemoBankingUi/pages/Home.module.scss | 38 ++++ .../mobile/src/pages/DemoBankingUi/style.scss | 139 ++++++++++++ 23 files changed, 1900 insertions(+) create mode 100644 03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab1.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab2.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/CardSlide.module.css create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/WeatherProperty.tsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/CurrentWeather/index.tsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/DebitCard.module.css create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/SkeletonDashboard/index.tsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/components/TransactionItem.module.css create mode 100644 03_source/mobile/src/pages/DemoBankingUi/data/AccountStore.js create mode 100644 03_source/mobile/src/pages/DemoBankingUi/data/CardStore.js create mode 100644 03_source/mobile/src/pages/DemoBankingUi/data/Utils.js create mode 100644 03_source/mobile/src/pages/DemoBankingUi/index.tsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/Account.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/Account.module.css create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/AddCard.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/AddTransaction.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/Home.jsx create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.css create mode 100644 03_source/mobile/src/pages/DemoBankingUi/pages/Home.module.scss create mode 100644 03_source/mobile/src/pages/DemoBankingUi/style.scss 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.alt} + )} + {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} +

+
+ +
+ condition + + +

{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 ( +
+
+ 1 + 1 + 2 +

**** **** **** {lastFourCardNumbers}

+
+ Card holder +

{`${profile.firstname} ${profile.surname}`}

+
+
+ Expires +

{expiry}

+
+
+ +
+
+
+
+

{secret}

+
+ + 3 + 5 +
+
+
+ ); +}; + +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 + + + + 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 + + + + + + + + + + + + + + account avatar + + + + + +
{`${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 ( + + + + + {/* */} + + toolbar avatar + + + + {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; + } +}