Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,168 changes: 313 additions & 855 deletions src/app/history/[id]/page.tsx

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ export default function HomePage() {
setOrderNo('')
}

const normalizePiName = (value: string) =>
value.trim().replace(/^@/, '');

const isSelfCounterparty = (value: string) => {
if (!currentUser?.pi_username) return false;
return normalizePiName(value) === normalizePiName(currentUser.pi_username);
};

// Validate inputs and open the appropriate modal
const handleOpen = async (orderType: OrderTypeEnum) => {
const name = counterparty.trim();
Expand All @@ -162,6 +170,10 @@ export default function HomePage() {
toast.error(orderType === OrderTypeEnum.Send ? 'Please enter Payee Pioneer Name' : 'Please enter Payer Pioneer Name');
return;
}
if (isSelfCounterparty(name)) {
toast.error('You cannot create an EscrowPi transaction with yourself');
return;
}
if (!desc) {
toast.error('Please enter EscrowPi Details');
return;
Expand Down Expand Up @@ -193,6 +205,10 @@ export default function HomePage() {
toast.error('SCREEN.MEMBERSHIP.VALIDATION.USER_NOT_LOGGED_IN_PAYMENT_MESSAGE')
return
}
if (isSelfCounterparty(counterparty)) {
toast.error('You cannot create an EscrowPi transaction with yourself');
return;
}
setIsSaveLoading(true)

// Create order with status initiated; store total amount
Expand Down Expand Up @@ -223,6 +239,10 @@ export default function HomePage() {

const handleRequest = async () => {
if (!currentUser) return
if (isSelfCounterparty(counterparty)) {
toast.error('You cannot create an EscrowPi transaction with yourself');
return;
}
setIsSaveLoading(true)
// Create order with status initiated; store total amount
const total = fees.total;
Expand Down
54 changes: 54 additions & 0 deletions src/components/AuditLogPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import React from "react";

type Comment = { author: string; text: string; ts: string };

interface AuditLogPreviewProps {
comments: Comment[];
offsetPx: number;
previewContainerRef: React.RefObject<HTMLDivElement>;
previewContentRef: React.RefObject<HTMLDivElement>;
onOpen: () => void;
}

export default function AuditLogPreview({
comments,
offsetPx,
previewContainerRef,
previewContentRef,
onOpen,
}: AuditLogPreviewProps) {
return (
<div className="min-h-28" aria-label="Open all comments">
<div className="font-semibold mb-2 md:mb-1 text-center">Audit Log</div>
<div
ref={previewContainerRef}
className={`rounded-lg border p-3 md:p-2 bg-white h-40 overflow-hidden cursor-pointer hover:bg-gray-50 flex flex-col justify-start`}
onClick={onOpen}
>
{comments.length === 0 ? (
<div className="text-xs text-gray-600">No comments yet.</div>
) : (
<div
ref={previewContentRef}
className="space-y-1 text-xs will-change-transform pb-3"
style={{ transform: `translateY(-${offsetPx}px)` }}
>
{comments.map((c, idx) => (
<div key={idx} className="py-1">
<div className="flex items-center gap-2">
<span className="font-medium text-[13px]">{c.author}</span>
<span className="text-[11px] text-gray-500">
{new Date(c.ts).toLocaleString()}
</span>
</div>
<div className="whitespace-pre-wrap break-words text-[13px]">{c.text}</div>
</div>
))}
</div>
)}
</div>
</div>
);
}
52 changes: 52 additions & 0 deletions src/components/CommentEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";

import React from "react";

interface CommentEditorProps {
value: string;
onChange: (value: string) => void;
onSubmit: () => void;
disabled: boolean;
}

export default function CommentEditor({
value,
onChange,
onSubmit,
disabled,
}: CommentEditorProps) {
const trimmed = value.trim();

return (
<div className="min-h-28">
<div className="font-semibold mb-2 md:mb-1 text-center">Add New Comment</div>
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
rows={4}
disabled={disabled}
placeholder={
disabled
? "Adding new comments is closed for this transaction."
: "Type your comment. You can include contact info or links."
}
className={`w-full rounded border p-2 text-xs ${
disabled ? "bg-gray-100 text-gray-400 cursor-not-allowed" : ""
}`}
/>
<div className="mt-2 flex justify-end">
<button
disabled={disabled || trimmed.length === 0}
className={`px-3 py-2 rounded text-xs font-semibold ${
!disabled && trimmed.length
? "bg-[var(--default-primary-color)] text-[var(--default-secondary-color)]"
: "bg-gray-200 text-gray-500 cursor-not-allowed"
}`}
onClick={onSubmit}
>
Add Comment
</button>
</div>
</div>
);
}
41 changes: 41 additions & 0 deletions src/components/ConfirmButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use client";

import React, { useState } from "react";

interface ConfirmButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick: () => void | Promise<void>;
}

export default function ConfirmButton({
onClick,
className = "",
disabled,
children,
...rest
}: ConfirmButtonProps) {
const [isSubmitting, setIsSubmitting] = useState(false);

const handleClick = async () => {
if (isSubmitting || disabled) return;
setIsSubmitting(true);
try {
await onClick();
} finally {
setIsSubmitting(false);
}
};

const mergedClassName = `${className} disabled:opacity-60 disabled:cursor-not-allowed`;

return (
<button
type="button"
{...rest}
className={mergedClassName}
disabled={disabled || isSubmitting}
onClick={handleClick}
>
{children}
</button>
);
}
29 changes: 26 additions & 3 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,38 @@ export default function Navbar() {
aria-label="Back"
className="w-full h-full flex items-center justify-center"
onClick={(e) => {
const goHome = () => {
try {
router.push('/?skipSplash=1');
} catch {
window.location.href = '/?skipSplash=1';
}
};
try {
e.preventDefault();
if (typeof window !== 'undefined') {
window.sessionStorage.setItem('escrowpi:cameFromInternalNav', '1');
}
router.push(backHref);
if (isTxDetails || isHistoryList) {
router.push(backHref);
return;
}
if (typeof window === 'undefined') {
goHome();
return;
}
const canGoBack = window.history.length > 1;
if (canGoBack) {
window.history.back();
} else {
goHome();
}
} catch {
// As a last resort, hard navigate
window.location.href = backHref;
if (isTxDetails || isHistoryList) {
window.location.href = backHref;
} else {
goHome();
}
}
}}
>
Expand Down
Loading