Add full-stack BTC portfolio web app

Multi-user FastAPI + React app with JWT auth, SQLite storage, and
CoinGecko price integration. Dockerized with docker-compose.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-03-23 22:15:40 +01:00
parent 84679639ef
commit 3907414742
27 changed files with 859 additions and 0 deletions
@@ -0,0 +1,76 @@
import React, { useState } from 'react';
const API = process.env.REACT_APP_API_URL || 'http://localhost:8000';
const styles = {
card: { background: '#1a1a1a', padding: '1.5rem', borderRadius: '12px', border: '1px solid #333', marginBottom: '1.5rem' },
title: { fontSize: '1.1rem', fontWeight: 700, marginBottom: '1rem', color: '#f7931a' },
row: { display: 'flex', gap: '0.75rem', flexWrap: 'wrap' },
input: { flex: 1, minWidth: '140px', padding: '0.65rem', background: '#2a2a2a', border: '1px solid #444', borderRadius: '8px', color: '#e0e0e0', fontSize: '1rem' },
button: { padding: '0.65rem 1.5rem', background: '#f7931a', color: '#000', border: 'none', borderRadius: '8px', fontWeight: 700, cursor: 'pointer' },
error: { color: '#ff6b6b', marginTop: '0.5rem', fontSize: '0.9rem' },
};
export default function AddPurchase({ onAdded }) {
const [amountEur, setAmountEur] = useState('');
const [priceEur, setPriceEur] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
const token = localStorage.getItem('token');
try {
const res = await fetch(`${API}/purchases`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
amount_eur: parseFloat(amountEur),
price_eur: parseFloat(priceEur),
}),
});
if (!res.ok) {
setError('Failed to add purchase');
return;
}
setAmountEur('');
setPriceEur('');
onAdded();
} catch {
setError('Network error');
}
};
return (
<div style={styles.card}>
<div style={styles.title}>Add Purchase</div>
<form onSubmit={handleSubmit}>
<div style={styles.row}>
<input
style={styles.input}
type="number"
step="any"
placeholder="Amount (EUR)"
value={amountEur}
onChange={e => setAmountEur(e.target.value)}
required
/>
<input
style={styles.input}
type="number"
step="any"
placeholder="BTC Price (EUR)"
value={priceEur}
onChange={e => setPriceEur(e.target.value)}
required
/>
<button style={styles.button} type="submit">Add</button>
</div>
{error && <div style={styles.error}>{error}</div>}
</form>
</div>
);
}