Fixed
This commit is contained in:
@ -6,29 +6,31 @@ const RegistryList = () => {
|
|||||||
const [claimedItems, setClaimedItems] = useState<Set<string>>(new Set());
|
const [claimedItems, setClaimedItems] = useState<Set<string>>(new Set());
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [claimantName, setClaimantName] = useState("");
|
||||||
|
const [showClaimants, setShowClaimants] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRegistryItems = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/registry");
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
setRegistryItems(data);
|
|
||||||
} catch (e: any) {
|
|
||||||
setError(e.message);
|
|
||||||
console.error("Failed to fetch registry items:", e);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchRegistryItems();
|
fetchRegistryItems();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchRegistryItems = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/registry");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setRegistryItems(data);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e.message);
|
||||||
|
console.error("Failed to fetch registry items:", e);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = (itemId: string) => {
|
const handleCheckboxChange = (itemId: string) => {
|
||||||
const newClaimedItems = new Set(claimedItems);
|
const newClaimedItems = new Set(claimedItems);
|
||||||
if (newClaimedItems.has(itemId)) {
|
if (newClaimedItems.has(itemId)) {
|
||||||
@ -39,31 +41,42 @@ const RegistryList = () => {
|
|||||||
setClaimedItems(newClaimedItems);
|
setClaimedItems(newClaimedItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const toggleClaimantVisibility = (itemId: string) => {
|
||||||
// Prepare updates for claimed items
|
const newShowClaimants = new Set(showClaimants);
|
||||||
const updates = registryItems.filter((item) =>
|
if (newShowClaimants.has(itemId)) {
|
||||||
claimedItems.has(item.id)
|
newShowClaimants.delete(itemId);
|
||||||
);
|
} else {
|
||||||
|
newShowClaimants.add(itemId);
|
||||||
|
}
|
||||||
|
setShowClaimants(newShowClaimants);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!claimantName.trim()) {
|
||||||
|
setError("Please enter your name before claiming items");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates = registryItems.filter((item) => claimedItems.has(item.id));
|
||||||
|
|
||||||
// Optimistically update the UI
|
|
||||||
const updatedRegistryItems = registryItems.map((item) => {
|
const updatedRegistryItems = registryItems.map((item) => {
|
||||||
if (claimedItems.has(item.id)) {
|
if (claimedItems.has(item.id)) {
|
||||||
return { ...item, taken: true };
|
return { ...item, taken: true, claimedBy: claimantName };
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
setRegistryItems(updatedRegistryItems);
|
setRegistryItems(updatedRegistryItems);
|
||||||
setClaimedItems(new Set()); // Clear claimed items after submission
|
setClaimedItems(new Set());
|
||||||
|
setClaimantName("");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Send updates to the server
|
|
||||||
for (const item of updates) {
|
for (const item of updates) {
|
||||||
const response = await fetch(`/api/registry/${item.id}`, {
|
const response = await fetch(`/api/registry/${item.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ taken: true }),
|
body: JSON.stringify({ taken: true, claimedBy: claimantName }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -85,9 +98,7 @@ const RegistryList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return <div className="text-red-500 text-center">Error: {error}</div>;
|
||||||
<div className="text-red-500 text-center">Error: {error}</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -99,17 +110,37 @@ const RegistryList = () => {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Item</th>
|
<th>Item</th>
|
||||||
<th>Link</th>
|
<th>Link</th>
|
||||||
|
<th>Status</th>
|
||||||
<th>Claim</th>
|
<th>Claim</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{registryItems.map((item) => (
|
{registryItems.map((item) => (
|
||||||
<tr key={item.id} className={item.taken ? "opacity-50" : ""}>
|
<tr key={item.id}>
|
||||||
<td>
|
<td>
|
||||||
{item.name}
|
<div className="flex flex-col gap-1">
|
||||||
{item.taken && (
|
<div className="flex items-center gap-2">
|
||||||
<span className="badge badge-success ml-2">Taken</span>
|
{item.name}
|
||||||
)}
|
{item.taken && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="badge badge-success">Taken</span>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleClaimantVisibility(item.id)}
|
||||||
|
className="btn btn-xs btn-ghost"
|
||||||
|
>
|
||||||
|
{showClaimants.has(item.id) ? "Hide" : "Show"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{item.taken &&
|
||||||
|
item.claimedBy &&
|
||||||
|
showClaimants.has(item.id) && (
|
||||||
|
<div className="text-sm text-gray-500 italic">
|
||||||
|
Claimed by: {item.claimedBy}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{item.link && (
|
{item.link && (
|
||||||
@ -123,28 +154,51 @@ const RegistryList = () => {
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td>{item.taken ? "Claimed" : "Available"}</td>
|
||||||
<td>
|
<td>
|
||||||
{!item.taken && (
|
{!item.taken ? (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="checkbox"
|
className="checkbox"
|
||||||
checked={claimedItems.has(item.id)}
|
checked={claimedItems.has(item.id)}
|
||||||
onChange={() => handleCheckboxChange(item.id)}
|
onChange={() => handleCheckboxChange(item.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
) : <input
|
||||||
|
type="checkbox"
|
||||||
|
className="checkbox"
|
||||||
|
disabled
|
||||||
|
checked={true}
|
||||||
|
/>}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<button
|
{claimedItems.size > 0 && (
|
||||||
className="btn btn-primary mt-4"
|
<div className="mt-4 flex flex-col gap-4">
|
||||||
onClick={handleSubmit}
|
<div className="form-control">
|
||||||
disabled={claimedItems.size === 0}
|
<label className="label">
|
||||||
>
|
<span className="label-text">Your Name</span>
|
||||||
Submit Claims
|
</label>
|
||||||
</button>
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={claimantName}
|
||||||
|
onChange={(e) => setClaimantName(e.target.value)}
|
||||||
|
placeholder="Enter your name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!claimantName.trim()}
|
||||||
|
>
|
||||||
|
Submit Claims
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ const RegistryManager = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [newItem, setNewItem] = useState({ name: "", link: "" });
|
const [newItem, setNewItem] = useState({ name: "", link: "" });
|
||||||
|
const [showClaimants, setShowClaimants] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRegistryItems();
|
fetchRegistryItems();
|
||||||
@ -46,7 +47,6 @@ const RegistryManager = () => {
|
|||||||
throw new Error(`Failed to add item: ${response.statusText}`);
|
throw new Error(`Failed to add item: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear form and refresh list
|
|
||||||
setNewItem({ name: "", link: "" });
|
setNewItem({ name: "", link: "" });
|
||||||
await fetchRegistryItems();
|
await fetchRegistryItems();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -67,7 +67,6 @@ const RegistryManager = () => {
|
|||||||
throw new Error(`Failed to delete item: ${response.statusText}`);
|
throw new Error(`Failed to delete item: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the list after deletion
|
|
||||||
await fetchRegistryItems();
|
await fetchRegistryItems();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setError(error.message);
|
setError(error.message);
|
||||||
@ -75,6 +74,16 @@ const RegistryManager = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleClaimantVisibility = (itemId: string) => {
|
||||||
|
const newShowClaimants = new Set(showClaimants);
|
||||||
|
if (newShowClaimants.has(itemId)) {
|
||||||
|
newShowClaimants.delete(itemId);
|
||||||
|
} else {
|
||||||
|
newShowClaimants.add(itemId);
|
||||||
|
}
|
||||||
|
setShowClaimants(newShowClaimants);
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="text-center">Loading registry items...</div>;
|
return <div className="text-center">Loading registry items...</div>;
|
||||||
}
|
}
|
||||||
@ -85,9 +94,6 @@ const RegistryManager = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-4">
|
<div className="container mx-auto p-4">
|
||||||
<h1 className="text-2xl font-bold mb-4">Registry Manager</h1>
|
|
||||||
|
|
||||||
{/* Add New Item Form */}
|
|
||||||
<form onSubmit={handleAddItem} className="mb-8">
|
<form onSubmit={handleAddItem} className="mb-8">
|
||||||
<div className="card bg-base-100 shadow-xl">
|
<div className="card bg-base-100 shadow-xl">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@ -130,7 +136,6 @@ const RegistryManager = () => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/* Registry Items List */}
|
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="table w-full">
|
<table className="table w-full">
|
||||||
<thead>
|
<thead>
|
||||||
@ -144,7 +149,35 @@ const RegistryManager = () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{registryItems.map((item) => (
|
{registryItems.map((item) => (
|
||||||
<tr key={item.id}>
|
<tr key={item.id}>
|
||||||
<td>{item.name}</td>
|
<td>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{item.name}
|
||||||
|
{item.taken && (
|
||||||
|
<>
|
||||||
|
<span className="badge badge-success">Taken</span>
|
||||||
|
{item.claimedBy && (
|
||||||
|
<button
|
||||||
|
onClick={() => toggleClaimantVisibility(item.id)}
|
||||||
|
className="btn btn-ghost btn-xs"
|
||||||
|
>
|
||||||
|
{showClaimants.has(item.id) ? (
|
||||||
|
<i className="fas fa-eye-slash" />
|
||||||
|
) : (
|
||||||
|
<i className="fas fa-eye" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{item.taken &&
|
||||||
|
item.claimedBy &&
|
||||||
|
showClaimants.has(item.id) && (
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
Claimed by: {item.claimedBy}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{item.link && (
|
{item.link && (
|
||||||
<a
|
<a
|
||||||
@ -157,13 +190,7 @@ const RegistryManager = () => {
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{item.taken ? "Claimed" : "Available"}</td>
|
||||||
{item.taken ? (
|
|
||||||
<span className="badge badge-success">Taken</span>
|
|
||||||
) : (
|
|
||||||
<span className="badge badge-info">Available</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
className="btn btn-error btn-sm"
|
className="btn btn-error btn-sm"
|
||||||
|
@ -3,6 +3,7 @@ export interface RegistryItem {
|
|||||||
name: string;
|
name: string;
|
||||||
taken: boolean;
|
taken: boolean;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
claimedBy?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RSVPItem {
|
export interface RSVPItem {
|
||||||
|
@ -2,7 +2,7 @@ import type { APIRoute } from "astro";
|
|||||||
import { getS3Data, putS3Data } from "../../../lib/s3";
|
import { getS3Data, putS3Data } from "../../../lib/s3";
|
||||||
import type { RegistryItem } from "../../../lib/types";
|
import type { RegistryItem } from "../../../lib/types";
|
||||||
|
|
||||||
const REGISTRY_FILE_KEY = "baby-registry.json";
|
const REGISTRY_FILE_KEY = "registry.json";
|
||||||
|
|
||||||
// GET: Get a specific registry item by ID
|
// GET: Get a specific registry item by ID
|
||||||
export const GET: APIRoute = async ({ params }) => {
|
export const GET: APIRoute = async ({ params }) => {
|
||||||
@ -50,7 +50,7 @@ export const PUT: APIRoute = async ({ request, params }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { name, taken, link } = body;
|
const { taken, claimedBy } = body;
|
||||||
|
|
||||||
const registry = await getS3Data<RegistryItem[]>(REGISTRY_FILE_KEY) || [];
|
const registry = await getS3Data<RegistryItem[]>(REGISTRY_FILE_KEY) || [];
|
||||||
const itemIndex = registry.findIndex((item) => item.id === id);
|
const itemIndex = registry.findIndex((item) => item.id === id);
|
||||||
@ -62,12 +62,10 @@ export const PUT: APIRoute = async ({ request, params }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the item with the provided values
|
|
||||||
registry[itemIndex] = {
|
registry[itemIndex] = {
|
||||||
...registry[itemIndex],
|
...registry[itemIndex],
|
||||||
name: name !== undefined ? name : registry[itemIndex].name,
|
|
||||||
taken: taken !== undefined ? taken : registry[itemIndex].taken,
|
taken: taken !== undefined ? taken : registry[itemIndex].taken,
|
||||||
link: link !== undefined ? link : registry[itemIndex].link,
|
claimedBy: claimedBy !== undefined ? claimedBy : registry[itemIndex].claimedBy,
|
||||||
};
|
};
|
||||||
|
|
||||||
await putS3Data(REGISTRY_FILE_KEY, registry);
|
await putS3Data(REGISTRY_FILE_KEY, registry);
|
||||||
@ -85,39 +83,54 @@ export const PUT: APIRoute = async ({ request, params }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// DELETE: Delete a registry item
|
// DELETE: Delete a registry item
|
||||||
export const DELETE: APIRoute = async ({ params }) => {
|
export const DELETE: APIRoute = async ({ params }) => {
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: false, error: "Item ID is required" }),
|
JSON.stringify({ success: false, error: "Item ID is required" }),
|
||||||
{ status: 400, headers: { "Content-Type": "application/json" } }
|
{ status: 400, headers }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current registry data
|
||||||
const registry = await getS3Data<RegistryItem[]>(REGISTRY_FILE_KEY) || [];
|
const registry = await getS3Data<RegistryItem[]>(REGISTRY_FILE_KEY) || [];
|
||||||
|
|
||||||
|
// Find the item index
|
||||||
const itemIndex = registry.findIndex((item) => item.id === id);
|
const itemIndex = registry.findIndex((item) => item.id === id);
|
||||||
|
|
||||||
if (itemIndex === -1) {
|
if (itemIndex === -1) {
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: false, error: "Item not found" }),
|
JSON.stringify({ success: false, error: "Item not found" }),
|
||||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
{ status: 404, headers }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.splice(itemIndex, 1); // Remove the item from the array
|
// Remove the item
|
||||||
|
registry.splice(itemIndex, 1);
|
||||||
|
|
||||||
|
// Save the updated registry
|
||||||
await putS3Data(REGISTRY_FILE_KEY, registry);
|
await putS3Data(REGISTRY_FILE_KEY, registry);
|
||||||
|
|
||||||
return new Response(JSON.stringify({ success: true }), {
|
return new Response(JSON.stringify({ success: true }), {
|
||||||
status: 204, // No Content
|
status: 200,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error("Error deleting registry item:", error.message);
|
console.error("Error deleting registry item:", error);
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: false, error: "Failed to delete item" }),
|
JSON.stringify({
|
||||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
success: false,
|
||||||
|
error: "Failed to delete item",
|
||||||
|
details: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
}),
|
||||||
|
{ status: 500, headers }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import { getS3Data, putS3Data } from "../../../lib/s3";
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import type { RegistryItem } from "../../../lib/types";
|
import type { RegistryItem } from "../../../lib/types";
|
||||||
|
|
||||||
const REGISTRY_FILE_KEY = "baby-registry.json";
|
const REGISTRY_FILE_KEY = "registry.json";
|
||||||
|
|
||||||
// GET: List all registry items
|
// GET: List all registry items
|
||||||
export const GET: APIRoute = async () => {
|
export const GET: APIRoute = async () => {
|
||||||
|
Reference in New Issue
Block a user