From b6bcaa49cc24608149598136dc682c0762b584fa Mon Sep 17 00:00:00 2001 From: unsiqasik Date: Sat, 30 May 2026 08:28:23 +0000 Subject: [PATCH 1/2] feat: add reusable Button component and replace 20+ button instances Create a standardized Button component with: - 5 variants: primary, secondary, outline, ghost, danger - 3 sizes: sm, md, lg - Loading state with spinner - Left/right icon support - Full-width option - Proper disabled states Replace inline button styles across 15 files: - FeaturedGoalCard, GoalCard, GoalOverviewCard - PassedProposalCard, ProposalCard, SavingsPoolCard - Settings, Portfolio, Referrals, Transactions - Notifications, Savings Pools, Savings, Support, GoalForm Fixes #828 --- .../components/dashboard/FeaturedGoalCard.tsx | 10 +-- .../app/components/dashboard/GoalCard.tsx | 5 +- .../components/dashboard/GoalOverviewCard.tsx | 19 ++-- .../dashboard/PassedProposalCard.tsx | 5 +- .../app/components/dashboard/ProposalCard.tsx | 17 ++-- .../components/dashboard/SavingsPoolCard.tsx | 8 +- frontend/app/components/ui/Button.tsx | 87 +++++++++++++++++++ frontend/app/dashboard/notifications/page.tsx | 10 ++- frontend/app/dashboard/portfolio/page.tsx | 13 +-- frontend/app/dashboard/referrals/page.tsx | 10 ++- frontend/app/dashboard/savings-pools/page.tsx | 11 +-- .../app/dashboard/settings/SettingsClient.tsx | 11 ++- frontend/app/dashboard/transactions/page.tsx | 10 ++- .../create-goal/components/GoalForm.tsx | 8 +- frontend/app/savings/page.tsx | 5 +- frontend/app/support/page.tsx | 9 +- 16 files changed, 175 insertions(+), 63 deletions(-) create mode 100644 frontend/app/components/ui/Button.tsx diff --git a/frontend/app/components/dashboard/FeaturedGoalCard.tsx b/frontend/app/components/dashboard/FeaturedGoalCard.tsx index c97d03a82..036d35884 100644 --- a/frontend/app/components/dashboard/FeaturedGoalCard.tsx +++ b/frontend/app/components/dashboard/FeaturedGoalCard.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Plane, Calendar, ChevronRight } from 'lucide-react'; import CircularProgress from './CircularProgress'; +import Button from '../ui/Button'; interface FeaturedGoalCardProps { title: string; @@ -82,13 +83,12 @@ const FeaturedGoalCard: React.FC = ({
- - + +
diff --git a/frontend/app/components/dashboard/GoalCard.tsx b/frontend/app/components/dashboard/GoalCard.tsx index 77d4948c0..547a7bcfc 100644 --- a/frontend/app/components/dashboard/GoalCard.tsx +++ b/frontend/app/components/dashboard/GoalCard.tsx @@ -1,6 +1,7 @@ import React from "react"; import Link from "next/link"; import { Calendar, ChevronRight } from "lucide-react"; +import Button from "../ui/Button"; export type GoalStatus = "On Track" | "At Risk" | "Completed" | "Paused"; @@ -111,9 +112,9 @@ export default function GoalCard({
- + = ({ {/* ── Action buttons ── */}
- - +
); diff --git a/frontend/app/components/dashboard/PassedProposalCard.tsx b/frontend/app/components/dashboard/PassedProposalCard.tsx index 46ac1a740..b6dcb2b9b 100644 --- a/frontend/app/components/dashboard/PassedProposalCard.tsx +++ b/frontend/app/components/dashboard/PassedProposalCard.tsx @@ -2,6 +2,7 @@ import React from "react"; import { CheckCircle2, ChevronRight } from "lucide-react"; +import Button from "../ui/Button"; export interface PassedProposal { id: string; @@ -74,9 +75,9 @@ export default function PassedProposalCard({ proposal }: { proposal: PassedPropo
- +
- +
{/* Mobile-only full-width Vote button placed as the last row */}
- +
); diff --git a/frontend/app/components/dashboard/SavingsPoolCard.tsx b/frontend/app/components/dashboard/SavingsPoolCard.tsx index 6334e214e..5901fe903 100644 --- a/frontend/app/components/dashboard/SavingsPoolCard.tsx +++ b/frontend/app/components/dashboard/SavingsPoolCard.tsx @@ -1,6 +1,7 @@ "use client"; import React from "react"; +import Button from "../ui/Button"; export type RiskLevel = "Low Risk" | "Medium Risk" | "High Risk"; @@ -106,12 +107,13 @@ const SavingsPoolCard: React.FC = ({ {/* Deposit Button */} - + ); }; diff --git a/frontend/app/components/ui/Button.tsx b/frontend/app/components/ui/Button.tsx new file mode 100644 index 000000000..62315319c --- /dev/null +++ b/frontend/app/components/ui/Button.tsx @@ -0,0 +1,87 @@ +"use client"; + +import React from "react"; + +type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "danger"; +type ButtonSize = "sm" | "md" | "lg"; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: ButtonVariant; + size?: ButtonSize; + loading?: boolean; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + fullWidth?: boolean; +} + +const variantStyles: Record = { + primary: + "bg-cyan-500 hover:bg-cyan-400 text-[#061a1a] font-bold shadow-lg hover:shadow-[0_15px_30px_rgba(0,212,192,0.4)] active:scale-95", + secondary: + "bg-cyan-500/20 border border-cyan-500/30 text-cyan-300 font-semibold hover:bg-cyan-500/30", + outline: + "border border-cyan-400/40 text-cyan-200 hover:text-white hover:border-cyan-300", + ghost: + "bg-transparent text-[#5e8c96] hover:text-[#e2f8f8]", + danger: + "bg-red-500/20 border border-red-500/30 text-red-300 hover:bg-red-500/30 hover:text-red-200", +}; + +const sizeStyles: Record = { + sm: "px-3 py-1.5 text-xs rounded-lg gap-1", + md: "px-5 py-2.5 text-sm rounded-xl gap-2", + lg: "px-6 py-3.5 text-base rounded-2xl gap-2", +}; + +export default function Button({ + variant = "primary", + size = "md", + loading = false, + leftIcon, + rightIcon, + fullWidth = false, + disabled, + children, + className = "", + ...props +}: ButtonProps) { + const baseStyles = + "inline-flex items-center justify-center transition-all duration-300 cursor-pointer disabled:cursor-not-allowed disabled:opacity-70"; + + const widthClass = fullWidth ? "w-full" : ""; + + return ( + + ); +} diff --git a/frontend/app/dashboard/notifications/page.tsx b/frontend/app/dashboard/notifications/page.tsx index 0d904dc31..3a16f2d3b 100644 --- a/frontend/app/dashboard/notifications/page.tsx +++ b/frontend/app/dashboard/notifications/page.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { Bell, CheckCheck, ArrowUpRight, ShieldCheck, Target, Megaphone } from "lucide-react"; +import Button from "../../../components/ui/Button"; type NotifType = "transaction" | "governance" | "milestone" | "announcement"; @@ -77,13 +78,14 @@ export default function NotificationsPage() { {unread > 0 && ( - + )} diff --git a/frontend/app/dashboard/portfolio/page.tsx b/frontend/app/dashboard/portfolio/page.tsx index efb404ea5..3880b4f95 100644 --- a/frontend/app/dashboard/portfolio/page.tsx +++ b/frontend/app/dashboard/portfolio/page.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { Briefcase, TrendingUp, TrendingDown, Download, MoreHorizontal } from "lucide-react"; +import Button from "../../components/ui/Button"; const ASSETS = [ { name: "USDC Flexible", type: "Savings", balance: 2400, value: 2400, apy: 6.5, pnl: 156, pnlPct: 6.9 }, @@ -66,14 +67,16 @@ export default function PortfolioPage() {

All assets and positions

- + Export CSV + {/* Summary cards */} diff --git a/frontend/app/dashboard/referrals/page.tsx b/frontend/app/dashboard/referrals/page.tsx index 7c6a963fc..375ad2695 100644 --- a/frontend/app/dashboard/referrals/page.tsx +++ b/frontend/app/dashboard/referrals/page.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { Users, Copy, Check, Trophy, Gift, TrendingUp } from "lucide-react"; import { env } from "../../lib/env"; +import Button from "../../components/ui/Button"; const REFERRED_USERS = [ { name: "Alice K.", joined: "Apr 20, 2026", status: "Active", reward: "$12.00" }, @@ -71,13 +72,14 @@ export default function ReferralsPage() {
{REFERRAL_LINK}
- +

Earn $12 USDC for every friend who deposits at least $100. diff --git a/frontend/app/dashboard/savings-pools/page.tsx b/frontend/app/dashboard/savings-pools/page.tsx index f079ade91..503cd0e00 100644 --- a/frontend/app/dashboard/savings-pools/page.tsx +++ b/frontend/app/dashboard/savings-pools/page.tsx @@ -13,6 +13,7 @@ import SavingsPoolCard, { type SavingsPool, } from "@/app/components/dashboard/SavingsPoolCard"; import { useToast } from "@/app/context/ToastContext"; +import Button from "../../components/ui/Button"; export default function GoalBasedSavingsPage() { const [searchQuery, setSearchQuery] = useState(""); @@ -134,9 +135,9 @@ export default function GoalBasedSavingsPage() { - + @@ -224,12 +225,12 @@ export default function GoalBasedSavingsPage() { Try adjusting your search terms or filters to find what you're looking for.

- + )} diff --git a/frontend/app/dashboard/settings/SettingsClient.tsx b/frontend/app/dashboard/settings/SettingsClient.tsx index c70f9331e..52d085240 100644 --- a/frontend/app/dashboard/settings/SettingsClient.tsx +++ b/frontend/app/dashboard/settings/SettingsClient.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { Monitor, Moon, Settings, Sun } from "lucide-react"; import { type Theme, useTheme } from "../../context/ThemeContext"; +import Button from "../../components/ui/Button"; type Prefs = { emailNotifications?: boolean; @@ -177,13 +178,15 @@ export default function SettingsClient() { />
- + Save Preferences +
diff --git a/frontend/app/dashboard/transactions/page.tsx b/frontend/app/dashboard/transactions/page.tsx index 054ec21eb..76c5d9e2c 100644 --- a/frontend/app/dashboard/transactions/page.tsx +++ b/frontend/app/dashboard/transactions/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { Download, History, Loader2, Search, ChevronDown } from "lucide-react"; import TransactionRow, { TransactionType, TransactionStatus } from "./components/TransactionRow"; import { useToast } from "../../context/ToastContext"; +import Button from "../../components/ui/Button"; type TransactionRowData = { date: string; @@ -134,13 +135,14 @@ export default function TransactionHistoryPage() { - +
diff --git a/frontend/app/savings/create-goal/components/GoalForm.tsx b/frontend/app/savings/create-goal/components/GoalForm.tsx index 3a0783096..96c68ad5e 100644 --- a/frontend/app/savings/create-goal/components/GoalForm.tsx +++ b/frontend/app/savings/create-goal/components/GoalForm.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Calendar, CircleDollarSign, Flag, Sparkles } from "lucide-react"; +import Button from "../../../components/ui/Button"; type FormState = { goalName: string; @@ -173,12 +174,9 @@ export default function GoalForm() { )}
- +

You’ll be able to contribute and track progress on the dashboard.

diff --git a/frontend/app/savings/page.tsx b/frontend/app/savings/page.tsx index a9072381d..b2a1ae509 100644 --- a/frontend/app/savings/page.tsx +++ b/frontend/app/savings/page.tsx @@ -17,6 +17,7 @@ import { ShoppingBag, } from "lucide-react"; import GoalCard, { GoalStatus } from "./components/GoalCard"; +import Button from "../components/ui/Button"; // export const metadata = { title: "Goal-Based Savings - Nestera" }; @@ -120,9 +121,9 @@ export default function GoalBasedSavingsPage() {

- +
- + )}
From 573d46df6343ffe95916aa55e68b0af51137a08c Mon Sep 17 00:00:00 2001 From: unsiqasik Date: Sat, 30 May 2026 09:14:20 +0000 Subject: [PATCH 2/2] docs: add comprehensive local development setup guide Addresses #781. Includes: - Prerequisites and tool installation (Node.js, pnpm, Rust, Soroban CLI, Docker) - Stellar testnet account creation via Friendbot - Smart contract build, test, and deploy instructions - Backend setup with Docker PostgreSQL and NestJS - Frontend setup with Next.js - Troubleshooting section for common issues - Project structure reference --- DEVELOPMENT.md | 305 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 DEVELOPMENT.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 000000000..820280a7c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,305 @@ +# Local Development Setup Guide + +This guide walks you through setting up Nestera for local development. By the end, you'll have the frontend, backend, and smart contracts running on your machine. + +--- + +## Prerequisites + +Install the following tools before proceeding: + +| Tool | Version | Install | +|------|---------|---------| +| **Node.js** | v18+ | [nodejs.org](https://nodejs.org/) | +| **pnpm** | v8+ | `npm install -g pnpm` | +| **Rust** | stable | [rustup.rs](https://rustup.rs/) | +| **Soroban CLI** | latest | `cargo install soroban-cli` (see below) | +| **Docker** | v24+ | [docker.com](https://docs.docker.com/get-docker/) | +| **Git** | v2+ | [git-scm.com](https://git-scm.com/) | + +### Installing Soroban CLI + +```bash +# Install via cargo (requires Rust stable) +cargo install --locked soroban-cli + +# Verify installation +soroban --version + +# Add the Stellar testnet network +soroban network add \ + --global testnet \ + --rpc-url https://soroban-testnet.stellar.org:443 \ + --network-passphrase "Test SDF Network ; September 2015" +``` + +### Creating a Stellar Testnet Account + +You'll need a funded testnet account for deploying and interacting with contracts: + +```bash +# Generate a new key pair +soroban keys generate --global --network testnet my-key + +# Fund it via the Friendbot (testnet faucet) +curl "https://friendbot.stellar.org?addr=$(soroban keys address my-key)" +``` + +--- + +## 1. Clone the Repository + +```bash +# Shallow clone (faster, recommended for contributors) +git clone --depth 1 https://github.com/Devsol-01/Nestera.git +cd Nestera + +# Or full clone (if you need full git history) +git clone https://github.com/Devsol-01/Nestera.git +cd Nestera +``` + +--- + +## 2. Smart Contracts (Soroban / Rust) + +The contracts live in `contracts/` and are written in Rust using the Soroban SDK. + +### Build + +```bash +cd contracts + +# Build all contracts +cargo build --target wasm32-unknown-unknown --release + +# Or build with the Soroban CLI +soroban contract build +``` + +### Run Tests + +```bash +cd contracts +cargo test +``` + +### Deploy to Testnet + +```bash +cd contracts + +# Deploy a contract (replace with actual contract .wasm path) +soroban contract deploy \ + --wasm target/wasm32-unknown-unknown/release/nestera.wasm \ + --source-account my-key \ + --network testnet +``` + +> **Note:** Save the contract ID returned after deployment — you'll need it for the backend and frontend configuration. + +--- + +## 3. Backend (NestJS) + +The backend is a NestJS API in `backend/`. + +### Environment Setup + +```bash +cd backend + +# Copy the example env file +cp .env.example .env + +# Edit .env with your local settings +# At minimum, configure: +# DATABASE_URL=postgresql://nestera:nestera@localhost:5432/nestera +# SOROBAN_RPC_URL=https://soroban-testnet.stellar.org:443 +# CONTRACT_ID= +``` + +### Start Database (Docker) + +```bash +cd backend + +# Start PostgreSQL and other services via Docker Compose +docker compose up -d postgres + +# Verify it's running +docker compose ps +``` + +This starts a PostgreSQL 15 instance on `localhost:5432` with: +- **User:** `nestera` +- **Password:** `nestera` +- **Database:** `nestera` + +### Install Dependencies & Run + +```bash +cd backend + +# Install dependencies +pnpm install + +# Run database migrations (if applicable) +pnpm run build + +# Start in development mode (hot-reload) +pnpm run start:dev +``` + +The API will be available at `http://localhost:3000` (default NestJS port). + +### Run Tests + +```bash +cd backend + +# Unit tests +pnpm run test + +# Watch mode +pnpm run test:watch + +# Coverage report +pnpm run test:cov + +# E2E tests +pnpm run test:e2e +``` + +--- + +## 4. Frontend (Next.js) + +The frontend is a Next.js app in `frontend/`. + +### Install Dependencies & Run + +```bash +cd frontend + +# Install dependencies +pnpm install + +# Start development server +pnpm run dev +``` + +The frontend will be available at `http://localhost:3000`. + +### Useful Commands + +```bash +# Type checking +pnpm run type-check + +# Linting +pnpm run lint + +# Fix lint issues +pnpm run lint:fix + +# Production build +pnpm run build +``` + +--- + +## 5. Running Everything Together + +Open three terminal tabs: + +```bash +# Tab 1: Database +cd backend && docker compose up -d postgres + +# Tab 2: Backend API +cd backend && pnpm run start:dev + +# Tab 3: Frontend +cd frontend && pnpm run dev +``` + +Or use Docker Compose to run the full stack: + +```bash +cd backend +docker compose up -d +``` + +--- + +## Troubleshooting + +### Soroban CLI: `wasm32-unknown-unknown` target not found + +```bash +rustup target add wasm32-unknown-unknown +``` + +### PostgreSQL connection refused + +Make sure Docker is running and the container is up: + +```bash +docker compose ps +docker compose logs postgres +``` + +### Port already in use + +If port 3000 or 5432 is occupied, either stop the conflicting service or update the port in: +- Backend: `backend/.env` (DATABASE_URL port) +- Frontend: `frontend/next.config.ts` (if custom port configured) + +### Frontend build errors after pulling latest changes + +```bash +cd frontend +rm -rf node_modules .next +pnpm install +pnpm run dev +``` + +### Contract deployment fails with "insufficient balance" + +Fund your testnet account: + +```bash +curl "https://friendbot.stellar.org?addr=$(soroban keys address my-key)" +``` + +--- + +## Project Structure Reference + +``` +Nestera/ +├── frontend/ # Next.js frontend (React, TailwindCSS) +│ ├── app/ # App router pages +│ ├── public/ # Static assets +│ └── package.json +├── backend/ # NestJS API server +│ ├── src/ # Source code +│ ├── docker-compose.yml +│ └── package.json +├── contracts/ # Soroban smart contracts (Rust) +│ ├── src/ # Contract source +│ ├── tests/ # Contract tests +│ └── Cargo.toml +├── scripts/ # Deployment & automation scripts +├── tests/ # Integration & E2E tests +└── DEVELOPMENT.md # ← You are here +``` + +--- + +## Need Help? + +- Check the [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines +- Open an [issue](https://github.com/Devsol-01/Nestera/issues) if you hit a bug +- Read the [contracts documentation](contracts/README.md) for smart contract details