update,
This commit is contained in:
96
03_source/mobile/src/pages/DemoBankingUi/AppPages/Tab1.jsx
Normal file
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
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
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
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
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
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
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
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
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user