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