Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# testnet | mainnet (default: testnet)
STELLAR_NETWORK=testnet

# Frontend network — must match STELLAR_NETWORK; Vite requires the VITE_ prefix
# to expose env vars to the browser bundle at build time.
VITE_STELLAR_NETWORK=testnet

# Horizon REST API for the chosen network
HORIZON_URL=https://horizon-testnet.stellar.org

Expand Down
5 changes: 4 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ services:
restart: unless-stopped

frontend:
build: ./frontend
build:
context: ./frontend
args:
VITE_STELLAR_NETWORK: ${STELLAR_NETWORK:-testnet}
# No ports exposed — nginx serves static assets from ./frontend/dist volume
depends_on:
backend:
Expand Down
4 changes: 4 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ RUN npm install

COPY . .

# Accept network at build time so Vite bakes it into the static bundle
ARG VITE_STELLAR_NETWORK=testnet
ENV VITE_STELLAR_NETWORK=$VITE_STELLAR_NETWORK

RUN npm run build


Expand Down
24 changes: 21 additions & 3 deletions frontend/src/components/DemoBanner.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import { isTestnet } from '../config/network';

export default function DemoBanner() {
if (!isTestnet) return null;

return (
<div className="bg-yellow-500 text-slate-900 text-center py-2 px-4 font-bold text-sm sticky top-0 z-50 shadow-md">
⚠️ PUBLIC DEMO ENVIRONMENT — Connected to Stellar Testnet. Records are simulated and database is wiped weekly. Do not enter real medical data.
<div
role="status"
style={{
background: '#f59e0b',
color: '#1e293b',
textAlign: 'center',
padding: '0.5rem 1rem',
fontWeight: 700,
fontSize: '0.85rem',
position: 'sticky',
top: 0,
zIndex: 50,
boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
}}
>
TESTNET ENVIRONMENT -- Connected to Stellar Testnet. Records are simulated and the database is wiped weekly. Do not enter real medical data.
</div>
);
}
}
6 changes: 5 additions & 1 deletion frontend/src/components/NavBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Tooltip from './Tooltip';
import WalletConnectionProgress from './WalletConnectionProgress';
import { useAuth } from '../hooks/useFreighter';
import FeedbackButton from './FeedbackButton';
import NetworkBadge from './NetworkBadge';

const NAV_LINKS = [
{ to: '/', label: 'Home' },
Expand Down Expand Up @@ -194,7 +195,10 @@ export default function NavBar({ dark, onToggleDark }) {

return (
<nav aria-label="Main navigation" style={{ padding: '1rem 2rem', background: 'var(--nav-bg)', display: 'flex', flexWrap: 'wrap', gap: '1.5rem', alignItems: 'center', position: 'relative' }}>
<strong style={{ color: 'var(--accent)', fontSize: '1.2rem', flex: 1 }}>💉 VacciChain</strong>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', flex: 1 }}>
<strong style={{ color: 'var(--accent)', fontSize: '1.2rem' }}>VacciChain</strong>
<NetworkBadge />
</div>

{/* Hamburger button — visible only below 640px */}
<button
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/components/NetworkBadge.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { STELLAR_NETWORK, isTestnet } from '../config/network';

/**
* Small pill badge that shows the active Stellar network (testnet / mainnet).
* Testnet is rendered in amber/yellow; mainnet in green.
*/
export default function NetworkBadge() {
const label = isTestnet ? 'Testnet' : 'Mainnet';

const badgeStyle = {
display: 'inline-flex',
alignItems: 'center',
gap: '0.35rem',
padding: '0.25rem 0.6rem',
borderRadius: '9999px',
fontSize: '0.7rem',
fontWeight: 600,
letterSpacing: '0.025em',
textTransform: 'uppercase',
border: '1px solid',
whiteSpace: 'nowrap',
...(isTestnet
? { background: '#fef3c7', color: '#92400e', borderColor: '#fcd34d' }
: { background: '#dcfce7', color: '#166534', borderColor: '#86efac' }),
};

const dotStyle = {
width: 6,
height: 6,
borderRadius: '50%',
display: 'inline-block',
background: isTestnet ? '#f59e0b' : '#22c55e',
};

return (
<span style={badgeStyle} aria-label={"Active network: " + label} title={"Stellar " + label}>
<span aria-hidden="true" style={dotStyle} />
{label}
</span>
);
}
20 changes: 20 additions & 0 deletions frontend/src/config/network.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Network configuration derived from the VITE_STELLAR_NETWORK environment
* variable set at build time. Defaults to 'testnet' when not specified.
*
* Usage:
* import { STELLAR_NETWORK, isTestnet } from '../config/network';
*/

const STELLAR_NETWORK = (import.meta.env.VITE_STELLAR_NETWORK || 'testnet').toLowerCase();

const isTestnet = STELLAR_NETWORK === 'testnet';
const isMainnet = STELLAR_NETWORK === 'mainnet';

/**
* Freighter expects the network name in UPPER CASE ('TESTNET' | 'PUBLIC').
* Mainnet is called 'PUBLIC' in the Freighter/Stellar SDK.
*/
const freighterNetwork = isMainnet ? 'PUBLIC' : 'TESTNET';

export { STELLAR_NETWORK, isTestnet, isMainnet, freighterNetwork };
3 changes: 2 additions & 1 deletion frontend/src/hooks/useFreighter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
signTransaction,
} from '@stellar/freighter-api';
import { useToast } from './useToast';
import { freighterNetwork } from '../config/network';

const AuthContext = createContext(null);
// Only persist publicKey and role — token is kept in memory only
Expand Down Expand Up @@ -40,7 +41,7 @@ export function AuthProvider({ children }) {
});
const { transaction, nonce } = await challengeRes.json();
onStep?.(CONNECTION_STEPS.SIGNING);
const signedXDR = await signTransaction(transaction, { network: 'TESTNET' });
const signedXDR = await signTransaction(transaction, { network: freighterNetwork });
onStep?.(CONNECTION_STEPS.VERIFYING);
const verifyRes = await fetch('/v1/auth/verify', {
method: 'POST',
Expand Down