Skip to content

Commit a98f846

Browse files
committed
wishlist pages merged
2 parents 5390ec8 + b7e6da9 commit a98f846

16 files changed

Lines changed: 965 additions & 158 deletions

File tree

‎.husky/pre-commit‎

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,50 @@
1+
#!/usr/bin/env bash
12

2-
# Run lint using local Next CLI via Node to avoid npm PATH issues on Windows/MSYS
3-
if command -v node >/dev/null 2>&1; then
4-
# Enforce Next.js Node engine range: ^18.18.0 || ^19.8.0 || >= 20.0.0
5-
VER=$(node -p "process.versions.node" 2>/dev/null)
6-
MAJOR=${VER%%.*}
7-
REST=${VER#*.}
8-
MINOR=${REST%%.*}
9-
ALLOWED=0
10-
if [ "$MAJOR" -ge 20 ]; then
11-
ALLOWED=1
12-
elif [ "$MAJOR" -eq 19 ] && [ "$MINOR" -ge 8 ]; then
13-
ALLOWED=1
14-
elif [ "$MAJOR" -eq 18 ] && [ "$MINOR" -ge 18 ]; then
15-
ALLOWED=1
16-
fi
17-
18-
if [ "$ALLOWED" -ne 1 ]; then
19-
echo "[husky] Detected Node $VER which is below Next.js requirement (^18.18 || ^19.8 || >=20). Skipping lint."
20-
exit 0
21-
fi
22-
23-
if [ -f "node_modules/next/dist/bin/next" ]; then
24-
echo "[husky] Running next lint"
25-
node node_modules/next/dist/bin/next lint
26-
exit $?
27-
else
28-
echo "[husky] next is not installed locally. Skipping lint."
29-
exit 0
30-
fi
31-
else
32-
echo "[husky] Node.js not found in PATH. Skipping lint."
3+
# Cross-platform Husky pre-commit hook
4+
# Avoid relying on global/npm path (Windows Git Bash sometimes points to missing C:/Program Files/nodejs/npm)
5+
# Runs Next.js lint directly via node. Skips if Node too old or dependencies missing.
6+
7+
set -euo pipefail
8+
9+
# Ensure we run from repo root (husky sets cwd to .husky by default in some versions)
10+
cd "$(dirname "$0")/.."
11+
12+
if ! command -v node >/dev/null 2>&1; then
13+
echo "[husky] node not found in PATH - skipping lint" >&2
14+
exit 0
15+
fi
16+
17+
# Minimal Node version (adjust if project engines requirement changes)
18+
NODE_VERSION=$(node -p "process.versions.node")
19+
NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1)
20+
NODE_MINOR=$(echo "$NODE_VERSION" | cut -d. -f2)
21+
22+
# Next.js requirement: ^18.18.0 || ^19.8.0 || >=20.0.0
23+
meets_req=false
24+
if [ "$NODE_MAJOR" -ge 20 ]; then
25+
meets_req=true
26+
elif [ "$NODE_MAJOR" -eq 19 ] && [ "$NODE_MINOR" -ge 8 ]; then
27+
meets_req=true
28+
elif [ "$NODE_MAJOR" -eq 18 ] && [ "$NODE_MINOR" -ge 18 ]; then
29+
meets_req=true
30+
fi
31+
32+
if [ "$meets_req" != true ]; then
33+
echo "[husky] Node $NODE_VERSION does not satisfy Next.js required range (^18.18 || ^19.8 || >=20) - skipping lint" >&2
3334
exit 0
3435
fi
36+
37+
# If dependencies not installed, skip (e.g., during initial clone CI step)
38+
if [ ! -f node_modules/next/dist/bin/next ]; then
39+
echo "[husky] node_modules not present - skipping lint" >&2
40+
exit 0
41+
fi
42+
43+
echo "[husky] Running lint (next lint)" >&2
44+
node node_modules/next/dist/bin/next lint || {
45+
status=$?
46+
echo "[husky] Lint failed (exit $status)" >&2
47+
exit $status
48+
}
49+
50+
echo "[husky] Lint passed" >&2

‎package-lock.json‎

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/app/api/wishlists/route.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export async function GET(req: NextRequest) {
8686
status: 200,
8787
});
8888
} catch (error) {
89+
console.error(error);
8990
return errorResponse(error);
9091
}
9192
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"use client";
2+
3+
import { useParams } from "next/navigation";
4+
import { useSession } from "next-auth/react";
5+
import { redirect } from "next/navigation";
6+
import PartnerWishlistScreen from "@/screens/Wishlist/PartnerWishlistScreen";
7+
import { isStaff } from "@/lib/userUtils";
8+
9+
export default function PartnerWishlistPage() {
10+
const params = useParams();
11+
const partnerId = Number(params?.partnerId);
12+
13+
const { data: session } = useSession();
14+
if (!session?.user?.type) {
15+
redirect("/signIn");
16+
}
17+
18+
// Only staff or above can access arbitrary partnerId; partners should use /wishlists
19+
if (!isStaff(session.user.type)) {
20+
redirect("/wishlists");
21+
}
22+
23+
return <PartnerWishlistScreen partnerId={partnerId} readOnly />;
24+
}

‎src/app/wishlists/page.tsx‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use client";
2+
3+
import { useSession } from "next-auth/react";
4+
import { redirect } from "next/navigation";
5+
import { isStaff } from "@/lib/userUtils";
6+
import AdminWishlistScreen from "@/screens/Wishlist/AdminWishlistScreen";
7+
import PartnerWishlistScreen from "@/screens/Wishlist/PartnerWishlistScreen";
8+
9+
export default function WishlistsPage() {
10+
const { data: session } = useSession();
11+
12+
if (!session?.user?.type) {
13+
redirect("/signIn");
14+
}
15+
16+
if (isStaff(session.user.type)) {
17+
return <AdminWishlistScreen />;
18+
}
19+
20+
// Partner sees their own wishlist
21+
return <PartnerWishlistScreen />;
22+
}
23+

‎src/components/CommentModal.tsx‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"use client";
2+
3+
import React, { useEffect, useState } from "react";
4+
import { X } from "@phosphor-icons/react";
5+
import ModalTextField from "./ModalTextField";
6+
7+
interface CommentModalProps {
8+
isOpen: boolean;
9+
title: string;
10+
initialValue?: string;
11+
placeholder?: string;
12+
onClose: () => void; // invoked on cancel/close (no value change)
13+
onSave: (value: string) => void; // invoked when clicking Save
14+
// Optional props for enhanced functionality
15+
useModalTextField?: boolean; // Use ModalTextField instead of textarea
16+
width?: string; // Custom width (default: max-w-lg)
17+
saveButtonText?: string; // Custom save button text
18+
cancelButtonText?: string; // Custom cancel button text
19+
fieldName?: string; // Name attribute for ModalTextField
20+
onFieldChange?: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void; // For external state management
21+
}
22+
23+
export default function CommentModal({
24+
isOpen,
25+
title,
26+
initialValue = "",
27+
placeholder = "Comment about this item here...",
28+
onClose,
29+
onSave,
30+
useModalTextField = false,
31+
width = "max-w-lg",
32+
saveButtonText = "Save",
33+
cancelButtonText = "Cancel",
34+
fieldName = "comments",
35+
onFieldChange,
36+
}: CommentModalProps) {
37+
const [value, setValue] = useState(initialValue);
38+
39+
useEffect(() => {
40+
// keep draft in sync when opening for a different item
41+
if (isOpen) setValue(initialValue ?? "");
42+
}, [isOpen, initialValue]);
43+
44+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
45+
setValue(e.target.value);
46+
if (onFieldChange) {
47+
onFieldChange(e);
48+
}
49+
};
50+
51+
if (!isOpen) return null;
52+
53+
return (
54+
<div className="fixed inset-0 flex items-center justify-center bg-black/50 z-50">
55+
<div className={`relative bg-white rounded-2xl p-6 w-full ${width} shadow-lg text-[#22070B]`}>
56+
<button
57+
onClick={onClose}
58+
aria-label="Close"
59+
className="absolute top-4 right-4 text-2xl text-gray-500 hover:text-gray-700"
60+
>
61+
<X className="size-6" />
62+
</button>
63+
<h2 className="text-[20px] font-semibold leading-[28px] mb-2">{title}</h2>
64+
<p className="text-[16px] leading-[24px] mb-2">Comments</p>
65+
66+
{useModalTextField ? (
67+
<ModalTextField
68+
name={fieldName}
69+
value={value}
70+
onChange={handleInputChange}
71+
placeholder={placeholder}
72+
/>
73+
) : (
74+
<textarea
75+
value={value}
76+
onChange={handleInputChange}
77+
placeholder={placeholder}
78+
className="w-full min-h-[150px] rounded-[4px] border border-[rgba(34,7,11,0.05)] bg-[rgba(34,7,11,0.05)] text-[16px] p-3 focus:outline-none focus:ring-1 focus:ring-[rgba(34,7,11,0.1)] resize-none"
79+
/>
80+
)}
81+
82+
<div className="mt-6 flex justify-end gap-4">
83+
<button
84+
onClick={onClose}
85+
className="bg-white hover:bg-gray-100 text-[#EF3340] border-2 border-[#EF3340] font-semibold py-2 px-4 rounded-md"
86+
>
87+
{cancelButtonText}
88+
</button>
89+
<button
90+
onClick={() => onSave(value)}
91+
className="bg-[#EF3340] hover:bg-[#a32027] text-white font-semibold py-2 px-4 rounded-md"
92+
>
93+
{saveButtonText}
94+
</button>
95+
</div>
96+
</div>
97+
</div>
98+
);
99+
}

0 commit comments

Comments
 (0)