mirror of
https://github.com/microsoft/Web-Dev-For-Beginners.git
synced 2025-08-22 22:34:29 +02:00
folder names
This commit is contained in:
13
7-bank-project/solution/README.md
Normal file
13
7-bank-project/solution/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Bank app
|
||||
|
||||
> Example solution for the bank app project, built with vanilla HTML5, CSS and JavaScript (no frameworks or libraries used).
|
||||
|
||||
## Running the app
|
||||
|
||||
First make sure you have the [API server](../api/README.md) running.
|
||||
|
||||
Any web server can be used to run the app, but since you should have [Node.js](https://nodejs.org) installed anyway to run the API, you can:
|
||||
|
||||
1. Git clone this repo.
|
||||
2. Open a terminal, then run `npx lite-server solution`. It will start a development web server on port `3000`
|
||||
3. Open `http://localhost:3000` in a browser to run the app.
|
271
7-bank-project/solution/app.js
Normal file
271
7-bank-project/solution/app.js
Normal file
@@ -0,0 +1,271 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const serverUrl = 'http://localhost:5000/api';
|
||||
const storageKey = 'savedState';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Router
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const routes = {
|
||||
'/dashboard': { title: 'My Account', templateId: 'dashboard', init: refresh },
|
||||
'/login': { title: 'Login', templateId: 'login' }
|
||||
};
|
||||
|
||||
function navigate(path) {
|
||||
window.history.pushState({}, path, window.location.origin + path);
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
function updateRoute() {
|
||||
const path = window.location.pathname;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
return navigate('/dashboard');
|
||||
}
|
||||
|
||||
const template = document.getElementById(route.templateId);
|
||||
const view = template.content.cloneNode(true);
|
||||
const app = document.getElementById('app');
|
||||
app.innerHTML = '';
|
||||
app.appendChild(view);
|
||||
|
||||
if (typeof route.init === 'function') {
|
||||
route.init();
|
||||
}
|
||||
|
||||
document.title = route.title;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API interactions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function sendRequest(api, method, body) {
|
||||
try {
|
||||
const response = await fetch(serverUrl + api, {
|
||||
method: method || 'GET',
|
||||
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return { error: error.message || 'Unknown error' };
|
||||
}
|
||||
}
|
||||
|
||||
async function getAccount(user) {
|
||||
return sendRequest('/accounts/' + encodeURIComponent(user));
|
||||
}
|
||||
|
||||
async function createAccount(account) {
|
||||
return sendRequest('/accounts', 'POST', account);
|
||||
}
|
||||
|
||||
async function createTransaction(user, transaction) {
|
||||
return sendRequest('/accounts/' + user + '/transactions', 'POST', transaction);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let state = {
|
||||
user: null,
|
||||
account: null
|
||||
};
|
||||
|
||||
function updateState(newState) {
|
||||
state = newState;
|
||||
localStorage.setItem(storageKey, JSON.stringify(state));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Login/register
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function login() {
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const user = loginForm.user.value;
|
||||
const data = await getAccount(user);
|
||||
|
||||
if (data.error) {
|
||||
return updateElement('loginError', data.error);
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
user: data.user
|
||||
};
|
||||
updateState(newState);
|
||||
|
||||
navigate('/dashboard');
|
||||
}
|
||||
|
||||
async function register() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData);
|
||||
const jsonData = JSON.stringify(data);
|
||||
const result = await createAccount(jsonData);
|
||||
|
||||
if (result.error) {
|
||||
return updateElement('registerError', result.error);
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
user: result.user
|
||||
};
|
||||
updateState(newState);
|
||||
|
||||
navigate('/dashboard');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dashboard
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function updateAccountData() {
|
||||
const user = state.user;
|
||||
|
||||
if (!user) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
const data = await getAccount(user);
|
||||
|
||||
if (data.error) {
|
||||
if (data.error === 'User does not exist') {
|
||||
return logout();
|
||||
}
|
||||
return updateElement('dashboardError', data.error);
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
account: data
|
||||
};
|
||||
updateState(newState);
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await updateAccountData();
|
||||
updateDashboard();
|
||||
}
|
||||
|
||||
function updateDashboard() {
|
||||
const account = state.account;
|
||||
if (!account) {
|
||||
return logout();
|
||||
}
|
||||
|
||||
updateElement('description', account.description);
|
||||
updateElement('balance', account.balance.toFixed(2));
|
||||
updateElement('currency', account.currency);
|
||||
|
||||
// Update transactions
|
||||
const transactionsRows = document.createDocumentFragment();
|
||||
for (const transaction of account.transactions) {
|
||||
const transactionRow = createTransactionRow(transaction);
|
||||
transactionsRows.appendChild(transactionRow);
|
||||
}
|
||||
updateElement('transactions', transactionsRows);
|
||||
}
|
||||
|
||||
function createTransactionRow(transaction) {
|
||||
const template = document.getElementById('transaction');
|
||||
const transactionRow = template.content.cloneNode(true);
|
||||
const tr = transactionRow.querySelector('tr');
|
||||
tr.children[0].textContent = transaction.date;
|
||||
tr.children[1].textContent = transaction.object;
|
||||
tr.children[2].textContent = transaction.amount.toFixed(2);
|
||||
return transactionRow;
|
||||
}
|
||||
|
||||
function addTransaction() {
|
||||
const dialog = document.getElementById('transactionDialog');
|
||||
dialog.classList.add('show');
|
||||
|
||||
// Reset form
|
||||
const transactionForm = document.getElementById('transactionForm');
|
||||
transactionForm.reset();
|
||||
|
||||
// Set date to today
|
||||
transactionForm.date.valueAsDate = new Date();
|
||||
}
|
||||
|
||||
async function confirmTransaction() {
|
||||
const dialog = document.getElementById('transactionDialog');
|
||||
dialog.classList.remove('show');
|
||||
|
||||
const transactionForm = document.getElementById('transactionForm');
|
||||
|
||||
const formData = new FormData(transactionForm);
|
||||
const jsonData = JSON.stringify(Object.fromEntries(formData));
|
||||
const data = await createTransaction(state.user, jsonData);
|
||||
|
||||
if (data.error) {
|
||||
return updateElement('transactionError', data.error);
|
||||
}
|
||||
|
||||
// Update local state with new transaction
|
||||
const newState = {
|
||||
...state,
|
||||
account: {
|
||||
...state.account,
|
||||
balance: state.account.balance + data.amount,
|
||||
transactions: [...state.account.transactions, data]
|
||||
}
|
||||
}
|
||||
updateState(newState);
|
||||
|
||||
// Update display
|
||||
updateDashboard();
|
||||
}
|
||||
|
||||
async function cancelTransaction() {
|
||||
const dialog = document.getElementById('transactionDialog');
|
||||
dialog.classList.remove('show');
|
||||
}
|
||||
|
||||
function logout() {
|
||||
const newState = {
|
||||
user: null,
|
||||
account: null
|
||||
};
|
||||
updateState(newState);
|
||||
|
||||
navigate('/login');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Utils
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function updateElement(id, textOrNode) {
|
||||
const element = document.getElementById(id);
|
||||
element.textContent = ''; // Removes all children
|
||||
element.append(textOrNode);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Init
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function init() {
|
||||
// Restore state
|
||||
const savedState = localStorage.getItem(storageKey);
|
||||
if (savedState) {
|
||||
updateState(JSON.parse(savedState));
|
||||
}
|
||||
|
||||
// Update route for browser back/next buttons
|
||||
window.onpopstate = () => updateRoute();
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
init();
|
110
7-bank-project/solution/index.html
Normal file
110
7-bank-project/solution/index.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Squirrel Banking</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="app.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Placeholder where we will insert our app HTML based on route -->
|
||||
<div id="app">Loading...</div>
|
||||
|
||||
<!-- Login page template -->
|
||||
<template id="login">
|
||||
<section class="login-page">
|
||||
<div class="login-container">
|
||||
<div class="login-title text-center">
|
||||
<span class="hide-xs">Squirrel</span>
|
||||
<img class="login-logo" src="logo.svg" alt="Squirrel Banking Logo">
|
||||
<span class="hide-xs">Banking</span>
|
||||
</div>
|
||||
<div class="login-content">
|
||||
<h2 class="text-center">Login</h2>
|
||||
<form id="loginForm" action="javascript:login()">
|
||||
<label for="user">Username</label>
|
||||
<input name="user" type="text" maxlength="20" required>
|
||||
<div id="loginError" class="error"></div>
|
||||
<button>Login</button>
|
||||
</form>
|
||||
<p class="login-separator text-center"><span>OR</span></p>
|
||||
<h2 class="text-center">Register</h2>
|
||||
<form id="registerForm" action="javascript:register()">
|
||||
<label for="user">Username</label>
|
||||
<input name="user" type="text" maxlength="20" required>
|
||||
<label for="currency">Currency</label>
|
||||
<input name="currency" type="text" maxlength="5" value="$" required>
|
||||
<label for="description">Description</label>
|
||||
<input name="description" type="text" maxlength="100">
|
||||
<label for="balance">Current balance</label>
|
||||
<input name="balance" type="number" value="0">
|
||||
<div id="registerError" class="error"></div>
|
||||
<button>Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- Dashboard page template -->
|
||||
<template id="dashboard">
|
||||
<section class="dashboard-page">
|
||||
<header class="dashboard-header">
|
||||
<img class="dashboard-logo" src="logo.svg" alt="Squirrel Banking Logo">
|
||||
<span class="dashboard-title hide-xs">Squirrel Banking</span>
|
||||
<button onclick="logout()">Logout</button>
|
||||
</header>
|
||||
<div class="balance">
|
||||
<div>Balance</div>
|
||||
<span id="balance"></span>
|
||||
<span id="currency"></span>
|
||||
</div>
|
||||
<div class="dashboard-content">
|
||||
<div class="transactions-title">
|
||||
<div id="description" aria-label="Account description"></div>
|
||||
<button onclick="addTransaction()">Add transaction</button>
|
||||
</div>
|
||||
<table class="transactions-table" aria-label="Transactions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Object</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="transactions"></tbody>
|
||||
</table>
|
||||
<p id="dashboardError" class="error"></p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="transactionDialog" class="dialog">
|
||||
<div class="dialog-content">
|
||||
<h2 class="text-center">Add transaction</h2>
|
||||
<form id="transactionForm" action="javascript:void(0)">
|
||||
<label for="date">Date</label>
|
||||
<input name="date" type="date" required>
|
||||
<label for="object">Object</label>
|
||||
<input name="object" type="text" maxlength="50" required>
|
||||
<label for="amount">Amount (use negative value for debit)</label>
|
||||
<input name="amount" type="number" value="0" step="any" required>
|
||||
<div id="transactionError" class="error"></div>
|
||||
<div class="dialog-buttons">
|
||||
<button class="button-alt" formaction="javascript:cancelTransaction()" formnovalidate>Cancel</button>
|
||||
<button formaction="javascript:confirmTransaction()">OK</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- Transaction row template -->
|
||||
<template id="transaction">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
1
7-bank-project/solution/logo.svg
Normal file
1
7-bank-project/solution/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="squirrel" data-name="squirrel" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 296.2 479.27"><defs><style>.cls-1{fill: #fff;}</style></defs><path class="cls-1" d="M216.54,17c-30.06,93.06,63.25,82.67,127,133.73,48.38,38.78,92.87,118,29,234.83-11.12-29-24.49-75.4-37.9-138.2-6.71-31.52-11.1-92.35-12.16-91.7-6.88,4.25-21,37-21,37s-2.43-.6-13.36-2.83S256,187,256,187s-21.09.6-32,2.83-13.36,2.83-13.36,2.83-14.16-32.78-21-37c-1.06-.65-5.45,60.18-12.17,91.7-6.93,32.5-15.7,65.2-23.28,91.19C79.26,248.12,92.5,102.73,216.54,17Zm85.82,254.1c3.84,0,7-6.14,7-13.71s-3.12-13.71-7-13.71-6.95,6.14-6.95,13.71,3.12,13.71,6.95,13.71Zm-85.71-13.71c0-7.57-3.12-13.71-7-13.71s-6.95,6.14-6.95,13.71,3.11,13.71,6.95,13.71,7-6.14,7-13.71Zm59,23.45c0-6.37-8.78-11.53-19.62-11.53s-19.62,5.16-19.62,11.53c0,5.88,7.52,10.74,17.24,11.43v8a2.39,2.39,0,0,0,2.38,2.39h0a2.39,2.39,0,0,0,2.38-2.39v-8c9.71-.69,17.24-5.55,17.24-11.43Zm-7.72,34.51c-2.6-.18-6.08,4.76-6.93,8.07s-4,26.07-4,26.07-22.8-5.74-38.06-6-23,5.77-28.09,13.74-2.22,18.75-.44,23a21.08,21.08,0,0,0,3.63,6.14,88.82,88.82,0,0,0-5,22.13c7.8,10.18,33.56,26,33.56,26s6.54,3.47,5.38,9.55-18,13.91-30.55,11.74c-.53-.09-1.09-.21-1.68-.35C206.45,478.28,227,496.27,227,496.27s40.86-2.08,64-21.89a100.57,100.57,0,0,0,8.1-7.77c-7-2.86-13.59-5.9-17.28-8.48-10.47-7.29-16.92-24.69-13.48-29.82s10.54-3,10.54-3,29.43,6.88,42.13,5.11a21.1,21.1,0,0,0,6.65-2.57c4-2.22,13-8.87,13.88-18.31S340.1,390.61,328,381.35s-33.59-18.88-33.59-18.88,11.6-19.83,13-23,1.7-9.17-.44-10.63-19.05-8.05-19.05-8.05S270.52,315.53,267.93,315.35Z" transform="translate(-108.03 -17)"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
351
7-bank-project/solution/styles.css
Normal file
351
7-bank-project/solution/styles.css
Normal file
@@ -0,0 +1,351 @@
|
||||
:root {
|
||||
/* Colors */
|
||||
--primary: #0091ea;
|
||||
--primary-light: #7bbde6;
|
||||
--accent: #546e7a;
|
||||
--grey: #445;
|
||||
--error: #f52;
|
||||
--background: #f5f5f6;
|
||||
--background-accent: #cfe5f2;
|
||||
--white: #fff;
|
||||
--border: #99a;
|
||||
/* Sizes */
|
||||
--radius: 10px;
|
||||
--space-xs: 5px;
|
||||
--space-sm: 10px;
|
||||
--space-md: 20px;
|
||||
--space-xl: 40px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Micro reset */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* General styles */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--primary);
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
margin: var(--space-md) 0;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: var(--space-sm) var(--space-md);
|
||||
}
|
||||
|
||||
input {
|
||||
margin-top: var(--space-xs);
|
||||
margin-bottom: var(--space-sm);
|
||||
height: 40px;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: var(--primary);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--grey);
|
||||
text-transform: uppercase;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
button {
|
||||
font-weight: bold;
|
||||
background-color: var(--primary);
|
||||
color: var(--white);
|
||||
height: 40px;
|
||||
padding: var(--space-xs);
|
||||
border: 0;
|
||||
border-radius: var(--radius);
|
||||
text-transform: uppercase;
|
||||
min-width: 100px;
|
||||
margin: var(--space-sm) 0;
|
||||
}
|
||||
|
||||
.button-alt {
|
||||
background-color: transparent;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(115%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
border: 2px solid var(--grey);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
margin: var(--space-xs) 0;
|
||||
}
|
||||
|
||||
.error:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Login page */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.login-title {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: var(--white);
|
||||
margin: var(--space-md);
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
height: 80px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(var(--primary), var(--primary-light));
|
||||
}
|
||||
|
||||
.login-container {
|
||||
flex: auto;
|
||||
max-width: 480px;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.login-content {
|
||||
background-color: var(--background);
|
||||
padding: var(--space-sm);
|
||||
}
|
||||
|
||||
.login-separator {
|
||||
position: relative;
|
||||
top: 0.5em;
|
||||
border-top: 1px solid var(--border);
|
||||
opacity: 0.6;
|
||||
margin: var(--space-md) 0;
|
||||
}
|
||||
|
||||
.login-separator > span {
|
||||
position: relative;
|
||||
top: -0.5em;
|
||||
background-color: var(--background);
|
||||
padding: var(--space-sm);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Dashboard page */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.dashboard-page {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
background-color: var(--grey);
|
||||
padding: 0 var(--space-sm)
|
||||
}
|
||||
|
||||
.dashboard-header button {
|
||||
float: right;
|
||||
border: 1px solid;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--white);
|
||||
vertical-align: middle;
|
||||
margin: 0 var(--space-sm)
|
||||
}
|
||||
|
||||
.dashboard-logo {
|
||||
height: 60px;
|
||||
vertical-align: middle;
|
||||
padding: var(--space-xs);
|
||||
}
|
||||
|
||||
.balance {
|
||||
background: radial-gradient(circle at center, var(--primary), var(--primary-light));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.balance > div {
|
||||
color: var(--white);
|
||||
padding-top: var(--space-xs);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.balance > span {
|
||||
color: var(--white);
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.transactions-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-sm);
|
||||
color: var(--accent);
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.transactions-title > div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.transactions-table {
|
||||
width: 100%;
|
||||
font-size: 1.2rem;
|
||||
padding: var(--space-sm);
|
||||
margin: 0;
|
||||
border-spacing: 0;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.transactions-table thead th {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.transactions-table tr:nth-child(even) {
|
||||
background-color: var(--background-accent);
|
||||
}
|
||||
|
||||
.transactions-table td,
|
||||
.transactions-table th {
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.transactions-table td:first-child,
|
||||
.transactions-table th:first-child {
|
||||
/* Make first column use the minimum width */
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.transactions-table td:last-child,
|
||||
.transactions-table th:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
animation: slideFromTop 0.3s ease-in-out;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.dialog.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes slideFromTop {
|
||||
from {
|
||||
top: -300px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
flex: auto;
|
||||
background-color: var(--white);
|
||||
max-width: 480px;
|
||||
max-height: 100%;
|
||||
padding: var(--space-sm);
|
||||
}
|
||||
|
||||
.dialog-buttons {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Utilities */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hide-xs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/* Responsive adaptations */
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
@media only screen and (min-width: 480px) {
|
||||
.hide-xs {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.login-content,
|
||||
.dialog-content {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
margin-top: var(--space-xl);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.transactions-table {
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.dashboard-content {
|
||||
width: 100%;
|
||||
max-width: 768px;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
13
7-bank-project/solution/translations/README.es.md
Normal file
13
7-bank-project/solution/translations/README.es.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Aplicación bancaria
|
||||
|
||||
> Solución de ejemplo para el proyecto de la aplicación bancaria, construida con HTML5 vanilla, CSS y JavaScript (sin marcos ni bibliotecas).
|
||||
|
||||
## Ejecutando la aplicación
|
||||
|
||||
Primero asegúrese de tener el [servidor API](../api/README.md) en ejecución.
|
||||
|
||||
Se puede usar cualquier servidor web para ejecutar la aplicación, pero dado que debe tener [Node.js]https://nodejs.org) instalado de todos modos para ejecutar la API, puede:
|
||||
|
||||
1. Git clone este repositorio.
|
||||
2. Abra una terminal, luego ejecute `npx lite-server solution`. Iniciará un servidor web de desarrollo en el puerto `3000`
|
||||
3. Abra `http://localhost: 3000` en un navegador para ejecutar la aplicación.
|
Reference in New Issue
Block a user