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
32 changes: 25 additions & 7 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,35 @@ Build the static Storybook bundle with:
npm run build-storybook
```

## Env
## Environment variables

- `VITE_API_URL`: Base URL for API requests. Leave empty to use the local Vite proxy.
- `VITE_SOROBAN_RPC_URL`: Soroban RPC endpoint for read/write contract calls.
- `VITE_REWARDS_CONTRACT_ID`: Rewards contract ID for balance and claim flows.
- `VITE_CAMPAIGN_CONTRACT_ID`: Campaign contract ID for participant registration.
Create a `.env.local` file in `frontend/` when you need to point the app at non-default services.

```bash
VITE_API_URL=http://localhost:3001
VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
VITE_REWARDS_CONTRACT_ID=CC...
VITE_CAMPAIGN_CONTRACT_ID=CC...
VITE_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
```

- `VITE_API_URL`: Base URL used for frontend `fetch` calls. Leave empty to use the local Vite proxy.
- `VITE_SOROBAN_RPC_URL`: Soroban RPC endpoint used by frontend contract helpers. Defaults to Stellar testnet RPC.
- `VITE_REWARDS_CONTRACT_ID`: Optional rewards contract ID for frontend Soroban calls.
- `VITE_CAMPAIGN_CONTRACT_ID`: Optional campaign contract ID for frontend Soroban calls.
- `VITE_STELLAR_NETWORK_PASSPHRASE`: Stellar network passphrase. Defaults to testnet.

## API routing

The frontend now targets `/api/v1/*` routes by default. Campaign loading uses the paginated response shape from `GET /api/v1/campaigns?page=1&limit=6`. Legacy `/api/*` routes are still supported by the backend for backward compatibility, but new integrations should use the v1 prefix.
The frontend targets `/api/v1/*` routes by default. Campaign loading uses the paginated response shape from `GET /api/v1/campaigns?page=1&limit=6`. Legacy `/api/*` routes are still supported by the backend for backward compatibility, but new integrations should use the v1 prefix.

## Config usage

The frontend reads these values from [src/config.js](/Users/CMI-James/od/Trivela/frontend/src/config.js):

- API requests are built with `apiUrl(...)`.
- Soroban RPC access goes through `createSorobanServer()`.
- Rewards and campaign contract IDs are exposed through `getRewardsContract()` and `getCampaignContract()`.

## Stellar integration

Expand All @@ -80,4 +98,4 @@ Use `@stellar/stellar-sdk` for:
- Building and signing transactions
- Invoking the rewards and campaign contracts

See [Stellar Developers](https://developers.stellar.org/docs) and the root README for contract IDs and flows.
See [Stellar Developers](https://developers.stellar.org/docs) and the root README for deployment flows.
40 changes: 40 additions & 0 deletions frontend/src/Landing.css
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,46 @@
color: var(--text-muted);
}

.config-grid {
margin-top: 1.5rem;
}

.config-card {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 16px;
padding: 1.5rem;
}

.config-card h3 {
font-family: var(--font-heading);
font-size: 1.1rem;
margin: 0 0 0.75rem;
}

.config-card p {
margin: 0 0 1rem;
color: var(--text-muted);
}

.config-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
gap: 0.65rem;
}

.config-list li {
color: var(--text-muted);
word-break: break-word;
}

.config-list strong {
color: var(--text);
}

/* ---- How it works ---- */
.how {
background: transparent;
}
Expand Down
28 changes: 26 additions & 2 deletions frontend/src/Landing.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { useEffect, useState } from 'react';
import {
apiUrl,
CAMPAIGN_CONTRACT_ID,
REWARDS_CONTRACT_ID,
SOROBAN_RPC_URL,
getCampaignContract,
getRewardsContract,
} from './config';
import {
fetchRewardsBalance,
formatPoints,
Expand Down Expand Up @@ -53,14 +61,15 @@ export default function Landing({
const [points, setPoints] = useState(null);
const [pointsError, setPointsError] = useState('');
const [isPointsLoading, setIsPointsLoading] = useState(false);
const rewardsContract = getRewardsContract();
const campaignContract = getCampaignContract();

useEffect(() => {
const controller = new AbortController();
const api = import.meta.env.VITE_API_URL || '';
setIsCampaignsLoading(true);
setCampaignsError('');

fetch(`${api}/api/v1/campaigns?page=${campaignPage}&limit=${CAMPAIGNS_PER_PAGE}`, {
fetch(apiUrl(`/api/v1/campaigns?page=${campaignPage}&limit=${CAMPAIGNS_PER_PAGE}`), {
signal: controller.signal,
})
.then(async (response) => {
Expand Down Expand Up @@ -259,6 +268,21 @@ export default function Landing({
<p>Vite UI with Freighter wallet connection, paginated campaigns, and contract interactions.</p>
</article>
</div>
<div className="config-grid">
<article className="config-card">
<h3>Environment-driven wiring</h3>
<p>
Frontend API and Soroban targets are configured through Vite env values so each deployment
can point at its own backend, rewards contract, and campaign contract without code changes.
</p>
<ul className="config-list">
<li><strong>Campaigns API:</strong> {apiUrl('/api/v1/campaigns')}</li>
<li><strong>Soroban RPC:</strong> {SOROBAN_RPC_URL}</li>
<li><strong>Rewards contract:</strong> {rewardsContract ? REWARDS_CONTRACT_ID : 'Not configured'}</li>
<li><strong>Campaign contract:</strong> {campaignContract ? CAMPAIGN_CONTRACT_ID : 'Not configured'}</li>
</ul>
</article>
</div>
</section>

<section className="section how" aria-labelledby="how-title">
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Contract, Networks, rpc } from '@stellar/stellar-sdk';

const DEFAULT_SOROBAN_RPC_URL = 'https://soroban-testnet.stellar.org';

function trimTrailingSlash(value) {
return value.replace(/\/+$/, '');
}

export const API_BASE_URL = trimTrailingSlash(import.meta.env.VITE_API_URL || '');
export const SOROBAN_RPC_URL = import.meta.env.VITE_SOROBAN_RPC_URL || DEFAULT_SOROBAN_RPC_URL;
export const REWARDS_CONTRACT_ID = import.meta.env.VITE_REWARDS_CONTRACT_ID || '';
export const CAMPAIGN_CONTRACT_ID = import.meta.env.VITE_CAMPAIGN_CONTRACT_ID || '';
export const NETWORK_PASSPHRASE =
import.meta.env.VITE_STELLAR_NETWORK_PASSPHRASE || Networks.TESTNET;

export function apiUrl(path) {
if (!path.startsWith('/')) {
throw new Error(`API path must start with "/": ${path}`);
}

return `${API_BASE_URL}${path}`;
}

export function createSorobanServer() {
return new rpc.Server(SOROBAN_RPC_URL);
}

export function getRewardsContract() {
return REWARDS_CONTRACT_ID ? new Contract(REWARDS_CONTRACT_ID) : null;
}

export function getCampaignContract() {
return CAMPAIGN_CONTRACT_ID ? new Contract(CAMPAIGN_CONTRACT_ID) : null;
}
Loading
Loading