Browse Source

Merge pull request #4 from dancojocaru2000/Frontend

Frontend
pull/6/head
DariusTFox24 3 years ago committed by GitHub
parent
commit
b44a48f241
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      .github/workflows/firebase-hosting-merge.yml
  2. 30
      .github/workflows/firebase-hosting-pull-request.yml
  3. 5
      client/.firebaserc
  4. 3
      client/.vscode/extensions.json
  5. 10
      client/firebase.json
  6. 24
      client/package-lock.json
  7. 1
      client/package.json
  8. 135
      client/public/404.html
  9. BIN
      client/public/favicon.png
  10. BIN
      client/public/img/Banner.jpg
  11. 2
      client/public/index.html
  12. 211
      client/src/AccountCard.svelte
  13. 141
      client/src/App.svelte
  14. 10
      client/src/BaseButton.svelte
  15. 18
      client/src/BottomBorder.svelte
  16. 23
      client/src/CardBG.svelte
  17. 88
      client/src/CheckNotifications.svelte
  18. 87
      client/src/CreateAccount.svelte
  19. 23
      client/src/DetailField.svelte
  20. 22
      client/src/GreenButton.svelte
  21. 22
      client/src/InputField.svelte
  22. 56
      client/src/Login.svelte
  23. 161
      client/src/MainPage.svelte
  24. 22
      client/src/OrangeButton.svelte
  25. 36
      client/src/Overlay.svelte
  26. 92
      client/src/SendMoney.svelte
  27. 48
      client/src/TextareaField.svelte
  28. 17
      client/src/TopBorder.svelte
  29. 57
      client/src/api.js
  30. 3
      client/src/main.js
  31. 22
      client/tailwind.config.js

26
.github/workflows/firebase-hosting-merge.yml

@ -0,0 +1,26 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on push
'on':
push:
branches:
- master
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install npm dependencies
working-directory: ./client
run: npm install
- name: Build Svelte app
working-directory: ./client
run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
entryPoint: ./client
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_FOXBANK_69 }}'
channelId: live
projectId: foxbank-69

30
.github/workflows/firebase-hosting-pull-request.yml

@ -0,0 +1,30 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on PR
'on':
pull_request:
branches:
- master
- Frontend
push:
branches:
- Frontend
jobs:
build_and_preview:
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install npm dependencies
working-directory: ./client
run: npm install
- name: Build Svelte app
working-directory: ./client
run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
entryPoint: ./client
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_FOXBANK_69 }}'
projectId: foxbank-69

5
client/.firebaserc

@ -0,0 +1,5 @@
{
"projects": {
"default": "foxbank-69"
}
}

3
client/.vscode/extensions.json vendored

@ -1,6 +1,7 @@
{
"recommendations": [
"svelte.svelte-vscode",
"bradlc.vscode-tailwindcss"
"bradlc.vscode-tailwindcss",
"fivethree.vscode-svelte-snippets"
]
}

10
client/firebase.json

@ -0,0 +1,10 @@
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}

24
client/package-lock.json generated

@ -27,6 +27,15 @@
"js-tokens": "^4.0.0"
}
},
"@iconify/svelte": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@iconify/svelte/-/svelte-2.1.0.tgz",
"integrity": "sha512-p2e42XCAGohnRRhnB/GouMXJtnlUQlbaqC3FEg7+aDOhqA0zGvNJzbvIW1TQJZpW5Leij5UcGP5ImQ5SZ8vJmQ==",
"dev": true,
"requires": {
"cross-fetch": "^3.1.4"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -389,6 +398,15 @@
"yaml": "^1.10.0"
}
},
"cross-fetch": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
"dev": true,
"requires": {
"node-fetch": "2.6.1"
}
},
"css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@ -894,6 +912,12 @@
"lodash": "^4.17.21"
}
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true
},
"node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",

1
client/package.json

@ -8,6 +8,7 @@
"start": "sirv public --no-clear"
},
"devDependencies": {
"@iconify/svelte": "^2.1.0",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"rollup": "^2.3.4",

135
client/public/404.html

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Not Found</title>
<style>
body {
font-family: Roboto, sans-serif;
margin: 0px;
}
.code {
position: relative;
width: 369px;
height: 42.0px;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 36px;
line-height: 42px;
text-decoration: underline;
display: flex;
align-items: center;
color: rgba(255, 255, 255, 0.84);
}
.text-msg {
position: relative;
width: 460px;
height: 320px;
margin: auto;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 36px;
line-height: 42px;
text-indent: 8px;
color: #FFFFFF;
}
.popup {
margin: auto;
min-width: 500px;
min-height: 400px;
width: 30%;
padding: 10px;
background: linear-gradient(165.31deg, rgba(67, 151, 141, 0.44) 18.49%, rgba(67, 151, 141, 0) 97.15%);
box-shadow: 0px 13px 6px 4px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(12px);
}
.major-blur {
height: 100vh;
background: rgba(38, 77, 89, 0.199);
backdrop-filter: blur(4px);
display: flex;
flex-direction: column;
}
.flex-grow {
flex-grow: 1;
}
.button-back {
margin: auto;
margin-bottom: 16px;
width: 269px;
height: 69px;
background: linear-gradient(272.39deg, rgba(247, 175, 67, 0.93) -34.2%, rgba(245, 72, 17, 0.93) 100%);
box-shadow: 0px 8px 4px rgba(0, 0, 0, 0.25);
border-radius: 20px;
}
.bg {
background-image: url('/img/Banner.jpg');
height: 100vh;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}
p {
padding-top: 12px;
font-size: 28px;
text-align: center;
font-family: Roboto;
font-style: normal;
font-weight: normal;
line-height: 42px;
text-indent: 8px;
color: #FFFFFF;
}
</style>
</head>
<body>
<div class="bg">
<div class="major-blur flex">
<div class="flex-grow"></div>
<div class="popup" >
<div>
<p class="code">404 Page not Found</p>
<p class="text-msg">It appears that you’ve wandered too far from the forest, press the magic button below to get back.</p>
<a style="text-decoration: none;" href="https://foxbank-69.web.app/" >
<div class="button-back">
<p class="text-center">Take me back!</p>
</div>
</a>
</div>
</div>
<div class="flex-grow"></div>
</div>
</div>
</body>
</html>

BIN
client/public/favicon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
client/public/img/Banner.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

2
client/public/index.html

@ -4,7 +4,7 @@
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<title>FOXBANK</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/build/bundle.css'>

211
client/src/AccountCard.svelte

@ -0,0 +1,211 @@
<script>
import Icon from '@iconify/svelte';
import CardBG from "./CardBG.svelte";
import DetailField from './DetailField.svelte';
import GreenButton from './GreenButton.svelte';
import {createEventDispatcher} from 'svelte';
import { fade, fly, slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
const dispatch = createEventDispatcher();
export let type="RON Account";
export let currency="RON";
export let balance="5425";
export let iban="RONFOX62188921";
export let isExpanded=false;
export let transactions=[];
let copied = false;
function copyIban(){
if(!copied){
navigator.clipboard.writeText(iban)
.then(() => copied = true)
.then(() => setTimeout(() => {
copied = false;
}, 1000));
}
}
function expand(){
//todo: Code here
dispatch("expanded",isExpanded);
}
function send(){
//todo: CHeck here
dispatch("createPopup",{
type: 'send_money',
account: {
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">{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>
<div class="w-full max-w-sm self-start border-solid border-gray-50 border mb-3"></div>
</div>
<div class="flex flex-row flex-grow max-h-full min-h-0">
<div class="flex flex-col">
<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>
</DetailField>
</div>
<div class="flex flex-col flex-grow pr-2 max-h-full relative scroller {isExpanded ? "overflow-auto overflow-x-hidden" : ""}">
{#if isExpanded }
{#each transactions as transaction,i (i)}
<div in:slide={{delay:100*i}} out:slide={{delay:50*(transactions.length-i)}}>
<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'>
<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 {transaction.time} </p>
{#if transaction.status == "PROCESSED"}
<span>
<Icon class="inline mb-1" icon="akar-icons:circle-check" color="#6DE25ACC"/>
</span>
{:else if transaction.status == "PENDING"}
<span>
<Icon class="inline mb-1" icon="akar-icons:arrow-cycle" color="#F6AF43"/>
</span>
{:else if transaction.status == "CANCELLED"}
<span>
<Icon class="inline mb-1" icon="akar-icons:circle-x" color="#F7630C"/>
</span>
{:else}
<span>
<Icon class="inline mb-1" icon="akar-icons:triangle-alert" color="#F7630C"/>
</span>
{/if}
{transaction.status}
</div>
</DetailField>
</div>
{/each}
{: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'>
<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 {transactions[0].time} </p>
{#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"}
<span>
<Icon class="inline mb-1" icon="akar-icons:arrow-cycle" color="#F6AF43"/>
</span>
{:else if transactions[0].status == "CANCELLED"}
<span>
<Icon class="inline mb-1" icon="akar-icons:circle-x" color="#F7630C"/>
</span>
{:else}
<span>
<Icon class="inline mb-1" icon="akar-icons:triangle-alert" color="#F7630C"/>
</span>
{/if}
{transactions[0].status}
</div>
</DetailField>
{:else}
<DetailField class="my-3 py-2 flex-shrink min-w-transaction">
<div class='font-sans text-gray-200 mt-2 mx-4 border-b-1'>
<h3 class="inline mr-3">No transactions made on this account.</h3>
</div>
</DetailField>
{/if}
</div>
</div>
<div class="flex flex-col flex-shrink">
<GreenButton on:click={send} class="mx-8">
<Icon class="inline" icon="akar-icons:arrow-right" color="rgba(249, 250, 251, 1)"/>
send money
</GreenButton>
</div>
</div>
<div class="self-center p-2">
{#if transactions.length > 1}
{#if isExpanded == false }
<button on:click={expand}><Icon icon="akar-icons:chevron-down" color="rgba(249, 250, 251, 1)"/></button>
{:else}
<button on:click={expand}><Icon icon="akar-icons:chevron-up" color="rgba(249, 250, 251, 1)"/></button>
{/if}
{/if}
</div>
</CardBG>
<style>
/* width */
::-webkit-scrollbar {
width: 3px;
}
/* Track */
::-webkit-scrollbar-track {
box-shadow: inset 0 0 2px rgba(141, 140, 140, 0.281);
border-radius: 10px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgba(238, 236, 236, 0.897);
border-radius: 10px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgba(182, 182, 182, 0.918);
}
.scroller {
scrollbar-width: thin;
scrollbar-color: rgba(238, 236, 236, 0.897) rgba(141, 140, 140, 0.281);
}
</style>

141
client/src/App.svelte

@ -1,18 +1,145 @@
<script>
import Card from "./Card.svelte";
import { onMount } from "svelte";
import { whoami } from "./api";
import BottomBorder from "./BottomBorder.svelte";
import CheckNotifications from "./CheckNotifications.svelte";
import CreateAccount from "./CreateAccount.svelte";
export let name;
import Login from "./Login.svelte";
import MainPage from "./MainPage.svelte";
import Overlay from "./Overlay.svelte";
import SendMoney from "./SendMoney.svelte";
import TopBorder from "./TopBorder.svelte";
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;
switch (eventType) {
case "new_account":
isCreatingAccount = true;
break;
case "create_acc_success":
var today = new Date();
var date = today.getDate()+'/'+(today.getMonth()+1)+'/'+today.getFullYear();
var time = today.getHours() + ":" + today.getMinutes();
notifications.push(
{
text: "The new account '" + event.detail.type+ "' was created successfully!",
time: time + " " + date,
});
isCreatingAccount = false;
break;
case "create_acc_cancelled":
isCreatingAccount = false;
break;
case "create_acc_failed":
isCreatingAccount = false;
alert(`Account creation failed! Reason: ${event.detail.reason}`);
break;
case "send_money":
sendingAccount = event.detail.account;
isSendingMoney = true;
break;
case "send_money_cancelled":
isSendingMoney = false;
break;
case "send_money_success":
isSendingMoney = false;
break;
case "check_notifications":
notifications = event.detail.notifications;
isCheckingNotifications = true;
break;
case "check_notifications_cancelled":
isCheckingNotifications = false;
break;
default:
alert(`Unhandled createPopup event: ${eventType}`);
}
}
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>
<h1 class="text-yellow-400">Hello {name}!</h1>
<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
<Card title="Test" text="123"></Card>
<Card title="Test 2" text="Hello, world!"></Card>
<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 loggedin}
{#if isCreatingAccount}
<Overlay>
<div class="flex items-center justify-center h-full">
<CreateAccount class="" on:createPopup={onCreatePopup}> </CreateAccount>
</div>
</Overlay>
{:else if isCheckingNotifications}
<Overlay>
<div class="flex items-center justify-center h-full">
<CheckNotifications on:createPopup={onCreatePopup} notifications={notifications}></CheckNotifications>
</div>
</Overlay>
{:else if isSendingMoney}
<Overlay>
<div class="flex items-center justify-center h-full w-full">
<SendMoney on:createPopup={onCreatePopup} account={sendingAccount}></SendMoney>
</div>
</Overlay>
{/if}
<MainPage on:createPopup={onCreatePopup} on:logOut={toggleLoggedIn}></MainPage>
{:else}
<Login on:loginSuccess={toggleLoggedIn}></Login>
{/if}
</div>
<BottomBorder class="flex-none"></BottomBorder>
</main>
<svelte:head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
</svelte:head>
<style global lang="postcss">
@import url('https://fonts.googleapis.com/css2?family=Geo&family=Roboto&family=Rochester&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;

10
client/src/BaseButton.svelte

@ -0,0 +1,10 @@
<script>
let clazz = "";
export { clazz as class };
</script>
<main class={clazz}>
<button on:click class="rounded-2xl p-6 w-full text-center text-3xl text-gray-50">
<slot></slot>
</button>
</main>

18
client/src/BottomBorder.svelte

@ -0,0 +1,18 @@
<script>
let clazz;
export {clazz as class};
</script>
<main class={clazz}>
</main>
<style>
main{
width:100%;
height:80px;
flex-basis: 80px;
background: linear-gradient(270.31deg, rgba(38, 77, 89, 0.7) 21.94%, rgba(38, 77, 89, 0) 89.29%);
}
</style>

23
client/src/CardBG.svelte

@ -0,0 +1,23 @@
<script>
export let width = "inherit";
export let height = "inherit";
export let padding = true;
let clazz;
export {clazz as class}
</script>
<main class="rounded-x {padding ? "p-3" : ""} m-14 {clazz}" style="--width: {width}; --height: {height};">
<slot></slot>
</main>
<style>
main {
width: var(--width);
height: var(--height);
background: linear-gradient(165.31deg, rgba(67, 151, 141, 0.44) 18.49%, rgba(67, 151, 141, 0) 97.15%);
box-shadow: 0px 13px 6px 4px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(12px);
}
</style>

88
client/src/CheckNotifications.svelte

@ -0,0 +1,88 @@
<script>
import CardBG from "./CardBG.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";
const dispatch = createEventDispatcher();
export let notifications = [{time: "15:38 27/11/2021", text: "A notification's text."}];
function cancelCheckNotifications(){
dispatch("createPopup",{type:"check_notifications_cancelled"});
}
</script>
<div class="h-full self-center">
<div class="h-full flex flex-col justify-center items-center md:items-start overflow-clip">
<CardBG class="flex flex-col items-stretch min-h-0">
<div class="flex flex-row">
<h1 class='font-sans text-4xl text-gray-50 mt-6 mx-6 mb-1'>Inbox</h1>
<button class="ml-auto mr-6" on:click={cancelCheckNotifications}> <Icon icon="akar-icons:cross" color="rgba(249, 250, 251, 1)" width="32" height="32" /> </button>
</div>
<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 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>
</CardBG>
</div>
</div>
<style>
/* width */
::-webkit-scrollbar {
width: 3px;
}
/* Track */
::-webkit-scrollbar-track {
box-shadow: inset 0 0 2px rgba(141, 140, 140, 0.281);
border-radius: 10px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgba(238, 236, 236, 0.897);
border-radius: 10px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgba(182, 182, 182, 0.918);
}
.scroller {
scrollbar-width: thin;
scrollbar-color: rgba(238, 236, 236, 0.897) rgba(141, 140, 140, 0.281);
}
.max-h-medium {
max-height: 50%;
}
</style>

87
client/src/CreateAccount.svelte

@ -0,0 +1,87 @@
<script>
import OrangeButton from "./OrangeButton.svelte";
import CardBG from "./CardBG.svelte";
import InputField from "./InputField.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';
const dispatch = createEventDispatcher();
let type = "";
let currencies = ["RON", "EUR"];
let currency = currencies[0];
let termsAccepted = false;
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{
//TODO Create account with provided details on the server
dispatch("createPopup",{type:"create_acc_success", account:{type:type, currency:currency, transactions:[]}});
}
}
function cancelCreate(){
dispatch("createPopup",{type:"create_acc_cancelled"});
}
function failCreate(){
dispatch("createPopup",{type:"create_acc_failed", reason:"Invalid arguments. [type: "+type+", currency: "+currency});
}
function termsOfService() {
termsAccepted = !termsAccepted;
}
</script>
<div class="h-full self-center" in:fade={{duration:300}} out:fade={{duration:300}}>
<div class="h-full flex flex-col justify-center items-center md:items-start">
<CardBG padding={false} class="flex flex-col items-stretch">
<div class="m-3">
<div class="flex flex-row">
<h1 class='font-sans text-4xl text-gray-50 mt-6 mx-6 mb-1'>Open a new account</h1>
<button class="ml-auto mr-6" on:click={cancelCreate}> <Icon icon="akar-icons:cross" color="rgba(249, 250, 251, 1)" width="32" height="32" /> </button>
</div>
<div class="w-full max-w-md self-start border-solid border-gray-50 border mb-3"></div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Account name:</h2>
<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>
<InputField placeholder="RON" isPassword={false} bind:value={currency}></InputField>
</div>
<div class="mx-1 flex-shrink max-w-2xl">
<h2 class=" font-sans text-2xl text-gray-50 mb-2 ">Terms of Service:</h2>
<button class="mb-1" on:click={termsOfService}> <Icon icon={termsAccepted ? "akar-icons:check-box" : "akar-icons:box"} color="rgba(249, 250, 251, 1)" width="18" height="18" /> </button>
<h3 class="inline m-0 mb-0 text-gray-300"> I have read and accepted the <a class="font-sans text-gray-50" href="https://c.tenor.com/TVRtbC8jKY0AAAAC/positive-fox-you-can-do-it.gif" target="_blank">terms and conditions</a> for creating a new account at FOXBANK. </h3>
</div>
<div class="m-10"></div>
</div>
<div class="flex-shrink flex flex-row mb-4" style="background: linear-gradient(89.1deg, rgba(236, 98, 68, 0.95) 0.77%, rgba(236, 98, 68, 0) 99.12%);">
<div class="flex-1"></div>
<OrangeButton class="flex-1 m-4" on:click={create}>Confirm</OrangeButton>
</div>
</CardBG>
</div>
</div>

23
client/src/DetailField.svelte

@ -0,0 +1,23 @@
<script>
export let width = "inherit";
export let height = "inherit";
let clazz;
export {clazz as class}
</script>
<main class="rounded-x {clazz}" style="--width: {width}; --height: {height};">
<slot></slot>
</main>
<style>
main{
width: var(--width);
height: var(--height);
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>

22
client/src/GreenButton.svelte

@ -0,0 +1,22 @@
<script>
import BaseButton from "./BaseButton.svelte";
let clazz = "";
export { clazz as class };
</script>
<div class="green {clazz}">
<BaseButton on:click>
<slot></slot>
</BaseButton>
</div>
<style>
.green {
background: linear-gradient(92.55deg, rgba(67, 151, 141, 0.95) -28.27%, rgba(249, 224, 127, 0) 115.79%);
filter: drop-shadow(0px 8px 4px rgba(0, 0, 0, 0.25));
border-radius: 16px;
}
</style>

22
client/src/InputField.svelte

@ -0,0 +1,22 @@
<script>
export let isPassword;
export let placeholder;
export let value;
const handleInput = e => {
value = e.target.value;
}
</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">
</main>
<style>
input{
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>

56
client/src/Login.svelte

@ -0,0 +1,56 @@
<script>
import OrangeButton from "./OrangeButton.svelte";
import CardBG from "./CardBG.svelte";
import InputField from "./InputField.svelte";
import {createEventDispatcher} from 'svelte';
import {login} from './api.js';
const dispatch = createEventDispatcher();
let username = "";
let code = "";
async function checkLogin(){
const result = await login(username, code);
if(result.status == "success") {
sessionStorage.setItem("token", result.token);
dispatch("loginSuccess",null);
}else{
alert(result.code);
}
}
</script>
<main class="h-full">
<div class="h-full flex flex-col justify-center items-center md:items-start">
<CardBG class="flex flex-col items-stretch">
<h1 class='font-welcome text-7xl text-gray-50 m-6 border-b-2'>Welcome,</h1>
<div class="m-3 flex-shrink">
<InputField placeholder="Username" isPassword={false} bind:value={username}></InputField>
</div>
<div class="m-3 flex-shrink">
<InputField placeholder="Code" isPassword={true} bind:value={code}></InputField>
</div>
<div class="m-3 flex-shrink">
<OrangeButton on:click={checkLogin}>Login</OrangeButton>
</div>
<div class="flex-grow">
</div>
<div class="flex">
<a href='https://support.google.com/accounts/answer/1066447?hl=ro&co=GENIE.Platform%3DAndroid' target="_blank" class='text-3xl text-gray-400 m-6 flex-auto text-center'>Help!</a>
<a href='https://support.google.com/accounts/answer/185834?hl=en' target="_blank" class='text-3xl text-gray-400 m-6 flex-auto text-center'>Can't login?</a>
</div>
</CardBG>
</div>
</main>

161
client/src/MainPage.svelte

@ -0,0 +1,161 @@
<script>
import Icon from '@iconify/svelte';
import CardBG from "./CardBG.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 } from './api';
const dispatch = createEventDispatcher();
let fullname = "";
let username = "";
let email = "";
let code = "";
let totalbalance = "2455.22";
let maincurrency = "RON";
let expandedAccount = null;
let showAllAccounts = true;
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."},
];
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(sessionStorage.getItem("token"));
sessionStorage.removeItem("token");
dispatch("logOut",null);
}
}
function expanded(index) {
if (!expandedAccount && expandedAccount !== 0) {
expandedAccount = index;
showAllAccounts = false;
}
else {
setShowAllAccounts();
expandedAccount = null;
}
}
function setShowAllAccounts() {
setTimeout(() => {
showAllAccounts = true;
}, (accounts[expandedAccount].transactions.length * 50) +400 );
}
function createAccount(){
//todo: CHeck here
dispatch("createPopup",{type: "new_account"});
}
function checkNotifications(){
//todo: CHeck here
dispatch("createPopup",{
type: "check_notifications",
notifications: notifications
});
}
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 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 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}
<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>
{/if}
</div>
</div>
<div class="flex-shrink md:flex-shrink-0 md:flex-grow"></div>
<div in:fade={{duration:250}}>
<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="akar-icons:envelope" color="#FB6666" width="36" height="36" /></button>
</div>
<div class="m-3 flex-shrink">
<h2 class='font-sans text-4xl text-gray-50'>Total balance: <span style="color: #264D59">{totalbalance}</span>{maincurrency}</h2>
<p class='font-sans text-1xl text-gray-50 m-2'>➜ from {accounts.length} accounts</p>
</div>
<div class="m-32"></div>
<div class="flex flex-row">
<button on:click={dispatchLogout}> <Icon icon="ri:logout-box-line" color="#264D59" width="34" height="34"/></button>
<div class="flex-grow"></div>
<div class="flex-grow"></div>
<div class="flex-grow"></div>
<div class="flex-grow"></div>
<a href='https://google.com' target="_blank" class='text-3xl text-gray-300 m-6 flex-auto text-center'>Help!</a>
<a href='mailto:foxbank.fx@gmail.com' target="_blank" class='text-3xl text-gray-300 m-6 flex-auto text-center'>Feedback</a>
</div>
</CardBG>
</div>
</main>

22
client/src/OrangeButton.svelte

@ -0,0 +1,22 @@
<script>
import BaseButton from "./BaseButton.svelte";
let clazz = "";
export {clazz as class};
</script>
<div class="orange {clazz}">
<BaseButton on:click>
<slot></slot>
</BaseButton>
</div>
<style>
.orange {
background: linear-gradient(272.39deg, rgba(247, 175, 67, 0.93) -34.2%, rgba(245, 72, 17, 0.93) 100%);
box-shadow: 0px 8px 4px rgba(0, 0, 0, 0.25);
border-radius: 20px;
}
</style>

36
client/src/Overlay.svelte

@ -0,0 +1,36 @@
<script>
let clazz = "";
export {clazz as class}
</script>
<div id="overlay" class="overlay {clazz}">
<div class="overlay-content">
<slot></slot>
</div>
</div>
<style>
/* The Overlay (background) */
.overlay {
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
bottom: 0;
right: 0;
overflow-x: hidden; /* Disable horizontal scroll */
background: linear-gradient(165.31deg, rgba(67, 151, 141, 0.44) 18.49%, rgba(67, 151, 141, 0) 97.15%);
backdrop-filter: blur(6px);
transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */
}
/* Position the content inside the overlay */
.overlay-content {
position: relative;
/* top: 25%; 25% from the top */
width: 100%; /* 100% width */
height: 100%;
}
</style>

92
client/src/SendMoney.svelte

@ -0,0 +1,92 @@
<script>
import OrangeButton from "./OrangeButton.svelte";
import CardBG from "./CardBG.svelte";
import InputField from "./InputField.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';
const dispatch = createEventDispatcher();
export let account={type: "", currency:"", balance:0};
let receivername="";
let receiveriban="";
let amount=0.00;
let description="";
let send_details={receivername:"", receiveriban:"", amount:0, description:""};
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 (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
send_details={receivername:receivername, receiveriban:receiveriban, amount:amount, description:description}
dispatch("createPopup",{type:"send_money_success", send_details:{send_details}});
}
}
function cancelSend(){
dispatch("createPopup",{type:"send_money_cancelled"});
}
</script>
<div class="h-full flex flex-col justify-center items-center md:items-start" in:fade={{duration:300}} out:fade={{duration:300}}>
<CardBG padding={false} class="flex flex-col items-stretch md:min-w-full">
<div class="flex-grow m-3 flex flex-col items-stretch">
<div class="flex flex-row ">
<h1 class='inline mt-6 mx-6 mb-1 font-sans text-4xl text-gray-50'>Send money</h1>
<span class="mb-1 ml-4 mr-2 mt-auto text-2xl text-gray-50"> from</span>
<span class="mb-1 mt-auto font-sans text-4xl">{account.type}</span>
<button class="mb-1 ml-auto" on:click={cancelSend}> <Icon icon="akar-icons:cross" color="rgba(249, 250, 251, 1)" width="32" height="32" /> </button>
</div>
<div class="w-full max-w-md self-start border-solid border-gray-50 border mb-3"></div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Receiver's full name:</h2>
<InputField placeholder="Mr Foxy Fox" isPassword={false} bind:value={receivername}></InputField>
</div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>IBAN:</h2>
<InputField placeholder={account.currency +"-0000-0000-0000-0000"} isPassword={false} bind:value={receiveriban}></InputField>
</div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Amount:</h2>
<InputField style="color: #264D59" placeholder=0 isPassword={false} bind:value={amount}>
<span class="text-gray-50">{account.currency}</span>
</InputField>
</div>
<div class="mx-1 flex-shrink">
<h2 class='font-sans text-2xl text-gray-50 mb-2 '>Description:</h2>
<TextareaField class="flex-grow mb-0" placeholder="Sent from FOXBANK!" rows={5} bind:value={description}></TextareaField>
</div>
</div>
<div class="m-4"></div>
<div class="flex-shrink flex flex-row mb-4 mt-0" style="background: linear-gradient(89.1deg, rgba(236, 98, 68, 0.95) 0.77%, rgba(236, 98, 68, 0) 99.12%);">
<div class="flex-1"></div>
<OrangeButton class="flex-1 m-4" on:click={create}>Confirm</OrangeButton>
</div>
</CardBG>
</div>

48
client/src/TextareaField.svelte

@ -0,0 +1,48 @@
<script>
export let placeholder;
export let value;
export let rows = 2;
const handleInput = e => {
value = e.target.value;
}
</script>
<main>
<textarea placeholder={placeholder} value={value} rows={rows} on:input={handleInput} class="placeholder-gray-300 p-3 text-gray-50 w-full text-3xl"></textarea>
</main>
<style>
textarea {
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;
}
::-webkit-scrollbar {
width: 3px;
}
/* Track */
::-webkit-scrollbar-track {
box-shadow: inset 0 0 2px rgba(141, 140, 140, 0.281);
border-radius: 10px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgba(238, 236, 236, 0.897);
border-radius: 10px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: rgba(182, 182, 182, 0.918);
}
textarea {
scrollbar-width: thin;
scrollbar-color: rgba(238, 236, 236, 0.897) rgba(141, 140, 140, 0.281);
}
</style>

17
client/src/TopBorder.svelte

@ -0,0 +1,17 @@
<script>
let clazz;
export {clazz as class};
</script>
<main class={clazz}>
<h1 class="font-title text-7xl text-gray-50 m-8">FOXBANK</h1>
</main>
<style>
main{
width:100%;
height:80px;
background: linear-gradient(90.12deg, rgba(38, 77, 89, 0.7) 19.7%, rgba(38, 77, 89, 0) 93.77%);
}
</style>

57
client/src/api.js

@ -0,0 +1,57 @@
const baseURL = "https://foxbank-api.extras.dcdevelop.xyz";
export async function login(username, code) {
try {
const result = await fetch(new URL("/login/", baseURL), {
method: "POST",
body: JSON.stringify({
username, code,
}),
headers: {
"Content-Type": "application/json"
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function whoami(token) {
try {
const result = await fetch(new URL("/login/whoami", baseURL), {
method: "GET",
headers: {
"Authorization": "Bearer " + token,
},
});
return (await result.json());
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}
export async function logout(token) {
try {
await fetch(new URL("/login/logout", baseURL), {
method: "POST",
headers: {
"Authorization": "Bearer " + token,
},
});
} catch (error) {
return {
status: "error",
code: "request/failure"
}
}
}

3
client/src/main.js

@ -2,9 +2,6 @@ import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world'
}
});
export default app;

22
client/tailwind.config.js

@ -8,7 +8,27 @@ module.exports = {
},
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
extend: {
backgroundImage:{
"banner":"url('/img/Banner.jpg')"
},
fontFamily:{
"title":['Geo', 'sans-serif'],
"welcome":['Rochester', 'cursive'],
"sans":['Roboto', 'sans-serif']
},
colors: {
'regal-blue': '#243c5a',
'lime-c': '#6DE25ACC',
'red-c': '#FB6666',
},
minWidth: {
'transaction': '420px',
},
},
},
variants: {
extend: {},

Loading…
Cancel
Save