diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts
index 61d3b59..befa6f5 100644
--- a/apps/web/next.config.ts
+++ b/apps/web/next.config.ts
@@ -18,6 +18,12 @@ const nextConfig: NextConfig = {
"@capakraken/ui",
],
typedRoutes: true,
+ async redirects() {
+ return [
+ // Common URL alias — redirect to the real auth entry point
+ { source: "/login", destination: "/auth/signin", permanent: true },
+ ];
+ },
async headers() {
return [
{
diff --git a/apps/web/src/components/projects/ProjectAssignmentsTable.tsx b/apps/web/src/components/projects/ProjectAssignmentsTable.tsx
index ef9006c..647c34b 100644
--- a/apps/web/src/components/projects/ProjectAssignmentsTable.tsx
+++ b/apps/web/src/components/projects/ProjectAssignmentsTable.tsx
@@ -2,6 +2,7 @@
import { useState, useMemo } from "react";
import { useRouter } from "next/navigation";
+import Link from "next/link";
import { formatCents, formatDate, formatMoney } from "~/lib/format.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { ALLOCATION_STATUS_BADGE } from "~/lib/status-styles.js";
@@ -199,7 +200,18 @@ export function ProjectAssignmentsTable({ assignments }: ProjectAssignmentsTable
})()}
{assignments.length === 0 && (
-
No assignments for this project.
+
+
No assignments for this project.
+ {canEdit && (
+
+ Create staffing entries via{" "}
+
+ Allocations → New Planning Entry
+
+ .
+
+ )}
+
)}
);
diff --git a/apps/web/src/components/projects/ProjectDemandsTable.tsx b/apps/web/src/components/projects/ProjectDemandsTable.tsx
index ecb767b..7a3544e 100644
--- a/apps/web/src/components/projects/ProjectDemandsTable.tsx
+++ b/apps/web/src/components/projects/ProjectDemandsTable.tsx
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
+import Link from "next/link";
import { formatCents, formatDate } from "~/lib/format.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { FillOpenDemandModal } from "~/components/allocations/FillOpenDemandModal.js";
@@ -59,11 +60,21 @@ export function ProjectDemandsTable({ demands, project }: ProjectDemandsTablePro
Open Demands ({allDemands.length})
- {activeDemands.length > 0 && (
-
- {activeDemands.reduce((sum, d) => sum + d.unfilledHeadcount, 0)} seats unfilled
-
- )}
+
+ {activeDemands.length > 0 && (
+
+ {activeDemands.reduce((sum, d) => sum + d.unfilledHeadcount, 0)} seats unfilled
+
+ )}
+ {canEdit && (
+
+ + New in Allocations
+
+ )}
+
@@ -192,7 +203,18 @@ export function ProjectDemandsTable({ demands, project }: ProjectDemandsTablePro
{allDemands.length === 0 && (
- No open demands for this project.
+
+
No open demands for this project.
+ {canEdit && (
+
+ Create staffing entries via{" "}
+
+ Allocations → New Planning Entry
+
+ .
+
+ )}
+
)}
diff --git a/apps/web/src/components/projects/ProjectWizard.tsx b/apps/web/src/components/projects/ProjectWizard.tsx
index 81fcd70..598b6ea 100644
--- a/apps/web/src/components/projects/ProjectWizard.tsx
+++ b/apps/web/src/components/projects/ProjectWizard.tsx
@@ -309,6 +309,9 @@ function ResourcePersonPicker({ value, onChange }: { value: string; onChange: (v
const [query, setQuery] = useState(value);
const [open, setOpen] = useState(false);
const [debouncedSearch, setDebouncedSearch] = useState("");
+ // Tracks whether the current value was explicitly chosen from the dropdown
+ // vs typed as free text. Cleared on any manual keypress.
+ const [isConfirmed, setIsConfirmed] = useState(false);
const inputRef = useRef(null);
// Debounce search query to avoid excessive API calls
@@ -331,6 +334,8 @@ function ResourcePersonPicker({ value, onChange }: { value: string; onChange: (v
// Sync local query when external value changes (e.g. wizard reset)
useEffect(() => {
setQuery(value);
+ // If the external value is cleared (reset), also clear the confirmed state
+ if (!value) setIsConfirmed(false);
}, [value]);
const { panelRef, position } = useAnchoredOverlay({
@@ -350,12 +355,20 @@ function ResourcePersonPicker({ value, onChange }: { value: string; onChange: (v
onChange={(e) => {
setQuery(e.target.value);
onChange(e.target.value);
+ setIsConfirmed(false); // User is typing — selection no longer confirmed
setOpen(true);
}}
onFocus={() => setOpen(true)}
placeholder="Search by name or EID…"
- className={INPUT_CLS}
+ className={`${INPUT_CLS} ${isConfirmed ? "ring-2 ring-green-400 ring-offset-0" : ""}`}
/>
+ {isConfirmed && (
+
+
+
+ )}
{open && filtered.length > 0 && typeof document !== "undefined"
? createPortal(
{
onChange(r.displayName);
setQuery(r.displayName);
+ setIsConfirmed(true);
setOpen(false);
}}
className="flex w-full items-baseline gap-2 px-3 py-2 text-left text-sm transition-colors hover:bg-gray-50"