fmt: apply prettier to frontend JS/TS/CSS/JSON files

Initial prettier pass with tabWidth=4.
This commit is contained in:
KS Jannette
2026-02-28 20:07:28 -05:00
parent a5d1bc171c
commit da8b45012a
23 changed files with 4590 additions and 4343 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -7,56 +7,71 @@ import Alerts from "./pages/Alerts";
import AlertHistory from "./pages/AlertHistory"; import AlertHistory from "./pages/AlertHistory";
export default function App() { export default function App() {
const { currentUser, logout } = useAuth(); const { currentUser, logout } = useAuth();
// Show login/signup routes if not authenticated // Show login/signup routes if not authenticated
if (!currentUser) { if (!currentUser) {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="*" element={<Navigate to="/login" />} />
</Routes>
);
}
// Show main app if authenticated
return ( return (
<Routes> <div style={{ padding: "1rem" }}>
<Route path="/login" element={<Login />} /> <nav
<Route path="/signup" element={<Signup />} /> style={{
<Route path="*" element={<Navigate to="/login" />} /> marginBottom: "1rem",
</Routes> display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div>
<Link to="/addresses">Addresses</Link>
{" | "}
<Link to="/alerts">Alerts</Link>
{" | "}
<Link to="/history">History</Link>
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: "1rem",
}}
>
<span style={{ fontSize: "0.9rem", color: "#999" }}>
{currentUser.email}
</span>
<button
onClick={logout}
style={{
padding: "0.5rem 1rem",
fontSize: "0.9rem",
backgroundColor: "#333",
color: "white",
border: "1px solid #555",
borderRadius: "4px",
cursor: "pointer",
}}
>
Logout
</button>
</div>
</nav>
<Routes>
<Route path="/" element={<Addresses />} />
<Route path="/addresses" element={<Addresses />} />
<Route path="/alerts" element={<Alerts />} />
<Route path="/history" element={<AlertHistory />} />
<Route path="*" element={<Navigate to="/addresses" />} />
</Routes>
</div>
); );
}
// Show main app if authenticated
return (
<div style={{ padding: "1rem" }}>
<nav style={{ marginBottom: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div>
<Link to="/addresses">Addresses</Link>{" | "}
<Link to="/alerts">Alerts</Link>{" | "}
<Link to="/history">History</Link>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
<span style={{ fontSize: "0.9rem", color: "#999" }}>
{currentUser.email}
</span>
<button
onClick={logout}
style={{
padding: "0.5rem 1rem",
fontSize: "0.9rem",
backgroundColor: "#333",
color: "white",
border: "1px solid #555",
borderRadius: "4px",
cursor: "pointer"
}}
>
Logout
</button>
</div>
</nav>
<Routes>
<Route path="/" element={<Addresses />} />
<Route path="/addresses" element={<Addresses />} />
<Route path="/alerts" element={<Alerts />} />
<Route path="/history" element={<AlertHistory />} />
<Route path="*" element={<Navigate to="/addresses" />} />
</Routes>
</div>
);
} }

View File

@@ -1,7 +1,7 @@
// API client for address management // API client for address management
import { getAuthHeaders, getAuthHeadersSimple } from './authHeaders'; import { getAuthHeaders, getAuthHeadersSimple } from "./authHeaders";
import { API_BASE } from './config'; import { API_BASE } from "./config";
/** /**
* Create a new blockchain address to monitor * Create a new blockchain address to monitor
@@ -11,35 +11,37 @@ import { API_BASE } from './config';
* @returns {Promise<Object>} Created address with id and metadata * @returns {Promise<Object>} Created address with id and metadata
*/ */
export async function createAddress(data) { export async function createAddress(data) {
try { try {
const headers = await getAuthHeaders(); const headers = await getAuthHeaders();
const response = await fetch(`${API_BASE}/addresses`, { const response = await fetch(`${API_BASE}/addresses`, {
method: 'POST', method: "POST",
headers: headers, headers: headers,
body: JSON.stringify(data) body: JSON.stringify(data),
}); });
if (!response.ok) { if (!response.ok) {
// Try to parse error response, but handle if it's not JSON // Try to parse error response, but handle if it's not JSON
let errorMessage = 'Failed to create address'; let errorMessage = "Failed to create address";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
// If JSON parse fails, use status text // If JSON parse fails, use status text
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
// Handle network errors // Handle network errors
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -47,33 +49,35 @@ export async function createAddress(data) {
* @returns {Promise<Array>} List of all addresses * @returns {Promise<Array>} List of all addresses
*/ */
export async function getAddresses() { export async function getAddresses() {
try { try {
const headers = await getAuthHeadersSimple(); const headers = await getAuthHeadersSimple();
const response = await fetch(`${API_BASE}/addresses`, { const response = await fetch(`${API_BASE}/addresses`, {
headers: headers headers: headers,
}); });
if (!response.ok) { if (!response.ok) {
// Try to parse error response, but handle if it's not JSON // Try to parse error response, but handle if it's not JSON
let errorMessage = 'Failed to fetch addresses'; let errorMessage = "Failed to fetch addresses";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
// If JSON parse fails, use status text // If JSON parse fails, use status text
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
// Handle network errors or JSON parse errors // Handle network errors or JSON parse errors
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -82,31 +86,32 @@ export async function getAddresses() {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function deleteAddress(addressId) { export async function deleteAddress(addressId) {
try { try {
const headers = await getAuthHeadersSimple(); const headers = await getAuthHeadersSimple();
const response = await fetch(`${API_BASE}/addresses/${addressId}`, { const response = await fetch(`${API_BASE}/addresses/${addressId}`, {
method: 'DELETE', method: "DELETE",
headers: headers headers: headers,
}); });
if (!response.ok && response.status !== 204) { if (!response.ok && response.status !== 204) {
// Try to parse error response, but handle if it's not JSON // Try to parse error response, but handle if it's not JSON
let errorMessage = 'Failed to delete address'; let errorMessage = "Failed to delete address";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
// If JSON parse fails, use status text // If JSON parse fails, use status text
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
}
} catch (error) {
// Handle network errors
if (error.message.includes("fetch")) {
throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
} catch (error) {
// Handle network errors
if (error.message.includes('fetch')) {
throw new Error('Cannot connect to server. Is the backend running?');
}
throw error;
}
} }

View File

@@ -1,35 +1,37 @@
// API client for alert event history // API client for alert event history
import { getAuthHeadersSimple } from './authHeaders'; import { getAuthHeadersSimple } from "./authHeaders";
import { API_BASE } from './config'; import { API_BASE } from "./config";
/** /**
* Get all alert events (history) * Get all alert events (history)
* @returns {Promise<Array>} List of alert events, sorted by most recent first * @returns {Promise<Array>} List of alert events, sorted by most recent first
*/ */
export async function getAlertEvents() { export async function getAlertEvents() {
try { try {
const headers = await getAuthHeadersSimple(); const headers = await getAuthHeadersSimple();
const response = await fetch(`${API_BASE}/alert-events`, { const response = await fetch(`${API_BASE}/alert-events`, {
headers: headers headers: headers,
}); });
if (!response.ok) { if (!response.ok) {
let errorMessage = 'Failed to fetch alert events'; let errorMessage = "Failed to fetch alert events";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }

View File

@@ -1,16 +1,16 @@
// API client for alert rule management // API client for alert rule management
import { getAuthHeaders, getAuthHeadersSimple } from './authHeaders'; import { getAuthHeaders, getAuthHeadersSimple } from "./authHeaders";
import { API_BASE } from './config'; import { API_BASE } from "./config";
/** /**
* Alert types supported by the system * Alert types supported by the system
*/ */
export const ALERT_TYPES = { export const ALERT_TYPES = {
INCOMING_TX: 'incoming_tx', INCOMING_TX: "incoming_tx",
OUTGOING_TX: 'outgoing_tx', OUTGOING_TX: "outgoing_tx",
LARGE_TRANSFER: 'large_transfer', LARGE_TRANSFER: "large_transfer",
BALANCE_BELOW: 'balance_below' BALANCE_BELOW: "balance_below",
}; };
/** /**
@@ -22,32 +22,37 @@ export const ALERT_TYPES = {
* @returns {Promise<Object>} Created alert rule * @returns {Promise<Object>} Created alert rule
*/ */
export async function createAlert(addressId, data) { export async function createAlert(addressId, data) {
try { try {
const headers = await getAuthHeaders(); const headers = await getAuthHeaders();
const response = await fetch(`${API_BASE}/addresses/${addressId}/alerts`, { const response = await fetch(
method: 'POST', `${API_BASE}/addresses/${addressId}/alerts`,
headers: headers, {
body: JSON.stringify(data) method: "POST",
}); headers: headers,
body: JSON.stringify(data),
},
);
if (!response.ok) { if (!response.ok) {
let errorMessage = 'Failed to create alert rule'; let errorMessage = "Failed to create alert rule";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -56,30 +61,35 @@ export async function createAlert(addressId, data) {
* @returns {Promise<Array>} List of alert rules * @returns {Promise<Array>} List of alert rules
*/ */
export async function getAlerts(addressId) { export async function getAlerts(addressId) {
try { try {
const headers = await getAuthHeadersSimple(); const headers = await getAuthHeadersSimple();
const response = await fetch(`${API_BASE}/addresses/${addressId}/alerts`, { const response = await fetch(
headers: headers `${API_BASE}/addresses/${addressId}/alerts`,
}); {
headers: headers,
},
);
if (!response.ok) { if (!response.ok) {
let errorMessage = 'Failed to fetch alert rules'; let errorMessage = "Failed to fetch alert rules";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -89,32 +99,34 @@ export async function getAlerts(addressId) {
* @returns {Promise<Object>} Updated alert rule * @returns {Promise<Object>} Updated alert rule
*/ */
export async function updateAlertStatus(alertId, enabled) { export async function updateAlertStatus(alertId, enabled) {
try { try {
const headers = await getAuthHeaders(); const headers = await getAuthHeaders();
const response = await fetch(`${API_BASE}/alerts/${alertId}`, { const response = await fetch(`${API_BASE}/alerts/${alertId}`, {
method: 'PATCH', method: "PATCH",
headers: headers, headers: headers,
body: JSON.stringify({ enabled }) body: JSON.stringify({ enabled }),
}); });
if (!response.ok) { if (!response.ok) {
let errorMessage = 'Failed to update alert rule'; let errorMessage = "Failed to update alert rule";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -123,27 +135,29 @@ export async function updateAlertStatus(alertId, enabled) {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function deleteAlert(alertId) { export async function deleteAlert(alertId) {
try { try {
const headers = await getAuthHeadersSimple(); const headers = await getAuthHeadersSimple();
const response = await fetch(`${API_BASE}/alerts/${alertId}`, { const response = await fetch(`${API_BASE}/alerts/${alertId}`, {
method: 'DELETE', method: "DELETE",
headers: headers headers: headers,
}); });
if (!response.ok && response.status !== 204) { if (!response.ok && response.status !== 204) {
let errorMessage = 'Failed to delete alert rule'; let errorMessage = "Failed to delete alert rule";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
}
} catch (error) {
if (error.message.includes("fetch")) {
throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
} catch (error) {
if (error.message.includes('fetch')) {
throw new Error('Cannot connect to server. Is the backend running?');
}
throw error;
}
} }

View File

@@ -5,26 +5,26 @@
* Includes Firebase ID token in Authorization header * Includes Firebase ID token in Authorization header
*/ */
import { auth } from '../firebase/config'; import { auth } from "../firebase/config";
/** /**
* Get headers with authentication token * Get headers with authentication token
* @returns {Promise<Object>} Headers object with Authorization * @returns {Promise<Object>} Headers object with Authorization
*/ */
export async function getAuthHeaders() { export async function getAuthHeaders() {
const currentUser = auth.currentUser; const currentUser = auth.currentUser;
if (!currentUser) { if (!currentUser) {
throw new Error('No authenticated user'); throw new Error("No authenticated user");
} }
// Get Firebase ID token // Get Firebase ID token
const token = await currentUser.getIdToken(); const token = await currentUser.getIdToken();
return { return {
'Content-Type': 'application/json', "Content-Type": "application/json",
'Authorization': `Bearer ${token}` Authorization: `Bearer ${token}`,
}; };
} }
/** /**
@@ -32,16 +32,15 @@ export async function getAuthHeaders() {
* @returns {Promise<Object>} Headers object with Authorization * @returns {Promise<Object>} Headers object with Authorization
*/ */
export async function getAuthHeadersSimple() { export async function getAuthHeadersSimple() {
const currentUser = auth.currentUser; const currentUser = auth.currentUser;
if (!currentUser) { if (!currentUser) {
throw new Error('No authenticated user'); throw new Error("No authenticated user");
} }
const token = await currentUser.getIdToken(); const token = await currentUser.getIdToken();
return { return {
'Authorization': `Bearer ${token}` Authorization: `Bearer ${token}`,
}; };
} }

View File

@@ -1 +1 @@
export const API_BASE = import.meta.env.VITE_API_BASE || '/v1'; export const API_BASE = import.meta.env.VITE_API_BASE || "/v1";

View File

@@ -1,37 +1,39 @@
// API client for notification configuration // API client for notification configuration
import { getAuthHeaders } from './authHeaders'; import { getAuthHeaders } from "./authHeaders";
import { API_BASE } from './config'; import { API_BASE } from "./config";
/** /**
* Get notification configuration for current user * Get notification configuration for current user
* @returns {Promise<Object>} Notification config * @returns {Promise<Object>} Notification config
*/ */
export async function getNotificationConfig() { export async function getNotificationConfig() {
try { try {
const headers = await getAuthHeaders(); const headers = await getAuthHeaders();
const response = await fetch(`${API_BASE}/notification-config`, { const response = await fetch(`${API_BASE}/notification-config`, {
headers: headers headers: headers,
}); });
if (!response.ok) { if (!response.ok) {
let errorMessage = 'Failed to fetch notification config'; let errorMessage = "Failed to fetch notification config";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -44,32 +46,34 @@ export async function getNotificationConfig() {
* @returns {Promise<Object>} Updated config * @returns {Promise<Object>} Updated config
*/ */
export async function updateNotificationConfig(config) { export async function updateNotificationConfig(config) {
try { try {
const headers = await getAuthHeaders(); const headers = await getAuthHeaders();
const response = await fetch(`${API_BASE}/notification-config`, { const response = await fetch(`${API_BASE}/notification-config`, {
method: 'PUT', method: "PUT",
headers: headers, headers: headers,
body: JSON.stringify(config) body: JSON.stringify(config),
}); });
if (!response.ok) { if (!response.ok) {
let errorMessage = 'Failed to update notification config'; let errorMessage = "Failed to update notification config";
try { try {
const error = await response.json(); const error = await response.json();
errorMessage = error.message || errorMessage; errorMessage = error.message || errorMessage;
} catch { } catch {
errorMessage = `Server error: ${response.status} ${response.statusText}`; errorMessage = `Server error: ${response.status} ${response.statusText}`;
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
return response.json(); return response.json();
} catch (error) { } catch (error) {
if (error.message.includes('fetch')) { if (error.message.includes("fetch")) {
throw new Error('Cannot connect to server. Is the backend running?'); throw new Error(
"Cannot connect to server. Is the backend running?",
);
}
throw error;
} }
throw error;
}
} }
/** /**
@@ -79,21 +83,21 @@ export async function updateNotificationConfig(config) {
* @returns {Promise<boolean>} True if test successful * @returns {Promise<boolean>} True if test successful
*/ */
export async function testDiscordWebhook(webhookUrl) { export async function testDiscordWebhook(webhookUrl) {
try { try {
const payload = { const payload = {
content: 'Koin Ping test notification - Your Discord webhook is configured correctly!', content:
}; "Koin Ping test notification - Your Discord webhook is configured correctly!",
};
const response = await fetch(webhookUrl, { const response = await fetch(webhookUrl, {
method: 'POST', method: "POST",
headers: { 'Content-Type': 'application/json' }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) body: JSON.stringify(payload),
}); });
return response.ok; return response.ok;
} catch (error) { } catch (error) {
console.error('Discord webhook test failed:', error); console.error("Discord webhook test failed:", error);
return false; return false;
} }
} }

View File

@@ -1,18 +1,18 @@
// API client for system status // API client for system status
import { API_BASE } from './config'; import { API_BASE } from "./config";
/** /**
* Get system status including latest processed block and health * Get system status including latest processed block and health
* @returns {Promise<Object>} System status * @returns {Promise<Object>} System status
*/ */
export async function getSystemStatus() { export async function getSystemStatus() {
const response = await fetch(`${API_BASE}/status`); const response = await fetch(`${API_BASE}/status`);
if (!response.ok) { if (!response.ok) {
const error = await response.json(); const error = await response.json();
throw new Error(error.message || 'Failed to fetch system status'); throw new Error(error.message || "Failed to fetch system status");
} }
return response.json(); return response.json();
} }

View File

@@ -3,43 +3,43 @@ import Input from "./Input";
import Button from "./Button"; import Button from "./Button";
export default function AddressForm({ onSubmit }) { export default function AddressForm({ onSubmit }) {
const [address, setAddress] = useState(""); const [address, setAddress] = useState("");
const [label, setLabel] = useState(""); const [label, setLabel] = useState("");
const canSubmit = address.trim().length > 0; const canSubmit = address.trim().length > 0;
function handleSubmit(e) { function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (!canSubmit) return; if (!canSubmit) return;
onSubmit({ onSubmit({
address: address.trim(), address: address.trim(),
label: label.trim(), label: label.trim(),
}); });
setAddress(""); setAddress("");
setLabel(""); setLabel("");
} }
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<Input <Input
label="Blockchain Address" label="Blockchain Address"
value={address} value={address}
onChange={setAddress} onChange={setAddress}
placeholder="0x..." placeholder="0x..."
/> />
<Input <Input
label="Label (optional)" label="Label (optional)"
value={label} value={label}
onChange={setLabel} onChange={setLabel}
placeholder="Treasury, Cold Wallet, etc." placeholder="Treasury, Cold Wallet, etc."
/> />
<Button type="submit" disabled={!canSubmit}> <Button type="submit" disabled={!canSubmit}>
Add Address Add Address
</Button> </Button>
</form> </form>
); );
} }

View File

@@ -3,69 +3,69 @@ import Input from "./Input";
import Button from "./Button"; import Button from "./Button";
const ALERT_TYPES = [ const ALERT_TYPES = [
{ value: "incoming_tx", label: "Incoming transaction" }, { value: "incoming_tx", label: "Incoming transaction" },
{ value: "outgoing_tx", label: "Outgoing transaction" }, { value: "outgoing_tx", label: "Outgoing transaction" },
{ value: "large_transfer", label: "Large transfer" }, { value: "large_transfer", label: "Large transfer" },
{ value: "balance_below", label: "Balance below threshold" }, { value: "balance_below", label: "Balance below threshold" },
]; ];
export default function AlertForm({ onSubmit }) { export default function AlertForm({ onSubmit }) {
const [type, setType] = useState("incoming_tx"); const [type, setType] = useState("incoming_tx");
const [threshold, setThreshold] = useState(""); const [threshold, setThreshold] = useState("");
const needsThreshold = const needsThreshold =
type === "large_transfer" || type === "balance_below"; type === "large_transfer" || type === "balance_below";
const canSubmit = needsThreshold const canSubmit = needsThreshold
? threshold.trim() !== "" && ? threshold.trim() !== "" &&
!isNaN(Number(threshold)) && !isNaN(Number(threshold)) &&
Number(threshold) > 0 Number(threshold) > 0
: true; : true;
function handleSubmit(e) { function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (!canSubmit) return; if (!canSubmit) return;
onSubmit({ onSubmit({
type, type,
threshold: needsThreshold ? Number(threshold) : undefined, threshold: needsThreshold ? Number(threshold) : undefined,
}); });
setThreshold(""); setThreshold("");
} }
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div style={{ marginBottom: "1rem" }}> <div style={{ marginBottom: "1rem" }}>
{ALERT_TYPES.map((opt) => ( {ALERT_TYPES.map((opt) => (
<label key={opt.value} style={{ display: "block" }}> <label key={opt.value} style={{ display: "block" }}>
<input <input
type="radio" type="radio"
name="alertType" name="alertType"
value={opt.value} value={opt.value}
checked={type === opt.value} checked={type === opt.value}
onChange={() => setType(opt.value)} onChange={() => setType(opt.value)}
/> />{" "}
{" "}{opt.label} {opt.label}
</label> </label>
))} ))}
</div> </div>
{needsThreshold && ( {needsThreshold && (
<Input <Input
label="Amount (ETH)" label="Amount (ETH)"
type="number" type="number"
step="0.000001" step="0.000001"
min="0" min="0"
value={threshold} value={threshold}
onChange={setThreshold} onChange={setThreshold}
placeholder="e.g. 10" placeholder="e.g. 10"
/> />
)} )}
<Button type="submit" disabled={!canSubmit}> <Button type="submit" disabled={!canSubmit}>
Create Alert Create Alert
</Button> </Button>
</form> </form>
); );
} }

View File

@@ -3,21 +3,20 @@ export default function Button({
onClick, onClick,
disabled = false, disabled = false,
type = "button", type = "button",
}) { }) {
return ( return (
<button <button
type={type} type={type}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
style={{ style={{
padding: "0.5rem 1rem", padding: "0.5rem 1rem",
fontSize: "1rem", fontSize: "1rem",
cursor: disabled ? "not-allowed" : "pointer", cursor: disabled ? "not-allowed" : "pointer",
opacity: disabled ? 0.6 : 1, opacity: disabled ? 0.6 : 1,
}} }}
> >
{children} {children}
</button> </button>
); );
} }

View File

@@ -7,27 +7,26 @@ export default function Input({
step, step,
min, min,
disabled = false, disabled = false,
}) { }) {
return ( return (
<label style={{ display: "block", marginBottom: "1rem" }}> <label style={{ display: "block", marginBottom: "1rem" }}>
<div style={{ marginBottom: "0.25rem", fontSize: "0.9rem" }}> <div style={{ marginBottom: "0.25rem", fontSize: "0.9rem" }}>
{label} {label}
</div> </div>
<input <input
type={type} type={type}
value={value} value={value}
placeholder={placeholder} placeholder={placeholder}
step={step} step={step}
min={min} min={min}
disabled={disabled} disabled={disabled}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
style={{ style={{
width: "100%", width: "100%",
padding: "0.5rem", padding: "0.5rem",
fontSize: "1rem", fontSize: "1rem",
}} }}
/> />
</label> </label>
); );
} }

View File

@@ -4,14 +4,14 @@
* Provides authentication state and methods throughout the app * Provides authentication state and methods throughout the app
*/ */
import { createContext, useContext, useEffect, useState } from 'react'; import { createContext, useContext, useEffect, useState } from "react";
import { import {
createUserWithEmailAndPassword, createUserWithEmailAndPassword,
signInWithEmailAndPassword, signInWithEmailAndPassword,
signOut, signOut,
onAuthStateChanged onAuthStateChanged,
} from 'firebase/auth'; } from "firebase/auth";
import { auth } from '../firebase/config'; import { auth } from "../firebase/config";
const AuthContext = createContext(); const AuthContext = createContext();
@@ -20,88 +20,95 @@ const AuthContext = createContext();
* @returns {Object} Auth context value * @returns {Object} Auth context value
*/ */
export function useAuth() { export function useAuth() {
const context = useContext(AuthContext); const context = useContext(AuthContext);
if (!context) { if (!context) {
throw new Error('useAuth must be used within AuthProvider'); throw new Error("useAuth must be used within AuthProvider");
} }
return context; return context;
} }
/** /**
* AuthProvider - Wraps app and provides auth state * AuthProvider - Wraps app and provides auth state
*/ */
export function AuthProvider({ children }) { export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(null); const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
/** /**
* Sign up with email and password * Sign up with email and password
*/ */
async function signup(email, password) { async function signup(email, password) {
try { try {
setError(null); setError(null);
const result = await createUserWithEmailAndPassword(auth, email, password); const result = await createUserWithEmailAndPassword(
return result.user; auth,
} catch (err) { email,
setError(err.message); password,
throw err; );
return result.user;
} catch (err) {
setError(err.message);
throw err;
}
} }
}
/** /**
* Log in with email and password * Log in with email and password
*/ */
async function login(email, password) { async function login(email, password) {
try { try {
setError(null); setError(null);
const result = await signInWithEmailAndPassword(auth, email, password); const result = await signInWithEmailAndPassword(
return result.user; auth,
} catch (err) { email,
setError(err.message); password,
throw err; );
return result.user;
} catch (err) {
setError(err.message);
throw err;
}
} }
}
/** /**
* Log out current user * Log out current user
*/ */
async function logout() { async function logout() {
try { try {
setError(null); setError(null);
await signOut(auth); await signOut(auth);
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
throw err; throw err;
}
} }
}
/** /**
* Listen for auth state changes * Listen for auth state changes
*/ */
useEffect(() => { useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => { const unsubscribe = onAuthStateChanged(auth, (user) => {
setCurrentUser(user); setCurrentUser(user);
setLoading(false); setLoading(false);
}); });
// Cleanup subscription // Cleanup subscription
return unsubscribe; return unsubscribe;
}, []); }, []);
const value = { const value = {
currentUser, currentUser,
signup, signup,
login, login,
logout, logout,
error, error,
loading loading,
}; };
return ( return (
<AuthContext.Provider value={value}> <AuthContext.Provider value={value}>
{!loading && children} {!loading && children}
</AuthContext.Provider> </AuthContext.Provider>
); );
} }

View File

@@ -1,6 +1,5 @@
import { initializeApp } from "firebase/app";
import { initializeApp } from 'firebase/app'; import { getAuth } from "firebase/auth";
import { getAuth } from 'firebase/auth';
import { import {
firebaseApiKey, firebaseApiKey,
firebaseAuthDomain, firebaseAuthDomain,
@@ -8,9 +7,8 @@ import {
firebaseStorageBucket, firebaseStorageBucket,
firebaseMessagingSenderId, firebaseMessagingSenderId,
firebaseAppId, firebaseAppId,
firebaseMeasurementId firebaseMeasurementId,
} from '../secrets.js'; } from "../secrets.js";
const firebaseConfig = { const firebaseConfig = {
apiKey: firebaseApiKey, apiKey: firebaseApiKey,
@@ -19,12 +17,11 @@ const firebaseConfig = {
storageBucket: firebaseStorageBucket, storageBucket: firebaseStorageBucket,
messagingSenderId: firebaseMessagingSenderId, messagingSenderId: firebaseMessagingSenderId,
appId: firebaseAppId, appId: firebaseAppId,
measurementId: firebaseMeasurementId measurementId: firebaseMeasurementId,
}; };
const app = initializeApp(firebaseConfig); const app = initializeApp(firebaseConfig);
export const auth = getAuth(app); export const auth = getAuth(app);
export default app; export default app;

View File

@@ -5,11 +5,11 @@ import { AuthProvider } from "./contexts/AuthContext";
import App from "./App"; import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render( ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<AuthProvider> <AuthProvider>
<App /> <App />
</AuthProvider> </AuthProvider>
</BrowserRouter> </BrowserRouter>
</React.StrictMode> </React.StrictMode>,
); );

View File

@@ -3,81 +3,91 @@ import AddressForm from "../components/AddressForm";
import { getAddresses, createAddress } from "../api/addresses"; import { getAddresses, createAddress } from "../api/addresses";
export default function Addresses() { export default function Addresses() {
const [addresses, setAddresses] = useState([]); const [addresses, setAddresses] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
// Load addresses on mount // Load addresses on mount
useEffect(() => { useEffect(() => {
async function fetchAddresses() { async function fetchAddresses() {
try { try {
setLoading(true); setLoading(true);
const data = await getAddresses(); const data = await getAddresses();
setAddresses(data); setAddresses(data);
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
console.error("Failed to fetch addresses:", err); console.error("Failed to fetch addresses:", err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}
fetchAddresses();
}, []);
// Handle new address submission
async function handleAddressSubmit(data) {
try {
const newAddress = await createAddress(data);
// Append new address to state
setAddresses((prev) => [...prev, newAddress]);
setError(null); // Clear any previous errors
} catch (err) {
setError(err.message);
console.error("Failed to create address:", err);
}
} }
fetchAddresses(); return (
}, []); <div style={{ maxWidth: "800px", margin: "0 auto", padding: "2rem" }}>
<h1>Tracked Addresses</h1>
// Handle new address submission <div style={{ marginBottom: "2rem" }}>
async function handleAddressSubmit(data) { <AddressForm onSubmit={handleAddressSubmit} />
try { </div>
const newAddress = await createAddress(data);
// Append new address to state
setAddresses((prev) => [...prev, newAddress]);
setError(null); // Clear any previous errors
} catch (err) {
setError(err.message);
console.error("Failed to create address:", err);
}
}
return ( <div>
<div style={{ maxWidth: "800px", margin: "0 auto", padding: "2rem" }}> <h2>Existing Addresses</h2>
<h1>Tracked Addresses</h1> {loading && <p>Loading addresses...</p>}
{error && <p style={{ color: "red" }}>Error: {error}</p>}
<div style={{ marginBottom: "2rem" }}> {!loading && !error && addresses.length === 0 && (
<AddressForm onSubmit={handleAddressSubmit} /> <p style={{ color: "#666" }}>
</div> No addresses tracked yet. Add one above to get started.
</p>
<div> )}
<h2>Existing Addresses</h2> {addresses.length > 0 && (
{loading && <p>Loading addresses...</p>} <ul style={{ listStyle: "none", padding: 0 }}>
{error && <p style={{ color: "red" }}>Error: {error}</p>} {addresses.map((addr, index) => (
{!loading && !error && addresses.length === 0 && ( <li
<p style={{ color: "#666" }}> key={addr.id || index}
No addresses tracked yet. Add one above to get started. style={{
</p> padding: "1rem",
)} marginBottom: "0.5rem",
{addresses.length > 0 && ( border: "1px solid #ddd",
<ul style={{ listStyle: "none", padding: 0 }}> borderRadius: "4px",
{addresses.map((addr, index) => ( }}
<li >
key={addr.id || index} <div
style={{ style={{
padding: "1rem", fontWeight: "bold",
marginBottom: "0.5rem", marginBottom: "0.25rem",
border: "1px solid #ddd", }}
borderRadius: "4px", >
}} {addr.label || "Unlabeled"}
> </div>
<div style={{ fontWeight: "bold", marginBottom: "0.25rem" }}> <div
{addr.label || "Unlabeled"} style={{
</div> fontFamily: "monospace",
<div style={{ fontFamily: "monospace", fontSize: "0.9rem" }}> fontSize: "0.9rem",
{addr.address} }}
</div> >
</li> {addr.address}
))} </div>
</ul> </li>
)} ))}
</div> </ul>
</div> )}
); </div>
</div>
);
} }

View File

@@ -2,73 +2,83 @@ import { useState, useEffect } from "react";
import { getAlertEvents } from "../api/alertEvents"; import { getAlertEvents } from "../api/alertEvents";
export default function AlertHistory() { export default function AlertHistory() {
const [alertEvents, setAlertEvents] = useState([]); const [alertEvents, setAlertEvents] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
// Fetch alert events on mount // Fetch alert events on mount
useEffect(() => { useEffect(() => {
async function fetchAlertEvents() { async function fetchAlertEvents() {
try { try {
setLoading(true); setLoading(true);
const data = await getAlertEvents(); const data = await getAlertEvents();
setAlertEvents(data); setAlertEvents(data);
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
console.error("Failed to fetch alert events:", err); console.error("Failed to fetch alert events:", err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}
fetchAlertEvents();
}, []);
if (loading) {
return <div style={{ padding: "2rem" }}>Loading...</div>;
} }
fetchAlertEvents(); if (error) {
}, []); return (
<div style={{ padding: "2rem", color: "red" }}>Error: {error}</div>
);
}
if (loading) { return (
return <div style={{ padding: "2rem" }}>Loading...</div>; <div style={{ maxWidth: "800px", margin: "0 auto", padding: "2rem" }}>
} <h1>Recent Alerts</h1>
if (error) { {alertEvents.length === 0 ? (
return <div style={{ padding: "2rem", color: "red" }}>Error: {error}</div>; <p style={{ color: "#666" }}>No alerts yet</p>
} ) : (
<ul style={{ listStyle: "none", padding: 0 }}>
return ( {alertEvents.map((event) => (
<div style={{ maxWidth: "800px", margin: "0 auto", padding: "2rem" }}> <li
<h1>Recent Alerts</h1> key={event.id}
style={{
{alertEvents.length === 0 ? ( padding: "1rem",
<p style={{ color: "#666" }}>No alerts yet</p> marginBottom: "0.75rem",
) : ( border: "1px solid #ddd",
<ul style={{ listStyle: "none", padding: 0 }}> borderRadius: "4px",
{alertEvents.map((event) => ( }}
<li >
key={event.id} <div style={{ marginBottom: "0.5rem" }}>
style={{ {event.message}
padding: "1rem", </div>
marginBottom: "0.75rem", {event.address_label && (
border: "1px solid #ddd", <div
borderRadius: "4px", style={{
}} fontSize: "0.9rem",
> color: "#666",
<div style={{ marginBottom: "0.5rem" }}>{event.message}</div> marginBottom: "0.25rem",
{event.address_label && ( }}
<div style={{ fontSize: "0.9rem", color: "#666", marginBottom: "0.25rem" }}> >
Address: {event.address_label} Address: {event.address_label}
</div> </div>
)} )}
<small style={{ color: "#999" }}> <small style={{ color: "#999" }}>
{formatTimestamp(event.timestamp)} {formatTimestamp(event.timestamp)}
</small> </small>
</li> </li>
))} ))}
</ul> </ul>
)} )}
</div> </div>
); );
} }
// Helper to format timestamp for display // Helper to format timestamp for display
function formatTimestamp(timestamp) { function formatTimestamp(timestamp) {
const date = new Date(timestamp); const date = new Date(timestamp);
return date.toLocaleString(); return date.toLocaleString();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,140 +4,143 @@
* Allows existing users to sign in with email and password * Allows existing users to sign in with email and password
*/ */
import { useState } from 'react'; import { useState } from "react";
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from "react-router-dom";
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from "../contexts/AuthContext";
export default function Login() { export default function Login() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState("");
const [password, setPassword] = useState(''); const [password, setPassword] = useState("");
const [error, setError] = useState(''); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { login } = useAuth(); const { login } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
// Validation // Validation
if (!email || !password) { if (!email || !password) {
setError('Please fill in all fields'); setError("Please fill in all fields");
return; return;
}
try {
setError("");
setLoading(true);
await login(email, password);
navigate("/addresses"); // Redirect to main app
} catch (err) {
setError("Failed to log in: " + err.message);
} finally {
setLoading(false);
}
} }
try { return (
setError(''); <div
setLoading(true);
await login(email, password);
navigate('/addresses'); // Redirect to main app
} catch (err) {
setError('Failed to log in: ' + err.message);
} finally {
setLoading(false);
}
}
return (
<div style={{
maxWidth: '400px',
margin: '4rem auto',
padding: '2rem',
border: '1px solid #333',
borderRadius: '8px'
}}>
<h1 style={{ marginBottom: '2rem', textAlign: 'center' }}>
Koin Ping - Login
</h1>
{error && (
<div style={{
padding: '0.75rem',
marginBottom: '1rem',
backgroundColor: '#ff000020',
border: '1px solid #ff0000',
borderRadius: '4px',
color: '#ff6666'
}}>
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '1rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem' }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
style={{ style={{
width: '100%', maxWidth: "400px",
padding: '0.5rem', margin: "4rem auto",
fontSize: '1rem', padding: "2rem",
backgroundColor: '#1a1a1a', border: "1px solid #333",
border: '1px solid #444', borderRadius: "8px",
borderRadius: '4px',
color: 'white'
}} }}
required
/>
</div>
<div style={{ marginBottom: '1.5rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem' }}>
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
style={{
width: '100%',
padding: '0.5rem',
fontSize: '1rem',
backgroundColor: '#1a1a1a',
border: '1px solid #444',
borderRadius: '4px',
color: 'white'
}}
required
/>
</div>
<button
type="submit"
disabled={loading}
style={{
width: '100%',
padding: '0.75rem',
fontSize: '1rem',
backgroundColor: loading ? '#333' : '#0066cc',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: loading ? 'not-allowed' : 'pointer'
}}
> >
{loading ? 'Logging in...' : 'Log In'} <h1 style={{ marginBottom: "2rem", textAlign: "center" }}>
</button> Koin Ping - Login
</form> </h1>
<div style={{ marginTop: '1.5rem', textAlign: 'center' }}> {error && (
<p style={{ color: '#999' }}> <div
Don't have an account?{' '} style={{
<Link padding: "0.75rem",
to="/signup" marginBottom: "1rem",
style={{ color: '#0066cc', textDecoration: 'none' }} backgroundColor: "#ff000020",
> border: "1px solid #ff0000",
Sign up here borderRadius: "4px",
</Link> color: "#ff6666",
</p> }}
</div> >
</div> {error}
); </div>
)}
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: "1rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
style={{
width: "100%",
padding: "0.5rem",
fontSize: "1rem",
backgroundColor: "#1a1a1a",
border: "1px solid #444",
borderRadius: "4px",
color: "white",
}}
required
/>
</div>
<div style={{ marginBottom: "1.5rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
style={{
width: "100%",
padding: "0.5rem",
fontSize: "1rem",
backgroundColor: "#1a1a1a",
border: "1px solid #444",
borderRadius: "4px",
color: "white",
}}
required
/>
</div>
<button
type="submit"
disabled={loading}
style={{
width: "100%",
padding: "0.75rem",
fontSize: "1rem",
backgroundColor: loading ? "#333" : "#0066cc",
color: "white",
border: "none",
borderRadius: "4px",
cursor: loading ? "not-allowed" : "pointer",
}}
>
{loading ? "Logging in..." : "Log In"}
</button>
</form>
<div style={{ marginTop: "1.5rem", textAlign: "center" }}>
<p style={{ color: "#999" }}>
Don't have an account?{" "}
<Link
to="/signup"
style={{ color: "#0066cc", textDecoration: "none" }}
>
Sign up here
</Link>
</p>
</div>
</div>
);
} }

View File

@@ -4,182 +4,185 @@
* Allows new users to create an account with email and password * Allows new users to create an account with email and password
*/ */
import { useState } from 'react'; import { useState } from "react";
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from "react-router-dom";
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from "../contexts/AuthContext";
export default function Signup() { export default function Signup() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState("");
const [password, setPassword] = useState(''); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState(''); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { signup } = useAuth(); const { signup } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
// Validation // Validation
if (!email || !password || !confirmPassword) { if (!email || !password || !confirmPassword) {
setError('Please fill in all fields'); setError("Please fill in all fields");
return; return;
}
if (password !== confirmPassword) {
setError("Passwords do not match");
return;
}
if (password.length < 6) {
setError("Password must be at least 6 characters");
return;
}
try {
setError("");
setLoading(true);
await signup(email, password);
navigate("/addresses"); // Auto-login and redirect
} catch (err) {
// Firebase-specific error messages
if (err.code === "auth/email-already-in-use") {
setError("Email already in use. Try logging in instead.");
} else if (err.code === "auth/invalid-email") {
setError("Invalid email address");
} else if (err.code === "auth/weak-password") {
setError("Password is too weak");
} else {
setError("Failed to create account: " + err.message);
}
} finally {
setLoading(false);
}
} }
if (password !== confirmPassword) { return (
setError('Passwords do not match'); <div
return;
}
if (password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
try {
setError('');
setLoading(true);
await signup(email, password);
navigate('/addresses'); // Auto-login and redirect
} catch (err) {
// Firebase-specific error messages
if (err.code === 'auth/email-already-in-use') {
setError('Email already in use. Try logging in instead.');
} else if (err.code === 'auth/invalid-email') {
setError('Invalid email address');
} else if (err.code === 'auth/weak-password') {
setError('Password is too weak');
} else {
setError('Failed to create account: ' + err.message);
}
} finally {
setLoading(false);
}
}
return (
<div style={{
maxWidth: '400px',
margin: '4rem auto',
padding: '2rem',
border: '1px solid #333',
borderRadius: '8px'
}}>
<h1 style={{ marginBottom: '2rem', textAlign: 'center' }}>
Koin Ping - Sign Up
</h1>
{error && (
<div style={{
padding: '0.75rem',
marginBottom: '1rem',
backgroundColor: '#ff000020',
border: '1px solid #ff0000',
borderRadius: '4px',
color: '#ff6666'
}}>
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '1rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem' }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
style={{ style={{
width: '100%', maxWidth: "400px",
padding: '0.5rem', margin: "4rem auto",
fontSize: '1rem', padding: "2rem",
backgroundColor: '#1a1a1a', border: "1px solid #333",
border: '1px solid #444', borderRadius: "8px",
borderRadius: '4px',
color: 'white'
}} }}
required
/>
</div>
<div style={{ marginBottom: '1rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem' }}>
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
style={{
width: '100%',
padding: '0.5rem',
fontSize: '1rem',
backgroundColor: '#1a1a1a',
border: '1px solid #444',
borderRadius: '4px',
color: 'white'
}}
required
/>
</div>
<div style={{ marginBottom: '1.5rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem' }}>
Confirm Password
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={loading}
style={{
width: '100%',
padding: '0.5rem',
fontSize: '1rem',
backgroundColor: '#1a1a1a',
border: '1px solid #444',
borderRadius: '4px',
color: 'white'
}}
required
/>
</div>
<button
type="submit"
disabled={loading}
style={{
width: '100%',
padding: '0.75rem',
fontSize: '1rem',
backgroundColor: loading ? '#333' : '#0066cc',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: loading ? 'not-allowed' : 'pointer'
}}
> >
{loading ? 'Creating account...' : 'Sign Up'} <h1 style={{ marginBottom: "2rem", textAlign: "center" }}>
</button> Koin Ping - Sign Up
</form> </h1>
<div style={{ marginTop: '1.5rem', textAlign: 'center' }}> {error && (
<p style={{ color: '#999' }}> <div
Already have an account?{' '} style={{
<Link padding: "0.75rem",
to="/login" marginBottom: "1rem",
style={{ color: '#0066cc', textDecoration: 'none' }} backgroundColor: "#ff000020",
> border: "1px solid #ff0000",
Log in here borderRadius: "4px",
</Link> color: "#ff6666",
</p> }}
</div> >
</div> {error}
); </div>
)}
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: "1rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
style={{
width: "100%",
padding: "0.5rem",
fontSize: "1rem",
backgroundColor: "#1a1a1a",
border: "1px solid #444",
borderRadius: "4px",
color: "white",
}}
required
/>
</div>
<div style={{ marginBottom: "1rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
style={{
width: "100%",
padding: "0.5rem",
fontSize: "1rem",
backgroundColor: "#1a1a1a",
border: "1px solid #444",
borderRadius: "4px",
color: "white",
}}
required
/>
</div>
<div style={{ marginBottom: "1.5rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Confirm Password
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={loading}
style={{
width: "100%",
padding: "0.5rem",
fontSize: "1rem",
backgroundColor: "#1a1a1a",
border: "1px solid #444",
borderRadius: "4px",
color: "white",
}}
required
/>
</div>
<button
type="submit"
disabled={loading}
style={{
width: "100%",
padding: "0.75rem",
fontSize: "1rem",
backgroundColor: loading ? "#333" : "#0066cc",
color: "white",
border: "none",
borderRadius: "4px",
cursor: loading ? "not-allowed" : "pointer",
}}
>
{loading ? "Creating account..." : "Sign Up"}
</button>
</form>
<div style={{ marginTop: "1.5rem", textAlign: "center" }}>
<p style={{ color: "#999" }}>
Already have an account?{" "}
<Link
to="/login"
style={{ color: "#0066cc", textDecoration: "none" }}
>
Log in here
</Link>
</p>
</div>
</div>
);
} }

View File

@@ -1,25 +1,24 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": ["src"] "include": ["src"]
} }

View File

@@ -1,17 +1,16 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
port: 3000, port: 3000,
proxy: { proxy: {
'/v1': { "/v1": {
target: 'http://localhost:3001', target: "http://localhost:3001",
changeOrigin: true, changeOrigin: true,
}, },
},
}, },
}, });
})