Skip to content
174 changes: 140 additions & 34 deletions apps/web/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default function AdminDashboard() {

const { data, isLoading } = trpc.user.getCurrent.useQuery();

const { data: pendingModifications } =
trpc.modification.getPending.useQuery();

const { data: pinCounts } = trpc.pin.getStatusCounts.useQuery();
const { data: userCount } = trpc.user.getCount.useQuery();
const { data: commentCount } = trpc.comment.getCount.useQuery();
Expand All @@ -28,11 +31,27 @@ export default function AdminDashboard() {
const rejectPin = trpc.pin.reject.useMutation({
onSuccess: (output) => {
utils.pin.getAllAdmin.invalidate();
utils.modification.getPending.invalidate();
},
});
const approvePin = trpc.pin.approve.useMutation({
onSuccess: (output) => {
utils.pin.getAll.invalidate();
utils.pin.getAllAdmin.invalidate();
utils.modification.getPending.invalidate();
},
});
const applyMod = trpc.pin.applyUpdate.useMutation({
onSuccess: (output) => {
utils.modification.getPending.invalidate();
utils.pin.getAll.invalidate();
utils.pin.getAllAdmin.invalidate();
utils.pin.getStatusCounts.invalidate();
},
});
const rejectMod = trpc.pin.rejectUpdate.useMutation({
onSuccess: (output) => {
utils.modification.getPending.invalidate();
},
});

Expand Down Expand Up @@ -114,40 +133,6 @@ export default function AdminDashboard() {
// newUsers30Days: 45,
};

const recentUsers = [
{
id: "u1",
name: "User 1",
email: "user1@up.edu.ph",
joinedAt: "2 hours ago",
},
{
id: "u2",
name: "User 2",
email: "user2@up.edu.ph",
joinedAt: "5 hours ago",
},
{
id: "u3",
name: "User 3",
email: "user3@up.edu.ph",
joinedAt: "1 day ago",
},
{
id: "u4",
name: "User 4",
email: "user4@up.edu.ph",
joinedAt: "2 days ago",
},
];

const topUsers = [
{ id: "u1", name: "User 1", pinCount: 142, rank: 1 },
{ id: "u2", name: "User 2", pinCount: 89, rank: 2 },
{ id: "u3", name: "User 3", pinCount: 75, rank: 3 },
{ id: "u4", name: "User 4", pinCount: 60, rank: 4 },
];

return (
<div className="dashboard-layout">
{/* --- MOBILE OVERLAY --- */}
Expand Down Expand Up @@ -749,6 +734,127 @@ export default function AdminDashboard() {
</div>
</div>
</div>

<div className="module-card">
<div className="card-header">
<h3>RECENT MODIFICATION REQUESTS</h3>
</div>

<div className="card-body">
<div className="pin-list">
{pendingModifications?.map((mod) => {
const color = "var(--text-primary)";
return (
<div key={mod.id} className="pin-list-item">
<div className="pin-info-group">
<div
className="list-diamond"
style={{
borderColor: color,
backgroundColor: `color-mix(in srgb, ${color} 15%, transparent)`,
}}
>
<span style={{ color }}>
{mod.pin.title.charAt(0).toUpperCase()}
</span>
</div>

<div className="pin-text">
<span className="pin-title">
{mod.pin.title}
</span>
<span className="pin-coords">
Modification by {mod.user.name}
</span>
</div>
</div>

<div
className="pin-actions"
style={{ display: "flex", gap: "8px" }}
>
<Link
className="locate-btn"
style={{
background: "transparent",
border: "1px solid var(--border-color)",
borderRadius: "8px",
width: "36px",
height: "36px",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "var(--text-secondary)",
cursor: "pointer",
transition: "all 0.2s",
flexShrink: "0",
}}
href={`/?pin=${mod.pin.id}`}
target="_blank"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</Link>

<button
type="button"
className="reject-btn"
title="Reject Pin"
onClick={() =>
rejectMod.mutate({ id: mod.id })
}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>

<button
type="button"
className="approve-btn"
title="Verify & Approve Pin"
onClick={() =>
applyMod.mutate({ id: mod.id })
}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</button>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
</section>

Expand Down
47 changes: 37 additions & 10 deletions apps/web/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { signOut } from "@/lib/auth-client";
import { trpc } from "@/lib/trpc";
import { PIN_CATEGORIES, getPinColor } from "@/data/pin-categories";
import { useTheme } from "@/lib/ThemeContext";
import Link from "next/link";

export default function Dashboard() {
const router = useRouter();
Expand Down Expand Up @@ -496,11 +497,24 @@ export default function Dashboard() {
</div>
</div>

<button
type="button"
<Link
className="locate-btn"
title="Locate on Map"
onClick={() => console.log("Locate pin:", pin.id)}
style={{
background: "transparent",
border: "1px solid var(--border-color)",
borderRadius: "8px",
width: "36px",
height: "36px",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "var(--text-secondary)",
cursor: "pointer",
transition: "all 0.2s",
flexShrink: "0",
}}
href={`/?pin=${pin.id}`}
target="_blank"
>
<svg
width="18"
Expand All @@ -512,7 +526,7 @@ export default function Dashboard() {
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</button>
</Link>
</div>
);
})}
Expand Down Expand Up @@ -564,11 +578,24 @@ export default function Dashboard() {
</div>
</div>

<button
type="button"
<Link
className="locate-btn"
title="Locate on Map"
onClick={() => console.log("Locate pin:", pin.id)}
style={{
background: "transparent",
border: "1px solid var(--border-color)",
borderRadius: "8px",
width: "36px",
height: "36px",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "var(--text-secondary)",
cursor: "pointer",
transition: "all 0.2s",
flexShrink: "0",
}}
href={`/?pin=${pin.id}`}
target="_blank"
>
<svg
width="18"
Expand All @@ -580,7 +607,7 @@ export default function Dashboard() {
>
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
</button>
</Link>
</div>
);
})}
Expand Down
12 changes: 9 additions & 3 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export default function Home() {
const session = useSession();
const params = useSearchParams();

const [hasPreselected, setHasPreselected] = useState(false);

const { data: pins } = trpc.pin.getAll.useQuery(undefined, {
refetchOnWindowFocus: false,
});
Expand Down Expand Up @@ -125,11 +127,12 @@ export default function Home() {
}, [isAddingPin]);

useEffect(() => {
if (params.has("pin")) {
if (params.has("pin") && !hasPreselected) {
const preselectedPin = params.get("pin") as string;
selectPin(preselectedPin);
setHasPreselected(true);
}
}, [params, selectPin]);
}, [params, selectPin, hasPreselected]);

return (
<APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY || ""}>
Expand Down Expand Up @@ -189,7 +192,10 @@ export default function Home() {
!!(matchesCategory && matchesSearch) &&
(currentUser?.userRole === "admin"
? pinData.status !== "DELETED"
: pinData.status === "ACTIVE");
: currentUser?.userRole === "user" &&
pinData.ownerId === currentUser?.id
? pinData.status !== "DELETED"
: pinData.status === "ACTIVE");

return (
<AdvancedMarker
Expand Down
27 changes: 14 additions & 13 deletions apps/web/components/EditPinModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"use client";

import { trpc } from "@/lib/trpc";
import type { PinRouterInputs, PinRouterOutputs } from "@repo/api";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import z from "zod";
import { fileSchema } from "@repo/api/schemas";
import { getPinColor } from "@/data/pin-categories";
import { useEffect } from "react";
type Pin = {
id?: string | undefined;
createdAt?: string | undefined;
Expand Down Expand Up @@ -35,8 +33,6 @@ type Pin = {
ownerId?: string | undefined;
};

type UpdatePin = PinRouterInputs["update"];

interface IEditPinModalProps {
onSave: (pinId: string) => void;
onCancel: () => void;
Expand Down Expand Up @@ -74,17 +70,14 @@ export function EditPinModal({ onSave, onCancel, pin }: IEditPinModalProps) {
handleCancel();
return;
}

const dirtyFields = Object.keys(formMethods.formState.dirtyFields);
console.log(dirtyFields);
const diffs = Object.keys(formMethods.formState.dirtyFields);
if (diffs.length === 0) return;

updatePin.mutate({
id: pin.id,
title: dirtyFields.includes("title") ? data.title : undefined,
description: dirtyFields.includes("description")
? data.description
: undefined,
tags: dirtyFields.includes("tags") ? data.tags || [] : [],
title: diffs.includes("title") ? data.title : undefined,
description: diffs.includes("description") ? data.description : undefined,
tags: diffs.includes("tags") ? data.tags || [] : [],
});
};

Expand All @@ -96,6 +89,14 @@ export function EditPinModal({ onSave, onCancel, pin }: IEditPinModalProps) {

const tags = formMethods.watch("tags");

useEffect(() => {
formMethods.reset({
tags: pin.pinTags?.map((pt) => pt.tagId),
title: pin.title,
description: pin.description || "",
});
}, [formMethods, pin.description, pin.pinTags, pin.title]);

return (
<div className="modal-overlay">
<div className="modal-card tactical-panel">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/ExpandedPinView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) {
</div>

<div className="header-actions">
{sessionData?.user?.id === pin?.ownerId && (
{!!sessionData && (
<button
type="button"
className="edit-btn"
Expand Down
Loading
Loading