style(i18n): fix lint — formatting and import ordering across 21 files
Biome auto-fix for formatting (line length, ternary wrapping) and import organization in files touched by phase 34 i18n work. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,11 @@ export function AddToCollectionModal() {
|
||||
closeAddToCollection();
|
||||
},
|
||||
onError: (err) => {
|
||||
setError(err instanceof Error ? err.message : t("collection:addToCollection.failedToAdd"));
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: t("collection:addToCollection.failedToAdd"),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -138,7 +142,9 @@ export function AddToCollectionModal() {
|
||||
type="number"
|
||||
value={purchasePrice}
|
||||
onChange={(e) => setPurchasePrice(e.target.value)}
|
||||
placeholder={t("collection:addToCollection.purchasePricePlaceholder")}
|
||||
placeholder={t(
|
||||
"collection:addToCollection.purchasePricePlaceholder",
|
||||
)}
|
||||
min="0"
|
||||
step="0.01"
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent"
|
||||
@@ -160,7 +166,9 @@ export function AddToCollectionModal() {
|
||||
disabled={createItem.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{createItem.isPending ? t("collection:addToCollection.addingButton") : t("collection:addToCollection.addButton")}
|
||||
{createItem.isPending
|
||||
? t("collection:addToCollection.addingButton")
|
||||
: t("collection:addToCollection.addButton")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -116,7 +116,9 @@ export function AddToThreadModal() {
|
||||
toast.success(`Added to "${thread?.name ?? "thread"}"`);
|
||||
closeAddToThread();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t("addToThread.failedToAdd"));
|
||||
setError(
|
||||
err instanceof Error ? err.message : t("addToThread.failedToAdd"),
|
||||
);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -144,7 +146,9 @@ export function AddToThreadModal() {
|
||||
toast.success(`Created "${trimmedName}" with first candidate`);
|
||||
closeAddToThread();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t("addToThread.failedToCreate"));
|
||||
setError(
|
||||
err instanceof Error ? err.message : t("addToThread.failedToCreate"),
|
||||
);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -175,7 +179,9 @@ export function AddToThreadModal() {
|
||||
onKeyDown={() => {}}
|
||||
>
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
{mode === "pick" ? t("addToThread.title") : t("addToThread.newThreadTitle")}
|
||||
{mode === "pick"
|
||||
? t("addToThread.title")
|
||||
: t("addToThread.newThreadTitle")}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500 mb-4">{globalItemName}</p>
|
||||
|
||||
|
||||
@@ -83,7 +83,9 @@ export function CategoryFilterDropdown({
|
||||
<span className="text-gray-900">{selectedCategory.name}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-gray-600">{t("categoryFilter.allCategories")}</span>
|
||||
<span className="text-gray-600">
|
||||
{t("categoryFilter.allCategories")}
|
||||
</span>
|
||||
)}
|
||||
{selectedCategory ? (
|
||||
<button
|
||||
|
||||
@@ -87,8 +87,8 @@ export function CategoryHeader({
|
||||
<LucideIcon name={icon} size={22} className="text-gray-500" />
|
||||
<h2 className="text-lg font-semibold text-gray-900">{name}</h2>
|
||||
<span className="text-sm text-gray-400">
|
||||
{t("categoryHeader.itemCount", { count: itemCount })} · {weight(totalWeight)}{" "}
|
||||
· {price(totalCost)}
|
||||
{t("categoryHeader.itemCount", { count: itemCount })} ·{" "}
|
||||
{weight(totalWeight)} · {price(totalCost)}
|
||||
</span>
|
||||
{!isUncategorized && (
|
||||
<div className="ml-auto flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
|
||||
@@ -235,7 +235,9 @@ export function CategoryPicker({ value, onChange }: CategoryPickerProps) {
|
||||
disabled={createCategory.isPending}
|
||||
className="text-xs font-medium text-gray-600 hover:text-gray-800 disabled:opacity-50"
|
||||
>
|
||||
{createCategory.isPending ? "..." : t("categoryPicker.create")}
|
||||
{createCategory.isPending
|
||||
? "..."
|
||||
: t("categoryPicker.create")}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -137,14 +137,18 @@ export function CollectionView() {
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<LucideIcon name="layers" size={14} className="text-gray-400" />
|
||||
<span className="text-xs text-gray-500">{t("common:stats.items")}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{t("common:stats.items")}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{totals.global.itemCount}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<LucideIcon name="weight" size={14} className="text-gray-400" />
|
||||
<span className="text-xs text-gray-500">{t("common:stats.totalWeight")}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{t("common:stats.totalWeight")}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{weight(totals.global.totalWeight)}
|
||||
</span>
|
||||
@@ -155,7 +159,9 @@ export function CollectionView() {
|
||||
size={14}
|
||||
className="text-gray-400"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">{t("common:stats.totalSpent")}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{t("common:stats.totalSpent")}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{price(totals.global.totalCost)}
|
||||
</span>
|
||||
@@ -205,7 +211,10 @@ export function CollectionView() {
|
||||
</div>
|
||||
{hasActiveFilters && (
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
{t("common:filter.showing", { filtered: filteredItems.length, total: items.length })}
|
||||
{t("common:filter.showing", {
|
||||
filtered: filteredItems.length,
|
||||
total: items.length,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,9 @@ export function CreateThreadModal() {
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={() => {}}
|
||||
>
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">{t("create.title")}</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{t("create.title")}
|
||||
</h2>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
@@ -135,7 +137,9 @@ export function CreateThreadModal() {
|
||||
disabled={createThread.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{createThread.isPending ? t("common:actions.creating") : t("create.createThread")}
|
||||
{createThread.isPending
|
||||
? t("common:actions.creating")
|
||||
: t("create.createThread")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -76,7 +76,11 @@ export function ItemPicker({
|
||||
}
|
||||
|
||||
return (
|
||||
<SlideOutPanel isOpen={isOpen} onClose={onClose} title={t("collection:itemPicker.title")}>
|
||||
<SlideOutPanel
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={t("collection:itemPicker.title")}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 overflow-y-auto -mx-6 px-6">
|
||||
{!items || items.length === 0 ? (
|
||||
@@ -146,7 +150,9 @@ export function ItemPicker({
|
||||
disabled={syncItems.isPending}
|
||||
className="flex-1 px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{syncItems.isPending ? t("common:actions.saving") : t("collection:itemPicker.done")}
|
||||
{syncItems.isPending
|
||||
? t("common:actions.saving")
|
||||
: t("collection:itemPicker.done")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,9 @@ export function LinkToGlobalItem({
|
||||
<div className="border-t border-gray-100 max-h-48 overflow-y-auto">
|
||||
{isSearching ? (
|
||||
<div className="p-3 text-center">
|
||||
<span className="text-xs text-gray-400">{t("linkToGlobal.searching")}</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{t("linkToGlobal.searching")}
|
||||
</span>
|
||||
</div>
|
||||
) : searchResults && searchResults.length > 0 ? (
|
||||
<div>
|
||||
@@ -197,7 +199,9 @@ export function LinkToGlobalItem({
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-3 text-center">
|
||||
<span className="text-xs text-gray-400">{t("linkToGlobal.noItemsFound")}</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{t("linkToGlobal.noItemsFound")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,11 @@ export function ManualEntryForm({
|
||||
onSuccess(item.name);
|
||||
},
|
||||
onError: (err) => {
|
||||
setError(err instanceof Error ? err.message : t("collection:manualEntry.failedToSave"));
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: t("collection:manualEntry.failedToSave"),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -223,7 +227,9 @@ export function ManualEntryForm({
|
||||
disabled={createItem.isPending || !name.trim() || categoryId === null}
|
||||
className="w-full px-4 py-2.5 text-sm font-medium text-white bg-gray-900 rounded-lg hover:bg-gray-800 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{createItem.isPending ? t("common:actions.saving") : t("collection:manualEntry.addToCollection")}
|
||||
{createItem.isPending
|
||||
? t("common:actions.saving")
|
||||
: t("collection:manualEntry.addToCollection")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -117,7 +117,9 @@ export function PlanningView() {
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{t("threads:planning.step1Title")}</p>
|
||||
<p className="font-medium text-gray-900">
|
||||
{t("threads:planning.step1Title")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("threads:planning.step1Description")}
|
||||
</p>
|
||||
@@ -128,7 +130,9 @@ export function PlanningView() {
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{t("threads:planning.step2Title")}</p>
|
||||
<p className="font-medium text-gray-900">
|
||||
{t("threads:planning.step2Title")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("threads:planning.step2Description")}
|
||||
</p>
|
||||
@@ -139,7 +143,9 @@ export function PlanningView() {
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{t("threads:planning.step3Title")}</p>
|
||||
<p className="font-medium text-gray-900">
|
||||
{t("threads:planning.step3Title")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("threads:planning.step3Description")}
|
||||
</p>
|
||||
@@ -171,7 +177,9 @@ export function PlanningView() {
|
||||
</div>
|
||||
) : filteredThreads.length === 0 ? (
|
||||
<div className="py-12 text-center">
|
||||
<p className="text-sm text-gray-500">{t("threads:empty.noThreads")}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("threads:empty.noThreads")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
|
||||
@@ -77,7 +77,10 @@ export function ProfileSection() {
|
||||
setDirty(true);
|
||||
} catch {
|
||||
setAvatarDisplayUrl(null);
|
||||
setMessage({ type: "error", text: t("collection:profileSection.avatarUploadFailed") });
|
||||
setMessage({
|
||||
type: "error",
|
||||
text: t("collection:profileSection.avatarUploadFailed"),
|
||||
});
|
||||
} finally {
|
||||
setUploading(false);
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
@@ -87,7 +90,9 @@ export function ProfileSection() {
|
||||
return (
|
||||
<form onSubmit={handleSave} className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">{t("collection:profileSection.title")}</h3>
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
{t("collection:profileSection.title")}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
{t("collection:profileSection.subtitle")}
|
||||
</p>
|
||||
@@ -150,7 +155,9 @@ export function ProfileSection() {
|
||||
disabled={uploading}
|
||||
className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
|
||||
>
|
||||
{uploading ? t("collection:profileSection.uploadingAvatar") : t("collection:profileSection.changeAvatar")}
|
||||
{uploading
|
||||
? t("collection:profileSection.uploadingAvatar")
|
||||
: t("collection:profileSection.changeAvatar")}
|
||||
</button>
|
||||
{avatarFilename && (
|
||||
<button
|
||||
@@ -235,7 +242,9 @@ export function ProfileSection() {
|
||||
disabled={updateProfile.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{updateProfile.isPending ? t("common:actions.saving") : t("collection:profileSection.saveProfile")}
|
||||
{updateProfile.isPending
|
||||
? t("common:actions.saving")
|
||||
: t("collection:profileSection.saveProfile")}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,9 @@ export function SetupsView() {
|
||||
disabled={!newSetupName.trim() || createSetup.isPending}
|
||||
className="px-4 py-2 bg-gray-700 hover:bg-gray-800 disabled:opacity-50 text-white text-sm font-medium rounded-lg transition-colors"
|
||||
>
|
||||
{createSetup.isPending ? t("setups:creating") : t("common:actions.create")}
|
||||
{createSetup.isPending
|
||||
? t("setups:creating")
|
||||
: t("common:actions.create")}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -61,7 +63,9 @@ export function SetupsView() {
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{t("setups:emptyState.step1Title")}</p>
|
||||
<p className="font-medium text-gray-900">
|
||||
{t("setups:emptyState.step1Title")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("setups:emptyState.step1Description")}
|
||||
</p>
|
||||
@@ -72,7 +76,9 @@ export function SetupsView() {
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{t("setups:emptyState.step2Title")}</p>
|
||||
<p className="font-medium text-gray-900">
|
||||
{t("setups:emptyState.step2Title")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("setups:emptyState.step2Description")}
|
||||
</p>
|
||||
@@ -83,7 +89,9 @@ export function SetupsView() {
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{t("setups:emptyState.step3Title")}</p>
|
||||
<p className="font-medium text-gray-900">
|
||||
{t("setups:emptyState.step3Title")}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("setups:emptyState.step3Description")}
|
||||
</p>
|
||||
|
||||
@@ -62,7 +62,11 @@ export function StatusBadge({ status, onStatusChange }: StatusBadgeProps) {
|
||||
}}
|
||||
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors cursor-pointer"
|
||||
>
|
||||
<LucideIcon name={STATUS_ICONS[status]} size={14} className="text-gray-500" />
|
||||
<LucideIcon
|
||||
name={STATUS_ICONS[status]}
|
||||
size={14}
|
||||
className="text-gray-500"
|
||||
/>
|
||||
{STATUS_LABELS[status]}
|
||||
</button>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Cell,
|
||||
Label,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
} from "recharts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormatters } from "../hooks/useFormatters";
|
||||
import type { SetupItemWithCategory } from "../hooks/useSetups";
|
||||
import { formatWeight, type WeightUnit } from "../lib/formatters";
|
||||
@@ -205,7 +205,9 @@ export function WeightSummaryCard({ items }: WeightSummaryCardProps) {
|
||||
<div className="bg-white rounded-xl border border-gray-100 p-5 mb-6">
|
||||
{/* Header with pill toggle */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-gray-700">{t("weightSummary.title")}</h3>
|
||||
<h3 className="text-sm font-medium text-gray-700">
|
||||
{t("weightSummary.title")}
|
||||
</h3>
|
||||
<div className="flex items-center gap-1 bg-gray-100 rounded-full px-1 py-0.5">
|
||||
{VIEW_MODES.map((mode) => (
|
||||
<button
|
||||
@@ -218,7 +220,9 @@ export function WeightSummaryCard({ items }: WeightSummaryCardProps) {
|
||||
: "text-gray-400 hover:text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{mode === "category" ? t("weightSummary.category") : t("weightSummary.classification")}
|
||||
{mode === "category"
|
||||
? t("weightSummary.category")
|
||||
: t("weightSummary.classification")}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { ArrowLeft, LayoutGrid, LayoutList, X } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { z } from "zod";
|
||||
import { GearImage } from "../../components/GearImage";
|
||||
import { GlobalItemCard } from "../../components/GlobalItemCard";
|
||||
@@ -663,9 +663,7 @@ function EmptyState({ hasQuery }: { hasQuery: boolean }) {
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-sm text-gray-500 text-center">
|
||||
{hasQuery
|
||||
? t("empty.noResults")
|
||||
: t("empty.noCatalogItems")}
|
||||
{hasQuery ? t("empty.noResults") : t("empty.noCatalogItems")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,9 @@ function PopularSetupsSection() {
|
||||
return (
|
||||
<section className="mb-12">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{t("home.popularSetups")}</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
{t("home.popularSetups")}
|
||||
</h2>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<SectionSkeleton count={6} aspect="none" />
|
||||
@@ -57,7 +59,9 @@ function RecentItemsSection() {
|
||||
return (
|
||||
<section className="mb-12">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{t("home.recentlyAdded")}</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
{t("home.recentlyAdded")}
|
||||
</h2>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<SectionSkeleton count={8} aspect="[4/3]" />
|
||||
|
||||
@@ -207,7 +207,9 @@ function ItemDetail() {
|
||||
search: shareToken ? { share: shareToken } : {},
|
||||
}
|
||||
: { to: "/collection" as const, params: {}, search: {} };
|
||||
const backLabel = setupId ? t("collection:item.backToSetup") : t("collection:item.backToCollection");
|
||||
const backLabel = setupId
|
||||
? t("collection:item.backToSetup")
|
||||
: t("collection:item.backToCollection");
|
||||
|
||||
if (error || !item) {
|
||||
return (
|
||||
@@ -221,7 +223,9 @@ function ItemDetail() {
|
||||
← {backLabel}
|
||||
</Link>
|
||||
<div className="text-center py-16">
|
||||
<p className="text-sm text-gray-500">{t("collection:item.notFound")}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("collection:item.notFound")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -270,15 +274,25 @@ function ItemDetail() {
|
||||
onClick={handleDelete}
|
||||
className="hidden md:inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
>
|
||||
{isReference ? t("collection:item.removeFromCollection") : t("common:actions.delete")}
|
||||
{isReference
|
||||
? t("collection:item.removeFromCollection")
|
||||
: t("common:actions.delete")}
|
||||
</button>
|
||||
{/* Delete — mobile */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
className="md:hidden inline-flex items-center justify-center min-w-[44px] min-h-[44px] p-2 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
aria-label={isReference ? t("collection:item.removeFromCollection") : t("common:actions.delete")}
|
||||
title={isReference ? t("collection:item.removeFromCollection") : t("common:actions.delete")}
|
||||
aria-label={
|
||||
isReference
|
||||
? t("collection:item.removeFromCollection")
|
||||
: t("common:actions.delete")
|
||||
}
|
||||
title={
|
||||
isReference
|
||||
? t("collection:item.removeFromCollection")
|
||||
: t("common:actions.delete")
|
||||
}
|
||||
>
|
||||
<LucideIcon name="trash-2" size={16} />
|
||||
</button>
|
||||
@@ -317,7 +331,9 @@ function ItemDetail() {
|
||||
disabled={updateItem.isPending || !form.name.trim()}
|
||||
className="px-4 py-1.5 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{updateItem.isPending ? t("common:actions.saving") : t("common:actions.save")}
|
||||
{updateItem.isPending
|
||||
? t("common:actions.saving")
|
||||
: t("common:actions.save")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -38,7 +38,9 @@ function ProfilePage() {
|
||||
>
|
||||
← {t("actions.back")}
|
||||
</Link>
|
||||
<h1 className="text-xl font-semibold text-gray-900">{t("profile.title")}</h1>
|
||||
<h1 className="text-xl font-semibold text-gray-900">
|
||||
{t("profile.title")}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Section 1: Profile Info (D-02) */}
|
||||
@@ -108,14 +110,20 @@ function AccountInfoSection({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">{t("profile.account")}</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{t("profile.accountInfo")}</p>
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
{t("profile.account")}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
{t("profile.accountInfo")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Email row */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">{t("profile.email")}</span>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
{t("profile.email")}
|
||||
</span>
|
||||
<span className="text-sm text-gray-900 ml-3">
|
||||
{email || t("profile.noEmail")}
|
||||
</span>
|
||||
@@ -147,7 +155,9 @@ function AccountInfoSection({
|
||||
disabled={changeEmail.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{changeEmail.isPending ? t("profile.updating") : t("profile.updateEmail")}
|
||||
{changeEmail.isPending
|
||||
? t("profile.updating")
|
||||
: t("profile.updateEmail")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -232,8 +242,12 @@ function SecuritySection() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">{t("profile.security")}</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{t("profile.managePassword")}</p>
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
{t("profile.security")}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
{t("profile.managePassword")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
@@ -335,7 +349,9 @@ function DangerZoneSection() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">{t("profile.dangerZone")}</h3>
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
{t("profile.dangerZone")}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
{t("profile.dangerZoneDescription")}
|
||||
</p>
|
||||
@@ -378,7 +394,9 @@ function DangerZoneSection() {
|
||||
disabled={confirmation !== "DELETE" || deleteAccount.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{deleteAccount.isPending ? t("actions.deleting") : t("profile.deleteAccount")}
|
||||
{deleteAccount.isPending
|
||||
? t("actions.deleting")
|
||||
: t("profile.deleteAccount")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,9 @@ function SetupDetailPage() {
|
||||
{isSharedView && setup && (
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-blue-50 border-b border-blue-100 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8">
|
||||
<LucideIcon name="link" size={16} className="text-blue-500" />
|
||||
<span className="text-sm text-blue-700">{t("setups:detail.sharedSetup")}</span>
|
||||
<span className="text-sm text-blue-700">
|
||||
{t("setups:detail.sharedSetup")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -448,7 +450,9 @@ function SetupDetailPage() {
|
||||
disabled={deleteSetup.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 rounded-lg transition-colors"
|
||||
>
|
||||
{deleteSetup.isPending ? t("common:actions.deleting") : t("common:actions.delete")}
|
||||
{deleteSetup.isPending
|
||||
? t("common:actions.deleting")
|
||||
: t("common:actions.delete")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -119,7 +119,9 @@ function ThreadDetailPage() {
|
||||
: "bg-gray-100 text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{isActive ? t("threads:detail.statusActive") : t("threads:detail.statusResolved")}
|
||||
{isActive
|
||||
? t("threads:detail.statusActive")
|
||||
: t("threads:detail.statusResolved")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,7 +421,9 @@ function AddCandidateModal({ threadId, onClose }: AddCandidateModalProps) {
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{t("threads:detail.addCandidateModal.title")}</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
{t("threads:detail.addCandidateModal.title")}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
@@ -617,7 +621,9 @@ function AddCandidateModal({ threadId, onClose }: AddCandidateModalProps) {
|
||||
disabled={createCandidate.isPending}
|
||||
className="flex-1 py-2.5 px-4 bg-gray-700 hover:bg-gray-800 disabled:opacity-50 text-white text-sm font-medium rounded-lg transition-colors"
|
||||
>
|
||||
{createCandidate.isPending ? t("threads:detail.addCandidateModal.adding") : t("threads:detail.addCandidateModal.submit")}
|
||||
{createCandidate.isPending
|
||||
? t("threads:detail.addCandidateModal.adding")
|
||||
: t("threads:detail.addCandidateModal.submit")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user