diff --git a/__tests__/accordion.test.ts b/__tests__/accordion.test.ts deleted file mode 100644 index bec5e7f..0000000 --- a/__tests__/accordion.test.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { describe, it } from "node:test"; -import assert from "node:assert/strict"; -import type { AccordionItem } from "../components/ui/accordion"; - -// Accordion State Management Logic Tests -// These tests verify the core expansion/collapse behavior of the Accordion component - -const MOCK_ITEMS: AccordionItem[] = [ - { - id: "item-1", - title: "First Question", - content: "This is the first answer.", - }, - { - id: "item-2", - title: "Second Question", - content: "This is the second answer.", - }, - { - id: "item-3", - title: "Third Question", - content: "This is the third answer.", - }, -]; - -// Simulate accordion state management logic -class AccordionStateManager { - private expandedIds: Set; - private allowMultiple: boolean; - - constructor(allowMultiple = false) { - this.expandedIds = new Set(); - this.allowMultiple = allowMultiple; - } - - toggleItem(id: string): void { - const newExpanded = new Set(this.expandedIds); - - if (newExpanded.has(id)) { - newExpanded.delete(id); - } else { - if (!this.allowMultiple) { - newExpanded.clear(); - } - newExpanded.add(id); - } - - this.expandedIds = newExpanded; - } - - isExpanded(id: string): boolean { - return this.expandedIds.has(id); - } - - getExpandedIds(): Set { - return new Set(this.expandedIds); - } - - reset(): void { - this.expandedIds.clear(); - } -} - -describe("Accordion State Management", () => { - describe("Single Expansion Mode (default)", () => { - it("starts with no items expanded", () => { - const manager = new AccordionStateManager(false); - - MOCK_ITEMS.forEach((item) => { - assert.equal( - manager.isExpanded(item.id), - false, - `Item ${item.id} should not be expanded initially` - ); - }); - }); - - it("expands an item when toggled", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - assert.equal(manager.isExpanded("item-1"), true, "Item should be expanded"); - }); - - it("collapses an item when toggled again", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - assert.equal(manager.isExpanded("item-1"), true); - - manager.toggleItem("item-1"); - assert.equal(manager.isExpanded("item-1"), false, "Item should be collapsed"); - }); - - it("closes previous item when opening a new one", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - assert.equal(manager.isExpanded("item-1"), true); - - manager.toggleItem("item-2"); - assert.equal( - manager.isExpanded("item-1"), - false, - "First item should be closed" - ); - assert.equal( - manager.isExpanded("item-2"), - true, - "Second item should be open" - ); - }); - - it("only one item can be expanded at a time", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - manager.toggleItem("item-2"); - manager.toggleItem("item-3"); - - const expandedIds = manager.getExpandedIds(); - assert.equal( - expandedIds.size, - 1, - "Only one item should be expanded" - ); - assert.equal( - Array.from(expandedIds)[0], - "item-3", - "Last toggled item should be expanded" - ); - }); - }); - - describe("Multiple Expansion Mode", () => { - it("allows multiple items to be expanded", () => { - const manager = new AccordionStateManager(true); - - manager.toggleItem("item-1"); - manager.toggleItem("item-2"); - - assert.equal(manager.isExpanded("item-1"), true); - assert.equal(manager.isExpanded("item-2"), true); - }); - - it("maintains all expanded items", () => { - const manager = new AccordionStateManager(true); - - manager.toggleItem("item-1"); - manager.toggleItem("item-2"); - manager.toggleItem("item-3"); - - const expandedIds = manager.getExpandedIds(); - assert.equal(expandedIds.size, 3, "All three items should be expanded"); - }); - - it("can close individual items without affecting others", () => { - const manager = new AccordionStateManager(true); - - manager.toggleItem("item-1"); - manager.toggleItem("item-2"); - manager.toggleItem("item-3"); - - // Close middle item - manager.toggleItem("item-2"); - - assert.equal(manager.isExpanded("item-1"), true); - assert.equal(manager.isExpanded("item-2"), false); - assert.equal(manager.isExpanded("item-3"), true); - }); - - it("allows toggling all items on and off", () => { - const manager = new AccordionStateManager(true); - - // Open all - MOCK_ITEMS.forEach((item) => manager.toggleItem(item.id)); - assert.equal( - manager.getExpandedIds().size, - MOCK_ITEMS.length, - "All items should be open" - ); - - // Close all - MOCK_ITEMS.forEach((item) => manager.toggleItem(item.id)); - assert.equal( - manager.getExpandedIds().size, - 0, - "All items should be closed" - ); - }); - }); - - describe("State Transitions", () => { - it("resets all expanded items", () => { - const manager = new AccordionStateManager(true); - - manager.toggleItem("item-1"); - manager.toggleItem("item-2"); - assert.equal(manager.getExpandedIds().size, 2); - - manager.reset(); - assert.equal(manager.getExpandedIds().size, 0, "Should have no expanded items"); - }); - - it("handles rapid toggling", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - manager.toggleItem("item-1"); - manager.toggleItem("item-1"); - - assert.equal(manager.isExpanded("item-1"), true, "Should be expanded"); - }); - - it("handles toggling non-existent items", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - manager.toggleItem("non-existent"); - - assert.equal(manager.isExpanded("item-1"), false, "Original item should close"); - assert.equal(manager.isExpanded("non-existent"), true, "Non-existent item should be tracked"); - }); - }); - - describe("Item Data Integrity", () => { - it("preserves all item data", () => { - const items = [...MOCK_ITEMS]; - - items.forEach((item) => { - assert.ok(item.id, "Item should have ID"); - assert.ok(item.title, "Item should have title"); - assert.ok(item.content, "Item should have content"); - }); - }); - - it("validates item IDs are unique", () => { - const ids = MOCK_ITEMS.map((item) => item.id); - const uniqueIds = new Set(ids); - - assert.equal(ids.length, uniqueIds.size, "All item IDs should be unique"); - }); - - it("ensures no empty titles or contents", () => { - const items = [...MOCK_ITEMS]; - - items.forEach((item) => { - assert.ok( - item.title.trim().length > 0, - `Item ${item.id} should have non-empty title` - ); - assert.ok( - item.content.trim().length > 0, - `Item ${item.id} should have non-empty content` - ); - }); - }); - }); - - describe("Edge Cases", () => { - it("handles empty item list", () => { - const manager = new AccordionStateManager(false); - - assert.equal(manager.getExpandedIds().size, 0); - manager.toggleItem("any-id"); - assert.equal(manager.isExpanded("any-id"), true); - }); - - it("handles single item", () => { - const manager = new AccordionStateManager(false); - - manager.toggleItem("item-1"); - assert.equal(manager.isExpanded("item-1"), true); - - manager.toggleItem("item-1"); - assert.equal(manager.isExpanded("item-1"), false); - }); - - it("switching from single to multiple mode", () => { - const singleManager = new AccordionStateManager(false); - - singleManager.toggleItem("item-1"); - singleManager.toggleItem("item-2"); - - assert.equal(singleManager.getExpandedIds().size, 1, "Single mode: only one item"); - - const multiManager = new AccordionStateManager(true); - multiManager.toggleItem("item-1"); - multiManager.toggleItem("item-2"); - - assert.equal(multiManager.getExpandedIds().size, 2, "Multiple mode: both items"); - }); - }); -}); diff --git a/app/faq/page.tsx b/app/faq/page.tsx deleted file mode 100644 index f31c37c..0000000 --- a/app/faq/page.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client"; - -import { Accordion } from "@/components/ui/accordion"; -import { PageTransition } from "@/components/ui/page-transition"; - -const FAQ_ITEMS = [ - { - id: "what-is-escrow", - title: "What is a Stellar escrow and how does it work?", - content: - "A Stellar escrow is a smart contract that holds funds until specific conditions are met. In PayEasy, when roommates split rent, the escrow holds the money until the payment deadline is reached. Once the deadline passes, the landlord can release the funds. If the deadline is missed or cancelled, funds can be refunded to renters. This ensures trustless, transparent payment sharing without needing a middleman.", - }, - { - id: "what-is-freighter", - title: "What is Freighter and why do I need it?", - content: - "Freighter is a browser wallet for the Stellar blockchain. It securely manages your private keys and allows you to sign transactions without exposing your credentials. PayEasy integrates with Freighter so you can connect your wallet, fund escrows, and manage payments directly from the app. Download it as a browser extension to get started.", - }, - { - id: "what-is-stellar", - title: "What is Stellar and how is it different from other blockchains?", - content: - "Stellar is a fast, low-cost blockchain designed for payments. Unlike Bitcoin or Ethereum, Stellar processes transactions in 3-5 seconds with minimal fees (typically fractions of a cent per transaction). This makes it ideal for rent payments where speed and affordability matter. Stellar is maintained by the Stellar Development Foundation and has been trusted for payments since 2014.", - }, - { - id: "what-are-fees", - title: "What fees does PayEasy charge?", - content: - "PayEasy itself charges no platform fees. You only pay Stellar network fees, which are extremely small—typically 0.00001 XLM per operation (worth less than a penny). There are no hidden charges, escrow fees, or processing costs. The only cost is the minimal Stellar network fee required by the blockchain to process your transaction.", - }, - { - id: "testnet-vs-mainnet", - title: "What's the difference between testnet and mainnet?", - content: - "Testnet is Stellar's sandbox for testing. You can use free test XLM from Friendbot to experiment without real money. It's perfect for learning PayEasy and creating test escrows. Mainnet is the live Stellar network where real XLM and real money transactions happen. Start on testnet to get comfortable, then move to mainnet when you're ready to use real funds.", - }, - { - id: "how-to-release", - title: "How do I release funds from an escrow after the deadline?", - content: - "Once the escrow deadline has passed, the payment recipient (usually the landlord) can release funds through the PayEasy dashboard. Navigate to the escrow, check that the deadline has passed, and click the Release button. You'll confirm the transaction in Freighter, and the funds will be transferred to the recipient's wallet. This typically completes within 5 seconds.", - }, - { - id: "how-to-contribute", - title: "How do I contribute funds to an escrow?", - content: - "To contribute to an escrow: 1) Connect your Freighter wallet, 2) Navigate to the escrow, 3) Enter your contribution amount, 4) Confirm the transaction in Freighter. Your funds will be held in the escrow until the deadline passes. You can see your contribution progress on the escrow dashboard, and if the deadline is cancelled for any reason, you can request a refund of your contribution.", - }, - { - id: "what-is-deadline", - title: "What is the escrow deadline and why does it matter?", - content: - "The escrow deadline is the date by which all roommates must contribute their share of rent. Once the deadline passes, the escrow becomes locked—no more contributions can be made. Only after the deadline can funds be released to the landlord. If someone misses the deadline, they'll need to either contribute late (if the landlord allows) or handle it separately. Choose deadlines strategically to give everyone enough time.", - }, - { - id: "can-contribute-after-deadline", - title: "Can I contribute to an escrow after the deadline?", - content: - "No, once the deadline passes, the escrow is locked and no new contributions can be accepted. This is by design to prevent disputes about who paid and when. If you miss a deadline, discuss with your roommates to either: 1) Update the deadline and freeze/unfreeze the escrow, or 2) Handle the late payment outside the escrow. It's important to set realistic deadlines that work for everyone.", - }, - { - id: "what-if-someone-doesnt-pay", - title: "What if a roommate doesn't contribute their share?", - content: - "PayEasy's escrow system enforces contribution transparency. If a roommate doesn't contribute by the deadline, you'll immediately see it on the dashboard. You have several options: 1) Negotiate with them directly, 2) Extend the deadline and freeze the escrow, 3) Cover their share yourself, or 4) Cancel the escrow and refund contributions. The blockchain ensures no one can secretly keep funds—everything is transparent and auditable.", - }, - { - id: "how-to-get-testnet-xlm", - title: "How do I get test XLM for testnet?", - content: - "When viewing the wallet on testnet, you'll see a 'Friendbot' button. Click it to receive 10,000 test XLM instantly. This testnet-only currency is free and lets you experiment with PayEasy without spending real money. Friendbot can be used multiple times, so you can always get more test XLM if you run out. Test XLM has no real-world value.", - }, - { - id: "can-i-refund-contribution", - title: "Can I refund my contribution once it's in the escrow?", - content: - "Yes, you can refund your contribution before the deadline is reached. Navigate to the escrow, view your contribution, and click the Refund button. You'll confirm in Freighter, and your funds will be returned to your wallet. Once the deadline passes, the escrow is locked—you'll need to contact the recipient to arrange a refund. Plan ahead and contribute only when you're certain.", - }, -]; - -export default function FAQPage() { - return ( - -
- {/* Hero Section */} -
-
-

- Frequently Asked Questions -

-

- Everything you need to know about PayEasy, Stellar escrows, and blockchain-powered rent sharing. -

-
- - {/* Accordion */} -
- -
- - {/* CTA Section */} -
-

- Still have questions? -

-

- Check out our documentation or reach out to the community on GitHub. -

- -
-
-
-
- ); -} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx deleted file mode 100644 index 5f06b9a..0000000 --- a/components/ui/accordion.tsx +++ /dev/null @@ -1,110 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import { ChevronDown } from "lucide-react"; - -export interface AccordionItem { - id: string; - title: string; - content: string; -} - -interface AccordionProps { - items: AccordionItem[]; - allowMultiple?: boolean; - data?: Record; -} - -export function Accordion({ - items, - allowMultiple = false, - data, -}: AccordionProps) { - const [expandedIds, setExpandedIds] = useState>(new Set()); - - const toggleItem = (id: string) => { - const newExpanded = new Set(expandedIds); - - if (newExpanded.has(id)) { - newExpanded.delete(id); - } else { - if (!allowMultiple) { - newExpanded.clear(); - } - newExpanded.add(id); - } - - setExpandedIds(newExpanded); - }; - - return ( -
- {items.map((item) => ( - toggleItem(item.id)} - /> - ))} -
- ); -} - -interface AccordionItemComponentProps { - item: AccordionItem; - isExpanded: boolean; - onToggle: () => void; -} - -function AccordionItemComponent({ - item, - isExpanded, - onToggle, -}: AccordionItemComponentProps) { - return ( -
- {/* Header */} - - - {/* Content */} - - {isExpanded && ( - -
- {item.content} -
-
- )} -
-
- ); -}