Skip to content

Commit

Permalink
[web] feat: escrow initialization api endpoint #48 (#86)
Browse files Browse the repository at this point in the history
* feat: Implement Escrow Initialization API Endpoint #48

* fix: escrow types

* fix: metadata fields

* fix: minor fixes

* fix: Number

* enhancement: dates

* enhancement: dates

* feat: refactoring

* fix: refactoring, fixes

* feat: enhance forms inputs accessibility (#79)

* feat: adding aria attributes

* feat: removing extra attributes

* Update apps/web/app/(auth-pages)/sign-in/page.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update apps/web/app/(auth-pages)/sign-up/page.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: adding error messages

* feat: adding values to footer input

* feat: fix build error

* feat: adding validation hook

* Update apps/web/hooks/use-form-validation.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update apps/web/hooks/use-form-validation.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update apps/web/hooks/use-form-validation.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: adjusting email regex

* Update apps/web/hooks/use-form-validation.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: adding resetValidation

* feat: adjusting resetValidation

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* [web] feat: add descriptive aria labels to all buttons  (#80)

* feat: update all buttons with descriptive aria-labels

* fix: remove redundant aria-labels

* fix: remove redundant aria-label from sign-up button

* fix: refactor aria-label text on sign up and sign in button

* feat: test commit

* refactor(buttons): simplify aria-labels for accessibility

* refactor(buttons): remove redundant aria-labels for clarity

* feat: adding semantic html for dialog (#76)

* feat: adding semantic html for dialog

* fix: code rabbit fixes

* feat: adding semantic html for dialog

* feat: adding semantic html for dialog

* feat: adding semantic html for dialog

* fix(dialog): last fixes for accessibility

* fix: adding coderabbit changes

* fix: adding coderabbit changes

* Update dialog.tsx

* feat(components): Create project details components for project page (#97)

Co-authored-by: Brandon Fernández <[email protected]>

* feat(alert):Implemented-aria-live-regions-#34 (#95)

* feat(alert):Implemented-aria-live-regions-#34

* Update apps/web/components/base/alert-dialog.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update apps/web/components/base/alert-dialog.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Brandon Fernández <[email protected]>

* update: improvementes on navigation menu (#89)

* update: improvementes on navigation menu

* update: improvement on navigation menu

* update: comment to sign the commit

* update remove comment to sign the commit

---------

Co-authored-by: Dario Zamora Rojas <[email protected]>
Co-authored-by: Brandon Fernández <[email protected]>

* feat: Feat building participation card v2 (#98)

* [web] impr: rename, organize and implement lazy loading in home sections (#82)

* feat(web): add sections directory structure and organize home components

* impr(web): implement lazy loading for home sections

* chore(web): resolve requested changes from PR #38

* chore(web): resolve requested changes from PR #38

* chore(web): resolve requested changes from PR #38

* chore(web): resolve requested changes from PR #38

* chore(web): resolve requested changes from PR #38

* chore(web): remove unnecessary files

* fix(web): build errors

---------

Co-authored-by: Roberto Lucas <[email protected]>

* [web] feat: project achievement components (#99)

* feat: implement user achievements and collection component - user view

* refactor: move fallbacks folder to components folder

---------

Co-authored-by: Roberto Lucas <[email protected]>

* [web] feat: kindfi theme colours and breakpoints for (#96)

* Add custom theme and breakpoints for ShadCN

* Add custom theme and breakpoints to Tailwind config update

* refactor: extract breakpoints into shared constant

* refactor: extract breakpoints into shared constant

* refactor: extract breakpoints into shared constant

* refactor: extract breakpoints into shared constant

* feat: updated colour patterns

* enhancements: AI recommendations

* enhancements: AI recommendations1

* enhancement: modify type

* [web] feat: improve dialog focus management and a11y + linting errors (#105)

* feat(dialog): improve focus management and a11y. Fix linting errors

1. Improve Dialog focus management and a11y
2. Fix linting errors.

* refactor: use event IDs as keys instead of titles/labels. Wrap media thumbnails in buttons

* refactor: add --no-install clause to husky commit-msg

* fix: assign appropraite IDs to mocked data in otder to resolve type errors in project-updates.tsx

* fix: remove duplicate ID in mock data

* [web] impr: remove duplicated layout #78 (#94)

* fix(layout): resolve duplicated layout issue and remove redundant wrapper

* fix: duplicate layout issues and lint errors

* fix(components): resolve linting issues in project components

---------

Co-authored-by: Favour Abangwu <[email protected]>

* fix: base img render + copilot instructions + biome config tweak (#112)

* fix: base img render + copilot instructions + biome config tweak

* fix: coderabbitai suggestions

* refactor(ui): create use-reduced alt, and apply it across affected components (#108)

Co-authored-by: Brandon Fernández <[email protected]>
Co-authored-by: Roberto Lucas <[email protected]>

---------

Co-authored-by: Diego Barquero Quesada <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Favour Abangwu <[email protected]>
Co-authored-by: Kevin Latino <[email protected]>
Co-authored-by: Josué Araya Marín <[email protected]>
Co-authored-by: Brandon Fernández <[email protected]>
Co-authored-by: Frankie Power <[email protected]>
Co-authored-by: Dario Zamora <[email protected]>
Co-authored-by: Dario Zamora Rojas <[email protected]>
Co-authored-by: davedumto <[email protected]>
Co-authored-by: Derian <[email protected]>
Co-authored-by: Roberto Lucas <[email protected]>
Co-authored-by: Nnaji Benjamin <[email protected]>
Co-authored-by: ayo-ola0710 <[email protected]>
Co-authored-by: Emmanuel Ejei-Okeke <[email protected]>
Co-authored-by: Lulu <[email protected]>
Co-authored-by: Marvelousmicheal <[email protected]>
  • Loading branch information
18 people authored Feb 2, 2025
1 parent aa80f9b commit 295b720
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 1 deletion.
8 changes: 7 additions & 1 deletion apps/web/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@
# Update these with your Supabase details from your project settings > API
# https://app.supabase.com/project/_/settings/api
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=supabase-service-key
STELLAR_NETWORK_URL=stellar-network-url



128 changes: 128 additions & 0 deletions apps/web/app/api/escrow/initialize/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";
import { validateEscrowInitialization } from "~/lib/validators/escrow";
import { initializeEscrowContract } from "~/lib/stellar/escrow";
import type { EscrowInitialization } from "~/lib/types/escrow";
import { AppError } from "~/lib/errors";

const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function POST(req: NextRequest) {
try {
const initializationData: EscrowInitialization = await req.json();
const validationResult =
validateEscrowInitialization(initializationData);

if (!validationResult.success) {
return NextResponse.json(
{
error: "Invalid escrow initialization",
details: validationResult.errors
},
{ status: 400 }
);
}

const contractResult = await initializeEscrowContract(
initializationData.contractParams,
initializationData.contractParams.parties.payerSecretKey
);

if (!contractResult.success) {
return NextResponse.json(
{
error: "Failed to initialize escrow contract",
details: contractResult.error
},
{ status: 500 }
);
}

const { data: dbResult, error: dbError } = await supabase
.from("escrow_contracts")
.insert({
engagement_id: contractResult.engagementId,
contract_id: contractResult.contractAddress,
project_id: initializationData.metadata.projectId,
contribution_id: contractResult.contributionId,
payer_address: initializationData.contractParams.parties.payer,
receiver_address:
initializationData.contractParams.parties.receiver,
amount: contractResult.totalAmount,
platform_fee: initializationData.contractParams.platformFee,
current_state: "PENDING",
metadata: initializationData.metadata
})
.select("id")
.single();

if (dbError) {
return NextResponse.json(
{
error: "Failed to track escrow contract",
details: dbError
},
{ status: 500 }
);
}

// If successful, update the state to INITIALIZED
await supabase
.from("escrow_contracts")
.update({ current_state: "INITIALIZED" })
.eq("id", dbResult.id);

return NextResponse.json(
{
escrowId: dbResult.id,
contractAddress: contractResult.contractAddress,
status: "INITIALIZED"
},
{ status: 201 }
);
} catch (error) {
if (error instanceof AppError) {
console.error("Escrow initialization error:", error);
return NextResponse.json(
{
error: error.message,
details: error.details // Include additional details for troubleshooting
},
{ status: error.statusCode }
);
}

console.error("Internal server error during escrow initialization:", error);
return NextResponse.json(
{
error: "Internal server error during escrow initialization"
},
{ status: 500 }

);
}
}

const initializeEscrow = async (data: EscrowInitialization) => {
const response = await fetch("/api/escrow/initialize", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});

if (!response.ok) {
const error = await response.json();
throw {
error: error.error || "Failed to initialize escrow",
details: error.details || null
};
}

return response.json();
};
17 changes: 17 additions & 0 deletions apps/web/lib/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export class AppError extends Error {
public statusCode: number;
public details?: T;

constructor(message: string, statusCode: number, details?: any) {
super(message);
this.statusCode = statusCode;
this.details = details as T;
this.name = "AppError";
}
}

export interface AppErrorResponse<T = unknown> {
error: string; // A brief error message
details?: T; // Optional detailed information about the error
statusCode?: number; // Optional HTTP status code
}
83 changes: 83 additions & 0 deletions apps/web/lib/stellar/escrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
TransactionBuilder,
Networks,
Operation,
Asset,
Predicate,
Server,
Keypair
} from "stellar-sdk";
import type { EscrowContractParams } from "../types/escrow";
import { generateUniqueId } from "../utils/id";
import { AppError } from "../errors";

interface EscrowContractResult {
success: boolean;
contractAddress?: string;
engagementId?: string;
contributionId?: string;
totalAmount?: number;
error?: string;
}

export async function initializeEscrowContract(
params: EscrowContractParams,
secretKey: string
): Promise<EscrowContractResult> {
try {
// Calculate total amount including all milestones
const totalAmount = params.milestones.reduce(
(sum, milestone) => sum + milestone.amount,
0
);

// Generate unique IDs
const engagementId = generateUniqueId();
const contributionId = generateUniqueId();

// Initialize contract on Stellar
const transaction = new TransactionBuilder(params.parties.payer, {
fee: "100",
networkPassphrase: Networks.TESTNET // or MAINNET for production
})
.addOperation(
Operation.createClaimableBalance({
amount: totalAmount.toString(),
asset: Asset.native(),
claimants: [
{
destination: params.parties.receiver,
predicate: Predicate.and([
Predicate.beforeRelativeTime("12096000"), // 140 days
Predicate.not(Predicate.beforeAbsoluteTime("0"))
])
}
]
})
)
.setTimeout(30)
.build();

// Sign the transaction
transaction.sign(Keypair.fromSecret(secretKey));

// Submit the transaction to the Stellar network
const server = new Server(process.env.STELLAR_NETWORK_URL!); // Set your Stellar network URL
const result = await server.submitTransaction(transaction);

return {
success: true,
contractAddress: result.hash,
engagementId,
contributionId,
totalAmount
};
} catch (error) {
console.error("Failed to initialize escrow contract:", error);
throw new AppError(
error instanceof Error ? error.message : "Unknown error occurred",
500,
error
);
}
}
59 changes: 59 additions & 0 deletions apps/web/lib/types/escrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

export type EscrowState =
| "NEW"
| "ACTIVE"
| "IN_DISPUTE"
| "COMPLETED"
| "CANCELLED";

export interface Milestone {
id: string;
title: string;
description: string;
amount: number;
status: "PENDING" | "APPROVED" | "REJECTED";
dueDate: string;
}

export interface EscrowParties {
payer: string; // Project supporter address
receiver: string; // Project creator address
reviewers: string[]; // Platform reviewers
}

export interface EscrowContractParams {
parties: EscrowParties;
milestones: Milestone[];
disputeResolver: string;
platformFee: number;
}

export interface EscrowMetadata {
projectId: string;
engagementType: string;
description: string;
tags: string[];
}

export interface EscrowInitialization {
contractParams: EscrowContractParams;
metadata: EscrowMetadata;
}

// Database types
export interface EscrowContract {
id: string;
engagement_id: string;
contract_id: string;
project_id: string;
contribution_id: string;
payer_address: string;
receiver_address: string;
amount: number;
current_state: EscrowState;
platform_fee: number;
created_at: Date;
updated_at: Date;
completed_at?: Date;
metadata: EscrowMetadata;
}
17 changes: 17 additions & 0 deletions apps/web/lib/utils/id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Generates a unique identifier combining timestamp and random elements
* Format: timestamp-randomString (e.g., 1706547362-a1b2c3d4)
*/
export function generateUniqueId(): string {
// Get current timestamp in seconds
const timestamp = Math.floor(Date.now() / 1000);

// Generate 8 random hex characters
const randomPart = Array.from(
{ length: 8 },
() => Math.floor(Math.random() * 16).toString(16)
).join('');

// Combine timestamp and random string
return `${timestamp}-${randomPart}`;
}
Loading

0 comments on commit 295b720

Please sign in to comment.