Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4e0b1ac
A very productive week
fccview Mar 12, 2026
c8c95bb
update translations and add notifications
fccview Mar 12, 2026
54ff063
improve d&d
fccview Mar 12, 2026
e539fee
improve d&d
fccview Mar 12, 2026
b19edb2
fix tests and update typing with search and replace
fccview Mar 12, 2026
435ec31
continue styling tabs and making things look up to standard now that …
fccview Mar 13, 2026
edd6e67
feat: LDAP support step 1
h-2 Mar 27, 2026
7c3d40f
feat: LDAP support step 2
h-2 Mar 27, 2026
f0e8810
feat: LDAP support step 3
h-2 Mar 27, 2026
43fb07a
feat: LDAP support step 4
h-2 Mar 27, 2026
0276948
feat: LDAP support step 5
h-2 Mar 27, 2026
35e2456
doc: add howto/LDAP.md
h-2 Mar 27, 2026
28ff1f6
random fixes
h-2 Mar 27, 2026
89115c2
finally making real progress on this
fccview Apr 8, 2026
2aef07f
fix tests
fccview Apr 8, 2026
81e039d
fix: replace hardcoded strings with translation keys and add ICU plurals
w00fmeow Apr 9, 2026
e709476
fix: use min-width for dropdown menu to prevent text wrapping
w00fmeow Apr 9, 2026
9161685
Merge pull request #482 from w00fmeow/translation/fix-hardcoded-strings
fccview Apr 9, 2026
6a47c6c
Merge remote-tracking branch 'public' into feature/kanban-refactor
fccview Apr 9, 2026
e43c73a
fix conflicts and merge kanban improvements
fccview Apr 9, 2026
959965e
Merge remote-tracking branch 'public/develop' into ldap_support2
fccview Apr 9, 2026
16af868
merge develop back in the branch, update env var name and remove redu…
fccview Apr 9, 2026
7ff3536
merge duplicate functions into one, create utils and helpers, clean u…
fccview Apr 9, 2026
1766a61
Merge pull request #473 from h-2/ldap_support2
fccview Apr 9, 2026
a6203bd
fix bugs raised on discord and fix the checklist DELETE route
fccview Apr 10, 2026
d1c65f5
fix shared items read functionality
fccview Apr 13, 2026
ecdc395
fix icon on shared items
fccview Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/(loggedInRoutes)/kanban/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TasksClient } from "@/app/_components/FeatureComponents/Checklists/TasksClient";
import { getCategories } from "@/app/_server/actions/category";
import { getCurrentUser } from "@/app/_server/actions/users";
import { Modes } from "@/app/_types/enums";
import { sanitizeUserForClient } from "@/app/_utils/user-sanitize-utils";

export default async function KanbanLayout({
children,
}: {
children: React.ReactNode;
}) {
const [checklistCategories, userRecord] = await Promise.all([
getCategories(Modes.CHECKLISTS),
getCurrentUser(),
]);

const categories = checklistCategories.data || [];
const user = sanitizeUserForClient(userRecord);

return (
<TasksClient categories={categories} user={user}>
{children}
</TasksClient>
);
}
23 changes: 23 additions & 0 deletions app/(loggedInRoutes)/kanban/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getUserChecklists } from "@/app/_server/actions/checklist";
import { getCurrentUser } from "@/app/_server/actions/users";
import { KanbanPageClient } from "@/app/_components/FeatureComponents/Kanban/KanbanPageClient";
import { Checklist } from "@/app/_types";
import { sanitizeUserForClient } from "@/app/_utils/user-sanitize-utils";
import { isKanbanType } from "@/app/_types/enums";

export const dynamic = "force-dynamic";

export default async function KanbanPage() {
const [listsResult, userRecord] = await Promise.all([
getUserChecklists(),
getCurrentUser(),
]);

const lists = listsResult.success && listsResult.data ? listsResult.data : [];
const kanbanLists = lists.filter((list) =>
isKanbanType(list.type),
) as Checklist[];
const user = sanitizeUserForClient(userRecord);

return <KanbanPageClient initialLists={kanbanLists} user={user} />;
}
12 changes: 5 additions & 7 deletions app/(loggedInRoutes)/tasks/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getCurrentUser } from "@/app/_server/actions/users";
import { TasksPageClient } from "@/app/_components/FeatureComponents/Checklists/TasksPageClient";
import { Checklist } from "@/app/_types";
import { sanitizeUserForClient } from "@/app/_utils/user-sanitize-utils";
import { isKanbanType } from "@/app/_types/enums";

export const dynamic = "force-dynamic";

Expand All @@ -13,13 +14,10 @@ export default async function TasksPage() {
]);

const lists = listsResult.success && listsResult.data ? listsResult.data : [];
const taskLists = lists.filter((list) => list.type === "task") as Checklist[];
const taskLists = lists.filter((list) =>
isKanbanType(list.type),
) as Checklist[];
const user = sanitizeUserForClient(userRecord);

return (
<TasksPageClient
initialLists={taskLists}
user={user}
/>
);
return <TasksPageClient initialLists={taskLists} user={user} />;
}
13 changes: 8 additions & 5 deletions app/(loggedOutRoutes)/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,31 @@ import LoginForm from "@/app/_components/GlobalComponents/Auth/LoginForm";
import { AuthShell } from "@/app/_components/GlobalComponents/Auth/AuthShell";
import { getTranslations } from "next-intl/server";
import { SsoOnlyLogin } from "@/app/_components/GlobalComponents/Auth/SsoOnlyLogin";
import { isEnvEnabled } from "@/app/_utils/env-utils";
import { isEnvEnabled, getAuthMode } from "@/app/_utils/env-utils";

export const dynamic = "force-dynamic";

export default async function LoginPage() {
const t = await getTranslations("auth");
const ssoEnabled = process.env.SSO_MODE === "oidc";
const authMode = getAuthMode();
const ssoIsOidc = authMode === "oidc";
const allowLocal = isEnvEnabled(process.env.SSO_FALLBACK_LOCAL);

const hasExistingUsers = await hasUsers();
if ((!hasExistingUsers && !ssoEnabled) || (!hasExistingUsers && allowLocal)) {
if (!hasExistingUsers && !authMode) {
redirect("/auth/setup");
}

if (ssoEnabled && !allowLocal) {
if (ssoIsOidc && !allowLocal) {
return <SsoOnlyLogin />;
}

const showRegisterLink = allowLocal && !hasExistingUsers;

return (
<AuthShell>
<div className="space-y-6">
<LoginForm ssoEnabled={ssoEnabled} />
<LoginForm ssoEnabled={ssoIsOidc} showRegisterLink={showRegisterLink} />
</div>
</AuthShell>
);
Expand Down
5 changes: 2 additions & 3 deletions app/(loggedOutRoutes)/auth/setup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { redirect } from "next/navigation";
import { hasUsers } from "@/app/_server/actions/users";
import SetupForm from "@/app/(loggedOutRoutes)/auth/setup/setup-form";
import { AuthShell } from "@/app/_components/GlobalComponents/Auth/AuthShell";
import { isEnvEnabled } from "@/app/_utils/env-utils";
import { isEnvEnabled, getAuthMode } from "@/app/_utils/env-utils";

export const dynamic = "force-dynamic";

export default async function SetupPage() {
const ssoEnabled = process.env.SSO_MODE === "oidc";
const allowLocal = isEnvEnabled(process.env.SSO_FALLBACK_LOCAL);

if (ssoEnabled && !allowLocal) {
if (getAuthMode() && !allowLocal) {
redirect("/auth/login");
}

Expand Down
8 changes: 4 additions & 4 deletions app/_components/FeatureComponents/Checklists/Checklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState, useEffect } from "react";
import { Checklist } from "@/app/_types";
import { KanbanBoard } from "@/app/_components/FeatureComponents/Checklists/Parts/Kanban/KanbanBoard";
import { Kanban } from "@/app/_components/FeatureComponents/Kanban/Kanban";
import { useChecklist } from "@/app/_hooks/useChecklist";
import { ChecklistHeader } from "@/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader";
import { ChecklistHeading } from "@/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeading";
Expand Down Expand Up @@ -116,7 +116,7 @@ export const ChecklistView = ({
),
},
]}
onRemove={() => {}}
onRemove={() => { }}
></ToastContainer>
)}

Expand All @@ -136,7 +136,7 @@ export const ChecklistView = ({
),
},
]}
onRemove={() => {}}
onRemove={() => { }}
></ToastContainer>
)}

Expand Down Expand Up @@ -165,7 +165,7 @@ export const ChecklistView = ({
/>
) : (
<div className="flex-1 overflow-hidden p-4">
<KanbanBoard checklist={localList} onUpdate={onUpdate} />
<Kanban checklist={localList} onUpdate={onUpdate} />
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useSettings } from "@/app/_utils/settings-store";
import { ChecklistListItem } from "@/app/_components/GlobalComponents/Cards/ChecklistListItem";
import { ChecklistGridItem } from "@/app/_components/GlobalComponents/Cards/ChecklistGridItem";
import { useChecklistsFilter } from "@/app/_components/FeatureComponents/Checklists/ChecklistsClient";
import { isKanbanType } from "@/app/_types/enums";

interface ChecklistsPageClientProps {
initialLists: Checklist[];
Expand All @@ -34,7 +35,7 @@ export const ChecklistsPageClient = ({
initialLists,
user,
}: ChecklistsPageClientProps) => {
const t = useTranslations('checklists');
const t = useTranslations("checklists");
const router = useRouter();
const { openCreateChecklistModal } = useShortcut();
const { isInitialized } = useAppMode();
Expand Down Expand Up @@ -63,16 +64,16 @@ export const ChecklistsPageClient = ({
filtered = filtered.filter(
(list) =>
list.items.length > 0 &&
list.items.every((item) => isItemCompleted(item, list.type))
list.items.every((item) => isItemCompleted(item, list.type)),
);
} else if (checklistFilter === "incomplete") {
filtered = filtered.filter(
(list) =>
list.items.length === 0 ||
!list.items.every((item) => isItemCompleted(item, list.type))
!list.items.every((item) => isItemCompleted(item, list.type)),
);
} else if (checklistFilter === "task") {
filtered = filtered.filter((list) => list.type === "task");
filtered = filtered.filter((list) => isKanbanType(list.type));
} else if (checklistFilter === "simple") {
filtered = filtered.filter((list) => list.type === "simple");
}
Expand All @@ -84,7 +85,7 @@ export const ChecklistsPageClient = ({
return selectedCategories.some(
(selected) =>
listCategory === selected ||
listCategory.startsWith(selected + "/")
listCategory.startsWith(selected + "/"),
);
}
return selectedCategories.includes(listCategory);
Expand All @@ -93,7 +94,7 @@ export const ChecklistsPageClient = ({

return filtered.sort(
(a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
);
}, [
initialLists,
Expand All @@ -103,7 +104,14 @@ export const ChecklistsPageClient = ({
user?.pinnedLists,
]);

const { currentPage, totalPages, paginatedItems, goToPage, totalItems, handleItemsPerPageChange } = usePagination({
const {
currentPage,
totalPages,
paginatedItems,
goToPage,
totalItems,
handleItemsPerPageChange,
} = usePagination({
items: filteredLists,
itemsPerPage,
onItemsPerPageChange: setItemsPerPage,
Expand All @@ -117,7 +125,14 @@ export const ChecklistsPageClient = ({
onPageChange: goToPage,
onItemsPerPageChange: handleItemsPerPageChange,
});
}, [currentPage, totalPages, totalItems, goToPage, handleItemsPerPageChange, setPaginationInfo]);
}, [
currentPage,
totalPages,
totalItems,
goToPage,
handleItemsPerPageChange,
setPaginationInfo,
]);

const handleTogglePin = async (list: Checklist) => {
if (!user || isTogglingPin === list.id) return;
Expand All @@ -127,7 +142,7 @@ export const ChecklistsPageClient = ({
const result = await togglePin(
list.id,
list.category || "Uncategorized",
ItemTypes.CHECKLIST
ItemTypes.CHECKLIST,
);
if (result.success) {
router.refresh();
Expand All @@ -149,7 +164,7 @@ export const ChecklistsPageClient = ({
}, 0);
const totalItems = initialLists.reduce(
(acc, list) => acc + list.items.length,
0
0,
);
const completionRate =
totalItems > 0 ? Math.round((completedItems / totalItems) * 100) : 0;
Expand All @@ -167,9 +182,9 @@ export const ChecklistsPageClient = ({
icon={
<CheckmarkSquare04Icon className="h-10 w-10 text-muted-foreground" />
}
title={t('noChecklistsYet')}
description={t('createFirstChecklist')}
buttonText={t('newChecklist')}
title={t("noChecklistsYet")}
description={t("createFirstChecklist")}
buttonText={t("newChecklist")}
onButtonClick={() => openCreateChecklistModal()}
/>
);
Expand All @@ -187,7 +202,9 @@ export const ChecklistsPageClient = ({
<div className="text-xl sm:text-2xl font-bold text-foreground">
{stats.totalLists}
</div>
<div className="text-md lg:text-xs text-muted-foreground">{t('lists')}</div>
<div className="text-md lg:text-xs text-muted-foreground">
{t("lists")}
</div>
</div>
</div>

Expand All @@ -199,7 +216,9 @@ export const ChecklistsPageClient = ({
<div className="text-xl sm:text-2xl font-bold text-foreground">
{stats.completedItems}
</div>
<div className="text-md lg:text-xs text-muted-foreground">{t('completed')}</div>
<div className="text-md lg:text-xs text-muted-foreground">
{t("completed")}
</div>
</div>
</div>

Expand All @@ -211,7 +230,9 @@ export const ChecklistsPageClient = ({
<div className="text-xl sm:text-2xl font-bold text-foreground">
{stats.completionRate}%
</div>
<div className="text-md lg:text-xs text-muted-foreground">{t('progress')}</div>
<div className="text-md lg:text-xs text-muted-foreground">
{t("progress")}
</div>
</div>
</div>

Expand All @@ -223,7 +244,9 @@ export const ChecklistsPageClient = ({
<div className="text-xl sm:text-2xl font-bold text-foreground">
{stats.totalItems}
</div>
<div className="text-md lg:text-xs text-muted-foreground">{t('totalItems')}</div>
<div className="text-md lg:text-xs text-muted-foreground">
{t("totalItems")}
</div>
</div>
</div>
</div>
Expand All @@ -233,15 +256,15 @@ export const ChecklistsPageClient = ({
<div className="text-center py-12">
<Folder01Icon className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-medium text-foreground mb-2">
{t('noChecklistsFound')}
{t("noChecklistsFound")}
</h3>
<p className="text-muted-foreground">
{t('tryAdjustingFiltersChecklist')}
{t("tryAdjustingFiltersChecklist")}
</p>
</div>
) : (
<div className="mt-6">
{viewMode === 'card' && (
{viewMode === "card" && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{paginatedItems.map((list) => (
<ChecklistCard
Expand All @@ -252,15 +275,15 @@ export const ChecklistsPageClient = ({
router.push(`/checklist/${categoryPath}`);
}}
isPinned={user?.pinnedLists?.includes(
`${list.category || "Uncategorized"}/${list.id}`
`${list.category || "Uncategorized"}/${list.id}`,
)}
onTogglePin={() => handleTogglePin(list)}
/>
))}
</div>
)}

{viewMode === 'list' && (
{viewMode === "list" && (
<div className="space-y-3">
{paginatedItems.map((list) => (
<ChecklistListItem
Expand All @@ -271,15 +294,15 @@ export const ChecklistsPageClient = ({
router.push(`/checklist/${categoryPath}`);
}}
isPinned={user?.pinnedLists?.includes(
`${list.category || "Uncategorized"}/${list.id}`
`${list.category || "Uncategorized"}/${list.id}`,
)}
onTogglePin={() => handleTogglePin(list)}
/>
))}
</div>
)}

{viewMode === 'grid' && (
{viewMode === "grid" && (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
{paginatedItems.map((list) => (
<ChecklistGridItem
Expand All @@ -290,7 +313,7 @@ export const ChecklistsPageClient = ({
router.push(`/checklist/${categoryPath}`);
}}
isPinned={user?.pinnedLists?.includes(
`${list.category || "Uncategorized"}/${list.id}`
`${list.category || "Uncategorized"}/${list.id}`,
)}
onTogglePin={() => handleTogglePin(list)}
/>
Expand Down
Loading
Loading