chore(style): codebase em-dash sweep + minor layout polish
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped

Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).

Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
  pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
  port switcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-04 22:57:01 +02:00
parent d62822c284
commit 8699f81879
225 changed files with 844 additions and 845 deletions

View File

@@ -186,7 +186,7 @@ function AddressCard({
<CountryFieldInline
value={address.countryIso}
onSave={async (iso) => {
// Clear subdivision if country changes codes are scoped per country.
// Clear subdivision if country changes - codes are scoped per country.
const patch: AddressPatch = { countryIso: iso };
if (iso !== address.countryIso) patch.subdivisionIso = null;
await onUpdate(patch);
@@ -256,7 +256,7 @@ function CountryFieldInline({
}}
clearable
className="w-full"
// Drop the user straight into the picker no extra click on the
// Drop the user straight into the picker - no extra click on the
// trigger required.
defaultOpen
onOpenChange={(open) => {

View File

@@ -3,7 +3,7 @@ const LOGO_URL =
'https://s3.portnimara.com/images/Port%20Nimara%20New%20Logo-Circular%20Frame_250px.png';
/**
* Branded shell shared by every auth/form surface CRM login, portal login,
* Branded shell shared by every auth/form surface - CRM login, portal login,
* password set/reset/activate, forgot-password. Renders the blurred Port
* Nimara overhead background, the circular logo, and a centered white card
* that consumers populate with their own form/content.
@@ -12,7 +12,7 @@ export function BrandedAuthShell({ children }: { children: React.ReactNode }) {
return (
<div className="relative min-h-screen min-h-[100dvh] flex items-center justify-center px-4 py-8">
{/*
Full-viewport background layer pinned to the visible viewport via
Full-viewport background layer - pinned to the visible viewport via
`fixed inset-0` so the marina image always reaches the actual screen
edges regardless of the iOS Safari URL bar showing/hiding. The shell's
layout layer above sits on top via z-index.

View File

@@ -23,7 +23,7 @@ interface ConfirmationDialogProps {
confirmLabel?: string;
/** Label for the cancel button (default: "Cancel") */
cancelLabel?: string;
/** Whether the confirm action is destructive renders in red (default: true) */
/** Whether the confirm action is destructive - renders in red (default: true) */
destructive?: boolean;
/** Called when the user confirms the action */
onConfirm: () => void | Promise<void>;

View File

@@ -141,7 +141,7 @@ export function CountryCombobox({
{options.map((opt) => (
<CommandItem
key={opt.code}
// cmdk filters by `value` include both code + name.
// cmdk filters by `value` - include both code + name.
value={`${opt.name} ${opt.code}`}
onSelect={() => {
onChange(opt.code);

View File

@@ -20,7 +20,7 @@ import {
* Filters and sort live above the rendered rows; callers pass them as
* `headerSlot`. On desktop the rows are sortable via column header clicks
* (TanStack default); on mobile, sort is exposed via a `<Drawer>` opened by
* the caller's headerSlot this primitive doesn't enforce a sort UI.
* the caller's headerSlot - this primitive doesn't enforce a sort UI.
*/
export function DataView<TData>({
table,

View File

@@ -41,7 +41,7 @@ export function DetailPageShell({
return (
<div className={cn('flex flex-col min-h-full', className)}>
{/* Desktop-only sticky header mobile topbar covers this on small viewports. */}
{/* Desktop-only sticky header - mobile topbar covers this on small viewports. */}
<div className="hidden sm:block sticky top-0 z-10 bg-background/95 backdrop-blur border-b border-border px-4 py-3 sm:px-6">
<div className="flex items-center gap-3 min-w-0">
<h2 className="truncate text-lg font-semibold text-foreground">{entityName}</h2>
@@ -49,7 +49,7 @@ export function DetailPageShell({
</div>
</div>
{/* Mobile inline status row only shown when the page wants to display a status pill. */}
{/* Mobile inline status row - only shown when the page wants to display a status pill. */}
{status ? (
<div className="sm:hidden flex items-center justify-end px-1 pt-1">
<div className="shrink-0">{status}</div>

View File

@@ -24,7 +24,7 @@ interface InlineCountryFieldProps {
export function InlineCountryField({
value,
onSave,
emptyText = '',
emptyText = '-',
disabled,
className,
'data-testid': testId,

View File

@@ -51,7 +51,7 @@ export type InlineEditableFieldProps = TextProps | SelectFieldProps | TextareaPr
* Enter/blur and cancels on Escape.
*/
export function InlineEditableField(props: InlineEditableFieldProps) {
const { value, onSave, placeholder, emptyText = '', className, disabled } = props;
const { value, onSave, placeholder, emptyText = '-', className, disabled } = props;
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(value ?? '');
const [saving, setSaving] = useState(false);

View File

@@ -35,7 +35,7 @@ export function InlinePhoneField({
defaultCountry,
onSave,
onEditingChange,
emptyText = '',
emptyText = '-',
disabled,
className,
'data-testid': testId,

View File

@@ -17,7 +17,7 @@ interface Tag {
}
export interface InlineTagEditorProps {
/** PUT endpoint for replacing the entity's tag list body shape `{ tagIds: string[] }`. */
/** PUT endpoint for replacing the entity's tag list - body shape `{ tagIds: string[] }`. */
endpoint: string;
currentTags: Tag[];
/** TanStack Query key to invalidate after a successful change. */

View File

@@ -24,7 +24,7 @@ export function InlineTimezoneField({
value,
onSave,
countryHint,
emptyText = '',
emptyText = '-',
disabled,
className,
'data-testid': testId,

View File

@@ -11,12 +11,12 @@ interface ListCardProps {
href: string;
/**
* Optional Tailwind background class painted on a 3px vertical strip on the
* left edge used to encode pipeline stage / status / category at a glance.
* left edge - used to encode pipeline stage / status / category at a glance.
* Pass `undefined` for entities with no status to surface (clients, etc.).
*/
accentClassName?: string;
/**
* Top-right action slot typically a `<DropdownMenu>` for edit/archive.
* Top-right action slot - typically a `<DropdownMenu>` for edit/archive.
* Rendered absolutely-positioned outside the navigation Link so its clicks
* don't trigger detail navigation.
*/
@@ -71,7 +71,7 @@ export function ListCard({
interface ListCardAvatarProps {
/** Two-letter initials (or one for single-word names). Caller derives. */
initials?: string;
/** Domain icon (Lucide). Used when the entity isn't a person yacht, berth, company. */
/** Domain icon (Lucide). Used when the entity isn't a person - yacht, berth, company. */
icon?: ReactNode;
className?: string;
}

View File

@@ -6,7 +6,7 @@ interface LoadingSkeletonProps {
}
/**
* Table skeleton mimics a data table with header + rows.
* Table skeleton - mimics a data table with header + rows.
*/
export function TableSkeleton({ rows = 6, columns = 5 }: { rows?: number; columns?: number }) {
return (
@@ -27,10 +27,7 @@ export function TableSkeleton({ rows = 6, columns = 5 }: { rows?: number; column
)}
>
{Array.from({ length: columns }).map((_, colIdx) => (
<Skeleton
key={colIdx}
className={cn('h-4', colIdx === 0 ? 'w-1/4' : 'flex-1')}
/>
<Skeleton key={colIdx} className={cn('h-4', colIdx === 0 ? 'w-1/4' : 'flex-1')} />
))}
</div>
))}
@@ -39,7 +36,7 @@ export function TableSkeleton({ rows = 6, columns = 5 }: { rows?: number; column
}
/**
* Card skeleton mimics a content card.
* Card skeleton - mimics a content card.
*/
export function CardSkeleton({ className }: LoadingSkeletonProps) {
return (
@@ -59,7 +56,7 @@ export function CardSkeleton({ className }: LoadingSkeletonProps) {
}
/**
* Form skeleton mimics a form with labeled inputs.
* Form skeleton - mimics a form with labeled inputs.
*/
export function FormSkeleton({ fields = 4 }: { fields?: number }) {
return (
@@ -79,7 +76,7 @@ export function FormSkeleton({ fields = 4 }: { fields?: number }) {
}
/**
* Grid skeleton a responsive card grid.
* Grid skeleton - a responsive card grid.
*/
export function GridSkeleton({ cards = 6 }: { cards?: number }) {
return (
@@ -92,7 +89,7 @@ export function GridSkeleton({ cards = 6 }: { cards?: number }) {
}
/**
* Page-level loading skeleton header + content area.
* Page-level loading skeleton - header + content area.
*/
export function PageSkeleton() {
return (

View File

@@ -64,7 +64,7 @@ export function OwnerPicker({
const options = data?.data ?? [];
// Selected display label show entity's name from current options if
// Selected display label - show entity's name from current options if
// available, otherwise a truncated id fallback.
const selectedLabel = (() => {
if (!value) return placeholder;

View File

@@ -21,7 +21,7 @@ interface PageHeaderProps {
* existing call-sites stay unchanged.
*
* Mobile-aware: below sm (640px) the title/eyebrow/description/gradient
* frame all collapse the page title is already shown by the mobile topbar,
* frame all collapse - the page title is already shown by the mobile topbar,
* so duplicating it in the body wastes scroll real estate. What remains is a
* flush right-aligned action row (or nothing if there are no actions). On sm+
* the full strip with title+description renders as before.
@@ -48,7 +48,10 @@ export function PageHeader({
{/* Desktop: full strip with title, eyebrow, description, kpi line, actions. */}
<div
className={cn(
'hidden sm:flex flex-col gap-3 sm:mb-6 sm:flex-row sm:items-start sm:justify-between sm:gap-4',
// Removed `sm:mb-6` - the parent shell already provides
// appropriate gap-y between header and the next section, and the
// double-spacing produced an oversized top margin on dashboards.
'hidden sm:flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4',
isGradient &&
'rounded-xl border border-slate-200 bg-gradient-brand-soft px-5 py-4 shadow-xs',
className,

View File

@@ -11,7 +11,7 @@ import { detectDefaultCountry, type CountryCode } from '@/lib/i18n/countries';
export interface PhoneInputValue {
/** E.164 form ('+442079460958'). Null when empty or unparseable. */
e164: string | null;
/** Country selected in the dropdown drives the AsYouType formatter. */
/** Country selected in the dropdown - drives the AsYouType formatter. */
country: CountryCode;
}
@@ -32,7 +32,7 @@ interface PhoneInputProps {
* Phone input with a country flag dropdown + format-as-you-type.
*
* Wire shape: emits `{ e164, country }` on every change. E.164 is null
* while the input is too short to parse that's a form-validation
* while the input is too short to parse - that's a form-validation
* concern, not an input concern. Pasting an international number
* (`+1 415…`) auto-switches the country dropdown to match.
*
@@ -62,7 +62,7 @@ export function PhoneInput({
const parsed = parsePhone(value.e164, value.country);
return parsed.national ?? '';
});
// Track whether the user has typed since mount keeps a controlled-from-props
// Track whether the user has typed since mount - keeps a controlled-from-props
// value sync on first render only.
const initialized = useRef(false);
@@ -78,7 +78,7 @@ export function PhoneInput({
function handleInput(raw: string) {
// Paste-detect: if user pasted an international format, parse it
// and flip the country dropdown to match better UX than asking
// and flip the country dropdown to match - better UX than asking
// them to also click the dropdown.
if (raw.startsWith('+')) {
const parsed = parsePhone(raw);

View File

@@ -11,7 +11,7 @@ import { stageLabel } from '@/lib/constants';
* toasts. Mounted once inside SocketProvider so reps see "EOI signed",
* "Deposit recorded", "Stage advanced" without having to refresh.
*
* Render-only no children. Intentionally narrow in scope: only toast on
* Render-only - no children. Intentionally narrow in scope: only toast on
* events that are noteworthy *to a user staring at any page*. Per-page
* cache invalidations stay in `useRealtimeInvalidation`.
*/
@@ -36,7 +36,7 @@ export function RealtimeToasts() {
}
function onDocumentCompleted(payload: { type?: string }) {
// Kick a generic "fully signed" the type-specific message is
// Kick a generic "fully signed" - the type-specific message is
// friendlier when we can identify it as an EOI.
if (payload?.type === 'eoi') {
toast.success('EOI fully signed', {
@@ -64,7 +64,7 @@ export function RealtimeToasts() {
const isWon = payload.outcome === 'won';
const label = payload.outcome.replace(/_/g, ' ');
const fn = isWon ? toast.success : toast.message;
fn(`Interest closed ${label}`);
fn(`Interest closed - ${label}`);
}
socket.on('interest:stageChanged', onStageChanged);