refactor: remove dead code identified by knip

Delete unused Fab component (superseded by QuickAddFab), unused server
actions and queries (listDashboards, reorderDashboards, deleteCareLog,
canAccessContainer, getOverduePlants, getCareDueSoon, addItemToDefaultList,
reorderItems, getOrCreateDefaultList), unused helpers (getCurrentUser,
BrandWordmark, TopbarSpacer, DEV_LOGIN_COOKIE, no-arg ensureDefault*
wrappers, addListItem re-export), and drop export keyword from five
internal-only functions.

Verified with typecheck, eslint, and production build after each removal.
This commit is contained in:
ginnoir
2026-06-09 15:55:21 -05:00
parent d4ac20f511
commit 944dcdf48f
16 changed files with 6 additions and 226 deletions
-29
View File
@@ -19,22 +19,6 @@ export type DashboardMeta = {
position: number;
};
export async function listDashboards(): Promise<DashboardMeta[]> {
const { user } = await getCurrentSession();
const rows = await db
.select({
id: dashboards.id,
name: dashboards.name,
slug: dashboards.slug,
isDefault: dashboards.isDefault,
position: dashboards.position,
})
.from(dashboards)
.where(eq(dashboards.userId, user.id))
.orderBy(asc(dashboards.position), asc(dashboards.createdAt));
return rows;
}
export async function getDefaultDashboardSlug(): Promise<string> {
const { user } = await getCurrentSession();
const rows = await db
@@ -160,19 +144,6 @@ export async function setDefaultDashboard(id: string): Promise<void> {
revalidatePath("/");
}
export async function reorderDashboards(orderedIds: string[]): Promise<void> {
const { user } = await getCurrentSession();
await Promise.all(
orderedIds.map((id, i) =>
db
.update(dashboards)
.set({ position: i })
.where(and(eq(dashboards.id, id), eq(dashboards.userId, user.id))),
),
);
revalidatePath("/");
}
export async function saveDashboardLayout(id: string, layout: DashboardLayout): Promise<void> {
const { user } = await getCurrentSession();
// Validate each widget's config against its registered schema
-9
View File
@@ -21,12 +21,3 @@ export function BrandMark({ size = "md", className }: { size?: "sm" | "md"; clas
</span>
);
}
export function BrandWordmark({ className }: { className?: string }) {
return (
<span className={cn("brand", className)}>
<BrandMark />
<span className="brand-name">famapp</span>
</span>
);
}
-14
View File
@@ -1,14 +0,0 @@
"use client";
import { useQuickAdd } from "@/components/quick-add-provider";
import { NavIcon } from "@/components/nav-icon";
export function Fab() {
const { openSheet } = useQuickAdd();
return (
<button type="button" className="fab" aria-label="Quick add" onClick={openSheet}>
<NavIcon name="plus" className="size-6" strokeWidth={2.4} />
</button>
);
}
+2 -2
View File
@@ -17,7 +17,7 @@ const SECTIONS = [
export type SectionId = (typeof SECTIONS)[number]["id"];
export function SettingsSidebar({ active }: { active: SectionId }) {
function SettingsSidebar({ active }: { active: SectionId }) {
const router = useRouter();
const pathname = usePathname();
@@ -78,7 +78,7 @@ function SectionHashSync({ pathname }: { pathname: string | null }) {
return null;
}
export function SettingsTabsMobile({ active }: { active: SectionId }) {
function SettingsTabsMobile({ active }: { active: SectionId }) {
const router = useRouter();
return (
<div className="grid grid-cols-2 gap-1.5 mb-1">
-12
View File
@@ -6,7 +6,6 @@ import { NotificationBell } from "@/components/notification-bell";
import { TopbarSearch } from "@/components/topbar-search";
import { TopbarTitle } from "@/components/topbar-title";
import { TopbarNewButton } from "@/components/topbar-new-button";
import { NavIcon } from "@/components/nav-icon";
async function getNotifications(userId: string) {
const rows = await db
@@ -78,14 +77,3 @@ export async function Topbar() {
</div>
);
}
export function TopbarSpacer() {
return (
<div className="topbar">
<h1 className="serif">famapp</h1>
<div className="topbar-actions">
<NavIcon name="bell" className="size-4 text-[var(--ink-mute)]" />
</div>
</div>
);
}
-9
View File
@@ -49,12 +49,3 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
},
},
});
export async function getCurrentUser() {
const session = await auth();
if (!session?.user?.id) return null;
const user = await db.query.users.findFirst({
where: (u, { eq }) => eq(u.id, session.user.id),
});
return user ?? null;
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { computePresetLayoutFromMetas } from "./dashboard";
/** Build a layout matching one of the design's three dashboard arrangements
* using the live module registry. Server-only — the registry is empty on
* the client. */
export function computePresetLayout(preset: PresetId): DashboardLayout {
function computePresetLayout(preset: PresetId): DashboardLayout {
const { widgets } = getRegistry();
if (widgets.length === 0) return { version: 1, widgets: [] };
-2
View File
@@ -1,5 +1,3 @@
export const DEV_LOGIN_COOKIE = "authjs.session-token";
if (
process.env.NODE_ENV === "production" &&
process.env.ENABLE_DEV_LOGIN === "true" &&
+1 -1
View File
@@ -54,7 +54,7 @@ export async function listReminders(entityType: string, entityId: string) {
.where(and(eq(reminders.entityType, entityType), eq(reminders.entityId, entityId)));
}
export async function tickReminders() {
async function tickReminders() {
let dueReminders: (typeof reminders.$inferSelect)[] = [];
try {
+1 -17
View File
@@ -1,6 +1,6 @@
import { and, eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { householdMembers, households, users } from "@/modules/_core/schema";
import { householdMembers } from "@/modules/_core/schema";
import { calendars } from "../schema";
const HOME_COLOR = "#2563eb";
@@ -17,22 +17,6 @@ export async function ensureDefaultCalendarsForMembership({
await ensurePersonalCalendar({ householdId, userId });
}
export async function ensureDefaultCalendars() {
const householdRows = await db.select().from(households);
for (const household of householdRows) {
await ensureHomeCalendar(household.id);
}
const memberships = await db
.select({ householdId: householdMembers.householdId, userId: users.id })
.from(householdMembers)
.innerJoin(users, eq(householdMembers.userId, users.id));
for (const membership of memberships) {
await ensurePersonalCalendar(membership);
}
}
async function ensureHomeCalendar(householdId: string) {
const [existing] = await db
.select({ id: calendars.id })
-16
View File
@@ -451,22 +451,6 @@ export async function logCare(input: z.input<typeof careLogInput>) {
return log;
}
export async function deleteCareLog(input: { id: string }) {
const parsed = z.object({ id: z.string().uuid() }).parse(input);
const { household } = await getCurrentSession();
const [row] = await db
.select({ id: gardenCareLogs.id, plantId: gardenCareLogs.plantId })
.from(gardenCareLogs)
.where(and(eq(gardenCareLogs.id, parsed.id), eq(gardenCareLogs.householdId, household.id)))
.limit(1);
if (!row) throw new Error("Forbidden");
await db.delete(gardenCareLogs).where(eq(gardenCareLogs.id, parsed.id));
revalidatePath(`/garden/plants/${row.plantId}`);
}
// ─── Care schedule actions ────────────────────────────────────────────────────
const careScheduleInput = z.object({
@@ -1,4 +1,3 @@
export { addItem as addListItem } from "@/modules/lists/server/actions";
export { getList, listLists } from "@/modules/lists/server/queries";
import { addItem } from "@/modules/lists/server/actions";
-78
View File
@@ -110,16 +110,6 @@ export async function getContainer(id: string): Promise<ContainerDetailDto | nul
};
}
export async function canAccessContainer(id: string, householdId: string): Promise<boolean> {
const [row] = await db
.select({ id: gardenContainers.id })
.from(gardenContainers)
.where(and(eq(gardenContainers.id, id), eq(gardenContainers.householdId, householdId)))
.limit(1);
return !!row;
}
export async function searchContainers(query: string, householdId: string) {
const rows = await db
.select({ id: gardenContainers.id, name: gardenContainers.name })
@@ -430,74 +420,6 @@ export async function getCareSchedules(plantId: string): Promise<CareScheduleDto
});
}
export type OverduePlantDto = {
id: string;
name: string;
primaryImageUrl: string | null;
mostOverdueAt: Date;
};
export async function getOverduePlants(householdId: string): Promise<OverduePlantDto[]> {
const rows = await db
.select({
id: gardenPlants.id,
name: gardenPlants.name,
primaryImageUrl: gardenPlants.primaryImageUrl,
mostOverdueAt: sql<Date>`min(${gardenCareSchedules.nextDueAt})`,
})
.from(gardenPlants)
.innerJoin(
gardenCareSchedules,
and(
eq(gardenCareSchedules.plantId, gardenPlants.id),
eq(gardenCareSchedules.enabled, true),
lte(gardenCareSchedules.nextDueAt, sql`now()`),
),
)
.where(eq(gardenPlants.householdId, householdId))
.groupBy(gardenPlants.id, gardenPlants.name, gardenPlants.primaryImageUrl)
.orderBy(sql`min(${gardenCareSchedules.nextDueAt})`);
return rows;
}
export type CareDueSoonDto = {
id: string;
name: string;
primaryImageUrl: string | null;
nextDueAt: Date;
};
export async function getCareDueSoon(
householdId: string,
withinDays: number,
): Promise<CareDueSoonDto[]> {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() + withinDays);
const rows = await db
.select({
id: gardenPlants.id,
name: gardenPlants.name,
primaryImageUrl: gardenPlants.primaryImageUrl,
nextDueAt: sql<Date>`min(${gardenCareSchedules.nextDueAt})`,
})
.from(gardenPlants)
.innerJoin(
gardenCareSchedules,
and(
eq(gardenCareSchedules.plantId, gardenPlants.id),
eq(gardenCareSchedules.enabled, true),
lte(gardenCareSchedules.nextDueAt, cutoff),
),
)
.where(eq(gardenPlants.householdId, householdId))
.groupBy(gardenPlants.id, gardenPlants.name, gardenPlants.primaryImageUrl)
.orderBy(sql`min(${gardenCareSchedules.nextDueAt})`);
return rows;
}
// ─── Widget queries ───────────────────────────────────────────────────────────
export type CareDueWidgetRow = {
+1 -1
View File
@@ -102,7 +102,7 @@ export async function searchSpecies(query: string): Promise<SpeciesSuggestion[]>
}
}
export async function getSpeciesById(id: string): Promise<SpeciesSuggestion | null> {
async function getSpeciesById(id: string): Promise<SpeciesSuggestion | null> {
const cutoff = new Date(Date.now() - CACHE_TTL_MS);
const [cached] = await db
.select()
-13
View File
@@ -8,7 +8,6 @@ import { getCurrentSession } from "@/lib/session";
import { logActivity } from "@/modules/_core/activity";
import { fireItemToggleHooks } from "@/modules/_core/registry";
import { listItems, lists } from "../schema";
import { getOrCreateDefaultList } from "./defaults";
import { canAccessList, getList } from "./queries";
import { notifyListChanged } from "./realtime";
@@ -122,18 +121,6 @@ export async function addItem(input: z.input<typeof itemInput>) {
return getList(parsed.listId);
}
export async function addItemToDefaultList(input: { type: string; text: string }) {
const parsed = z
.object({
type: listInput.shape.type,
text: itemInput.shape.text,
})
.parse(input);
const { household } = await getCurrentSession();
const list = await getOrCreateDefaultList({ householdId: household.id, type: parsed.type });
return addItem({ listId: list.id, text: parsed.text });
}
export async function toggleItem(input: { id: string; done?: boolean }) {
const parsed = z.object({ id: z.string().uuid(), done: z.boolean().optional() }).parse(input);
const { household, user } = await getCurrentSession();
-21
View File
@@ -1,6 +1,5 @@
import { and, eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { households } from "@/modules/_core/schema";
import { lists } from "../schema";
const DEFAULT_LISTS = [
@@ -14,26 +13,6 @@ export async function ensureDefaultListsForHousehold(householdId: string) {
}
}
export async function ensureDefaultLists() {
const householdRows = await db.select({ id: households.id }).from(households);
for (const household of householdRows) {
await ensureDefaultListsForHousehold(household.id);
}
}
export async function getOrCreateDefaultList({
householdId,
type,
}: {
householdId: string;
type: "shopping" | "task" | string;
}) {
const defaults = DEFAULT_LISTS.find((list) => list.type === type);
const name = defaults?.name ?? type;
return ensureDefaultList({ householdId, type, name });
}
async function ensureDefaultList({
householdId,
type,