From aa71550173c194431c4aed540d464be597508dd5 Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Thu, 18 Jun 2026 00:40:58 +0100 Subject: [PATCH 1/7] Added frontend CI workflow (TypeScript build/typecheck) --- .github/workflows/frontend-ci.yml | 46 +++++++++++++++++++++++++++++++ .github/workflows/test.yml | 4 +++ README.md | 43 +++++++++++++++++++++-------- frontend/.env.ci | 19 +++++++++++++ frontend/.gitignore | 3 +- 5 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/frontend-ci.yml create mode 100644 frontend/.env.ci diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 0000000..76e5197 --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,46 @@ +name: CI – Frontend Build & Typecheck + +on: + push: + paths: + - 'frontend/**' + pull_request: + paths: + - 'frontend/**' + workflow_dispatch: + +jobs: + build: + name: Build & Typecheck Frontend + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + cache-dependency-path: frontend/pnpm-lock.yaml + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup environment + run: cp .env.ci .env.local + + - name: TypeScript typecheck + run: npx tsc --noEmit + + - name: Lint + run: pnpm run lint + + - name: Build + run: pnpm run build \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b123b09..7325395 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,11 @@ name: CI – Build & Test Soroban Contracts on: push: + paths: + - 'smartcontract/**' pull_request: + paths: + - 'smartcontract/**' workflow_dispatch: jobs: diff --git a/README.md b/README.md index adce283..5bee35b 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ # JointSave - [![CI – Build & Test Soroban Contracts](https://github.com/Sendi0011/Joint_Save/actions/workflows/test.yml/badge.svg)](https://github.com/Sendi0011/Joint_Save/actions/workflows/test.yml) +[![CI – Build & Test Soroban Contracts](https://github.com/Sendi0011/Joint_Save/actions/workflows/test.yml/badge.svg)](https://github.com/Sendi0011/Joint_Save/actions/workflows/test.yml) +[![CI – Frontend Build & Typecheck](https://github.com/Sendi0011/Joint_Save/actions/workflows/frontend-ci.yml/badge.svg)](https://github.com/Sendi0011/Joint_Save/actions/workflows/frontend-ci.yml) + +A decentralized community savings platform built on Stellar, enabling trusted groups to automate contributions, payouts, and transparency using Soroban smart contracts. - A decentralized community savings platform built on Stellar, enabling trusted groups to automate contributions, payouts, and transparency using Soroban smart contracts. ## Live Demo @@ -52,11 +54,13 @@ The frontend is built with Next.js and integrates with multiple Stellar wallets ## Technology Stack ### Smart Contracts + - **Rust** with Soroban SDK for smart contract development - **WebAssembly (WASM)** compilation for efficient execution - **Stellar Testnet** for secure, low-cost transactions ### Frontend + - **Next.js 14** with App Router for modern React development - **TypeScript** for type-safe development - **Tailwind CSS** for responsive, mobile-first styling @@ -65,6 +69,7 @@ The frontend is built with Next.js and integrates with multiple Stellar wallets - **Stellar Wallets Kit** for multi-wallet support ### Infrastructure + - **Vercel** for frontend deployment and hosting - **Supabase** for off-chain metadata and user data - **GitHub Actions** for automated CI/CD pipeline @@ -73,14 +78,14 @@ The frontend is built with Next.js and integrates with multiple Stellar wallets All contracts are deployed on **Stellar Testnet**: -| Contract | Address | -|----------|---------| -| **Factory** | `CBZNGP52FLFZ4BOGC265FUAMP5KFMAYPQK3KTI5UHMYVMM3QCST3IMRI` | +| Contract | Address | +| ------------------- | ------------------------------------------------------------------ | +| **Factory** | `CBZNGP52FLFZ4BOGC265FUAMP5KFMAYPQK3KTI5UHMYVMM3QCST3IMRI` | | **Rotational WASM** | `d350a325d8734263a3d7150c875555d8956e13a527fb3497d5141b8b3f3d2c74` | -| **Target WASM** | `133a62226501fc5443e70007d79deeeb0b33fdf8c85c7fcd3cf16293bb5c7292` | -| **Flexible WASM** | `df6ff088fd79f13d8d03e72160434517fdb4a83b8c7bfdd887be4369805e0d6b` | +| **Target WASM** | `133a62226501fc5443e70007d79deeeb0b33fdf8c85c7fcd3cf16293bb5c7292` | +| **Flexible WASM** | `df6ff088fd79f13d8d03e72160434517fdb4a83b8c7bfdd887be4369805e0d6b` | -*Deployed on April 16, 2026* +_Deployed on April 16, 2026_ For complete API documentation — functions, events, storage keys, error conditions, and CLI examples — see **[docs/contract-api.md](docs/contract-api.md)**. @@ -96,12 +101,14 @@ For complete API documentation — functions, events, storage keys, error condit ### Quick Start 1. **Clone the repository** + ```bash git clone https://github.com/Sendi0011/Joint_Save.git cd Joint_Save ``` 2. **Set up the frontend** + ```bash cd frontend npm install @@ -109,6 +116,7 @@ For complete API documentation — functions, events, storage keys, error condit ``` 3. **Configure environment variables** + ```env NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_key @@ -119,6 +127,7 @@ For complete API documentation — functions, events, storage keys, error condit ``` 4. **Start the development server** + ```bash npm run dev ``` @@ -167,17 +176,17 @@ JointSave demonstrates several advanced Soroban patterns:
- - ### Desktop Features + ![CI/CD Pipeline](docs/ci-screenshot.png) -*Automated testing and deployment* +_Automated testing and deployment_
## Roadmap ### 🚀 Phase 1 - Foundation (Current) + - ✅ Core savings pool functionality (Rotational, Target, Flexible) - ✅ Multi-wallet Stellar integration - ✅ Factory contract for pool discovery @@ -185,7 +194,13 @@ JointSave demonstrates several advanced Soroban patterns: - ✅ Real-time on-chain state synchronization - ✅ Automated CI/CD pipeline +### CI Checks + +- **Smart Contracts** – Rust/Soroban contracts are built and tested on push/PR (any branch) +- **Frontend** – TypeScript typecheck, lint, and Next.js build run on PRs affecting `frontend/**` + ### 🔧 Phase 2 - Enhancement + - **DeFi Integration** – Connect flexible pools to Stellar DeFi protocols for yield - **Mobile App** – Native iOS and Android applications - **Advanced Analytics** – Detailed savings insights and projections @@ -193,6 +208,7 @@ JointSave demonstrates several advanced Soroban patterns: - **Reputation System** – Trust scores based on participation history ### 🌍 Phase 3 - Scale + - **Mainnet Deployment** – Production-ready contracts on Stellar mainnet - **Fiat Integration** – Direct bank transfers and credit card support - **Social Features** – Friend invitations and community building @@ -204,17 +220,20 @@ JointSave demonstrates several advanced Soroban patterns: We welcome contributions from the community! Here's how you can help: ### Development + - 🐛 **Bug Reports** – Found an issue? [Open an issue](https://github.com/Sendi0011/Joint_Save/issues) - 💡 **Feature Requests** – Have an idea? We'd love to hear it - 🔧 **Code Contributions** – Submit pull requests for improvements - 📖 **Documentation** – Help improve our docs and guides ### Testing + - 🧪 **Testnet Testing** – Try the app and report issues - 📱 **Device Testing** – Test on different devices and browsers - 🔍 **Security Review** – Help audit smart contracts and frontend code ### Community + - 💬 **Discussions** – Join conversations in GitHub Discussions - 🌟 **Spread the Word** – Share JointSave with your network - 🎓 **Education** – Help others learn about decentralized savings @@ -246,7 +265,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file **Built with ❤️ for communities worldwide** -*Powered by [Stellar](https://stellar.org) • [Soroban](https://soroban.stellar.org) • [Next.js](https://nextjs.org)* +_Powered by [Stellar](https://stellar.org) • [Soroban](https://soroban.stellar.org) • [Next.js](https://nextjs.org)_ [Live Demo](https://joint-save.vercel.app) • [Watch Video](https://youtu.be/Iuy-As9im7A) • [View Code](https://github.com/Sendi0011/Joint_Save) diff --git a/frontend/.env.ci b/frontend/.env.ci new file mode 100644 index 0000000..ac1c5a6 --- /dev/null +++ b/frontend/.env.ci @@ -0,0 +1,19 @@ +# Supabase +NEXT_PUBLIC_SUPABASE_URL=https://placeholder.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=placeholder-key + +# Stellar +NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org +NEXT_PUBLIC_STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org + +# JointSave Contracts +NEXT_PUBLIC_FACTORY_CONTRACT_ID=CBZNGP52FLFZ4BOGC265FUAMP5KFMAYPQK3KTI5UHMYVMM3QCST3IMRI +NEXT_PUBLIC_TOKEN_CONTRACT_ID=native + +# Pool WASM hashes +NEXT_PUBLIC_ROTATIONAL_WASM_HASH=d350a325d8734263a3d7150c875555d8956e13a527fb3497d5141b8b3f3d2c74 +NEXT_PUBLIC_TARGET_WASM_HASH=133a62226501fc5443e70007d79deeeb0b33fdf8c85c7fcd3cf16293bb5c7292 +NEXT_PUBLIC_FLEXIBLE_WASM_HASH=df6ff088fd79f13d8d03e72160434517fdb4a83b8c7bfdd887be4369805e0d6b + +# Dev Tooling +NEXT_PUBLIC_DEBUG_DATA_LAYER=false \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index f641965..271dc5e 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -16,9 +16,10 @@ yarn-debug.log* yarn-error.log* .pnpm-debug.log* -# env files (but keep example) +# env files (but keep example and ci) .env* !.env.example +!.env.ci # vercel .vercel From c511752bb96058c7dd52e616da51ae6dab42693a Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Fri, 19 Jun 2026 00:03:47 +0100 Subject: [PATCH 2/7] Updated the frontend.yml --- .github/workflows/frontend-ci.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 76e5197..a0e8710 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -3,33 +3,37 @@ name: CI – Frontend Build & Typecheck on: push: paths: - - 'frontend/**' + - "frontend/**" pull_request: paths: - - 'frontend/**' + - "frontend/**" workflow_dispatch: jobs: build: name: Build & Typecheck Frontend runs-on: ubuntu-latest + defaults: run: working-directory: frontend steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 - - name: Setup Node + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "pnpm" cache-dependency-path: frontend/pnpm-lock.yaml - - name: Install pnpm - run: npm install -g pnpm - - name: Install dependencies run: pnpm install --frozen-lockfile @@ -37,10 +41,10 @@ jobs: run: cp .env.ci .env.local - name: TypeScript typecheck - run: npx tsc --noEmit + run: pnpm exec tsc --noEmit - name: Lint run: pnpm run lint - name: Build - run: pnpm run build \ No newline at end of file + run: pnpm run build From 7d165721b693e88bce1ec77ddad607b7e83cbcec Mon Sep 17 00:00:00 2001 From: Oyibe Date: Fri, 19 Jun 2026 20:15:19 +0100 Subject: [PATCH 3/7] updated frontend CI --- .github/workflows/frontend-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index a0e8710..42721e3 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -35,7 +35,7 @@ jobs: cache-dependency-path: frontend/pnpm-lock.yaml - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --no-frozen-lockfile - name: Setup environment run: cp .env.ci .env.local From be14e1ea614bfe191fcde663afd3ef41646930f5 Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Fri, 19 Jun 2026 22:19:37 +0100 Subject: [PATCH 4/7] fixed failing CI by cleaning cached files before installing deps in CI workflow --- .github/workflows/frontend-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 42721e3..d1ff7ea 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -34,6 +34,9 @@ jobs: cache: "pnpm" cache-dependency-path: frontend/pnpm-lock.yaml + - name: Clean workspace + run: git clean -fdx + - name: Install dependencies run: pnpm install --no-frozen-lockfile From f3869ad75011bd940dffb979d0273559b2c080cb Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Sat, 20 Jun 2026 12:19:48 +0100 Subject: [PATCH 5/7] fixed faling CI --- frontend/eslint.config.mjs | 18 ++++++++++++++++++ frontend/next.config.mjs | 3 --- frontend/package.json | 5 +++-- 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 frontend/eslint.config.mjs diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..8334171 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,18 @@ +import globals from "globals"; + +export default [ + { + files: ["**/*.{js,jsx,ts,tsx}"], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + parserOptions: { + ecmaVersion: 2024, + sourceType: "module", + }, + }, + rules: {}, + }, +]; diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index f5cbc38..4cd9948 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -1,8 +1,5 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - }, typescript: { ignoreBuildErrors: true, }, diff --git a/frontend/package.json b/frontend/package.json index 874193e..8eb54bf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "next build", "dev": "next dev", - "lint": "next lint", + "lint": "eslint .", "start": "next start" }, "dependencies": { @@ -77,7 +77,8 @@ "postcss": "^8.5", "tailwindcss": "^4.1.9", "tw-animate-css": "1.3.3", - "typescript": "^5" + "typescript": "^5", + "eslint": "^9" }, "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" } From 3ec8a3157bfd3ebd34cf3dc100fee38ff48a3403 Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Sun, 21 Jun 2026 12:27:32 +0100 Subject: [PATCH 6/7] modified config package --- frontend/components/dashboard/my-groups.tsx | 295 +++++++++--------- frontend/components/group/yield-dashboard.tsx | 19 +- frontend/eslint.config.mjs | 26 +- frontend/hooks/useJointSaveContracts.ts | 2 +- frontend/package.json | 7 +- 5 files changed, 189 insertions(+), 160 deletions(-) diff --git a/frontend/components/dashboard/my-groups.tsx b/frontend/components/dashboard/my-groups.tsx index e1ab30c..8fe5c23 100644 --- a/frontend/components/dashboard/my-groups.tsx +++ b/frontend/components/dashboard/my-groups.tsx @@ -3,7 +3,6 @@ import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" -import { Skeleton } from "@/components/ui/skeleton" import { Pagination, PaginationContent, @@ -11,7 +10,7 @@ import { PaginationNext, PaginationPrevious, } from "@/components/ui/pagination" -import { Users, TrendingUp, Calendar, ArrowRight } from "lucide-react" +import { Users, TrendingUp, Calendar, ArrowRight, Loader2 } from "lucide-react" import Link from "next/link" import { motion } from "framer-motion" import { useState, useEffect, useCallback } from "react" @@ -24,8 +23,6 @@ import { TargetPoolState, FlexiblePoolState, } from "@/hooks/useJointSaveContracts" -import { EmptyState } from "@/components/dashboard/empty-state" -import { FirstPoolTooltip } from "@/components/dashboard/first-pool-tooltip" const PAGE_SIZE = 6 @@ -49,71 +46,34 @@ interface MyGroupsProps { onCreateClick?: () => void } -const container = { - hidden: { opacity: 0 }, - show: { opacity: 1, transition: { staggerChildren: 0.1 } }, +type PoolWithLive = Pool & { + liveTotalSaved?: number + liveProgress?: number + progressLabel?: string } -const item = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } } -// ── Skeleton for a single pool card ────────────────────────────────────────── -function PoolCardSkeleton() { - return ( - - ) -} +const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1 } } } +const item = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } } +const formatXlm = (n: number) => `${n.toFixed(2)} XLM` // ── Per-pool card that hydrates live balances from the unified cache ────────── function PoolCard({ pool }: { pool: Pool }) { + // Use contract address as cache key for deployed pools, fallback to DB id const cacheKey = pool.contract_address && pool.contract_address !== "pending_deployment" ? pool.contract_address : pool.id + const { data, isLoading } = usePoolData(cacheKey) - const getLiveStats = (): { - totalSaved: number - progress: number - progressLabel: string - } => { + // Derive live stats from cache when available + const getLiveStats = (): { totalSaved: number; progress: number; progressLabel: string } => { const onchain = data?.onchain ?? null + if (pool.type === "rotational" && onchain) { const s = onchain as RotationalPoolState const totalMembers = s.members.length || pool.members_count || 1 - const progress = Math.min( - 100, - Math.round((s.currentRound / totalMembers) * 100) - ) + const progress = Math.min(100, Math.round((s.currentRound / totalMembers) * 100)) const perRound = (pool.contribution_amount || 0) * totalMembers const totalSaved = s.currentRound * perRound return { @@ -122,6 +82,7 @@ function PoolCard({ pool }: { pool: Pool }) { progressLabel: `Round ${s.currentRound + 1} of ${totalMembers}`, } } + if (pool.type === "target" && onchain) { const s = onchain as TargetPoolState const saved = stroopsToXlm(s.totalDeposited) @@ -133,6 +94,7 @@ function PoolCard({ pool }: { pool: Pool }) { progressLabel: `${saved.toFixed(2)} / ${target.toFixed(2)} XLM`, } } + if (pool.type === "flexible" && onchain) { const s = onchain as FlexiblePoolState const totalSaved = stroopsToXlm(s.totalBalance) @@ -152,15 +114,12 @@ function PoolCard({ pool }: { pool: Pool }) { : `${totalSaved.toFixed(2)} XLM saved`, } } - return { - totalSaved: pool.total_saved ?? 0, - progress: pool.progress ?? 0, - progressLabel: "", - } + + // Fallback to DB data + return { totalSaved: pool.total_saved ?? 0, progress: pool.progress ?? 0, progressLabel: "" } } const { totalSaved, progress, progressLabel } = getLiveStats() - const formatXlm = (n: number) => `${n.toFixed(2)} XLM` return ( @@ -168,30 +127,25 @@ function PoolCard({ pool }: { pool: Pool }) {

{pool.name}

- - {pool.type.charAt(0).toUpperCase() + pool.type.slice(1)} - + {pool.type.charAt(0).toUpperCase() + pool.type.slice(1)}
- - {pool.status} - + {pool.status}
+
- - Members + Members {pool.members_count}
- - Total Saved + Total Saved {isLoading && !data?.onchain ? ( - + ) : ( formatXlm(totalSaved) )} @@ -205,6 +159,7 @@ function PoolCard({ pool }: { pool: Pool }) { {pool.frequency || pool.status}
+
Progress @@ -222,6 +177,7 @@ function PoolCard({ pool }: { pool: Pool }) {

{progressLabel}

)}
+ +
+ +
) : ( <> - - - - {pools.map((pool) => ( - - ))} - + + {pools.map((pool) => { + const totalSaved = pool.liveTotalSaved ?? pool.total_saved ?? 0 + const progress = pool.liveProgress ?? pool.progress ?? 0 + return ( + + +
+
+

{pool.name}

+ {pool.type.charAt(0).toUpperCase() + pool.type.slice(1)} +
+ {pool.status} +
+ +
+
+ + Members + + {pool.members_count} +
+
+ + Total Saved + + {formatXlm(totalSaved)} +
+
+ + + {pool.type === "rotational" ? "Frequency" : "Status"} + + {pool.frequency || pool.status} +
+
+ +
+
+ Progress + {progress.toFixed(1)}% +
+
+ +
+ {pool.progressLabel && ( +

{pool.progressLabel}

+ )} +
+ + +
+
+ ) + })} +
{totalPages > 1 && (

- Showing {page * PAGE_SIZE + 1}– - {Math.min((page + 1) * PAGE_SIZE, total)} of {total} pools + Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total} pools

@@ -362,22 +359,14 @@ export function MyGroups({ onCreateClick }: MyGroupsProps) { setPage(page - 1)} aria-disabled={page === 0} - className={ - page === 0 - ? "pointer-events-none opacity-50" - : "cursor-pointer" - } + className={page === 0 ? "pointer-events-none opacity-50" : "cursor-pointer"} /> setPage(page + 1)} aria-disabled={page >= totalPages - 1} - className={ - page >= totalPages - 1 - ? "pointer-events-none opacity-50" - : "cursor-pointer" - } + className={page >= totalPages - 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} /> diff --git a/frontend/components/group/yield-dashboard.tsx b/frontend/components/group/yield-dashboard.tsx index 4a4165f..183856e 100644 --- a/frontend/components/group/yield-dashboard.tsx +++ b/frontend/components/group/yield-dashboard.tsx @@ -15,6 +15,7 @@ import { } from "@stellar/stellar-sdk" import { STELLAR_RPC_URL } from "@/components/web3-provider" import { STELLAR_NETWORK_PASSPHRASE } from "@/components/web3-provider" +import { getRpc } from "@/hooks/useJointSaveContracts" const TX_TIMEOUT = 300 @@ -87,9 +88,21 @@ export function YieldDashboard({ poolAddress }: YieldDashboardProps) { strategyAddress = null } else { // scvOption wrapping an address - const inner = stratVal.switch().name === "scvAddress" - ? stratVal - : stratVal.value() as xdr.ScVal + const kind = stratVal.switch().name; + + let inner: xdr.ScVal; + + if (kind === "scvAddress") { + inner = stratVal; + } else { + const value = stratVal.value(); + + if (!(value instanceof xdr.ScVal)) { + throw new Error("Expected ScVal"); + } + + inner = value; + } strategyAddress = Address.fromScVal(inner).toString() } } catch {} diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 8334171..7a17642 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -1,4 +1,7 @@ import globals from "globals"; +import tsParser from "@typescript-eslint/parser"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; +import reactHooks from "eslint-plugin-react-hooks"; export default [ { @@ -12,7 +15,28 @@ export default [ ecmaVersion: 2024, sourceType: "module", }, + parser: { + ts: tsParser, + tsx: tsParser, + }, + }, + plugins: { + "@typescript-eslint": tsPlugin, + "react-hooks": reactHooks, + }, + rules: { + ...tsPlugin.configs.recommended.rules, + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + }, + }, + { + files: ["**/*.{ts,tsx}"], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, - rules: {}, }, ]; diff --git a/frontend/hooks/useJointSaveContracts.ts b/frontend/hooks/useJointSaveContracts.ts index 690da0a..f5e572a 100644 --- a/frontend/hooks/useJointSaveContracts.ts +++ b/frontend/hooks/useJointSaveContracts.ts @@ -665,7 +665,7 @@ async function fetchContractStorage(contractId: string, keySymbol: string): Prom if (entry && typeof entry === "object") { if ("xdr" in entry) { - rawXdr = entry.xdr + rawXdr = (entry.xdr as string) } else if (entry.val && typeof (entry.val as any).toXDR === "function") { rawXdr = (entry.val as any).toXDR("base64") } diff --git a/frontend/package.json b/frontend/package.json index 0a29b74..28529e5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -76,11 +76,14 @@ "@types/node": "^22", "@types/react": "^19", "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.48.1", + "@typescript-eslint/parser": "^8.48.1", + "eslint": "^9", + "eslint-plugin-react-hooks": "^7.0.1", "postcss": "^8.5", "tailwindcss": "^4.1.9", "tw-animate-css": "1.3.3", - "typescript": "^5", - "eslint": "^9" + "typescript": "^5" }, "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" } From 8ddb9753641160416b2a674731224f994195904b Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Sun, 21 Jun 2026 12:29:02 +0100 Subject: [PATCH 7/7] small commit of group members --- frontend/components/group/group-members.tsx | 22 ++++----------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/frontend/components/group/group-members.tsx b/frontend/components/group/group-members.tsx index fe07c43..2936252 100644 --- a/frontend/components/group/group-members.tsx +++ b/frontend/components/group/group-members.tsx @@ -3,11 +3,11 @@ import { Card } from "@/components/ui/card"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; -import { Skeleton } from "@/components/ui/skeleton"; import { CheckCircle2, Clock, XCircle, + Loader2, AlertCircle, Award, } from "lucide-react"; @@ -87,23 +87,9 @@ export function GroupMembers({ if (isLoading && members.length === 0) { return ( - - -
- {Array.from({ length: 4 }).map((_, i) => ( -
-
- {/* avatar */} - -
- - -
-
- {/* status icon */} - -
- ))} + +
+
);