1
0
Fork 0
mirror of https://github.com/dancojocaru2000/foxbank.git synced 2025-06-19 11:02:28 +03:00

Compare commits

..

No commits in common. "1c56bf2a604306681c5037daa93fcd527a31bba7" and "c7c8f3c765c44bd47c779c5206bea9fefe17cad0" have entirely different histories.

14 changed files with 151 additions and 2868 deletions

View file

@ -1,15 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost:5000",
"webRoot": "${workspaceFolder}/public"
}
]
}

2257
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,6 @@
"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": {

View file

@ -5,24 +5,20 @@
import CardBG from "./CardBG.svelte";
import DetailField from './DetailField.svelte';
import GreenButton from './GreenButton.svelte';
import {createEventDispatcher, onMount, getContext} from 'svelte';
import {createEventDispatcher} from 'svelte';
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { gettransactions } from './api';
import { amountToString } from './utils';
const dispatch = createEventDispatcher();
const token = getContext("token");
export let name="RON Account";
export let type="RON Account";
export let currency="RON";
export let balance="5425";
export let iban="RONFOX62188921";
export let isExpanded=false;
export let transactions=[];
export let accountId;
let copied = false;
@ -46,22 +42,20 @@
dispatch("createPopup",{
type: 'send_money',
account: {
id: accountId,
type: name,
type,
currency,
balance,
iban,
}
});
}
</script>
<CardBG class="flex-shrink flex flex-col items-stretch md:self-start mt-16 mb-6 px-6 pt-6 pb-0 max-h-full overflow-clip min-h-0">
<div class="flex flex-col flex-shrink">
<div class='font-sans mt-2 mx-2 border-b-1'>
<h3 class="text-gray-50 inline mr-4">{name}</h3>
<h3 class="text-gray-50 inline mr-4">{type}</h3>
<span class="text-gray-100">IBAN: {iban}</span>
<button on:click={copyIban} class="inline {copied ? "cursor-default" : ""}"> <Icon icon={copied ? "akar-icons:check" : "akar-icons:copy"} color="rgba(249, 250, 251, 1)"/></button>
</div>
@ -74,7 +68,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">{amountToString(balance)}</span>{currency}</h2>
<h2 class='font-sans mt-3 mb-2 pl-2 text-4xl text-gray-50'>Balance: <span style="color: #264D59">{balance}</span>{currency}</h2>
</DetailField>
</div>
@ -86,27 +80,18 @@
<DetailField class="my-3 py-1 flex-shrink min-w-transaction">
<div class='font-sans text-gray-50 mt-2 mx-4 border-b-1'>
{#if transaction.transactionType == "send_transfer"}
<h3 class="inline mr-3">Sent money: </h3>
{:else if transaction.transactionType == "receive_transfer"}
<h3 class="inline mr-3">Received money: </h3>
{:else if transaction.transactionType == "card_payment"}
<h3 class="inline mr-3">{transaction.otherParty.store}: </h3>
{:else if transaction.transactionType == "fee"}
<h3 class="inline mr-3">Fees: </h3>
{/if}
<span class="text-4xl {transaction.transactionType == "receive_transfer" ? "text-lime-c" : "text-red-c"}">{amountToString(transaction.extra.amount)}</span>
<span class="text-4xl">{transaction.extra.currency}</span>
<h3 class="inline mr-3">{transaction.title}: </h3>
<span class="text-4xl {transaction.type == "send" ? "text-red-c" : "text-lime-c"}">{transaction.amount}</span>
<span class="text-4xl">{currency}</span>
</div>
<div class='font-sans text-2xl text-gray-100 mt-2 mx-6 border-b-1'>
<p class="inline">at {new Date(transaction.datetime).toLocaleString()} </p>
<p class="inline">at {transaction.time} </p>
{#if transaction.status == "processed"}
{#if transaction.status == "PROCESSED"}
<span>
<Icon class="inline mb-1" icon="akar-icons:circle-check" color="#6DE25ACC"/>
</span>
{:else if transaction.status == "pending"}
{:else if transaction.status == "PENDING"}
<span>
<Icon class="inline mb-1" icon="akar-icons:arrow-cycle" color="#F6AF43"/>
</span>
@ -129,27 +114,18 @@
{:else if transactions.length > 0}
<DetailField class="my-3 py-2 flex-shrink min-w-transaction">
<div class='font-sans text-gray-50 mt-2 mx-4 border-b-1'>
{#if transactions[0].transactionType == "send_transfer"}
<h3 class="inline mr-3">Sent money: </h3>
{:else if transactions[0].transactionType == "receive_transfer"}
<h3 class="inline mr-3">Received money: </h3>
{:else if transactions[0].transactionType == "card_payment"}
<h3 class="inline mr-3">{transactions[0].otherParty.store}: </h3>
{:else if transactions[0].transactionType == "fee"}
<h3 class="inline mr-3">Fees: </h3>
{/if}
<span class="text-4xl {transactions[0].transactionType == "receive_transfer" ? "text-lime-c" : "text-red-c"}">{amountToString(transactions[0].extra.amount)}</span>
<span class="text-4xl">{transactions[0].extra.currency}</span>
<h3 class="inline mr-3">{transactions[0].title}: </h3>
<span class="text-4xl {transactions[0].type == "send" ? "text-red-c" : "text-lime-c"}">{transactions[0].amount}</span>
<span class="text-4xl">{currency}</span>
</div>
<div class='font-sans text-2xl text-gray-100 mt-2 mx-6 border-b-1'>
<p class="inline">at {new Date(transactions[0].datetime).toLocaleString()} </p>
<p class="inline">at {transactions[0].time} </p>
{#if transactions[0].status == "processed"}
{#if transactions[0].status == "PROCESSED"}
<span>
<Icon class="inline mb-1" icon="akar-icons:circle-check" color="#6DE25ACC"/>
</span>
{:else if transactions[0].status == "pending"}
{:else if transactions[0].status == "PENDING"}
<span>
<Icon class="inline mb-1" icon="akar-icons:arrow-cycle" color="#F6AF43"/>
</span>

View file

@ -1,7 +1,6 @@
<script>
import { onMount, setContext } from "svelte";
import { writable, readable } from "svelte/store";
import { whoami, createnotification, getaccountlist, getnotificationlist } from "./api";
import { onMount } from "svelte";
import { whoami } from "./api";
import BottomBorder from "./BottomBorder.svelte";
import CheckNotifications from "./CheckNotifications.svelte";
@ -13,115 +12,16 @@
import SendMoney from "./SendMoney.svelte";
import TopBorder from "./TopBorder.svelte";
const userToken = writable(sessionStorage.getItem("token"));
userToken.subscribe(newToken => {
sessionStorage.setItem("token", newToken);
})
setContext("token", userToken);
const user = readable(null, set => {
const unsubscribe = userToken.subscribe(token => {
if(token == null) {
set(null);
}else{
whoami(token)
.then(result =>{
if(result.status != "success") {
$userToken = null;
}else {
set(result);
}
})
}
})
return () => {
unsubscribe();
}
})
setContext("user", user);
const refreshAccounts = writable(null);
setContext("refreshAccounts", refreshAccounts);
const accounts = readable(null, set => {
function getAccounts(token){
if(token==null){
set(null);
}else{
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);
}
})
setContext("accounts", accounts);
const refreshNotifications = writable(null);
setContext("refreshNotifications", refreshNotifications);
const notifications = readable(null, set=> {
function getNotifications(token){
if(token==null){
set(null);
}else{
getnotificationlist(token)
.then(result => {
set(result);
})
}
}
let token = null;
refreshNotifications.set( () => {
getNotifications(token);
});
const unsubscribe = userToken.subscribe(newToken => {
token = newToken;
getNotifications(token);
})
const intervalId = setInterval(() => {
getNotifications(token);
}, 10000);
return () => {
unsubscribe();
clearInterval(intervalId);
}
})
setContext("notifications", notifications);
let loggedin = false;
function toggleLoggedIn() {
loggedin = !loggedin;
}
let isCreatingAccount = false;
let isCheckingNotifications = false;
let isSendingMoney = false;
let sendingAccount = "";
let notifications = [];
function onCreatePopup(event) {
const eventType = event.detail.type;
@ -134,18 +34,12 @@
var today = new Date();
var date = today.getDate()+'/'+(today.getMonth()+1)+'/'+today.getFullYear();
var time = today.getHours() + ":" + today.getMinutes();
var body = "The "+ eventType.currency + " account with the name " + eventType.type + " was created succesfully!";
//add notification about created account
createnotification(async function() {
const result = await createnotification($userToken, body, date+time);
if(result.status == "success") {
console.log("Succesfully created notification.");
}else{
console.log("Failed to create notification.");
}
})
notifications.push(
{
text: "The new account '" + event.detail.type+ "' was created successfully!",
time: time + " " + date,
});
isCreatingAccount = false;
break;
@ -154,7 +48,7 @@
break;
case "create_acc_failed":
// isCreatingAccount = false;
isCreatingAccount = false;
alert(`Account creation failed! Reason: ${event.detail.reason}`);
break;
@ -172,6 +66,7 @@
break;
case "check_notifications":
notifications = event.detail.notifications;
isCheckingNotifications = true;
break;
@ -184,13 +79,26 @@
}
}
onMount(async function() {
const token = sessionStorage.getItem("token");
if(token == null){
loggedin = false;
}else {
const result = await whoami(token);
if (result.status == "success") {
loggedin = true;
}else {
loggedin = false;
}
}
})
</script>
<main class="flex flex-col items-stretch bg-banner bg-cover bg-center bg-fixed h-screen font-sans">
<TopBorder class="flex-shrink"></TopBorder>
<div class="flex-grow max-h-full overflow-hidden">
{#if $user}
{#if loggedin}
{#if isCreatingAccount}
<Overlay>
@ -201,7 +109,7 @@
{:else if isCheckingNotifications}
<Overlay>
<div class="flex items-center justify-center h-full">
<CheckNotifications on:createPopup={onCreatePopup}></CheckNotifications>
<CheckNotifications on:createPopup={onCreatePopup} notifications={notifications}></CheckNotifications>
</div>
</Overlay>
{:else if isSendingMoney}
@ -212,10 +120,10 @@
</Overlay>
{/if}
<MainPage on:createPopup={onCreatePopup} on:logOut={()=>$userToken=null}></MainPage>
<MainPage on:createPopup={onCreatePopup} on:logOut={toggleLoggedIn}></MainPage>
{:else}
<Login on:loginSuccess={(e)=> $userToken = e.detail.token}></Login>
<Login on:loginSuccess={toggleLoggedIn}></Login>
{/if}

View file

@ -1,32 +1,21 @@
<script>
import CardBG from "./CardBG.svelte";
import {createEventDispatcher, getContext} from 'svelte';
import {createEventDispatcher} from 'svelte';
import Icon from "@iconify/svelte";
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import DetailField from "./DetailField.svelte";
import { marknotificationread } from "./api";
const dispatch = createEventDispatcher();
const notificationsStore = getContext("notifications");
const refreshNotifications = getContext("refreshNotifications");
const token = getContext("token");
$: notifications = $notificationsStore ? $notificationsStore.notifications : [];
export let notifications = [{time: "15:38 27/11/2021", text: "A notification's text."}];
function cancelCheckNotifications(){
dispatch("createPopup",{type:"check_notifications_cancelled"});
}
async function onNotificationClick(id){
await marknotificationread($token, id);
if($refreshNotifications){
$refreshNotifications();
}
}
</script>
<div class="h-full self-center">
@ -39,44 +28,23 @@
<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">
{#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>
</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>
</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>
{/if}
{#each notifications as notification,i (i)}
<div in:slide={{delay:100*i}} out:slide={{delay:50*(notifications.length-i)}}>
<DetailField class="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.text}
</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 {notification.time} </span>
</div>
</div>
</DetailField>
</div>
{/each}
</div>
<div class="m-2"></div>

View file

@ -3,42 +3,31 @@
import CardBG from "./CardBG.svelte";
import InputField from "./InputField.svelte";
import {createEventDispatcher, onMount} from 'svelte';
import {createEventDispatcher} from 'svelte';
import Icon from "@iconify/svelte";
import Overlay from "./Overlay.svelte";
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { createaccount, getcurrencies, getaccounttypes } from "./api";
import { getContext } from "svelte";
const token = getContext("token");
const refreshAccounts = getContext("refreshAccounts");
const dispatch = createEventDispatcher();
let type = null;
let name = "";
let currency = null;
let type = "";
let currencies = ["RON", "EUR"];
let currency = currencies[0];
let termsAccepted = false;
$: placeholder = type==null ? "Checking Account" : `${type} Account`;
async function create(){
if(type == null){
alert("Type is not selected!");
}else if(currency == null){
alert("Currency is not selected!");
function create(){
if(type == "" || type == null) {
alert("Account Name field can not be empty!");
console.debug(`account name: ${type}`)
}else if(!currencies.includes(currency)){
alert("Currency is not supported!");
}else if (!termsAccepted){
alert("Terms of Service not accepted!");
}else{
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});
}
//TODO Create account with provided details on the server
dispatch("createPopup",{type:"create_acc_success", account:{type:type, currency:currency, transactions:[]}});
}
}
@ -54,11 +43,6 @@
termsAccepted = !termsAccepted;
}
onMount(() => {
getcurrencies().then(result => currency = result.currencies[0]);
getaccounttypes().then(result => type = result.accountTypes[0]);
})
</script>
@ -74,31 +58,12 @@
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Account name:</h2>
<InputField placeholder={placeholder} isPassword={false} bind:value={name}></InputField>
</div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Type:</h2>
<select bind:value={type}>
{#await getaccounttypes() then result}
{#each result.accountTypes as option}
<option class="custom-option" value={option}>{option}</option>
{/each}
{/await}
</select>
<InputField placeholder="New Account" isPassword={false} bind:value={type}></InputField>
</div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Currency:</h2>
<select bind:value={currency}>
{#await getcurrencies() then result}
{#each result.currencies as option}
<option class="custom-option" value={option}>{option}</option>
{/each}
{/await}
</select>
<InputField placeholder="RON" isPassword={false} bind:value={currency}></InputField>
</div>
<div class="mx-1 flex-shrink max-w-2xl">
@ -120,28 +85,3 @@
</div>
</div>
<style>
select{
min-width: 120px;
min-height: 32px;
color: rgba(233, 231, 231, 0.842);
background: linear-gradient(92.55deg, rgba(76, 172, 135, 0.95) -28.27%, rgba(249, 224, 127, 0.096) 115.79%);
filter: drop-shadow(0px 8px 4px rgba(0, 0, 0, 0.25));
border-radius: 3px;
}
select option{
min-width: 120px;
min-height: 32px;
color: rgba(233, 231, 231, 0.842);
background: linear-gradient(92.55deg, rgba(76, 172, 135, 0.95) -28.27%, rgba(249, 224, 127, 0.096) 115.79%);
filter: drop-shadow(0px 8px 4px rgba(0, 0, 0, 0.25));
border-radius: 3px;
}
</style>

View file

@ -9,7 +9,7 @@
</script>
<main>
<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">
<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">
</main>
<style>

View file

@ -15,28 +15,14 @@
async function checkLogin(){
const result = await login(username, code);
if(result.status == "success") {
dispatch("loginSuccess",{
token: result.token,
});
sessionStorage.setItem("token", result.token);
dispatch("loginSuccess",null);
}else{
alert(result.code);
}
}
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">
@ -48,11 +34,11 @@
</div>
<div class="m-3 flex-shrink">
<InputField on:keydown={enter} placeholder="Code" isPassword={true} bind:value={code}></InputField>
<InputField 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">

View file

@ -1,58 +1,66 @@
<script>
import Icon from '@iconify/svelte';
import CardBG from "./CardBG.svelte";
import {createEventDispatcher, onMount, getContext} from 'svelte';
import {createEventDispatcher, onMount} from 'svelte';
import AccountCard from './AccountCard.svelte';
import GreenButton from './GreenButton.svelte';
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { logout, whoami, getaccountlist, getnotificationlist, getForex } from './api';
import { amountToString } from './utils';
import { logout, whoami } from './api';
const token = getContext("token");
const user = getContext("user");
const accountsStore = getContext("accounts");
const notificationsStore = getContext("notifications");
const dispatch = createEventDispatcher();
$: fullname = $user.user.fullname;
$: username = $user.user.username;
$: email = $user.user.email;
$: notifications = $notificationsStore ? $notificationsStore.notifications : [];
let totalbalance = "0.00";
let fullname = "";
let username = "";
let email = "";
let code = "";
let totalbalance = "2455.22";
let maincurrency = "RON";
let expandedAccount = null;
let showAllAccounts = true;
$: notifications_unread = notifications.filter(n => !n.read).length;
let notifications = [
{time: "15:38 27/11/2021", text: "A notification's text."},
{time: "15:38 27/11/2021", text: "A notification's text but longer aaaaaaaaaaaa asddagagfabsdhubaiufbau bdauhsbabsdbayub badysabdyba ybbdbasbd bbdabsdb aybdbaysbdya bybdabs bdabsdbadbua."},
{time: "15:38 27/11/2021", text: "A notification's text but way longer absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd absdb aybdbaysbdya bybdabs bdabsd."},
{time: "15:38 27/11/2021", text: "A notification's text."},
{time: "15:38 27/11/2021", text: "A notification's text."},
];
$: accounts = $accountsStore ? $accountsStore.accounts.map(account => {
return {
name: account.customName ? account.customName : `${account.accountType} Account`,
currency: account.currency,
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));
})
}
let accounts = [
{type:"RON Account", currency:"RON", balance:"420.42", iban:"RONFOX62188921",
transactions: [
{title:"Transaction Name#1", status:"PROCESSED", amount:"-45.09", time:"15:38 27/11/2021", type:"send"},
{title:"Transaction Name#2", status:"PENDING", amount:"+25.00", time:"15:38 27/11/2021", type:"received"},
{title:"Transaction Name#3", status:"CANCELLED", amount:"-469.09", time:"15:38 27/11/2021", type:"send"},
{title:"Transaction Name#1", status:"PROCESSED", amount:"-45.09", time:"15:38 27/11/2021", type:"send"},
{title:"Transaction Name#2", status:"PENDING", amount:"+25.00", time:"15:38 27/11/2021", type:"received"},
{title:"Transaction Name#3", status:"CANCELLED", amount:"-469.09", time:"15:38 27/11/2021", type:"send"},
{title:"Transaction Name#1", status:"PROCESSED", amount:"-45.09", time:"15:38 27/11/2021", type:"send"},
{title:"Transaction Name#2", status:"PENDING", amount:"+25.00", time:"15:38 27/11/2021", type:"received"},
{title:"Transaction Name#3", status:"CANCELLED", amount:"-469.09", time:"15:38 27/11/2021", type:"send"},
]
},
{type:"EUR Account", currency:"EUR", balance:"620,42", iban:"EURFOX62188921",
transactions: [
{title:"Transaction Name#2", status:"PENDING", amount:"+25.00", time:"15:38 27/11/2021", type:"received"},
{title:"Transaction Name#1", status:"PROCESSED", amount:"-45.09", time:"15:38 27/11/2021", type:"send"},
{title:"Transaction Name#3", status:"CANCELLED", amount:"-469.09", time:"15:38 27/11/2021", type:"send"},
]
},
];
function dispatchLogout(){
//todo: CHeck here
if (confirm("Log out?")) {
logout($token);
logout(sessionStorage.getItem("token"));
sessionStorage.removeItem("token");
dispatch("logOut",null);
}
}
function expanded(index) {
if (!expandedAccount && expandedAccount !== 0) {
expandedAccount = index;
@ -84,24 +92,34 @@
});
}
onMount( async function() {
const token = sessionStorage.getItem("token");
const result = await whoami(token);
if(result.status == "success") {
fullname = result.user.fullname;
email = result.user.email;
username = result.user.username;
}
})
</script>
<main class="h-full flex flex-col items-stretch md:flex-row">
<div class="flex flex-col items-stretch max-h-full">
{#if expandedAccount || expandedAccount === 0}
<AccountCard accountId={accounts[expandedAccount].id} name={accounts[expandedAccount].name} currency={accounts[expandedAccount].currency} balance={accounts[expandedAccount].balance} iban={accounts[expandedAccount].iban} transactions={accounts[expandedAccount].transactions} isExpanded={true} on:expanded={() => expanded(null)}></AccountCard>
<AccountCard type={accounts[expandedAccount].type} currency={accounts[expandedAccount].currency} balance={accounts[expandedAccount].balance} iban={accounts[expandedAccount].iban} transactions={accounts[expandedAccount].transactions} isExpanded={true} on:expanded={() => expanded(null)}></AccountCard>
{:else}
{#if showAllAccounts}
{#each accounts as account,i}
<div in:slide={{delay:500*i, duration:250*(i==0 ? 1 : i) }}>
<AccountCard accountId={account.id} name={account.name} currency={account.currency} balance={account.balance} iban={account.iban} transactions={account.transactions} isExpanded={false} on:expanded={() => expanded(i)} on:createPopup></AccountCard>
<AccountCard type={account.type} currency={account.currency} balance={account.balance} iban={account.iban} transactions={account.transactions} isExpanded={false} on:expanded={() => expanded(i)} on:createPopup></AccountCard>
</div>
{/each}
{/if}
{/if}
<div class="self-center m-0">
{#if showAllAccounts && (accounts.length < 3) }
{#if showAllAccounts}
<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>
@ -116,7 +134,7 @@
<CardBG class="flex-shrink flex flex-col min-w-transaction items-stretch md:self-start p-6">
<div class="flex flex-row">
<h1 class='font-sans flex-grow text-5xl text-gray-50 m-6 border-b-2'>{fullname}</h1>
<button on:click={checkNotifications} style=" filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));"> <Icon icon={notifications_unread==0 ? "akar-icons:envelope" : "akar-icons:open-envelope"} color="#FB6666" width="36" height="36" /></button>
<button on:click={checkNotifications} style=" filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));"> <Icon icon="akar-icons:envelope" color="#FB6666" width="36" height="36" /></button>
</div>
<div class="m-3 flex-shrink">

View file

@ -3,46 +3,37 @@
import CardBG from "./CardBG.svelte";
import InputField from "./InputField.svelte";
import {createEventDispatcher, getContext} from 'svelte';
import {createEventDispatcher} from 'svelte';
import Icon from "@iconify/svelte";
import Overlay from "./Overlay.svelte";
import TextareaField from "./TextareaField.svelte";
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { createnotification, createtransaction } from "./api";
const dispatch = createEventDispatcher();
export let account={id: -1, type: "", currency:"", balance:0};
export let account={type: "", currency:"", balance:0};
let receivername="";
let receiveriban="";
let amount=0.00;
let description="";
const token = getContext("token");
const refreshAccounts = getContext("refreshAccounts");
let send_details={receivername:"", receiveriban:"", amount:0, description:""};
async function create(){
function create(){
if(receivername == "" || receivername == null) {
alert("Receiver's name field can not be empty!");
}else if(receiveriban == "" || receiveriban == null){
alert("Receiver's iBan field can not be empty!");
}else if (parseFloat(amount) > parseFloat(account.balance) ){
}else if (amount > parseFloat(account.balance) ){
alert("Not enough money in your account!");
}else if (amount <= 0.00 ){
alert("Insert a valid amount!");
}else{
//TODO Create account with provided details on the server
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();
}
}
});
send_details={receivername:receivername, receiveriban:receiveriban, amount:amount, description:description}
dispatch("createPopup",{type:"send_money_success", send_details:{send_details}});
}
}
@ -72,7 +63,7 @@
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>IBAN:</h2>
<InputField placeholder={"RO00 FOXB 0"+account.currency +" 0000 0000 0000"} isPassword={false} bind:value={receiveriban}></InputField>
<InputField placeholder={account.currency +"-0000-0000-0000-0000"} isPassword={false} bind:value={receiveriban}></InputField>
</div>
<div class="mx-1 flex-shrink">

View file

@ -54,234 +54,4 @@ export async function logout(token) {
code: "request/failure"
}
}
}
export async function getaccountlist(token) {
try {
const result = await fetch(new URL("/accounts/", baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
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",
code: "request/failure"
}
}
}
export async function getcurrencies() {
try {
const result = await fetch(new URL("/accounts/meta/currencies", baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function getaccounttypes() {
try {
const result = await fetch(new URL("/accounts/meta/account_types", baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function createaccount(token, name, currency, type) {
try {
const result = await fetch(new URL("/accounts/", baseURL), {
method: "POST",
body: JSON.stringify({
customName: name,
currency: currency,
accountType: type,
}),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function getnotificationlist(token) {
try {
const result = await fetch(new URL("/notifications/", baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
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",
code: "request/failure"
}
}
}
export async function createnotification(token, body, read) {
try {
const result = await fetch(new URL("/notifications/", baseURL), {
method: "POST",
body: JSON.stringify({
body: body,
read: read,
}),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function marknotificationread(token, id) {
try {
const result = await fetch(new URL("/notifications/"+id+"/mark_read", baseURL), {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function createtransaction(token, otherparty, amount, accountId, description) {
try {
const result = await fetch(new URL("/transactions/", baseURL), {
method: "POST",
body: JSON.stringify({
description: description,
account_id: accountId,
destination_iban: otherparty,
amount: amount,
}),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function gettransactions(token, id) {
try {
const result = await fetch(new URL("/transactions/?account_id="+id, baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
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",
code: "request/failure"
}
}
}
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;
}
}
}

View file

@ -1,4 +0,0 @@
export function amountToString(amount) {
amount = amount.toString().padStart(3, "0");
return amount.replace(/(.{2})$/, ".$1");
}

View file

@ -4,7 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "python",