Vocabulary cleanup follow-up #55
12 changed files with 71 additions and 107 deletions
|
|
@ -8,14 +8,19 @@ success_metric: |
|
||||||
with vocabulary table.
|
with vocabulary table.
|
||||||
skip:
|
skip:
|
||||||
- arch
|
- arch
|
||||||
status: open
|
status: closed
|
||||||
created: 2026-05-27
|
created: 2026-05-27
|
||||||
|
closed: 2026-05-29
|
||||||
|
pr: 54
|
||||||
---
|
---
|
||||||
|
|
||||||
# Convoy: rename-collections-vocabulary
|
# Convoy: rename-collections-vocabulary
|
||||||
|
|
||||||
Align user-facing copy with the product taxonomy: owned cards vs curated lists.
|
Align user-facing copy with the product taxonomy: owned cards vs curated lists.
|
||||||
|
|
||||||
|
**Shipped:** PR #54 (squash `fd78114`, 2026-05-29). Follow-up cleanup tracked in
|
||||||
|
PR after deep review (dashboard h1, marketing copy, seed description).
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
The scanner audit and IA review found inconsistent vocabulary: "Owned Cards",
|
The scanner audit and IA review found inconsistent vocabulary: "Owned Cards",
|
||||||
|
|
@ -48,95 +53,20 @@ schema migration.
|
||||||
- **Schema renames** — table/column names stay; UI copy only.
|
- **Schema renames** — table/column names stay; UI copy only.
|
||||||
- **URL slug changes** — `/collections` path unchanged in v1.
|
- **URL slug changes** — `/collections` path unchanged in v1.
|
||||||
- **Architecture decisions** — `skip: arch`; IA + UX run explicitly.
|
- **Architecture decisions** — `skip: arch`; IA + UX run explicitly.
|
||||||
|
- **Admin UI** — separate pass if needed.
|
||||||
## Roles invoked
|
|
||||||
|
|
||||||
1. `role-ia-architect` — vocabulary table + file inventory (**primary owner**).
|
|
||||||
2. `role-ux-reviewer` — scan flow + nav label consistency.
|
|
||||||
3. `role-implementer` — 2 briefs (serial: Brief 2 after Brief 1).
|
|
||||||
4. `role-reviewer` + `role-design-system-auditor` + `role-a11y-auditor` —
|
|
||||||
copy changes affect screen reader strings.
|
|
||||||
|
|
||||||
Note: **`role-architect` skipped** per `skip: arch`. IA architect owns
|
|
||||||
taxonomy; implementer briefs written by IA + conductor handoff or parent
|
|
||||||
agent.
|
|
||||||
|
|
||||||
## Todos
|
## Todos
|
||||||
|
|
||||||
- [ ] IA: publish vocabulary table + grep inventory of stale strings
|
- [x] IA: publish vocabulary table + grep inventory of stale strings
|
||||||
- [ ] UX: review scanner + nav + collection views for consistency
|
- [x] UX: review scanner + nav + collection views for consistency
|
||||||
- [ ] Brief 1 — pages/ + components/ copy sweep
|
- [x] Brief 1 — pages/ + components/ copy sweep
|
||||||
- [ ] Brief 2 — AGENTS.md + rules + SCHEMA_MAP glossary
|
- [x] Brief 2 — AGENTS.md + rules + SCHEMA_MAP glossary
|
||||||
- [ ] Add `forbidden-stale-strings` CI job
|
- [x] Add `forbidden-stale-strings` CI job
|
||||||
|
|
||||||
## Operator action required
|
|
||||||
|
|
||||||
**None.**
|
|
||||||
|
|
||||||
## Multitask dispatch
|
|
||||||
|
|
||||||
### Slice dependencies
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
slice_dependencies:
|
|
||||||
- brief: 1
|
|
||||||
depends_on: []
|
|
||||||
files:
|
|
||||||
- pages/**/*.js
|
|
||||||
- components/*.js
|
|
||||||
notes: exclude pages/api/**
|
|
||||||
- brief: 2
|
|
||||||
depends_on: [1]
|
|
||||||
files:
|
|
||||||
- AGENTS.md
|
|
||||||
- .cursor/rules/ui-and-theming.mdc
|
|
||||||
- docs/SCHEMA_MAP.md
|
|
||||||
- .github/workflows/ci.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Serial: Brief 2 after Brief 1 (docs reference final copy).
|
|
||||||
|
|
||||||
**Cross-convoy:** parallel with #1+#2 after `secure-scanner-gemini-key`
|
|
||||||
merges — `/multitask role-implementer` **#5 Brief 1 + #6 + #2 Brief 1**
|
|
||||||
(disjoint files).
|
|
||||||
|
|
||||||
Post-PR audit:
|
|
||||||
|
|
||||||
```
|
|
||||||
/multitask role-reviewer + role-design-system-auditor + role-a11y-auditor
|
|
||||||
```
|
|
||||||
|
|
||||||
Group id: `audit-rename-collections-vocabulary-<pr>`.
|
|
||||||
|
|
||||||
## CI impact
|
|
||||||
|
|
||||||
| Workflow / job | Behavior |
|
|
||||||
| --- | --- |
|
|
||||||
| `forbidden-stale-strings` | **New blocking job** — grep `pages/` + `components/`. |
|
|
||||||
| `visual-diff.yml` | **Likely fires** — widespread UI string changes in pages/components. |
|
|
||||||
| `preview-smoke.yml` | Fires; sign-in CTA wording must stay smoke-compatible. |
|
|
||||||
|
|
||||||
**Smoke caveat:** smoke test 2 asserts `/sign in/i` on login page — do not
|
|
||||||
rename that CTA in this convoy.
|
|
||||||
|
|
||||||
## Decisions to ratify (IA architect)
|
|
||||||
|
|
||||||
1. **"Lists" vs "Binders"** — when to use each term in nav vs empty states.
|
|
||||||
2. **Scanner button label** — replacement for "Mark Owned" (e.g. "Add to
|
|
||||||
session" vs "Confirm card").
|
|
||||||
3. **Admin UI** — out of copy sweep or separate pass?
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
1. Zero rendered occurrences of the three forbidden strings in `pages/` +
|
|
||||||
`components/`.
|
|
||||||
2. Vocabulary table committed in `AGENTS.md`.
|
|
||||||
3. `forbidden-stale-strings` CI green.
|
|
||||||
4. Schema DDL unchanged (grep `migrations/` — no new files).
|
|
||||||
5. Vitest 21/21; smoke 3/3 (sign-in CTA intact).
|
|
||||||
|
|
||||||
## Out of scope follow-ups
|
## Out of scope follow-ups
|
||||||
|
|
||||||
- **`schema-cleanup-from-scanner-audit`** — `is_system_collection` vs
|
- **`schema-cleanup-from-scanner-audit`** — `is_system_collection` vs
|
||||||
`user_cards` unification (separate convoy).
|
`user_cards` unification (separate convoy).
|
||||||
- **`rename-repo-and-vercel-project`** — infra naming, not UI copy.
|
- **`rename-repo-and-vercel-project`** — infra naming, not UI copy.
|
||||||
|
- **Existing DB seed descriptions** — users registered pre-#54 retain the old
|
||||||
|
system-list description until a one-off data migration or manual edit.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ export const VOCAB = {
|
||||||
ADD_TO_LISTS: 'Add to Lists',
|
ADD_TO_LISTS: 'Add to Lists',
|
||||||
SYNCED_BINDER: 'Synced binder',
|
SYNCED_BINDER: 'Synced binder',
|
||||||
SYSTEM_COLLECTION_SYNC_HINT: 'Automatically syncs with My Collection',
|
SYSTEM_COLLECTION_SYNC_HINT: 'Automatically syncs with My Collection',
|
||||||
|
SYSTEM_COLLECTION_SEED_DESCRIPTION:
|
||||||
|
'Automatically syncs with My Collection. This list cannot be deleted or made public.',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** User-facing label for a collection row (maps system collection DB name). */
|
/** User-facing label for a collection row (maps system collection DB name). */
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { sql } from '@vercel/postgres';
|
import { sql } from '@vercel/postgres';
|
||||||
import { generateUniqueSlug } from '../../../lib/slug-utils.js';
|
import { generateUniqueSlug } from '../../../lib/slug-utils.js';
|
||||||
|
import { SYSTEM_COLLECTION_DB_NAME, VOCAB } from '../../../lib/collection-vocabulary.js';
|
||||||
import { generateToken } from '../auth-utils.js';
|
import { generateToken } from '../auth-utils.js';
|
||||||
import { checkAuthRateLimit } from '../../../lib/rate-limit.js';
|
import { checkAuthRateLimit } from '../../../lib/rate-limit.js';
|
||||||
|
|
||||||
|
|
@ -90,7 +91,7 @@ export default async function handler(req, res) {
|
||||||
const existingSlugs = (existingSlugsData.rows || []).map(row => row.slug);
|
const existingSlugs = (existingSlugsData.rows || []).map(row => row.slug);
|
||||||
|
|
||||||
// Generate unique slug for "All My Cards"
|
// Generate unique slug for "All My Cards"
|
||||||
const uniqueSlug = await generateUniqueSlug("All My Cards", existingSlugs);
|
const uniqueSlug = await generateUniqueSlug(SYSTEM_COLLECTION_DB_NAME, existingSlugs);
|
||||||
|
|
||||||
// Create the special collection
|
// Create the special collection
|
||||||
const collectionResult = await sql`
|
const collectionResult = await sql`
|
||||||
|
|
@ -106,8 +107,8 @@ export default async function handler(req, res) {
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
'All My Cards',
|
${SYSTEM_COLLECTION_DB_NAME},
|
||||||
'Automatically contains all cards you mark as owned. This collection cannot be deleted or made public.',
|
${VOCAB.SYSTEM_COLLECTION_SEED_DESCRIPTION},
|
||||||
'All',
|
'All',
|
||||||
false,
|
false,
|
||||||
${user.id},
|
${user.id},
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ function PublicCardsView() {
|
||||||
<div className="bg-gradient-to-r from-orange-50 to-red-50 dark:from-orange-900/20 dark:to-red-900/20 rounded-2xl p-6 border border-orange-200 dark:border-orange-800">
|
<div className="bg-gradient-to-r from-orange-50 to-red-50 dark:from-orange-900/20 dark:to-red-900/20 rounded-2xl p-6 border border-orange-200 dark:border-orange-800">
|
||||||
<h3 className="text-lg font-semibold mb-2 gradient-text-ember">Sign Up to Unlock Full Features</h3>
|
<h3 className="text-lg font-semibold mb-2 gradient-text-ember">Sign Up to Unlock Full Features</h3>
|
||||||
<p className="mb-4" style={{ color: 'var(--text-secondary)' }}>
|
<p className="mb-4" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Create collections, mark favorites, add cards to your inventory, and more!
|
Create lists, add cards to {VOCAB.MY_COLLECTION}, mark favorites, and more!
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -62,7 +62,7 @@ function PublicCardsView() {
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2 gradient-text-flame">Full Card Browser Coming Soon</h3>
|
<h3 className="text-xl font-semibold mb-2 gradient-text-flame">Full Card Browser Coming Soon</h3>
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
Sign up now to get early access to our complete card database and collection tools.
|
Sign up now to get early access to our complete card database and list tools.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -634,7 +634,7 @@ export default function Collections() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Create Collection Modal */}
|
{/* Create List Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div className="card max-w-md w-full mx-4">
|
<div className="card max-w-md w-full mx-4">
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Layout from '../components/Layout';
|
||||||
import PermissionIndicator from '../components/PermissionIndicator';
|
import PermissionIndicator from '../components/PermissionIndicator';
|
||||||
import { useAuth } from '../lib/use-auth';
|
import { useAuth } from '../lib/use-auth';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { VOCAB } from '../lib/collection-vocabulary.js';
|
import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -80,10 +80,10 @@ export default function Dashboard() {
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
<h1 className="text-2xl sm:text-3xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
{VOCAB.LISTS}
|
{VOCAB.MY_COLLECTION}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-base sm:text-lg" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-base sm:text-lg" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Manage your collection of trading cards and decks
|
Overview of your lists and owned cards
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2 sm:space-x-4">
|
<div className="flex space-x-2 sm:space-x-4">
|
||||||
|
|
@ -170,7 +170,7 @@ export default function Dashboard() {
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>No Lists Yet</h3>
|
<h3 className="text-lg font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>No Lists Yet</h3>
|
||||||
<p className="mb-4" style={{ color: 'var(--text-secondary)' }}>
|
<p className="mb-4" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Create your first collection to start organizing your cards
|
Create your first list to start organizing your cards
|
||||||
</p>
|
</p>
|
||||||
<Link href="/collections">
|
<Link href="/collections">
|
||||||
<button className="px-6 py-3 font-medium rounded-xl transition-all duration-200 hover:opacity-90" style={{ backgroundColor: 'var(--accent-ember)', color: 'white' }}>
|
<button className="px-6 py-3 font-medium rounded-xl transition-all duration-200 hover:opacity-90" style={{ backgroundColor: 'var(--accent-ember)', color: 'white' }}>
|
||||||
|
|
@ -191,7 +191,7 @@ export default function Dashboard() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-semibold truncate" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="font-semibold truncate" style={{ color: 'var(--text-primary)' }}>
|
||||||
{collection.name}
|
{collectionDisplayName(collection)}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm truncate" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-sm truncate" style={{ color: 'var(--text-secondary)' }}>
|
||||||
{collection.cardCount || 0} cards
|
{collection.cardCount || 0} cards
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export default function Home() {
|
||||||
<span className="gradient-text-flame">Deck Hearth</span>
|
<span className="gradient-text-flame">Deck Hearth</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl md:text-2xl mb-8 max-w-3xl mx-auto leading-relaxed" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-xl md:text-2xl mb-8 max-w-3xl mx-auto leading-relaxed" style={{ color: 'var(--text-secondary)' }}>
|
||||||
The ultimate hub for trading card collectors. Organize your collection,
|
The ultimate hub for trading card collectors. Organize your cards into lists,
|
||||||
discover rare cards, and connect with fellow enthusiasts in one beautiful platform.
|
discover rare cards, and connect with fellow enthusiasts in one beautiful platform.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||||
|
|
@ -139,7 +139,7 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-4 gradient-text-flame">Organize Lists</h3>
|
<h3 className="text-xl font-bold mb-4 gradient-text-flame">Organize Lists</h3>
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
Create custom collections, track card values, and organize by sets, rarity, or any system that works for you.
|
Create custom lists, track card values, and organize by sets, rarity, or any system that works for you.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-8 rounded-2xl" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
<div className="text-center p-8 rounded-2xl" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
||||||
|
|
@ -150,7 +150,7 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-4 gradient-text-gold">Discover Cards</h3>
|
<h3 className="text-xl font-bold mb-4 gradient-text-gold">Discover Cards</h3>
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
Search through thousands of cards across multiple TCGs. Find that missing piece for your collection.
|
Search through thousands of cards across multiple TCGs. Find that missing piece for your binder.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-8 rounded-2xl" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
<div className="text-center p-8 rounded-2xl" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
||||||
|
|
@ -161,7 +161,7 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-4 gradient-text-ember">Connect & Share</h3>
|
<h3 className="text-xl font-bold mb-4 gradient-text-ember">Connect & Share</h3>
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
Share your collections with the community, collaborate with friends, and discover amazing collections from other collectors.
|
Share your lists with the community, collaborate with friends, and discover amazing lists from other collectors.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -176,7 +176,7 @@ export default function Home() {
|
||||||
Featured Lists
|
Featured Lists
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-xl" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Discover amazing collections from our community
|
Discover amazing lists from our community
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -263,7 +263,7 @@ export default function Home() {
|
||||||
Ready to Start Your Journey?
|
Ready to Start Your Journey?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl mb-8" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-xl mb-8" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Join thousands of collectors who trust Deck Hearth to manage their trading card collections.
|
Join thousands of collectors who trust Deck Hearth to manage their cards and lists.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||||
<Link
|
<Link
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import AuthLayout from '../components/AuthLayout';
|
import AuthLayout from '../components/AuthLayout';
|
||||||
import AnimatedFireLogo from '../components/AnimatedFireLogo';
|
import AnimatedFireLogo from '../components/AnimatedFireLogo';
|
||||||
|
import { VOCAB } from '../lib/collection-vocabulary.js';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -76,7 +77,7 @@ export default function Login() {
|
||||||
Welcome to Deck Hearth
|
Welcome to Deck Hearth
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-2 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
<p className="mt-2 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Sign in to access your trading card collection
|
Sign in to access {VOCAB.MY_COLLECTION}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ export default function MyCards() {
|
||||||
{VOCAB.MY_COLLECTION}
|
{VOCAB.MY_COLLECTION}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-base sm:text-lg" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-base sm:text-lg" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Browse and manage cards in your collection
|
Browse and manage cards in {VOCAB.MY_COLLECTION}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2 sm:space-x-4">
|
<div className="flex space-x-2 sm:space-x-4">
|
||||||
|
|
@ -420,7 +420,7 @@ export default function MyCards() {
|
||||||
{!hasMore && cards.length > 0 && (
|
{!hasMore && cards.length > 0 && (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
You've reached the end of your collection!
|
You've reached the end of {VOCAB.MY_COLLECTION}!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -383,7 +383,7 @@ export default function Scanner() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to add to collection');
|
throw new Error(`Failed to add to ${VOCAB.LIST}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -758,7 +758,7 @@ export default function Scanner() {
|
||||||
{/* Bulk Actions Modal */}
|
{/* Bulk Actions Modal */}
|
||||||
{/* This modal is no longer needed as bulk actions are in a floating toolbar */}
|
{/* This modal is no longer needed as bulk actions are in a floating toolbar */}
|
||||||
|
|
||||||
{/* Create Collection Modal */}
|
{/* Create List Modal */}
|
||||||
{showCreateCollection && (
|
{showCreateCollection && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import AuthLayout from '../components/AuthLayout';
|
import AuthLayout from '../components/AuthLayout';
|
||||||
import AnimatedFireLogo from '../components/AnimatedFireLogo';
|
import AnimatedFireLogo from '../components/AnimatedFireLogo';
|
||||||
|
import { VOCAB } from '../lib/collection-vocabulary.js';
|
||||||
|
|
||||||
export default function Signup() {
|
export default function Signup() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -215,7 +216,7 @@ export default function Signup() {
|
||||||
Join Deck Hearth
|
Join Deck Hearth
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-2 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
<p className="mt-2 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
Create your account to start building your collection
|
Create your account to start building {VOCAB.MY_COLLECTION}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
29
test/lib/collection-vocabulary.test.js
Normal file
29
test/lib/collection-vocabulary.test.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SYSTEM_COLLECTION_DB_NAME,
|
||||||
|
VOCAB,
|
||||||
|
collectionDisplayName,
|
||||||
|
formatProcessedDestination,
|
||||||
|
} from '../../lib/collection-vocabulary.js';
|
||||||
|
|
||||||
|
describe('collection-vocabulary', () => {
|
||||||
|
it('maps system collection rows to Synced binder', () => {
|
||||||
|
expect(collectionDisplayName({ name: SYSTEM_COLLECTION_DB_NAME, isSystemCollection: true })).toBe(
|
||||||
|
VOCAB.SYNCED_BINDER
|
||||||
|
);
|
||||||
|
expect(collectionDisplayName({ name: SYSTEM_COLLECTION_DB_NAME, is_system_collection: true })).toBe(
|
||||||
|
VOCAB.SYNCED_BINDER
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes through user list names', () => {
|
||||||
|
expect(collectionDisplayName({ name: 'Modern Staples' })).toBe('Modern Staples');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats scanner processed destinations', () => {
|
||||||
|
expect(formatProcessedDestination('owned')).toBe(VOCAB.MY_COLLECTION);
|
||||||
|
expect(formatProcessedDestination('collection')).toBe(VOCAB.LIST);
|
||||||
|
expect(formatProcessedDestination('deck')).toBe('deck');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue