Compare commits
No commits in common. "bc6358ce5fe8eaa96f10fc85fa2baaf99448cf6d" and "97bdf2f40c9355bb29868b72362243c2149e106f" have entirely different histories.
bc6358ce5f
...
97bdf2f40c
4 changed files with 3 additions and 152 deletions
131
DESIGN.md
131
DESIGN.md
|
|
@ -1,131 +0,0 @@
|
||||||
# Polaris Task Force Design System
|
|
||||||
|
|
||||||
## 1. Atmosphere & Identity
|
|
||||||
|
|
||||||
Polaris is a dark operations console: focused, quiet, and information-dense without feeling cramped. The signature is a neutral charcoal surface hierarchy with restrained white controls and small, purposeful state accents.
|
|
||||||
|
|
||||||
## 2. Color
|
|
||||||
|
|
||||||
### Palette
|
|
||||||
|
|
||||||
| Role | Token | Usage |
|
|
||||||
|------|-------|-------|
|
|
||||||
| Surface / page | `bg-background` | Frontend page background |
|
|
||||||
| Surface / card | `bg-card` | Elevated content sections |
|
|
||||||
| Text / primary | `text-foreground` | Headings and primary labels |
|
|
||||||
| Text / secondary | `text-muted-foreground` | Descriptions and supporting copy |
|
|
||||||
| Interactive / primary | `bg-primary`, `text-primary-foreground` | Primary actions and checked controls |
|
|
||||||
| Interactive / focus | `ring-ring` | Keyboard focus indicators |
|
|
||||||
| Border | `border-border` | Card and control boundaries |
|
|
||||||
| Status / error | `text-destructive` | Inline validation and failure copy |
|
|
||||||
| Status / success | `text-emerald-500` | Existing save confirmations |
|
|
||||||
|
|
||||||
### Rules
|
|
||||||
|
|
||||||
- Use semantic Tailwind tokens backed by `src/app/(frontend)/styles.css`; do not add raw colors in frontend components.
|
|
||||||
- Preserve the dark neutral palette and use accent color only for interactive state.
|
|
||||||
|
|
||||||
## 3. Typography
|
|
||||||
|
|
||||||
### Scale
|
|
||||||
|
|
||||||
| Level | Token | Usage |
|
|
||||||
|-------|-------|-------|
|
|
||||||
| Page title | `text-lg font-semibold` | Route heading |
|
|
||||||
| Card title | `font-semibold` | Section heading |
|
|
||||||
| Body | inherited `text-base` | Primary content |
|
|
||||||
| Supporting | `text-sm text-muted-foreground` | Descriptions and helper text |
|
|
||||||
| Compact | `text-xs` | Secondary metadata only |
|
|
||||||
|
|
||||||
### Font Stack
|
|
||||||
|
|
||||||
- Primary: Geist, sans-serif
|
|
||||||
- Mono: Geist Mono, monospace
|
|
||||||
|
|
||||||
## 4. Spacing & Layout
|
|
||||||
|
|
||||||
### Base Unit
|
|
||||||
|
|
||||||
Spacing follows Tailwind's 4px scale. Existing frontend stacks use `gap-1`, `gap-2`, `gap-3`, `gap-4`, and `gap-6`.
|
|
||||||
|
|
||||||
### Rules
|
|
||||||
|
|
||||||
- Use `flex flex-col gap-*` for vertical stacks; do not use `space-y-*`.
|
|
||||||
- Account content is constrained with `max-w-2xl` and uses `gap-6` between cards.
|
|
||||||
- Preference sections use `CardHeader` and `CardContent`; controls use `gap-4`.
|
|
||||||
- Preference sub-options are indented with `pl-6` and remain one readable column on narrow screens.
|
|
||||||
|
|
||||||
## 5. Components
|
|
||||||
|
|
||||||
### Card sections
|
|
||||||
|
|
||||||
- **Structure**: `Card` → `CardHeader` → `CardTitle` + `CardDescription`; `CardContent` body.
|
|
||||||
- **Variants**: default dark card surface.
|
|
||||||
- **Spacing**: built-in card padding; internal control stack uses `gap-4`.
|
|
||||||
- **States**: default, focus/error states belong to child controls.
|
|
||||||
- **Accessibility**: headings and descriptions provide section context.
|
|
||||||
- **Motion**: no section-level motion.
|
|
||||||
- **Layout**: vertical stack.
|
|
||||||
|
|
||||||
### Preference row
|
|
||||||
|
|
||||||
- **Structure**: `label.flex.items-center.justify-between` with text on the left and a shadcn control on the right.
|
|
||||||
- **Variants**: primary setting and indented notification setting.
|
|
||||||
- **Spacing**: `gap-3` for label copy; `pl-6` for dependent settings.
|
|
||||||
- **States**: default, checked, unchecked, disabled, focus-visible.
|
|
||||||
- **Accessibility**: the label owns the control relationship; disabled dependent rows use both disabled controls and reduced-opacity presentation.
|
|
||||||
- **Motion**: shadcn control transition only.
|
|
||||||
- **Layout**: responsive cluster with a single-column text block.
|
|
||||||
|
|
||||||
### Switch
|
|
||||||
|
|
||||||
- **Structure**: shadcn `Switch` with an associated label and optional description.
|
|
||||||
- **Variants**: primary and dependent/disabled.
|
|
||||||
- **Spacing**: control row uses `gap-4`.
|
|
||||||
- **States**: unchecked, checked, hover, active, focus-visible, disabled.
|
|
||||||
- **Accessibility**: keyboard reachable with visible focus and native switch semantics.
|
|
||||||
- **Motion**: 100–150ms control transition.
|
|
||||||
- **Layout**: inline control at the row edge.
|
|
||||||
|
|
||||||
### RadioGroup
|
|
||||||
|
|
||||||
- **Structure**: shadcn `RadioGroup` with `RadioGroupItem` and `Label` pairs.
|
|
||||||
- **Variants**: two-option roster label selection.
|
|
||||||
- **Spacing**: option cluster uses `gap-4`.
|
|
||||||
- **States**: selected, unselected, hover, active, focus-visible.
|
|
||||||
- **Accessibility**: grouped options retain native radio semantics and labels.
|
|
||||||
- **Motion**: control transition only.
|
|
||||||
- **Layout**: wraps on narrow screens without horizontal overflow.
|
|
||||||
|
|
||||||
## 6. Motion & Interaction
|
|
||||||
|
|
||||||
### Timing
|
|
||||||
|
|
||||||
| Type | Duration | Usage |
|
|
||||||
|------|----------|-------|
|
|
||||||
| Micro | 100–150ms | Switch and radio state feedback |
|
|
||||||
| Standard | 200–300ms | Button hover and focus transitions |
|
|
||||||
|
|
||||||
### Rules
|
|
||||||
|
|
||||||
- Keep motion in existing shadcn controls and buttons; do not add decorative animation to the account form.
|
|
||||||
- Preserve visible keyboard focus and `prefers-reduced-motion` behavior from the component primitives.
|
|
||||||
|
|
||||||
## 7. Depth & Surface
|
|
||||||
|
|
||||||
The frontend uses a mixed strategy: card borders plus the existing shadcn card shadow, with tonal changes for muted controls. New preference sections should compose the existing `Card` primitive rather than introduce custom shadows or radii.
|
|
||||||
|
|
||||||
## 8. Accessibility Constraints & Accepted Debt
|
|
||||||
|
|
||||||
### Constraints
|
|
||||||
|
|
||||||
- Target WCAG 2.2 AA.
|
|
||||||
- Every interactive control must be keyboard reachable with a visible focus state.
|
|
||||||
- Dependent notification controls must expose their disabled state through the actual control, not only visual opacity.
|
|
||||||
- The form must reflow to one readable column at mobile widths without horizontal scrolling.
|
|
||||||
|
|
||||||
### Accepted Debt
|
|
||||||
|
|
||||||
| Item | Location | Why accepted | Owner / Exit |
|
|
||||||
|------|----------|--------------|--------------|
|
|
||||||
| Existing success color uses `text-emerald-500` | Account forms | Preserve established feedback styling in this focused redesign | Consolidate account feedback tokens in a future account UI pass |
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import type { CollectionConfig } from "payload";
|
import type { CollectionConfig } from "payload";
|
||||||
import {
|
import { requirePermission, hasPermission, isSuperuser } from "@/utils/access-control/hasPermission";
|
||||||
requirePermission,
|
|
||||||
hasPermission,
|
|
||||||
isSuperuser,
|
|
||||||
} from "@/utils/access-control/hasPermission";
|
|
||||||
import { ensurePersonalAccount } from "@/lib/banking/index";
|
import { ensurePersonalAccount } from "@/lib/banking/index";
|
||||||
import { MUTEABLE_NOTIFICATION_TYPES } from "@/lib/notifications/notificationTypes";
|
import { MUTEABLE_NOTIFICATION_TYPES } from "@/lib/notifications/notificationTypes";
|
||||||
|
|
||||||
|
|
@ -60,13 +56,9 @@ export const Users: CollectionConfig = {
|
||||||
try {
|
try {
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: "profiles",
|
collection: "profiles",
|
||||||
draft: false,
|
|
||||||
data: {
|
data: {
|
||||||
user: userId,
|
user: userId,
|
||||||
rank: recruit?.id ?? (null as unknown as number),
|
rank: recruit?.id ?? (null as unknown as number),
|
||||||
dossier: {
|
|
||||||
enlistmentDate: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
progression: {
|
progression: {
|
||||||
qualifications: [],
|
qualifications: [],
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
|
@ -222,8 +214,7 @@ export const Users: CollectionConfig = {
|
||||||
: options;
|
: options;
|
||||||
},*/
|
},*/
|
||||||
admin: {
|
admin: {
|
||||||
description:
|
description: "Legacy role enum — being replaced by the dynamic RBAC system (see 'Role Assignments' below).",
|
||||||
"Legacy role enum — being replaced by the dynamic RBAC system (see 'Role Assignments' below).",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -236,8 +227,7 @@ export const Users: CollectionConfig = {
|
||||||
update: requirePermission("users:assign-roles"),
|
update: requirePermission("users:assign-roles"),
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
description:
|
description: "Dynamic RBAC role assignments. These determine the user's permissions via the roles collection.",
|
||||||
"Dynamic RBAC role assignments. These determine the user's permissions via the roles collection.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,9 @@ export const backfillProfiles = async () => {
|
||||||
try {
|
try {
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: "profiles",
|
collection: "profiles",
|
||||||
draft: false,
|
|
||||||
data: {
|
data: {
|
||||||
user: user.id,
|
user: user.id,
|
||||||
rank: recruitRankId,
|
rank: recruitRankId,
|
||||||
dossier: {
|
|
||||||
enlistmentDate: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
progression: {
|
progression: {
|
||||||
qualifications: [],
|
qualifications: [],
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ export const seedProfiles = async () => {
|
||||||
try {
|
try {
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: "profiles",
|
collection: "profiles",
|
||||||
draft: false,
|
|
||||||
data: {
|
data: {
|
||||||
user: (
|
user: (
|
||||||
await payload.find({
|
await payload.find({
|
||||||
|
|
@ -42,9 +41,6 @@ export const seedProfiles = async () => {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
})
|
})
|
||||||
).docs[0].id,
|
).docs[0].id,
|
||||||
dossier: {
|
|
||||||
enlistmentDate: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue