Browse Source

Merge pull request #6 from dancojocaru2000/Frontend

Frontend Accounts functionality
pull/9/head
Kenneth Bruen 3 years ago committed by GitHub
parent
commit
c429506bdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2244
      client/package-lock.json
  2. 6
      client/src/AccountCard.svelte
  3. 95
      client/src/App.svelte
  4. 83
      client/src/CreateAccount.svelte
  5. 5
      client/src/Login.svelte
  6. 81
      client/src/MainPage.svelte
  7. 163
      client/src/api.js

2244
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

6
client/src/AccountCard.svelte

@ -13,7 +13,7 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let type="RON Account"; export let name="RON Account";
export let currency="RON"; export let currency="RON";
export let balance="5425"; export let balance="5425";
export let iban="RONFOX62188921"; export let iban="RONFOX62188921";
@ -42,7 +42,7 @@
dispatch("createPopup",{ dispatch("createPopup",{
type: 'send_money', type: 'send_money',
account: { account: {
type, type: name,
currency, currency,
balance, balance,
iban, iban,
@ -55,7 +55,7 @@
<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"> <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="flex flex-col flex-shrink">
<div class='font-sans mt-2 mx-2 border-b-1'> <div class='font-sans mt-2 mx-2 border-b-1'>
<h3 class="text-gray-50 inline mr-4">{type}</h3> <h3 class="text-gray-50 inline mr-4">{name}</h3>
<span class="text-gray-100">IBAN: {iban}</span> <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> <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> </div>

95
client/src/App.svelte

@ -1,6 +1,7 @@
<script> <script>
import { onMount } from "svelte"; import { onMount, setContext } from "svelte";
import { whoami } from "./api"; import { writable, readable } from "svelte/store";
import { whoami, createnotification, getaccountlist } from "./api";
import BottomBorder from "./BottomBorder.svelte"; import BottomBorder from "./BottomBorder.svelte";
import CheckNotifications from "./CheckNotifications.svelte"; import CheckNotifications from "./CheckNotifications.svelte";
@ -12,16 +13,59 @@ import { whoami } from "./api";
import SendMoney from "./SendMoney.svelte"; import SendMoney from "./SendMoney.svelte";
import TopBorder from "./TopBorder.svelte"; import TopBorder from "./TopBorder.svelte";
let loggedin = false; const userToken = writable(sessionStorage.getItem("token"));
function toggleLoggedIn() { userToken.subscribe(newToken => {
loggedin = !loggedin; 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 accounts = readable(null, set => {
const unsubscribe = userToken.subscribe(token => {
if(token == null) {
set(null);
}else{
getaccountlist(token)
.then(result =>{
set(result);
})
}
})
return () => {
unsubscribe();
}
})
setContext("accounts", accounts);
let isCreatingAccount = false; let isCreatingAccount = false;
let isCheckingNotifications = false; let isCheckingNotifications = false;
let isSendingMoney = false; let isSendingMoney = false;
let sendingAccount = ""; let sendingAccount = "";
let notifications = [];
function onCreatePopup(event) { function onCreatePopup(event) {
const eventType = event.detail.type; const eventType = event.detail.type;
@ -34,12 +78,18 @@ import { whoami } from "./api";
var today = new Date(); var today = new Date();
var date = today.getDate()+'/'+(today.getMonth()+1)+'/'+today.getFullYear(); var date = today.getDate()+'/'+(today.getMonth()+1)+'/'+today.getFullYear();
var time = today.getHours() + ":" + today.getMinutes(); 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; isCreatingAccount = false;
break; break;
@ -48,7 +98,7 @@ import { whoami } from "./api";
break; break;
case "create_acc_failed": case "create_acc_failed":
isCreatingAccount = false; // isCreatingAccount = false;
alert(`Account creation failed! Reason: ${event.detail.reason}`); alert(`Account creation failed! Reason: ${event.detail.reason}`);
break; break;
@ -79,26 +129,13 @@ import { whoami } from "./api";
} }
} }
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> </script>
<main class="flex flex-col items-stretch bg-banner bg-cover bg-center bg-fixed h-screen font-sans"> <main class="flex flex-col items-stretch bg-banner bg-cover bg-center bg-fixed h-screen font-sans">
<TopBorder class="flex-shrink"></TopBorder> <TopBorder class="flex-shrink"></TopBorder>
<div class="flex-grow max-h-full overflow-hidden"> <div class="flex-grow max-h-full overflow-hidden">
{#if loggedin} {#if $user}
{#if isCreatingAccount} {#if isCreatingAccount}
<Overlay> <Overlay>
@ -120,10 +157,10 @@ import { whoami } from "./api";
</Overlay> </Overlay>
{/if} {/if}
<MainPage on:createPopup={onCreatePopup} on:logOut={toggleLoggedIn}></MainPage> <MainPage on:createPopup={onCreatePopup} on:logOut={()=>$userToken=null}></MainPage>
{:else} {:else}
<Login on:loginSuccess={toggleLoggedIn}></Login> <Login on:loginSuccess={(e)=> $userToken = e.detail.token}></Login>
{/if} {/if}

83
client/src/CreateAccount.svelte

@ -3,31 +3,41 @@
import CardBG from "./CardBG.svelte"; import CardBG from "./CardBG.svelte";
import InputField from "./InputField.svelte"; import InputField from "./InputField.svelte";
import {createEventDispatcher} from 'svelte'; import {createEventDispatcher, onMount} from 'svelte';
import Icon from "@iconify/svelte"; import Icon from "@iconify/svelte";
import Overlay from "./Overlay.svelte"; import Overlay from "./Overlay.svelte";
import { fade, fly, slide } from 'svelte/transition'; import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { createaccount, getcurrencies, getaccounttypes } from "./api";
import { getContext } from "svelte";
const token = getContext("token");
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let type = ""; let type = null;
let currencies = ["RON", "EUR"]; let name = "";
let currency = currencies[0]; let currency = null;
let termsAccepted = false; let termsAccepted = false;
$: placeholder = type==null ? "Checking Account" : `${type} Account`;
function create(){ async function create(){
if(type == "" || type == null) { if(name == "" || name == null) {
alert("Account Name field can not be empty!"); alert("Account Name field can not be empty!");
console.debug(`account name: ${type}`) console.debug(`account name: ${type}`)
}else if(!currencies.includes(currency)){ }else if(type == null){
alert("Currency is not supported!"); alert("Type is not selected!");
}else if(currency == null){
alert("Currency is not selected!");
}else if (!termsAccepted){ }else if (!termsAccepted){
alert("Terms of Service not accepted!"); alert("Terms of Service not accepted!");
}else{ }else{
//TODO Create account with provided details on the server const result = await createaccount($token, name, currency, type);
dispatch("createPopup",{type:"create_acc_success", account:{type:type, currency:currency, transactions:[]}}); if(result.status == "success") {
dispatch("createPopup",{type:"create_acc_success", account:{type:type, currency:currency, transactions:[]}});
}else{
dispatch("createPopup",{type:"create_acc_failed", reason:"Failed to create account. Error:"+result.status});
}
} }
} }
@ -43,6 +53,11 @@
termsAccepted = !termsAccepted; termsAccepted = !termsAccepted;
} }
onMount(() => {
getcurrencies().then(result => currency = result.currencies[0]);
getaccounttypes().then(result => type = result.accountTypes[0]);
})
</script> </script>
@ -58,12 +73,31 @@
<div class="mx-1 flex-shrink"> <div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Account name:</h2> <h2 class='font-sans text-2xl text-gray-50 mb-2 '>Account name:</h2>
<InputField placeholder="New Account" isPassword={false} bind:value={type}></InputField> <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>
</div> </div>
<div class="mx-1 flex-shrink"> <div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Currency:</h2> <h2 class='font-sans text-2xl text-gray-50 mb-2 '>Currency:</h2>
<InputField placeholder="RON" isPassword={false} bind:value={currency}></InputField> <select bind:value={currency}>
{#await getcurrencies() then result}
{#each result.currencies as option}
<option class="custom-option" value={option}>{option}</option>
{/each}
{/await}
</select>
</div> </div>
<div class="mx-1 flex-shrink max-w-2xl"> <div class="mx-1 flex-shrink max-w-2xl">
@ -85,3 +119,28 @@
</div> </div>
</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>

5
client/src/Login.svelte

@ -15,8 +15,9 @@
async function checkLogin(){ async function checkLogin(){
const result = await login(username, code); const result = await login(username, code);
if(result.status == "success") { if(result.status == "success") {
sessionStorage.setItem("token", result.token); dispatch("loginSuccess",{
dispatch("loginSuccess",null); token: result.token,
});
}else{ }else{
alert(result.code); alert(result.code);
} }

81
client/src/MainPage.svelte

@ -1,20 +1,22 @@
<script> <script>
import Icon from '@iconify/svelte'; import Icon from '@iconify/svelte';
import CardBG from "./CardBG.svelte"; import CardBG from "./CardBG.svelte";
import {createEventDispatcher, onMount} from 'svelte'; import {createEventDispatcher, onMount, getContext} from 'svelte';
import AccountCard from './AccountCard.svelte'; import AccountCard from './AccountCard.svelte';
import GreenButton from './GreenButton.svelte'; import GreenButton from './GreenButton.svelte';
import { fade, fly, slide } from 'svelte/transition'; import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { logout, whoami } from './api'; import { logout, whoami, getaccountlist } from './api';
const token = getContext("token");
const user = getContext("user");
const accountsStore = getContext("accounts");
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let fullname = ""; $: fullname = $user.user.fullname;
let username = ""; $: username = $user.user.username;
let email = ""; $: email = $user.user.email;
let code = "";
let totalbalance = "2455.22"; let totalbalance = "2455.22";
let maincurrency = "RON"; let maincurrency = "RON";
let expandedAccount = null; let expandedAccount = null;
@ -28,34 +30,45 @@
]; ];
let accounts = [ $: accounts = $accountsStore ? $accountsStore.accounts.map(account => {
{type:"RON Account", currency:"RON", balance:"420.42", iban:"RONFOX62188921", return {
name: account.customName ? account.customName : `${account.accountType} Account`,
currency: account.currency,
balance: "123.12",
iban: account.iban.replace(/(.{4})/g, "$1 "),
transactions: [ transactions: [
{title:"Transaction Name#1", status:"PROCESSED", amount:"-45.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#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"}, // let accounts = [
{title:"Transaction Name#1", status:"PROCESSED", amount:"-45.09", time:"15:38 27/11/2021", type:"send"}, // {type:"RON Account", currency:"RON", balance:"420.42", iban:"RONFOX62188921",
{title:"Transaction Name#2", status:"PENDING", amount:"+25.00", time:"15:38 27/11/2021", type:"received"}, // transactions: [
{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", // {title:"Transaction Name#1", status:"PROCESSED", amount:"-45.09", time:"15:38 27/11/2021", type:"send"},
transactions: [ // {title:"Transaction Name#2", status:"PENDING", amount:"+25.00", time:"15:38 27/11/2021", type:"received"},
{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#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"}, // {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(){ function dispatchLogout(){
//todo: CHeck here //todo: CHeck here
if (confirm("Log out?")) { if (confirm("Log out?")) {
logout(sessionStorage.getItem("token")); logout($token);
sessionStorage.removeItem("token");
dispatch("logOut",null); dispatch("logOut",null);
} }
} }
@ -92,27 +105,17 @@
}); });
} }
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> </script>
<main class="h-full flex flex-col items-stretch md:flex-row"> <main class="h-full flex flex-col items-stretch md:flex-row">
<div class="flex flex-col items-stretch max-h-full"> <div class="flex flex-col items-stretch max-h-full">
{#if expandedAccount || expandedAccount === 0} {#if expandedAccount || expandedAccount === 0}
<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> <AccountCard 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>
{:else} {:else}
{#if showAllAccounts} {#if showAllAccounts}
{#each accounts as account,i} {#each accounts as account,i}
<div in:slide={{delay:500*i, duration:250*(i==0 ? 1 : i) }}> <div in:slide={{delay:500*i, duration:250*(i==0 ? 1 : i) }}>
<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> <AccountCard name={account.name} currency={account.currency} balance={account.balance} iban={account.iban} transactions={account.transactions} isExpanded={false} on:expanded={() => expanded(i)} on:createPopup></AccountCard>
</div> </div>
{/each} {/each}
{/if} {/if}

163
client/src/api.js

@ -54,4 +54,167 @@ export async function logout(token) {
code: "request/failure" 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,
},
});
return (await result.json());
} 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 getnotificationlist(token) {
try {
const result = await fetch(new URL("/notifications", baseURL), {
method: "GET",
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?accountId="+id, baseURL), {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
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 createnotification(token, body, datetime) {
try {
const result = await fetch(new URL("/notification/create", baseURL), {
method: "POST",
body: JSON.stringify({
body, datetime,
}),
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, type, ) {
try {
const result = await fetch(new URL("/transaction/create", baseURL), {
method: "POST",
body: JSON.stringify({
otherparty, amount, type,
}),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
} }
Loading…
Cancel
Save