Browse Source

Merge pull request #11 from dancojocaru2000/Frontend

Finished frontend implementation with api
pull/12/head
DariusTFox24 3 years ago committed by GitHub
parent
commit
1c56bf2a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      client/package-lock.json
  2. 1
      client/package.json
  3. 12
      client/src/AccountCard.svelte
  4. 26
      client/src/App.svelte
  5. 59
      client/src/CheckNotifications.svelte
  6. 4
      client/src/CreateAccount.svelte
  7. 2
      client/src/InputField.svelte
  8. 17
      client/src/Login.svelte
  9. 19
      client/src/MainPage.svelte
  10. 4
      client/src/SendMoney.svelte
  11. 50
      client/src/api.js

13
client/package-lock.json generated

@ -23,6 +23,7 @@
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0",
"svelte-keydown": "^0.4.0",
"svelte-preprocess": "^4.9.8"
}
},
@ -1947,6 +1948,12 @@
"node": ">= 8"
}
},
"node_modules/svelte-keydown": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/svelte-keydown/-/svelte-keydown-0.4.0.tgz",
"integrity": "sha512-a0S5m+u78FE1jgpuSCZPtgh/KhwNAO9t8kkcBkK1sK9ZiVC+Iu5XOCqIu1F+PEyenRTIxGaA4yr6KAsYFB6kkQ==",
"dev": true
},
"node_modules/svelte-preprocess": {
"version": "4.9.8",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.9.8.tgz",
@ -3700,6 +3707,12 @@
"integrity": "sha512-4DrCEJoBvdR689efHNSxIQn2pnFwB7E7j2yLEJtHE/P8hxwZWIphCtJ8are7bjl/iVMlcEf5uh5pJ68IwR09vQ==",
"dev": true
},
"svelte-keydown": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/svelte-keydown/-/svelte-keydown-0.4.0.tgz",
"integrity": "sha512-a0S5m+u78FE1jgpuSCZPtgh/KhwNAO9t8kkcBkK1sK9ZiVC+Iu5XOCqIu1F+PEyenRTIxGaA4yr6KAsYFB6kkQ==",
"dev": true
},
"svelte-preprocess": {
"version": "4.9.8",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.9.8.tgz",

1
client/package.json

@ -17,6 +17,7 @@
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0",
"svelte-keydown": "^0.4.0",
"svelte-preprocess": "^4.9.8"
},
"dependencies": {

12
client/src/AccountCard.svelte

@ -21,7 +21,7 @@
export let balance="5425";
export let iban="RONFOX62188921";
export let isExpanded=false;
let transactions=[];
export let transactions=[];
export let accountId;
let copied = false;
@ -55,14 +55,6 @@
});
}
onMount( () => {
gettransactions($token, accountId).then( result => {
if(result.status == "success") {
transactions = result.transactions;
transactions.sort((e1, e2) => new Date(e2.datetime) - new Date(e1.datetime));
}
});
})
</script>
@ -82,7 +74,7 @@
<div>
<DetailField class="p-1 flex-shrink">
<h2 class='font-sans mt-3 mb-2 pl-2 text-4xl text-gray-50'>Balance: <span style="color: #264D59">{balance}</span>{currency}</h2>
<h2 class='font-sans mt-3 mb-2 pl-2 text-4xl text-gray-50'>Balance: <span style="color: #264D59">{amountToString(balance)}</span>{currency}</h2>
</DetailField>
</div>

26
client/src/App.svelte

@ -42,21 +42,39 @@
})
setContext("user", user);
const refreshAccounts = writable(null);
setContext("refreshAccounts", refreshAccounts);
const accounts = readable(null, set => {
const unsubscribe = userToken.subscribe(token => {
if(token == null) {
function getAccounts(token){
if(token==null){
set(null);
}else{
getaccountlist(token)
.then(result =>{
getaccountlist(token)
.then(result => {
set(result);
})
}
}
let token = null;
refreshAccounts.set( () => {
getAccounts(token);
});
const unsubscribe = userToken.subscribe(newToken => {
token = newToken;
getAccounts(token);
})
const intervalId = setInterval(() => {
getAccounts(token);
}, 10000);
return () => {
unsubscribe();
clearInterval(intervalId);
}
})

59
client/src/CheckNotifications.svelte

@ -39,35 +39,44 @@
<div class="w-full max-w-md self-start border-solid border mb-3"></div>
<div class="flex flex-col flex-grow pl-8 pr-10 relative scroller overflow-auto overflow-x-hidden max-h-full min-h-0">
{#each notifications as notification,i (i)}
<div on:click={() => onNotificationClick(notification.id)} in:slide={{delay:100*i}} out:slide={{delay:50*(notifications.length-i)}}>
<DetailField class="relative my-3 py-1 flex-shrink min-w-transaction max-w-4xl">
<div class='font-sans text-gray-50 text-2xl mt-2 mx-4 border-b-1'>
{notification.body}
</div>
{#if notifications.length > 0}
{#each notifications as notification,i (i)}
<div on:click={() => onNotificationClick(notification.id)} in:slide={{delay:100*i}} out:slide={{delay:50*(notifications.length-i)}}>
<DetailField class="relative my-3 py-1 flex-shrink min-w-transaction max-w-4xl">
<div class='font-sans text-gray-50 text-2xl mt-2 mx-4 border-b-1'>
{notification.body}
</div>
<div class="flex flex-row">
<div class='inline font-sans ml-auto mr-4 text-xl text-gray-100 mt-2 mx-6 border-b-1'>
<span> at {new Date(notification.datetime).toLocaleString()} </span>
<div class="flex flex-row">
<div class='inline font-sans ml-auto mr-4 text-xl text-gray-100 mt-2 mx-6 border-b-1'>
<span> at {new Date(notification.datetime).toLocaleString()} </span>
</div>
</div>
</div>
<div class='fixed font-sans text-6xl text-gray-100 left-2 bottom-2'>
{#if !notification.read}
<svg class="fill-current text-gray-100" viewBox="0 0 16 16" width="16" height="16">
<circle cx="8" cy="8" r="8"></circle>
</svg>
{:else}
<svg class="fill-current text-gray-100" viewBox="0 0 24 24" width="16" height="16">
<path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"/>
</svg>
{/if}
<div class='fixed font-sans text-6xl text-gray-100 left-2 bottom-2'>
{#if !notification.read}
<svg class="fill-current text-gray-100" viewBox="0 0 16 16" width="16" height="16">
<circle cx="8" cy="8" r="8"></circle>
</svg>
{:else}
<svg class="fill-current text-gray-100" viewBox="0 0 24 24" width="16" height="16">
<path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"/>
</svg>
{/if}
</div>
</DetailField>
</div>
{/each}
{:else}
<DetailField class="relative my-3 py-1 flex-shrink min-w-transaction max-w-4xl">
<div class='font-sans text-gray-50 text-2xl mt-2 mx-4 border-b-1'>
No notifications.
</div>
</DetailField>
</div>
{/each}
</DetailField>
{/if}
</div>
<div class="m-2"></div>

4
client/src/CreateAccount.svelte

@ -12,6 +12,7 @@
import { getContext } from "svelte";
const token = getContext("token");
const refreshAccounts = getContext("refreshAccounts");
const dispatch = createEventDispatcher();
@ -32,6 +33,9 @@
const result = await createaccount($token, name, currency, type);
if(result.status == "success") {
dispatch("createPopup",{type:"create_acc_success"});
if($refreshAccounts){
$refreshAccounts();
}
}else{
dispatch("createPopup",{type:"create_acc_failed", reason:"Failed to create account. Error:"+result.status});
}

2
client/src/InputField.svelte

@ -9,7 +9,7 @@
</script>
<main>
<input type={isPassword ? "password" : "text"} placeholder={placeholder} value={value} on:input={handleInput} class="placeholder-gray-300 p-3 text-gray-50 w-full text-3xl">
<input on:keydown type={isPassword ? "password" : "text"} placeholder={placeholder} value={value} on:input={handleInput} class="placeholder-gray-300 p-3 text-gray-50 w-full text-3xl">
</main>
<style>

17
client/src/Login.svelte

@ -24,6 +24,19 @@
}
async function enter(event) {
if (event.key === 'Enter') {
const result = await login(username, code);
if(result.status == "success") {
dispatch("loginSuccess",{
token: result.token,
});
}else{
alert(result.code);
}
}
}
</script>
<main class="h-full">
@ -35,11 +48,11 @@
</div>
<div class="m-3 flex-shrink">
<InputField placeholder="Code" isPassword={true} bind:value={code}></InputField>
<InputField on:keydown={enter} placeholder="Code" isPassword={true} bind:value={code}></InputField>
</div>
<div class="m-3 flex-shrink">
<OrangeButton on:click={checkLogin}>Login</OrangeButton>
<OrangeButton on:click={checkLogin}>Login</OrangeButton>
</div>
<div class="flex-grow">

19
client/src/MainPage.svelte

@ -6,8 +6,8 @@
import GreenButton from './GreenButton.svelte';
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { logout, whoami, getaccountlist, getnotificationlist } from './api';
import { amountToString } from './utils';
import { logout, whoami, getaccountlist, getnotificationlist, getForex } from './api';
import { amountToString } from './utils';
const token = getContext("token");
const user = getContext("user");
@ -20,7 +20,7 @@ import { amountToString } from './utils';
$: username = $user.user.username;
$: email = $user.user.email;
$: notifications = $notificationsStore ? $notificationsStore.notifications : [];
let totalbalance = "2455.22";
let totalbalance = "0.00";
let maincurrency = "RON";
let expandedAccount = null;
let showAllAccounts = true;
@ -30,12 +30,21 @@ import { amountToString } from './utils';
return {
name: account.customName ? account.customName : `${account.accountType} Account`,
currency: account.currency,
balance: amountToString(account.balance),
balance: account.balance,
iban: account.iban.replace(/(.{4})/g, "$1 "),
id: account.id,
transactions: account.transactions,
}
}) : [];
$: {
Promise.all(accounts.map(account => {
return getForex(account.currency, maincurrency, account.balance);
})).then( balances => {
const sum = balances.reduce((acc, current) => acc+current, 0);
totalbalance = amountToString(Math.round(sum));
})
}
function dispatchLogout(){
if (confirm("Log out?")) {
@ -92,7 +101,7 @@ import { amountToString } from './utils';
{/if}
<div class="self-center m-0">
{#if showAllAccounts}
{#if showAllAccounts && (accounts.length < 3) }
<div in:slide={{delay:500*accounts.length, duration:250*accounts.length}}>
<GreenButton on:click={createAccount}><Icon icon="akar-icons:plus" color="rgba(249, 250, 251, 1)" width="26" height="26" /></GreenButton>
</div>

4
client/src/SendMoney.svelte

@ -20,6 +20,7 @@
let amount=0.00;
let description="";
const token = getContext("token");
const refreshAccounts = getContext("refreshAccounts");
async function create(){
@ -36,6 +37,9 @@
await createtransaction($token, receiveriban, Math.round(amount*100), account.id, description).then( result => {
if(result.status == "success") {
dispatch("createPopup",{type:"send_money_success"});
if($refreshAccounts){
$refreshAccounts();
}
}
});

50
client/src/api.js

@ -67,7 +67,18 @@ export async function getaccountlist(token) {
},
});
return (await result.json());
const data = await result.json();
if(data.status == "success"){
for(let i=0; i < data.accounts.length; i++){
const transactionsreq = await gettransactions(token, data.accounts[i].id);
if (transactionsreq.status == "success") {
data.accounts[i].transactions = transactionsreq.transactions;
}
}
}
return data;
} catch (error) {
return {
status: "error",
@ -146,7 +157,12 @@ export async function getnotificationlist(token) {
},
});
return (await result.json());
const data = await result.json();
if(data.status == "success") {
data.notifications.sort((e1, e2) => new Date(e2.datetime) - new Date(e1.datetime));
}
return data;
} catch (error) {
return {
status: "error",
@ -233,7 +249,12 @@ export async function gettransactions(token, id) {
},
});
return (await result.json());
const data = await result.json();
if(data.status == "success") {
data.transactions.sort((e1, e2) => new Date(e2.datetime) - new Date(e1.datetime));
}
return data;
} catch (error) {
return {
status: "error",
@ -241,3 +262,26 @@ export async function gettransactions(token, id) {
}
}
}
export async function getForex(from, to, amount) {
try {
const result = await fetch(new URL("/forex/"+from+"/"+to, baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const data = await result.json();
if(data.status == "success") {
const exchanged = amount * data.rate;
return exchanged;
}
return null;
} catch (error) {
return null;
}
}

Loading…
Cancel
Save