Next.js web app for secure freelance payments using blockchain technology. Includes a dashboard and Stellar wallet integration.
- Node.js 18+
- npm or yarn
# Clone and enter the repo
git clone <your-repo-url>
cd talenttrust-frontend
# Install dependencies
npm install
# Run dev server
npm run devOpen http://localhost:3000.
| Script | Description |
|---|---|
npm run dev |
Start dev server (3000) |
npm run build |
Production build |
npm start |
Start production server |
npm run lint |
Run ESLint |
npm test |
Run Jest tests |
The project is built on Next.js App Router. The UI layer shares components, whilst global state is handled via an ordered provider stack.
| Route | Description | Status |
|---|---|---|
/ |
Landing page / Home | Placeholder (contains a login form demo and toast demo) |
/contracts |
Contracts list | Placeholder handler (uses local storage stub) |
/contracts/[id] |
Contract details | Placeholder (sample milestones and stubbed action handlers) |
/milestones |
Milestones list | Implemented (filterable status list) |
/reputation |
User reputation | Placeholder (empty state) |
Providers are wired in src/app/layout.tsx with a specific nesting order:
PreferencesProvider(Outermost) Provides user-level preferences (locale, currency) which can be consumed by any subsequent provider or component.ToastProviderProvides the global notification system. Placed here so that the wallet context or other deeper components can trigger alerts.WalletProvider(Innermost) Manages Stellar wallet connections. It can consume preferences and dispatch toast notifications if connections fail or succeed.
Shared components live in src/components/ (e.g., src/components/toast/). Shared utilities and domain types live in src/lib/ and src/types/.
The app includes a global accessible toast system for transient feedback:
ToastProvideris mounted in the root layout so notifications work across the app.- Use
useToast()in client components to triggershowSuccess(...)andshowError(...). - Success messages announce through a polite
aria-liveregion. - Error messages announce through an assertive
aria-liveregion. - Viewport overflow protection: at most 4 toasts are visible at once (
MAX_VISIBLE_TOASTS = 4). When a new toast would exceed this cap, the oldest visible toast is evicted and its auto-dismiss timer is cancelled before the new toast is appended. The live-region announcer always reflects the newest toast.
To improve security on shared or public machines, the WalletProvider includes an optional idle auto-disconnect safeguard.
- Configurable Timeout: Pass an
idleTimeoutprop (in milliseconds) toWalletProviderinsrc/app/layout.tsx. - Activity Monitoring: The timer resets on user activity (pointer moves, key presses, clicks, etc.).
- Auto-Disconnect: Once the idle period expires, the wallet is automatically disconnected and a notification is shown.
- Default Behaviour: The safeguard is disabled by default (
idleTimeout={0}). Recommended value for production is 15 minutes (900000ms).
Example:
<WalletProvider idleTimeout={900000}>
{children}
</WalletProvider>The app connects to the Freighter Stellar wallet extension via @stellar/freighter-api.
- Install the Freighter browser extension for Chrome or Firefox.
- Create or import a Stellar wallet in Freighter.
- The app detects Freighter automatically — no API keys or configuration required.
WalletProvider(insrc/contexts/WalletContext.tsx) manages the connection lifecycle.connect()checks for Freighter availability (window.freighter), callsrequestAccess()to prompt the user, and persists theG...public key inlocalStorage.- On page refresh, the address is rehydrated from
localStorageusing the same pattern asPreferencesProvider(src/lib/safeStorage.ts). disconnect()clears the address from state and removes it from storage.- The
useWallet()hook exposes{ address, isConnecting, error, connect, disconnect }.
| Condition | Message |
|---|---|
| Freighter not installed | Freighter wallet is not installed. Please install the Freighter browser extension. |
| User rejected the prompt | User rejected the connection request. |
| Unexpected failure | Propagated from the underlying error |
- Only the Stellar public key (
G...) is persisted inlocalStorage— no private keys, seeds, or personal information. - The public key is never logged to the console or sent to external services.
- All
window/ wallet access is guarded for SSR (Next.js App Router).
To ensure the app provides first-class support for search engine crawlers using Next.js metadata routes.
/robots.txt: Generated bysrc/app/robots.ts, allows all crawlers and points to the sitemap./sitemap.xml: Generated bysrc/app/sitemap.ts, lists all public static routes with a sensiblelastModifiedtimestamp.
The root layout exports typed Next.js metadata in src/app/layout.tsx so shared links include Open Graph and Twitter card previews.
metadataBaseis derived fromNEXT_PUBLIC_SITE_URL, falling back tohttp://localhost:3000during local development.- Open Graph and Twitter fields reuse the same safe, user-facing copy used elsewhere in the app: "Safe, secure payments that protect both freelancers and clients throughout your project."
- Preview image: the static social card lives at
public/og-preview.svgand is referenced with a relative path so Next.js can resolve it correctly frommetadataBase. - Copy guidance: keep future preview copy aligned with
docs/COPYWRITING_GUIDE.mdand avoid absolute guarantees or technical jargon.
This app uses Next.js environment variables. Public variables must be prefixed with NEXT_PUBLIC_ and are exposed to browser JavaScript. Secrets must never be stored in NEXT_PUBLIC_ variables.
-
NEXT_PUBLIC_SITE_URL(required in production)- Used by
src/app/layout.tsx,src/app/robots.ts, andsrc/app/sitemap.ts. - Provides the canonical site URL for
metadataBase, Open Graph/Twitter previews, generatedrobots.txt, andsitemap.xmlentries. - If unset, the app falls back to
http://localhost:3000for local development.
- Used by
-
NEXT_PUBLIC_WALLET_RPC_URL(reserved)- Reserved for future wallet integration and RPC provider configuration.
- Do not store private keys or secrets here; this is only for public JSON-RPC endpoints.
-
NEXT_PUBLIC_WALLET_CONNECT_RELAY(reserved)- Reserved for future WalletConnect relay support.
- Example relay URL:
wss://relay.walletconnect.com.
The repo includes a sample file at .env.example with the current public config and reserved future variables.
The app exposes a Web App Manifest at /manifest.webmanifest via src/app/manifest.ts, enabling users to install TalentTrust on their device home screen with proper branding.
-
src/app/manifest.ts— Generates the manifest with name, short name, description, theme/background colors (aligned tosrc/app/globals.css), standalone display mode, and icon references. -
Icon assets — Three icon formats are provided under
public/:public/icon.svg— Scalable vector icon (preferred format).public/icon-192x192.png— 192×192 PNG placeholder.public/icon-512x512.png— 512×512 PNG placeholder.
Note: The PNG files are blue-square placeholders generated for development. A designer should replace them with branded raster icons before production deployment.
The manifest is automatically linked via the root layout metadata (src/app/layout.tsx), which also declares the favicon and Apple touch icon.
The shared utility layer now includes lightweight Stellar address helpers in src/lib/stellarAddress.ts for display and form use:
isValidStellarAddress(value)returnstrueonly for a trimmed, uppercased value that looks like a Stellar public key: it starts withG, is exactly 56 characters long, and uses the base32 alphabetA-Zand2-7.normalizeStellarAddress(value)trims whitespace and uppercases the value without throwing on invalid input.- The display truncation path uses these helpers so clearly malformed addresses are treated as ordinary strings rather than being shortened as if they were valid keys.
The homepage contains an accessible, fully validated sign-in form:
- Validation logic: Form validation is extracted to a pure, isolated helper (
src/lib/validateLogin.ts) that enforces required checks and format policies (email structure, minimum length of 8 for password). - Error Summary: If validation fails, an
ErrorSummarycomponent is rendered at the top of the form. It usesrole="alert"and automatically gains focus via standard DOM ref to announce form errors immediately to screen reader users. The items in the list act as anchor links to directly jump focus to the respective input field. - Form Fields & Inputs: Individual fields are wrapped in
FormFieldto handle accessibility connections. It automatically assigns:- An associative
<label>linked byid. aria-invalid="true"to denote inputs that contain errors.aria-describedbypointing to the helper text and error message paragraph elements so screen readers read the context when targeting the inputs.
- An associative
- Success notification: Upon valid submission, the
useToasthook triggers a success notification instead of standard browser alerts.
- Fork the repo and create a branch from
main. - Install deps, run tests and build:
npm install && npm test && npm run build. - Open a pull request. CI runs lint, build, and tests on push/PR to
main.
When filing issues or opening PRs, please use the provided GitHub templates — they make reviews faster and keep the project history clean:
- Bug reports —
.github/ISSUE_TEMPLATE/bug_report.md: includes reproduction steps, environment details, and an impacted-route field. - Feature requests —
.github/ISSUE_TEMPLATE/feature_request.md: covers problem statement, proposed solution, and alternatives. - Pull requests —
.github/pull_request_template.md: includes a pre-flight checklist (lint/test/build), a 95% coverage confirmation, and accessibility & security notes.
If you need quick help before filing an issue, the community Discord is the fastest way to reach the team.
The /milestones route renders a typed Milestone[] list with a status filter:
- Status filter — an accessible
radiogroup(fieldset+legend) lets users narrow results by All, Pending, Completed, Paid, or Disputed. - Empty state — when no items match the active filter, a contextual
EmptyStateis shown with a prompt to add a milestone. - Accessible result announcement — an
aria-live="polite"region announces the filtered count (e.g. "Showing 2 pending milestones") to assistive-technology users. - Currency formatting — payouts are formatted via
formatAmountfromsrc/lib/preferences.tsx, respecting the user's chosen locale and currency preference.
GitHub Actions runs on push and pull requests to main:
- Install dependencies
- Lint (
npm run lint) - Build (
npm run build) - Tests (
npm test) - Dependency audit (
npm audit --audit-level=high --production)
Ensure these pass locally before pushing.
Dependency management uses two complementary layers that work in tandem:
| Layer | Mechanism | Nature |
|---|---|---|
| Proactive | Dependabot (.github/dependabot.yml) |
Opens weekly PRs to keep packages and action pins current |
| Reactive | npm audit step in ci.yml |
Blocks any PR — including Dependabot's — that introduces a new high/critical advisory |
Because every Dependabot PR must pass the full CI pipeline (lint → build → test → audit) before it can be merged, the two layers reinforce each other: Dependabot brings updates in, and the audit gate ensures none of them silently introduce a vulnerability.
Reviewing grouped minor/patch PRs
Dependabot batches all minor and patch npm updates into a single weekly PR labelled dependencies. When reviewing:
- Check the CI status — all green means lint, build, tests, and the audit gate passed.
- Scan the diff in
package-lock.jsonfor any unexpected indirect dependency changes. - If everything looks clean, approve and merge. No manual testing is normally required for grouped minor/patch updates.
Reviewing major version PRs
Each major npm bump arrives as its own PR so breaking changes can be assessed individually:
- Read the package's changelog or migration guide for the version jump.
- Run
npm installlocally and executenpm test && npm run buildto catch any compile-time or runtime breakage. - Update any affected code, then approve the PR once CI is green.
GitHub Actions pin updates
Dependabot also opens separate weekly PRs to keep actions/checkout, actions/setup-node, and similar pins current. These are low-risk and can generally be merged as long as CI passes. Confirm the pinned SHA in the updated workflow still points to a tagged release, not an arbitrary commit.
Auto-merge is not enabled. Every Dependabot PR requires a passing CI run and human approval before it lands on
main.
The pipeline runs npm audit --audit-level=high --production after tests. Any high or critical advisory blocks the merge.
Triage a finding
- Run
npm auditlocally to read the advisory details and affected package. - Check whether a patched version exists:
npm audit fix(add--forceonly if you accept semver-major bumps and have reviewed the changelog). - If no fix is available and the advisory is a false positive or cannot be exploited in this context, document the reason and add the advisory ID to a
.nsprc/audit-resolve.jsonfile (npm ≥ 10:npm audit --ignore <id>).
Waiving an advisory in CI
Add the --ignore <advisory-id> flag to the audit step in .github/workflows/ci.yml and leave a comment explaining the waiver, the expected fix date, and a link to the advisory. Example:
# Advisory 1234567 – lodash prototype pollution, not reachable in production
# Revisit when lodash@5 is released (tracked in #123).
run: npm audit --audit-level=high --production --ignore 1234567MIT
Centralized domain types (Contract, Milestone, Reputation) are defined and re-exported from src/types/domain.ts to ensure strict type safety across pages and components.