chore(style): codebase em-dash sweep + minor layout polish
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:
@@ -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) => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -24,7 +24,7 @@ interface InlineCountryFieldProps {
|
||||
export function InlineCountryField({
|
||||
value,
|
||||
onSave,
|
||||
emptyText = '—',
|
||||
emptyText = '-',
|
||||
disabled,
|
||||
className,
|
||||
'data-testid': testId,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -35,7 +35,7 @@ export function InlinePhoneField({
|
||||
defaultCountry,
|
||||
onSave,
|
||||
onEditingChange,
|
||||
emptyText = '—',
|
||||
emptyText = '-',
|
||||
disabled,
|
||||
className,
|
||||
'data-testid': testId,
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -24,7 +24,7 @@ export function InlineTimezoneField({
|
||||
value,
|
||||
onSave,
|
||||
countryHint,
|
||||
emptyText = '—',
|
||||
emptyText = '-',
|
||||
disabled,
|
||||
className,
|
||||
'data-testid': testId,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user