From b7adc202bacdf0d8714a980081237de7b22923d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Mon, 24 Nov 2025 17:15:03 +0100
Subject: [PATCH 01/45] Add hasPermission function
---
fdm-core/src/authorization.ts | 157 ++++++++++++++++++++++++----------
fdm-core/src/index.ts | 1 +
2 files changed, 114 insertions(+), 44 deletions(-)
diff --git a/fdm-core/src/authorization.ts b/fdm-core/src/authorization.ts
index a6c4e5142..9965cf6dd 100644
--- a/fdm-core/src/authorization.ts
+++ b/fdm-core/src/authorization.ts
@@ -160,50 +160,19 @@ export async function checkPermission(
resource_id: string,
principal_id: PrincipalId,
origin: string,
-): Promise {
+) {
const start = performance.now()
-
- let isAllowed = false
- let granting_resource = ""
- let granting_resource_id = ""
try {
- const roles = getRolesForAction(action, resource)
- const chain = await getResourceChain(fdm, resource, resource_id)
-
- // Convert principal_id to array
- const principal_ids = Array.isArray(principal_id)
- ? principal_id
- : [principal_id]
+ const permission = await getPermission(
+ fdm,
+ resource,
+ action,
+ resource_id,
+ principal_id,
+ )
- await fdm.transaction(async (tx: FdmType) => {
- for (const bead of chain) {
- const check = await tx
- .select({
- resource_id: authZSchema.role.resource_id,
- })
- .from(authZSchema.role)
- .where(
- and(
- eq(authZSchema.role.resource, bead.resource),
- eq(authZSchema.role.resource_id, bead.resource_id),
- inArray(
- authZSchema.role.principal_id,
- principal_ids,
- ),
- inArray(authZSchema.role.role, roles),
- isNull(authZSchema.role.deleted),
- ),
- )
- .limit(1)
-
- if (check.length > 0) {
- isAllowed = true
- granting_resource = bead.resource
- granting_resource_id = bead.resource_id
- break
- }
- }
- })
+ const granting_resource = permission?.granting_resource ?? ""
+ const granting_resource_id = permission?.granting_resource_id ?? ""
// Store check in audit
await fdm.insert(authZSchema.audit).values({
@@ -215,15 +184,15 @@ export async function checkPermission(
granting_resource: granting_resource,
granting_resource_id: granting_resource_id,
action: action,
- allowed: isAllowed,
+ allowed: !!permission,
duration: Math.round(performance.now() - start),
})
- if (!isAllowed) {
+ if (!permission) {
throw new Error("Permission denied")
}
- return isAllowed
+ return !!permission
} catch (err) {
let message = "Exception for checkPermission"
if (err instanceof Error && err.message === "Permission denied") {
@@ -239,6 +208,106 @@ export async function checkPermission(
}
}
+/**
+ * Gets the granting resource type and ID if the principal has permission to perform the action in the given resource.
+ *
+ * If an action is actually going to be taken based on the result, you should use `checkPermission` instead.
+ *
+ * @param fdm The FDM instance providing the connection to the database. The instance can be created with {@link createFdmServer}.
+ * @param resource - The type of resource being accessed.
+ * @param action - The action the principal intends to perform.
+ * @param resource_id - The unique identifier of the specific resource.
+ * @param principal_id - The principal identifier(s); supports a single ID or an array.
+ * @returns Resolves to true if the principal is permitted to perform the action.
+ */
+export async function hasPermission(
+ fdm: FdmType,
+ resource: Resource,
+ action: Action,
+ resource_id: string,
+ principal_id: PrincipalId,
+) {
+ try {
+ return !!(await getPermission(
+ fdm,
+ resource,
+ action,
+ resource_id,
+ principal_id,
+ ))
+ } catch (err) {
+ const message = "Exception for hasPermission"
+ throw handleError(err, message, {
+ resource: resource,
+ action: action,
+ resource_id: resource_id,
+ principal_id: principal_id,
+ })
+ }
+}
+
+/**
+ * Gets the granting resource type and ID if the principal has permission to perform the action in the given resource.
+ *
+ * @param fdm The FDM instance providing the connection to the database. The instance can be created with {@link createFdmServer}.
+ * @param resource - The type of resource being accessed.
+ * @param action - The action the principal intends to perform.
+ * @param resource_id - The unique identifier of the specific resource.
+ * @param principal_id - The principal identifier(s); supports a single ID or an array.
+ * @returns `granting_resource` is the resource type, `granting_resource_id` is the id of the specific granting resource.
+ * `null` is returned if the principal does not have the permission.
+ */
+async function getPermission(
+ fdm: FdmType,
+ resource: Resource,
+ action: Action,
+ resource_id: string,
+ principal_id: PrincipalId,
+): Promise<{
+ granting_resource: string
+ granting_resource_id: string
+} | null> {
+ let isAllowed = false
+ let granting_resource = ""
+ let granting_resource_id = ""
+ const roles = getRolesForAction(action, resource)
+ const chain = await getResourceChain(fdm, resource, resource_id)
+
+ // Convert principal_id to array
+ const principal_ids = Array.isArray(principal_id)
+ ? principal_id
+ : [principal_id]
+
+ await fdm.transaction(async (tx: FdmType) => {
+ for (const bead of chain) {
+ const check = await tx
+ .select({
+ resource_id: authZSchema.role.resource_id,
+ })
+ .from(authZSchema.role)
+ .where(
+ and(
+ eq(authZSchema.role.resource, bead.resource),
+ eq(authZSchema.role.resource_id, bead.resource_id),
+ inArray(authZSchema.role.principal_id, principal_ids),
+ inArray(authZSchema.role.role, roles),
+ isNull(authZSchema.role.deleted),
+ ),
+ )
+ .limit(1)
+
+ if (check.length > 0) {
+ isAllowed = true
+ granting_resource = bead.resource
+ granting_resource_id = bead.resource_id
+ break
+ }
+ }
+ })
+
+ return isAllowed ? { granting_resource, granting_resource_id } : null
+}
+
/**
* Retrieves a list of roles a principal has for a specific resource.
*
diff --git a/fdm-core/src/index.ts b/fdm-core/src/index.ts
index c0793f36c..544487f37 100644
--- a/fdm-core/src/index.ts
+++ b/fdm-core/src/index.ts
@@ -22,6 +22,7 @@ export {
updateUserProfile,
} from "./authentication"
export type { FdmAuth } from "./authentication.d"
+export { checkPermission, hasPermission } from "./authorization"
export type { PrincipalId } from "./authorization.d"
export {
getCachedCalculation,
From f1d03f09ab273d324981d75c035b7c2b4276f444 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Mon, 24 Nov 2025 17:19:05 +0100
Subject: [PATCH 02/45] Add disabled buttons to field cultivations
---
.../blocks/cultivation/card-details.tsx | 51 ++++++-----
.../blocks/cultivation/card-harvests.tsx | 5 ++
.../blocks/cultivation/card-list.tsx | 3 +
.../blocks/cultivation/form-add.tsx | 7 +-
.../components/blocks/cultivation/types.tsx | 1 +
.../app/components/blocks/harvest/form.tsx | 87 +++++++++++--------
fdm-app/app/components/custom/date-picker.tsx | 3 +
...ivation.$b_lu.harvest.$b_id_harvesting.tsx | 13 ++-
...calendar.field.$b_id.cultivation.$b_lu.tsx | 20 +++++
...farm.$calendar.field.$b_id.cultivation.tsx | 11 +++
10 files changed, 139 insertions(+), 62 deletions(-)
diff --git a/fdm-app/app/components/blocks/cultivation/card-details.tsx b/fdm-app/app/components/blocks/cultivation/card-details.tsx
index 487182ec8..74a682c95 100644
--- a/fdm-app/app/components/blocks/cultivation/card-details.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-details.tsx
@@ -30,13 +30,16 @@ import {
export function CultivationDetailsCard({
cultivation,
b_lu_variety_options,
+ editable = true,
}: {
cultivation: Cultivation
b_lu_variety_options: { value: string; label: string }[]
+ editable?: boolean
}) {
const fetcher = useFetcher()
const form = useRemixForm({
resolver: zodResolver(CultivationDetailsFormSchema),
+ disabled: !editable,
mode: "onTouched",
defaultValues: {
b_lu_start: new Date(cultivation.b_lu_start),
@@ -72,7 +75,7 @@ export function CultivationDetailsCard({
{cultivation.b_lu_name}
- {!isCreateWizard ? (
+ {!isCreateWizard && editable ? (
@@ -132,6 +137,7 @@ export function CultivationDetailsCard({
field.onChange
}
disabled={
+ !editable ||
form.formState
.isSubmitting ||
fetcher.state ===
@@ -158,8 +164,9 @@ export function CultivationDetailsCard({
onValueChange={field.onChange}
value={field.value ?? undefined}
disabled={
+ !editable ||
b_lu_variety_options.length ===
- 0
+ 0
}
>
@@ -196,25 +203,27 @@ export function CultivationDetailsCard({
)}
/>
-
-
- {form.formState.isSubmitting ||
- fetcher.state === "submitting" ? (
-
- ) : (
- "Bijwerken"
- )}
-
-
+ {editable && (
+
+
+ {form.formState.isSubmitting ||
+ fetcher.state === "submitting" ? (
+
+ ) : (
+ "Bijwerken"
+ )}
+
+
+ )}
diff --git a/fdm-app/app/components/blocks/cultivation/card-harvests.tsx b/fdm-app/app/components/blocks/cultivation/card-harvests.tsx
index 17817f8c3..ab75f6150 100644
--- a/fdm-app/app/components/blocks/cultivation/card-harvests.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-harvests.tsx
@@ -10,10 +10,12 @@ export function CultivationHarvestsCard({
harvests,
b_lu_harvestable,
harvestParameters,
+ editable = true,
}: {
harvests: Harvest[]
b_lu_harvestable: HarvestableType
harvestParameters: HarvestParameters
+ editable?: boolean
}) {
let canAddHarvest = false
if (b_lu_harvestable === "once" && harvests.length === 0) {
@@ -22,6 +24,9 @@ export function CultivationHarvestsCard({
if (b_lu_harvestable === "multiple") {
canAddHarvest = true
}
+ if (typeof editable !== "undefined") {
+ canAddHarvest = editable
+ }
return (
diff --git a/fdm-app/app/components/blocks/cultivation/card-list.tsx b/fdm-app/app/components/blocks/cultivation/card-list.tsx
index b7471aced..d3349ba0e 100644
--- a/fdm-app/app/components/blocks/cultivation/card-list.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-list.tsx
@@ -14,10 +14,12 @@ export function CultivationListCard({
cultivationsCatalogueOptions,
cultivations,
harvests,
+ editable = true,
}: {
cultivationsCatalogueOptions: CultivationOption[]
cultivations: Cultivation[]
harvests: Harvest[]
+ editable?: boolean
}) {
return (
@@ -28,6 +30,7 @@ export function CultivationListCard({
{cultivations.length !== 0 ? (
) : null}
diff --git a/fdm-app/app/components/blocks/cultivation/form-add.tsx b/fdm-app/app/components/blocks/cultivation/form-add.tsx
index 435e28043..33ac01d67 100644
--- a/fdm-app/app/components/blocks/cultivation/form-add.tsx
+++ b/fdm-app/app/components/blocks/cultivation/form-add.tsx
@@ -17,13 +17,16 @@ import {
import { CultivationAddFormSchema } from "./schema"
import type { CultivationsFormProps } from "./types"
-export function CultivationAddFormDialog({ options }: CultivationsFormProps) {
+export function CultivationAddFormDialog({
+ options,
+ editable = true,
+}: CultivationsFormProps) {
const [isOpen, setIsOpen] = useState(false)
return (
- Gewas toevoegen
+ Gewas toevoegen
diff --git a/fdm-app/app/components/blocks/cultivation/types.tsx b/fdm-app/app/components/blocks/cultivation/types.tsx
index bf1804be8..2559c86b2 100644
--- a/fdm-app/app/components/blocks/cultivation/types.tsx
+++ b/fdm-app/app/components/blocks/cultivation/types.tsx
@@ -14,4 +14,5 @@ export interface CultivationOption {
export interface CultivationsFormProps {
options: CultivationOption[]
+ editable: boolean
}
diff --git a/fdm-app/app/components/blocks/harvest/form.tsx b/fdm-app/app/components/blocks/harvest/form.tsx
index a73bd2663..de046e96b 100644
--- a/fdm-app/app/components/blocks/harvest/form.tsx
+++ b/fdm-app/app/components/blocks/harvest/form.tsx
@@ -50,8 +50,9 @@ type HarvestFormDialogProps = {
b_lu_harvestable: "once" | "multiple" | "none"
b_lu_start: Date | undefined | null
b_lu_end: Date | undefined | null
- action: string
+ action?: string
handleConfirmation?: (data: z.infer) => Promise
+ editable?: boolean
}
function useHarvestRemixForm({
@@ -69,10 +70,12 @@ function useHarvestRemixForm({
b_lu_harvestable,
b_lu_start,
b_lu_end,
+ editable = true,
handleConfirmation,
}: HarvestFormDialogProps) {
const form = useRemixForm>({
mode: "onTouched",
+ disabled: !editable,
resolver: async (values, bypass, options) => {
// Do the validation using Zod
const validation = await zodResolver(FormSchema)(
@@ -485,7 +488,7 @@ function HarvestFormExplainer() {
)
}
export function HarvestFormDialog(props: HarvestFormDialogProps) {
- const { b_lu_harvest_date, action } = props
+ const { b_lu_harvest_date, action, editable = true } = props
const navigate = useNavigate()
const fetcher = useFetcher()
const form = useHarvestRemixForm(props)
@@ -524,26 +527,30 @@ export function HarvestFormDialog(props: HarvestFormDialogProps) {
-
- {fetcher.state === "submitting" ? (
-
-
-
- ) : null}
- Verwijderen
-
+ {editable && (
+
+ {fetcher.state === "submitting" ? (
+
+
+
+ ) : null}
+ Verwijderen
+
+ )}
-
- {form.formState.isSubmitting ? (
-
-
- Opslaan...
-
- ) : isHarvestUpdate ? (
- "Bijwerken"
- ) : (
- "Toevoegen"
- )}
-
+ {editable && (
+
+ {form.formState.isSubmitting ? (
+
+
+ Opslaan...
+
+ ) : isHarvestUpdate ? (
+ "Bijwerken"
+ ) : (
+ "Toevoegen"
+ )}
+
+ )}
diff --git a/fdm-app/app/components/custom/date-picker.tsx b/fdm-app/app/components/custom/date-picker.tsx
index 594772cb5..99cae7c60 100644
--- a/fdm-app/app/components/custom/date-picker.tsx
+++ b/fdm-app/app/components/custom/date-picker.tsx
@@ -74,6 +74,7 @@ interface DatePickerProps {
name: Path // Use Path for better type inference with react-hook-form
label: string
description: string
+ disabled?: boolean
}
export function DatePicker({
@@ -81,6 +82,7 @@ export function DatePicker({
name,
label,
description,
+ disabled = false,
}: DatePickerProps) {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState(
@@ -123,6 +125,7 @@ export function DatePicker({
value={value}
placeholder="Kies een datum"
className="bg-background pr-10"
+ disabled={disabled}
onChange={(e) => {
const newDate = parseDateString(
e.target.value,
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
index e130ba167..b046db558 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
@@ -2,6 +2,7 @@ import {
getCultivation,
getHarvest,
getParametersForHarvestCat,
+ hasPermission,
removeHarvest,
updateHarvest,
} from "@svenvw/fdm-core"
@@ -16,11 +17,11 @@ import { dataWithWarning, redirectWithSuccess } from "remix-toast"
import { HarvestFormDialog } from "~/components/blocks/harvest/form"
import { FormSchema } from "~/components/blocks/harvest/schema"
import { getSession } from "~/lib/auth.server"
+import { getCalendar } from "~/lib/calendar"
import { clientConfig } from "~/lib/config"
import { handleActionError, handleLoaderError } from "~/lib/error"
import { fdm } from "~/lib/fdm.server"
import { extractFormValuesFromRequest } from "~/lib/form"
-import { getCalendar } from "~/lib/calendar"
import { getHarvestParameterLabel } from "../components/blocks/harvest/parameters"
// Meta
@@ -87,6 +88,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
const session = await getSession(request)
const calendar = getCalendar(params)
+ const harvestingWritePermission = hasPermission(
+ fdm,
+ "harvesting",
+ "write",
+ b_id_harvesting,
+ session.principal_id,
+ )
+
// Get details of cultivation
const cultivation = await getCultivation(
fdm,
@@ -120,6 +129,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
b_id_farm: b_id_farm,
calendar: calendar,
harvestParameters: harvestParameters,
+ harvestingWritePermission: await harvestingWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -156,6 +166,7 @@ export default function FarmFieldsOverviewBlock() {
b_lu_harvestable={loaderData.cultivation.b_lu_harvestable}
b_lu_start={loaderData.cultivation.b_lu_start}
b_lu_end={loaderData.cultivation.b_lu_end}
+ editable={loaderData.harvestingWritePermission}
/>
)
}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
index 050cf90b1..1b33aaaf1 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
@@ -5,6 +5,7 @@ import {
getField,
getHarvests,
getParametersForHarvestCat,
+ hasPermission,
removeCultivation,
updateCultivation,
} from "@svenvw/fdm-core"
@@ -166,6 +167,22 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
cultivation.b_lu_harvestcat,
)
+ const fieldWritePermission = hasPermission(
+ fdm,
+ "field",
+ "write",
+ b_id,
+ session.principal_id,
+ )
+
+ const cultivationWritePermission = hasPermission(
+ fdm,
+ "cultivation",
+ "write",
+ b_lu,
+ session.principal_id,
+ )
+
// Return user information from loader
return {
field: field,
@@ -177,6 +194,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
b_lu_variety_options: b_lu_variety_options,
b_id_farm: b_id_farm,
calendar: calendar,
+ cultivationWritePermission: await cultivationWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -198,11 +216,13 @@ export default function FarmFieldsOverviewBlock() {
harvests={loaderData.harvests}
b_lu_harvestable={loaderData.b_lu_harvestable}
b_lu_variety_options={loaderData.b_lu_variety_options}
+ editable={loaderData.cultivationWritePermission}
/>
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
index 2dc597101..5d1b1a442 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
@@ -4,6 +4,7 @@ import {
getCultivationsFromCatalogue,
getField,
getHarvests,
+ hasPermission,
removeCultivation,
} from "@svenvw/fdm-core"
import {
@@ -77,6 +78,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
// Get timeframe from calendar store
const timeframe = getTimeframe(params)
+ const fieldWritePermission = await hasPermission(
+ fdm,
+ "field",
+ "write",
+ b_id,
+ session.principal_id,
+ )
+
// Get details of field
const field = await getField(fdm, session.principal_id, b_id)
if (!field) {
@@ -130,6 +139,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
cultivationsCatalogueOptions: cultivationsCatalogueOptions,
cultivations: cultivations,
harvests: harvests,
+ fieldWritePermission: fieldWritePermission,
}
} catch (error) {
return handleLoaderError(error)
@@ -155,6 +165,7 @@ export default function FarmFieldsOverviewBlock() {
}
cultivations={loaderData.cultivations}
harvests={loaderData.harvests}
+ editable={loaderData.fieldWritePermission}
/>
From 52b2059e537447b44066846e8644e28619176b10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 09:30:49 +0100
Subject: [PATCH 03/45] Add disabled buttons to field fertilizer applications
---
.../blocks/fertilizer-applications/card.tsx | 9 +-
.../blocks/fertilizer-applications/list.tsx | 89 +++++++++++--------
...calendar.field.$b_id.fertilizer._index.tsx | 38 ++++++++
3 files changed, 96 insertions(+), 40 deletions(-)
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
index f086e2feb..556982aad 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
@@ -24,6 +24,8 @@ export function FertilizerApplicationCard({
applicationMethodOptions,
fertilizers,
fertilizerOptions,
+ canCreateFertilizerApplication = true,
+ canModifyFertilizerApplication = {},
}: {
fertilizerApplications: FertilizerApplication[]
applicationMethodOptions: {
@@ -34,6 +36,8 @@ export function FertilizerApplicationCard({
fertilizerOptions: FertilizerOption[]
dose: Dose
className?: string
+ canCreateFertilizerApplication?: boolean
+ canModifyFertilizerApplication?: Record
}) {
const fetcher = useFetcher()
const location = useLocation()
@@ -143,7 +147,7 @@ export function FertilizerApplicationCard({
onOpenChange={handleDialogOpenChange}
>
-
+
Toevoegen
@@ -181,6 +185,9 @@ export function FertilizerApplicationCard({
fertilizers={fertilizers}
handleDelete={handleDelete}
handleEdit={handleEdit}
+ canModifyFertilizerApplication={
+ canModifyFertilizerApplication
+ }
/>
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/list.tsx b/fdm-app/app/components/blocks/fertilizer-applications/list.tsx
index 8c5d54bda..ac3f0e027 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/list.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/list.tsx
@@ -32,6 +32,7 @@ export function FertilizerApplicationsList({
fertilizerApplications,
applicationMethodOptions,
fertilizers,
+ canModifyFertilizerApplication = {},
handleDelete,
handleEdit,
}: {
@@ -41,6 +42,7 @@ export function FertilizerApplicationsList({
label: string
}[]
fertilizers: Fertilizer[]
+ canModifyFertilizerApplication?: Record
handleDelete: (p_app_id: string | string[]) => void
handleEdit: (fertilizerApplication: FertilizerApplication) => () => void
}) {
@@ -57,6 +59,10 @@ export function FertilizerApplicationsList({
if (!fertilizer) {
return null
}
+ const editable =
+ canModifyFertilizerApplication[
+ application.p_app_id
+ ] ?? true
return (
@@ -82,8 +88,9 @@ export function FertilizerApplicationsList({
variant="link"
className="p-0 mt-0"
disabled={
+ !editable ||
fetcher.state ===
- "submitting"
+ "submitting"
}
onClick={handleEdit(
application,
@@ -117,45 +124,49 @@ export function FertilizerApplicationsList({
-
-
-
-
- {
- if (
- application.p_app_ids
- ) {
- handleDelete(
- application.p_app_ids,
- )
- } else {
- handleDelete([
- application.p_app_id,
- ])
+ {editable && (
+
+
+
+
+
- {fetcher.state ===
- "submitting" ? (
-
- ) : (
-
- )}
-
-
-
- Verwijder
-
-
-
-
+ onClick={() => {
+ if (
+ application.p_app_ids
+ ) {
+ handleDelete(
+ application.p_app_ids,
+ )
+ } else {
+ handleDelete(
+ [
+ application.p_app_id,
+ ],
+ )
+ }
+ }}
+ >
+ {fetcher.state ===
+ "submitting" ? (
+
+ ) : (
+
+ )}
+
+
+
+ Verwijder
+
+
+
+
+ )}
)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
index 1bdc0ea9d..7776852e6 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
@@ -5,6 +5,7 @@ import {
getFertilizerParametersDescription,
getFertilizers,
getField,
+ hasPermission,
removeFertilizerApplication,
updateFertilizerApplication,
} from "@svenvw/fdm-core"
@@ -165,6 +166,34 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
calendar: getCalendar(params),
}
+ const fieldWritePermission = hasPermission(
+ fdm,
+ "field",
+ "write",
+ b_id,
+ session.principal_id,
+ )
+
+ const fertilizerApplicationWritePermissionsEntries = await Promise.all(
+ fertilizerApplications.map(
+ async (app) =>
+ [
+ app.p_app_id,
+ await hasPermission(
+ fdm,
+ "fertilizer_application",
+ "write",
+ app.p_app_id,
+ session.principal_id,
+ ),
+ ] as [string, boolean],
+ ),
+ )
+
+ const fertilizerApplicationWritePermissions = Object.fromEntries(
+ fertilizerApplicationWritePermissionsEntries,
+ )
+
// Return user information from loader, including the promises
return {
field: field,
@@ -175,6 +204,9 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
applicationMethodOptions: applicationMethods.options,
fertilizerApplicationMetricsData: fertilizerApplicationMetricsData,
calendar: getCalendar(params),
+ fieldWritePermission: await fieldWritePermission,
+ fertilizerApplicationWritePermissions:
+ fertilizerApplicationWritePermissions,
}
} catch (error) {
throw handleLoaderError(error)
@@ -209,6 +241,12 @@ export default function FarmFieldsOverviewBlock() {
fertilizers={loaderData.fertilizers}
fertilizerOptions={loaderData.fertilizerOptions}
dose={loaderData.dose}
+ canCreateFertilizerApplication={
+ loaderData.fieldWritePermission
+ }
+ canModifyFertilizerApplication={
+ loaderData.fertilizerApplicationWritePermissions
+ }
/>
From f66c71516ae62044fff0d793c681b007ff70c6f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 10:03:54 +0100
Subject: [PATCH 04/45] Add disabled buttons to field soil analyses
---
fdm-app/app/components/blocks/soil/list.tsx | 76 ++++++++++---------
...farm.$calendar.field.$b_id.soil._index.tsx | 56 +++++++++++---
2 files changed, 86 insertions(+), 46 deletions(-)
diff --git a/fdm-app/app/components/blocks/soil/list.tsx b/fdm-app/app/components/blocks/soil/list.tsx
index 4a288b476..733d55922 100644
--- a/fdm-app/app/components/blocks/soil/list.tsx
+++ b/fdm-app/app/components/blocks/soil/list.tsx
@@ -9,12 +9,14 @@ import type { SoilAnalysis } from "./types"
export function SoilAnalysesList({
soilAnalyses,
fetcher,
+ canModifySoilAnalysis = {},
}: {
soilAnalyses: SoilAnalysis[]
fetcher: {
state: string
submit: (data: { a_id: string }, options: { method: string }) => void
}
+ canModifySoilAnalysis?: Record
}) {
const handleDelete = (a_id: string) => {
if (fetcher.state === "submitting") return
@@ -47,49 +49,53 @@ export function SoilAnalysesList({
{""}
-
-
-
+ {(canModifySoilAnalysis[analysis.a_id] ?? true) ? (
+
+
+
+
+ Bewerk
+
+
{
+ handleDelete(analysis.a_id)
+ }}
>
- Bewerk
+ {fetcher.state === "submitting" ? (
+
+
+ Verwijderen...
+
+ ) : (
+ "Verwijder"
+ )}
-
-
{
- handleDelete(analysis.a_id)
- }}
- >
- {fetcher.state === "submitting" ? (
-
-
- Verwijderen...
-
- ) : (
- "Verwijder"
- )}
-
+
-
+ ) : null}
))}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
index dc613d912..59b4281be 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
@@ -3,6 +3,7 @@ import {
getField,
getSoilAnalyses,
getSoilParametersDescription,
+ hasPermission,
removeSoilAnalysis,
} from "@svenvw/fdm-core"
import { Plus } from "lucide-react"
@@ -95,12 +96,38 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
// Get soil parameter descriptions
const soilParameterDescription = getSoilParametersDescription()
+ const fieldWritePermission = hasPermission(
+ fdm,
+ "field",
+ "write",
+ b_id,
+ session.principal_id,
+ )
+
+ const soilAnalysisWritePermissionsEntries = await Promise.all(
+ soilAnalyses.map(async (analysis) => [
+ analysis.a_id,
+ await hasPermission(
+ fdm,
+ "soil_analysis",
+ "write",
+ analysis.a_id,
+ session.principal_id,
+ ),
+ ]),
+ )
+ const soilAnalysisWritePermissions = Object.fromEntries(
+ soilAnalysisWritePermissionsEntries,
+ )
+
// Return user information from loader
return {
field: field,
+ fieldWritePermission: await fieldWritePermission,
currentSoilData: currentSoilData,
soilParameterDescription: soilParameterDescription,
soilAnalyses: soilAnalyses,
+ soilAnalysisWritePermissions: soilAnalysisWritePermissions,
}
} catch (error) {
throw handleLoaderError(error)
@@ -133,12 +160,14 @@ export default function FarmFieldSoilOverviewBlock() {
Parameters
Analyses
-
-
-
- Bodemanalyse toevoegen
-
-
+ {loaderData.fieldWritePermission && (
+
+
+
+ Bodemanalyse toevoegen
+
+
+ )}
@@ -155,11 +184,13 @@ export default function FarmFieldSoilOverviewBlock() {
bodem bij te houden
-
-
- Bodemanalyse toevoegen
-
-
+ {loaderData.fieldWritePermission && (
+
+
+ Bodemanalyse toevoegen
+
+
+ )}
) : (
From e9baf560ed03ae9419437864b8b31c7df40d3ee4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 10:38:03 +0100
Subject: [PATCH 05/45] Remove remove field tab if the user has no permission
---
.../farm.$b_id_farm.$calendar.field.$b_id.tsx | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
index 9636fa63d..e92eaae45 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
@@ -1,4 +1,4 @@
-import { getFarms, getField, getFields } from "@svenvw/fdm-core"
+import { getFarms, getField, getFields, hasPermission } from "@svenvw/fdm-core"
import {
data,
type LoaderFunctionArgs,
@@ -150,11 +150,22 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/atlas`,
title: "Kaart",
},
- {
+ ]
+
+ const fieldWritePermission = await hasPermission(
+ fdm,
+ "field",
+ "write",
+ b_id,
+ session.principal_id,
+ )
+
+ if (fieldWritePermission) {
+ sidebarPageItems.push({
to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/delete`,
title: "Verwijderen",
- },
- ]
+ })
+ }
// Return user information from loader
return {
From 4474c0884acc7f3021a8d03f3fd30d5ba7c55f13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 11:32:53 +0100
Subject: [PATCH 06/45] Add disabled buttons to the field table
---
.../app/components/blocks/fields/columns.tsx | 15 ++--
.../app/components/blocks/fields/table.tsx | 89 ++++++++++---------
...farm.$b_id_farm.$calendar.field._index.tsx | 19 ++++
3 files changed, 76 insertions(+), 47 deletions(-)
diff --git a/fdm-app/app/components/blocks/fields/columns.tsx b/fdm-app/app/components/blocks/fields/columns.tsx
index cfc39f4ce..8ee09b3a1 100644
--- a/fdm-app/app/components/blocks/fields/columns.tsx
+++ b/fdm-app/app/components/blocks/fields/columns.tsx
@@ -28,7 +28,7 @@ export type FieldExtended = {
b_lu_name: string
b_lu_croprotation: string
b_lu_start: Date
- }[]
+ }[]
fertilizers: {
p_name_nl: string
p_id: string
@@ -37,6 +37,7 @@ export type FieldExtended = {
a_som_loi: number
b_soiltype_agr: string
b_area: number
+ has_write_permission: boolean
}
export const columns: ColumnDef[] = [
@@ -265,11 +266,13 @@ export const columns: ColumnDef[] = [
Kaart
-
-
- Verwijderen
-
-
+ {field.has_write_permission && (
+
+
+ Verwijderen
+
+
+ )}
)
diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx
index c8fe909a2..53e1cc0a4 100644
--- a/fdm-app/app/components/blocks/fields/table.tsx
+++ b/fdm-app/app/components/blocks/fields/table.tsx
@@ -46,11 +46,13 @@ import type { FieldExtended } from "./columns"
interface DataTableProps {
columns: ColumnDef[]
data: TData[]
+ canAddItem: boolean
}
export function DataTable({
columns,
data,
+ canAddItem,
}: DataTableProps) {
const [sorting, setSorting] = useState([])
const [columnFilters, setColumnFilters] = useState([])
@@ -212,51 +214,56 @@ export function DataTable({
-
-
-
-
- {isFertilizerButtonDisabled ? (
-
-
- Bemesting
-
- ) : (
-
+ {canAddItem && (
+ <>
+
+
+
+
+ {isFertilizerButtonDisabled ? (
+
+
+ Bemesting
+
+ ) : (
+
+
+
+ Bemesting
+
+
+ )}
+
+
+
+ {fertilizerTooltipContent}
+
+
+
+
+
+
+
+
- Bemesting
+ Nieuw perceel
- )}
-
-
-
- {fertilizerTooltipContent}
-
-
-
-
-
-
-
-
-
- Nieuw perceel
-
-
-
-
- Voeg een nieuw perceel toe
-
-
-
+
+
+ Voeg een nieuw perceel toe
+
+
+
+ >
+ )}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
index ff9bbf882..209519c41 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
@@ -5,6 +5,7 @@ import {
getFertilizerApplications,
getFertilizers,
getFields,
+ hasPermission,
} from "@svenvw/fdm-core"
import {
data,
@@ -154,6 +155,13 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
(x) => x.parameter === "b_soiltype_agr",
)?.value ?? null
+ const has_write_permission = await hasPermission(
+ fdm,
+ "field",
+ "write",
+ field.b_id,
+ session.principal_id,
+ )
return {
b_id: field.b_id,
b_name: field.b_name,
@@ -163,10 +171,19 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
b_soiltype_agr: b_soiltype_agr,
b_area: Math.round(field.b_area * 10) / 10,
b_isproductive: field.b_isproductive ?? true,
+ has_write_permission: has_write_permission,
}
}),
)
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ )
+
// Return user information from loader
return {
b_id_farm: b_id_farm,
@@ -174,6 +191,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
fieldOptions: fieldOptions,
fieldsExtended: fieldsExtended,
userName: session.userName,
+ farmWritePermission: farmWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -262,6 +280,7 @@ export default function FarmFieldIndex() {
From 0cf89b6c8d8e5f4b5767c55060a371a1e4e1670f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 11:37:20 +0100
Subject: [PATCH 07/45] Add disabled buttons to the farm overview
---
fdm-app/app/routes/farm.$b_id_farm._index.tsx | 55 ++++++++++++-------
1 file changed, 34 insertions(+), 21 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm._index.tsx b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
index 77b5858ec..3517bcc66 100644
--- a/fdm-app/app/routes/farm.$b_id_farm._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
@@ -1,5 +1,5 @@
import { cowHead } from "@lucide/lab"
-import { getFarm, getFarms, getFields } from "@svenvw/fdm-core"
+import { getFarm, getFarms, getFields, hasPermission } from "@svenvw/fdm-core"
import {
ArrowRightLeft,
BookOpenText,
@@ -103,6 +103,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
})
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ )
+
// Return the farm ID and session info
return {
b_id_farm: b_id_farm,
@@ -111,6 +119,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
farmArea: Math.round(farmArea),
farmOptions: farmOptions,
roles: farm.roles,
+ farmWritePermission: farmWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -449,26 +458,30 @@ export default function FarmDashboardIndex() {
Beweiding
-
-
-
- Toegang
-
-
-
-
-
- Verwijderen
-
-
+ {loaderData.farmWritePermission && (
+
+
+
+ Toegang
+
+
+ )}
+ {loaderData.farmWritePermission && (
+
+
+
+ Verwijderen
+
+
+ )}
From ff417f31ace88320a7a44062ee5662aad5c1849a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 12:00:00 +0100
Subject: [PATCH 08/45] Add disabled buttons to the cultivation overview
---
.../app/components/blocks/rotation/table.tsx | 146 +++++++++---------
...m.$b_id_farm.$calendar.rotation._index.tsx | 15 +-
2 files changed, 90 insertions(+), 71 deletions(-)
diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx
index 83cfbdeb9..9c9da23a9 100644
--- a/fdm-app/app/components/blocks/rotation/table.tsx
+++ b/fdm-app/app/components/blocks/rotation/table.tsx
@@ -51,11 +51,13 @@ import { useFieldFilterStore } from "@/app/store/field-filter"
interface DataTableProps {
columns: ColumnDef[]
data: TData[]
+ canAddItem: boolean
}
export function DataTable({
columns,
data,
+ canAddItem,
}: DataTableProps) {
const [sorting, setSorting] = useState([])
const [columnFilters, setColumnFilters] = useState([])
@@ -276,75 +278,81 @@ export function DataTable({
-
-
-
-
- {isFertilizerButtonDisabled ? (
-
-
- Bemesting
-
- ) : (
-
-
-
- Bemesting
-
-
- )}
-
-
-
- {fertilizerTooltipContent}
-
-
-
-
-
-
-
- {isHarvestButtonDisabled ? (
-
-
- Oogst toevoegen
-
- ) : harvestErrorMessage ? (
-
- notify.error(
- harvestErrorMessage,
- )
- }
- >
-
- Oogst toevoegen
-
- ) : (
-
-
-
- Oogst toevoegen
-
-
- )}
-
-
-
- {harvestTooltipContent}
-
-
-
+ {canAddItem && (
+ <>
+
+
+
+
+ {isFertilizerButtonDisabled ? (
+
+
+ Bemesting
+
+ ) : (
+
+
+
+ Bemesting
+
+
+ )}
+
+
+
+ {fertilizerTooltipContent}
+
+
+
+
+
+
+
+ {isHarvestButtonDisabled ? (
+
+
+ Oogst toevoegen
+
+ ) : harvestErrorMessage ? (
+
+ notify.error(
+ harvestErrorMessage,
+ )
+ }
+ >
+
+ Oogst toevoegen
+
+ ) : (
+
+
+
+ Oogst toevoegen
+
+
+ )}
+
+
+
+ {harvestTooltipContent}
+
+
+
+ >
+ )}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
index 92d4594f9..abe1fdca5 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
@@ -8,6 +8,7 @@ import {
getFertilizers,
getFields,
getHarvests,
+ hasPermission,
} from "@svenvw/fdm-core"
import {
type LoaderFunctionArgs,
@@ -18,13 +19,13 @@ import {
} from "react-router"
import { FarmContent } from "~/components/blocks/farm/farm-content"
import { FarmTitle } from "~/components/blocks/farm/farm-title"
+import { Header } from "~/components/blocks/header/base"
+import { HeaderFarm } from "~/components/blocks/header/farm"
import {
columns,
type RotationExtended,
} from "~/components/blocks/rotation/columns"
import { DataTable } from "~/components/blocks/rotation/table"
-import { Header } from "~/components/blocks/header/base"
-import { HeaderFarm } from "~/components/blocks/header/farm"
import { BreadcrumbItem, BreadcrumbSeparator } from "~/components/ui/breadcrumb"
import { Button } from "~/components/ui/button"
import { SidebarInset } from "~/components/ui/sidebar"
@@ -303,6 +304,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
fieldsExtended,
cultivationCatalogue,
)
+
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ )
// Return user information from loader
return {
@@ -311,6 +320,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
fieldOptions: fieldOptions,
rotationExtended: rotationExtended, // Return filtered data
userName: session.userName,
+ farmWritePermission: farmWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -389,6 +399,7 @@ export default function FarmRotationIndex() {
From 883680379c1fbf877c643064e1997086338cbeae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 12:08:01 +0100
Subject: [PATCH 09/45] Handle farm share and delete permissions properly
---
fdm-app/app/routes/farm.$b_id_farm._index.tsx | 29 +++++++++++++++----
1 file changed, 23 insertions(+), 6 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm._index.tsx b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
index 3517bcc66..98eb8109f 100644
--- a/fdm-app/app/routes/farm.$b_id_farm._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
@@ -1,5 +1,11 @@
import { cowHead } from "@lucide/lab"
-import { getFarm, getFarms, getFields, hasPermission } from "@svenvw/fdm-core"
+import {
+ getFarm,
+ getFarms,
+ getFields,
+ hasPermission,
+ isAllowedToDeleteFarm,
+} from "@svenvw/fdm-core"
import {
ArrowRightLeft,
BookOpenText,
@@ -103,13 +109,23 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
})
- const farmWritePermission = await hasPermission(
+ const farmSharePermission = hasPermission(
fdm,
"farm",
- "write",
+ "share",
b_id_farm,
session.principal_id,
)
+ // Check if user is allowed to delete farm
+ let canDeleteFarm = await isAllowedToDeleteFarm(
+ fdm,
+ session.principal_id,
+ b_id_farm,
+ )
+ // Add temporary workaround (until implemented in fdm-core) so that advisors are not able to delete a farm
+ if (canDeleteFarm && farm.roles.includes("advisor")) {
+ canDeleteFarm = false
+ }
// Return the farm ID and session info
return {
@@ -119,7 +135,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
farmArea: Math.round(farmArea),
farmOptions: farmOptions,
roles: farm.roles,
- farmWritePermission: farmWritePermission,
+ canDeleteFarm: canDeleteFarm,
+ farmSharePermission: await farmSharePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -458,7 +475,7 @@ export default function FarmDashboardIndex() {
Beweiding
- {loaderData.farmWritePermission && (
+ {loaderData.farmSharePermission && (
)}
- {loaderData.farmWritePermission && (
+ {loaderData.canDeleteFarm && (
Date: Tue, 25 Nov 2025 12:30:59 +0100
Subject: [PATCH 10/45] Add disabled buttons to the fertilizer manager
---
.../components/blocks/fertilizer/table.tsx | 20 +++++++++++--------
.../farm.$b_id_farm.fertilizers.$p_id.tsx | 13 ++++++++++++
.../farm.$b_id_farm.fertilizers._index.tsx | 5 +++++
3 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/fdm-app/app/components/blocks/fertilizer/table.tsx b/fdm-app/app/components/blocks/fertilizer/table.tsx
index 52e0440cd..454e5a37b 100644
--- a/fdm-app/app/components/blocks/fertilizer/table.tsx
+++ b/fdm-app/app/components/blocks/fertilizer/table.tsx
@@ -25,11 +25,13 @@ import {
interface DataTableProps {
columns: ColumnDef[]
data: TData[]
+ canAddItem: boolean
}
export function DataTable({
columns,
data,
+ canAddItem,
}: DataTableProps) {
const [sorting, setSorting] = useState([])
const [columnFilters, setColumnFilters] = useState([])
@@ -65,14 +67,16 @@ export function DataTable({
}
className="max-w-sm"
/>
-
-
-
-
- Meststof toevoegen
-
-
-
+ {canAddItem && (
+
+
+
+
+ Meststof toevoegen
+
+
+
+ )}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
index a352c8df7..69f6d63c6 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
@@ -5,6 +5,7 @@ import {
getFertilizer,
getFertilizerParametersDescription,
getFertilizers,
+ hasPermission,
updateFertilizerFromCatalogue,
} from "@svenvw/fdm-core"
import {
@@ -110,6 +111,18 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
if (fertilizer.p_source === b_id_farm) {
editable = true
}
+ if (
+ editable &&
+ !(await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ ))
+ ) {
+ editable = false
+ }
// Return user information from loader
return {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
index 584be095b..ac9f30069 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
@@ -3,6 +3,7 @@ import {
getFarms,
getFertilizerParametersDescription,
getFertilizers,
+ hasPermission,
} from "@svenvw/fdm-core"
import {
data,
@@ -102,12 +103,15 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
})),
)
+ const farmWritePermission = await hasPermission(fdm, "farm", "write", b_id_farm, session.principal_id)
+
// Return user information from loader
return {
farm: farm,
b_id_farm: b_id_farm,
farmOptions: farmOptions,
fertilizers: fertilizers,
+ farmWritePermission: farmWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -143,6 +147,7 @@ export default function FarmFertilizersIndexPage({
From 3337f33acc776a02f3930a8b3175854da123cc36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 12:39:52 +0100
Subject: [PATCH 11/45] Hide add cultivation button altogether if the user
can't add a cultivation
---
fdm-app/app/components/blocks/cultivation/card-list.tsx | 3 +--
fdm-app/app/components/blocks/cultivation/form-add.tsx | 8 ++++----
fdm-app/app/components/blocks/cultivation/types.tsx | 1 -
3 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/fdm-app/app/components/blocks/cultivation/card-list.tsx b/fdm-app/app/components/blocks/cultivation/card-list.tsx
index d3349ba0e..6f652eff0 100644
--- a/fdm-app/app/components/blocks/cultivation/card-list.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-list.tsx
@@ -27,10 +27,9 @@ export function CultivationListCard({
Gewassen
- {cultivations.length !== 0 ? (
+ {cultivations.length !== 0 && editable ? (
) : null}
diff --git a/fdm-app/app/components/blocks/cultivation/form-add.tsx b/fdm-app/app/components/blocks/cultivation/form-add.tsx
index 33ac01d67..684e0669c 100644
--- a/fdm-app/app/components/blocks/cultivation/form-add.tsx
+++ b/fdm-app/app/components/blocks/cultivation/form-add.tsx
@@ -19,14 +19,13 @@ import type { CultivationsFormProps } from "./types"
export function CultivationAddFormDialog({
options,
- editable = true,
}: CultivationsFormProps) {
const [isOpen, setIsOpen] = useState(false)
return (
- Gewas toevoegen
+ Gewas toevoegen
@@ -44,7 +43,8 @@ export function CultivationAddFormDialog({
function CultivationAddForm({
options,
onSuccess,
-}: CultivationsFormProps & { onSuccess?: () => void }) {
+ editable = true,
+}: CultivationsFormProps & { editable?: boolean; onSuccess?: () => void }) {
const form = useRemixForm>({
mode: "onTouched",
resolver: zodResolver(CultivationAddFormSchema),
@@ -73,7 +73,7 @@ function CultivationAddForm({
onSubmit={form.handleSubmit}
method="post"
>
-
+
Date: Tue, 25 Nov 2025 13:46:22 +0100
Subject: [PATCH 12/45] Disable calendar button on disabled v1 date pickers
---
fdm-app/app/components/custom/date-picker.tsx | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/fdm-app/app/components/custom/date-picker.tsx b/fdm-app/app/components/custom/date-picker.tsx
index 99cae7c60..e160682e1 100644
--- a/fdm-app/app/components/custom/date-picker.tsx
+++ b/fdm-app/app/components/custom/date-picker.tsx
@@ -111,6 +111,12 @@ export function DatePicker({
}
}, [form, name, date])
+ React.useEffect(() => {
+ if (disabled && open) {
+ setOpen(false)
+ }
+ }, [disabled, open])
+
return (
({
id={`${field.name}-picker`}
variant="ghost"
className="absolute top-1/2 right-2 size-6 -translate-y-1/2"
+ disabled={disabled}
>
From cac548cddf98c100b40022df87aaf812076b6682 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 13:48:08 +0100
Subject: [PATCH 13/45] Remove unused permission from loader
---
...$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx | 8 --------
1 file changed, 8 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
index 1b33aaaf1..c10777ad6 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
@@ -167,14 +167,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
cultivation.b_lu_harvestcat,
)
- const fieldWritePermission = hasPermission(
- fdm,
- "field",
- "write",
- b_id,
- session.principal_id,
- )
-
const cultivationWritePermission = hasPermission(
fdm,
"cultivation",
From af614b31d17654986efc720b4b5e146a73cc0f6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 14:05:40 +0100
Subject: [PATCH 14/45] Handle access change in fertilizer application card
---
.../components/blocks/fertilizer-applications/card.tsx | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
index 556982aad..9f06fd419 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
@@ -112,10 +112,16 @@ export function FertilizerApplicationCard({
if (savedFormValues && !isDialogOpen) {
if (savedFormValues.p_app_id) {
// Do not open the form if there is a risk it will create a new application
- if (applicationToEdit) {
+ if (
+ applicationToEdit &&
+ (canModifyFertilizerApplication[
+ applicationToEdit.p_app_id
+ ] ??
+ true)
+ ) {
setIsDialogOpen(true)
}
- } else {
+ } else if (canCreateFertilizerApplication) {
setIsDialogOpen(true)
}
}
From 91c357cce5861ebea9adf3bdffe00e97fd8ccf7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 14:08:25 +0100
Subject: [PATCH 15/45] Disable fieldset in cultivation form for consistency
---
fdm-app/app/components/blocks/cultivation/card-details.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fdm-app/app/components/blocks/cultivation/card-details.tsx b/fdm-app/app/components/blocks/cultivation/card-details.tsx
index 74a682c95..30b0b97c5 100644
--- a/fdm-app/app/components/blocks/cultivation/card-details.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-details.tsx
@@ -39,7 +39,6 @@ export function CultivationDetailsCard({
const fetcher = useFetcher()
const form = useRemixForm({
resolver: zodResolver(CultivationDetailsFormSchema),
- disabled: !editable,
mode: "onTouched",
defaultValues: {
b_lu_start: new Date(cultivation.b_lu_start),
@@ -101,6 +100,7 @@ export function CultivationDetailsCard({
diff --git a/fdm-app/app/components/blocks/soil/list.tsx b/fdm-app/app/components/blocks/soil/list.tsx
index 733d55922..9ee6984e0 100644
--- a/fdm-app/app/components/blocks/soil/list.tsx
+++ b/fdm-app/app/components/blocks/soil/list.tsx
@@ -49,31 +49,31 @@ export function SoilAnalysesList({
{""}
- {(canModifySoilAnalysis[analysis.a_id] ?? true) ? (
-
-
-
+
+
+
-
- Bewerk
-
-
+ Bewerk
+
+
+ {(canModifySoilAnalysis[analysis.a_id] ??
+ true) ? (
-
+ ) : null}
- ) : null}
+
))}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
index 159030a65..29a9f78e4 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
@@ -2,6 +2,7 @@ import {
getField,
getSoilAnalysis,
getSoilParametersDescription,
+ hasPermission,
updateSoilAnalysis,
} from "@svenvw/fdm-core"
import { ArrowLeft } from "lucide-react"
@@ -98,11 +99,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
(item: { parameter: string }) => soilAnalysis[item.parameter],
)
+ const soilAnalysisWritePermission = await hasPermission(fdm, "soil_analysis", "write", a_id, session.principal_id)
+
// Return user information from loader
return {
field: field,
soilParameterDescription: soilParameterDescription,
soilAnalysis: soilAnalysis,
+ soilAnalysisWritePermission: soilAnalysisWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -139,6 +143,7 @@ export default function FarmFieldSoilOverviewBlock() {
soilAnalysis={loaderData.soilAnalysis}
soilParameterDescription={loaderData.soilParameterDescription}
action="."
+ editable={loaderData.soilAnalysisWritePermission}
/>
)
From 9dbee5933170c9325e360ec98fa741130d5631ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 14:28:46 +0100
Subject: [PATCH 17/45] Make entire harvest form disabled
---
fdm-app/app/components/blocks/harvest/form.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/fdm-app/app/components/blocks/harvest/form.tsx b/fdm-app/app/components/blocks/harvest/form.tsx
index de046e96b..93fa12059 100644
--- a/fdm-app/app/components/blocks/harvest/form.tsx
+++ b/fdm-app/app/components/blocks/harvest/form.tsx
@@ -509,7 +509,9 @@ export function HarvestFormDialog(props: HarvestFormDialogProps) {
method="post"
action={action}
>
-
+
@@ -592,7 +594,7 @@ export function HarvestFormDialog(props: HarvestFormDialogProps) {
}
export function HarvestForm(props: HarvestFormDialogProps) {
- const { b_lu_harvest_date, action } = props
+ const { b_lu_harvest_date, action, editable } = props
const fetcher = useFetcher()
const form = useHarvestRemixForm(props)
@@ -614,7 +616,7 @@ export function HarvestForm(props: HarvestFormDialogProps) {
action={action}
>
Date: Tue, 25 Nov 2025 14:32:19 +0100
Subject: [PATCH 18/45] Disable calendar button on disabled v2 date pickers
---
fdm-app/app/components/custom/date-picker-v2.tsx | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/fdm-app/app/components/custom/date-picker-v2.tsx b/fdm-app/app/components/custom/date-picker-v2.tsx
index 2a6132140..748c64124 100644
--- a/fdm-app/app/components/custom/date-picker-v2.tsx
+++ b/fdm-app/app/components/custom/date-picker-v2.tsx
@@ -63,6 +63,12 @@ export function DatePicker({
}
}, [field.value])
+ useEffect(() => {
+ if (field.disabled && open) {
+ setOpen(false)
+ }
+ }, [field.disabled, open])
+
const handleInputChange = (e: ChangeEvent) => {
setInputValue(e.target.value)
}
@@ -115,6 +121,7 @@ export function DatePicker({
id="date-picker"
variant="ghost"
className="absolute top-1/2 right-2 size-6 -translate-y-1/2"
+ disabled={field.disabled}
>
Kies een datum
From 07ea0de4c58b3e4bb63db7a48a8c6a2b551750fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 14:57:25 +0100
Subject: [PATCH 19/45] Add some editable prop default values
---
fdm-app/app/components/blocks/harvest/form.tsx | 4 +---
fdm-app/app/components/blocks/soil/form.tsx | 2 +-
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/fdm-app/app/components/blocks/harvest/form.tsx b/fdm-app/app/components/blocks/harvest/form.tsx
index 93fa12059..b9e0bfdc1 100644
--- a/fdm-app/app/components/blocks/harvest/form.tsx
+++ b/fdm-app/app/components/blocks/harvest/form.tsx
@@ -70,12 +70,10 @@ function useHarvestRemixForm({
b_lu_harvestable,
b_lu_start,
b_lu_end,
- editable = true,
handleConfirmation,
}: HarvestFormDialogProps) {
const form = useRemixForm>({
mode: "onTouched",
- disabled: !editable,
resolver: async (values, bypass, options) => {
// Do the validation using Zod
const validation = await zodResolver(FormSchema)(
@@ -594,7 +592,7 @@ export function HarvestFormDialog(props: HarvestFormDialogProps) {
}
export function HarvestForm(props: HarvestFormDialogProps) {
- const { b_lu_harvest_date, action, editable } = props
+ const { b_lu_harvest_date, action, editable = true } = props
const fetcher = useFetcher()
const form = useHarvestRemixForm(props)
diff --git a/fdm-app/app/components/blocks/soil/form.tsx b/fdm-app/app/components/blocks/soil/form.tsx
index 072d3971a..4ecc888bb 100644
--- a/fdm-app/app/components/blocks/soil/form.tsx
+++ b/fdm-app/app/components/blocks/soil/form.tsx
@@ -32,7 +32,7 @@ export function SoilAnalysisForm(props: {
action: string
editable?: boolean
}) {
- const { soilAnalysis, soilParameterDescription, editable } = props
+ const { soilAnalysis, soilParameterDescription, editable = true } = props
const defaultValues: {
[key: string]: string | number | Date | undefined | null
From 19a284429d52e553c11aeba841a960f88de71575 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 15:33:11 +0100
Subject: [PATCH 20/45] Add fallback value support to hasPermission and make
use of it
---
...ivation.$b_lu.harvest.$b_id_harvesting.tsx | 1 +
...calendar.field.$b_id.cultivation.$b_lu.tsx | 1 +
...farm.$calendar.field.$b_id.cultivation.tsx | 1 +
...calendar.field.$b_id.fertilizer._index.tsx | 2 +
...farm.$calendar.field.$b_id.soil._index.tsx | 2 +
...lendar.field.$b_id.soil.analysis.$a_id.tsx | 9 ++-
.../farm.$b_id_farm.$calendar.field.$b_id.tsx | 1 +
...farm.$b_id_farm.$calendar.field._index.tsx | 2 +
...m.$b_id_farm.$calendar.rotation._index.tsx | 1 +
fdm-app/app/routes/farm.$b_id_farm._index.tsx | 1 +
.../farm.$b_id_farm.fertilizers.$p_id.tsx | 1 +
.../farm.$b_id_farm.fertilizers._index.tsx | 9 ++-
fdm-core/src/authorization.test.ts | 58 +++++++++++++++++++
fdm-core/src/authorization.ts | 12 +++-
14 files changed, 97 insertions(+), 4 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
index b046db558..bf3175c2a 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
@@ -94,6 +94,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id_harvesting,
session.principal_id,
+ { fallback: true },
)
// Get details of cultivation
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
index c10777ad6..0f0ebec1e 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
@@ -173,6 +173,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_lu,
session.principal_id,
+ { fallback: true },
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
index 5d1b1a442..418cad2cf 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
@@ -84,6 +84,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id,
session.principal_id,
+ { fallback: true },
)
// Get details of field
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
index 7776852e6..ae66a548f 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
@@ -172,6 +172,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id,
session.principal_id,
+ { fallback: true },
)
const fertilizerApplicationWritePermissionsEntries = await Promise.all(
@@ -185,6 +186,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
app.p_app_id,
session.principal_id,
+ { fallback: true },
),
] as [string, boolean],
),
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
index 59b4281be..e734ee237 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
@@ -102,6 +102,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id,
session.principal_id,
+ { fallback: true },
)
const soilAnalysisWritePermissionsEntries = await Promise.all(
@@ -113,6 +114,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
analysis.a_id,
session.principal_id,
+ { fallback: true },
),
]),
)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
index 29a9f78e4..510f49865 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
@@ -99,7 +99,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
(item: { parameter: string }) => soilAnalysis[item.parameter],
)
- const soilAnalysisWritePermission = await hasPermission(fdm, "soil_analysis", "write", a_id, session.principal_id)
+ const soilAnalysisWritePermission = await hasPermission(
+ fdm,
+ "soil_analysis",
+ "write",
+ a_id,
+ session.principal_id,
+ { fallback: true },
+ )
// Return user information from loader
return {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
index e92eaae45..f1695b0e1 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
@@ -158,6 +158,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id,
session.principal_id,
+ { fallback: true },
)
if (fieldWritePermission) {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
index 209519c41..fa5de995b 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
@@ -161,6 +161,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
field.b_id,
session.principal_id,
+ { fallback: true },
)
return {
b_id: field.b_id,
@@ -182,6 +183,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id_farm,
session.principal_id,
+ { fallback: true },
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
index abe1fdca5..07d918979 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
@@ -311,6 +311,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id_farm,
session.principal_id,
+ { fallback: true },
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm._index.tsx b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
index 98eb8109f..fef7b17e7 100644
--- a/fdm-app/app/routes/farm.$b_id_farm._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
@@ -115,6 +115,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"share",
b_id_farm,
session.principal_id,
+ { fallback: true },
)
// Check if user is allowed to delete farm
let canDeleteFarm = await isAllowedToDeleteFarm(
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
index 69f6d63c6..487655171 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
@@ -119,6 +119,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id_farm,
session.principal_id,
+ { fallback: true },
))
) {
editable = false
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
index ac9f30069..596d57776 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
@@ -103,7 +103,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
})),
)
- const farmWritePermission = await hasPermission(fdm, "farm", "write", b_id_farm, session.principal_id)
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ { fallback: true },
+ )
// Return user information from loader
return {
diff --git a/fdm-core/src/authorization.test.ts b/fdm-core/src/authorization.test.ts
index e095b9ad0..0d7f972b7 100644
--- a/fdm-core/src/authorization.test.ts
+++ b/fdm-core/src/authorization.test.ts
@@ -5,6 +5,7 @@ import {
checkPermission,
getRolesOfPrincipalForResource,
grantRole,
+ hasPermission,
listPrincipalsForResource,
listResources,
resources,
@@ -150,6 +151,63 @@ describe("Authorization Functions", () => {
})
})
+ describe("hasPermission", () => {
+ it("should not store the audit log", async () => {
+ const principal_id_new = createId()
+
+ await expect(
+ hasPermission(fdm, "farm", "read", farm_id, principal_id_new),
+ ).resolves.toBe(false)
+
+ const auditLogs = await fdm
+ .select()
+ .from(authZSchema.audit)
+ .where(eq(authZSchema.audit.principal_id, principal_id_new))
+ .orderBy(desc(authZSchema.audit.audit_timestamp))
+ expect(auditLogs).toHaveLength(0)
+ })
+
+ it("should return the fallback value when specified", async () => {
+ await grantRole(fdm, "farm", "owner", farm_id, principal_id)
+ await expect(
+ hasPermission(
+ fdm,
+ // biome-ignore lint/suspicious/noExplicitAny: Used for testing validation
+ "unknown_resource" as any,
+ "read",
+ farm_id,
+ principal_id,
+ { fallback: true },
+ ),
+ ).resolves.toBe(true)
+ await expect(
+ hasPermission(
+ fdm,
+ // biome-ignore lint/suspicious/noExplicitAny: Used for testing validation
+ "unknown_resource" as any,
+ "read",
+ farm_id,
+ principal_id,
+ { fallback: false },
+ ),
+ ).resolves.toBe(false)
+ })
+
+ it("should throw an error if the fallback value is not specified", async () => {
+ await grantRole(fdm, "farm", "owner", farm_id, principal_id)
+ await expect(
+ hasPermission(
+ fdm,
+ // biome-ignore lint/suspicious/noExplicitAny: Used for testing validation
+ "unknown_resource" as any,
+ "read",
+ farm_id,
+ principal_id,
+ ),
+ ).rejects.toThrowError("Exception for checkPermission")
+ })
+ })
+
describe("grantRole", () => {
it("should grant a role to a principal for a resource", async () => {
await grantRole(fdm, "farm", "owner", farm_id, principal_id)
diff --git a/fdm-core/src/authorization.ts b/fdm-core/src/authorization.ts
index 9965cf6dd..1e6093065 100644
--- a/fdm-core/src/authorization.ts
+++ b/fdm-core/src/authorization.ts
@@ -209,7 +209,7 @@ export async function checkPermission(
}
/**
- * Gets the granting resource type and ID if the principal has permission to perform the action in the given resource.
+ * Returns whether the principal has permission to perform the action in the given resource.
*
* If an action is actually going to be taken based on the result, you should use `checkPermission` instead.
*
@@ -218,7 +218,8 @@ export async function checkPermission(
* @param action - The action the principal intends to perform.
* @param resource_id - The unique identifier of the specific resource.
* @param principal_id - The principal identifier(s); supports a single ID or an array.
- * @returns Resolves to true if the principal is permitted to perform the action.
+ * @param options - Options to customize the behavior
+ * @returns Resolves to true if the principal is permitted to perform the action. Returns undefined in case of error.
*/
export async function hasPermission(
fdm: FdmType,
@@ -226,6 +227,10 @@ export async function hasPermission(
action: Action,
resource_id: string,
principal_id: PrincipalId,
+ options?: {
+ /** The value to return in case of error. Omit if you would like the return promise to be rejected instead. */
+ fallback?: boolean
+ },
) {
try {
return !!(await getPermission(
@@ -236,6 +241,9 @@ export async function hasPermission(
principal_id,
))
} catch (err) {
+ if (options && typeof options.fallback !== "undefined") {
+ return options.fallback
+ }
const message = "Exception for hasPermission"
throw handleError(err, message, {
resource: resource,
From f545dd437751ff09102d57650e9142d260f7d190 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 15:39:14 +0100
Subject: [PATCH 21/45] Fix and improve tests
---
fdm-core/src/authorization.test.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/fdm-core/src/authorization.test.ts b/fdm-core/src/authorization.test.ts
index 0d7f972b7..5bd60b782 100644
--- a/fdm-core/src/authorization.test.ts
+++ b/fdm-core/src/authorization.test.ts
@@ -167,7 +167,7 @@ describe("Authorization Functions", () => {
expect(auditLogs).toHaveLength(0)
})
- it("should return the fallback value when specified", async () => {
+ it("should return the fallback value on error when specified", async () => {
await grantRole(fdm, "farm", "owner", farm_id, principal_id)
await expect(
hasPermission(
@@ -193,7 +193,7 @@ describe("Authorization Functions", () => {
).resolves.toBe(false)
})
- it("should throw an error if the fallback value is not specified", async () => {
+ it("should throw any error encountered if the fallback value is not specified", async () => {
await grantRole(fdm, "farm", "owner", farm_id, principal_id)
await expect(
hasPermission(
@@ -204,7 +204,7 @@ describe("Authorization Functions", () => {
farm_id,
principal_id,
),
- ).rejects.toThrowError("Exception for checkPermission")
+ ).rejects.toThrowError("Exception for hasPermission")
})
})
From fb7126b8024ce4286ff3761db2af98bb106b486e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 16:16:39 +0100
Subject: [PATCH 22/45] Call hasPermission concurrently in the loader loader
for field cultivations
---
...$calendar.field.$b_id.cultivation.$b_lu.tsx | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
index 0f0ebec1e..039360df7 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
@@ -99,6 +99,15 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
const timeframe = getTimeframe(params)
const calendar = getCalendar(params)
+ const cultivationWritePermission = hasPermission(
+ fdm,
+ "cultivation",
+ "write",
+ b_lu,
+ session.principal_id,
+ { fallback: true },
+ )
+
// Get details of field
const field = await getField(fdm, session.principal_id, b_id)
if (!field) {
@@ -167,15 +176,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
cultivation.b_lu_harvestcat,
)
- const cultivationWritePermission = hasPermission(
- fdm,
- "cultivation",
- "write",
- b_lu,
- session.principal_id,
- { fallback: true },
- )
-
// Return user information from loader
return {
field: field,
From 1b0dff7a5747d42e7f33dec641f08d105655c986 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 16:31:06 +0100
Subject: [PATCH 23/45] Update loader documentation for some routes
---
.../farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx | 1 +
.../routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx | 1 +
fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx | 1 +
fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx | 1 +
4 files changed, 4 insertions(+)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
index 039360df7..0351208f7 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
@@ -60,6 +60,7 @@ export const meta: MetaFunction = () => {
* - harvests: The list of harvests related to the cultivation.
* - b_lu_harvestable: The harvestable type from the catalogue, or "none" if not applicable.
* - b_id_farm: The farm ID.
+ * - cultivationWritePermission: A Boolean indicating if the user is able to edit or delete the cultivation or add harvests to it. Set to true if the information could not be obtained.
*
* @throws {Response} If the farm, field, or cultivation ID is missing (status 400) or if the field or cultivation cannot be found (status 404).
*/
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
index 418cad2cf..26507c82f 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
@@ -49,6 +49,7 @@ export const meta: MetaFunction = () => {
* - cultivationsCatalogueOptions: A list of catalogue options for cultivations, formatted for use in a combobox.
* - cultivations: The list of cultivations associated with the field.
* - harvests: The harvest data for the first collection of cultivation harvests, or an empty array if none are available.
+ * - fieldWritePermissions: A Boolean indicating if the user is able to add cultivations to the field. Set to true if the information could not be obtained.
*
* @throws {Response} When the "b_id_farm" or "b_id" parameters are missing or if the field is not found.
*/
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
index fa5de995b..0716c51dc 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
@@ -58,6 +58,7 @@ export const meta: MetaFunction = () => {
* - farmOptions: An array of validated farm options.
* - fieldOptions: A sorted array of processed field options.
* - userName: The name of the current user.
+ * - farmWritePermission: A Boolean indicating if the user is able to add fields to the farm. Set to true if the information could not be obtained.
*/
export async function loader({ request, params }: LoaderFunctionArgs) {
try {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
index 07d918979..1184d2589 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
@@ -62,6 +62,7 @@ export const meta: MetaFunction = () => {
* - farmOptions: An array of validated farm options.
* - fieldOptions: A sorted array of processed field options.
* - userName: The name of the current user.
+ * - farmWritePermission: A Boolean indicating if the user is able to add things to the farm. Set to true if the information could not be obtained.
*/
export async function loader({ request, params }: LoaderFunctionArgs) {
try {
From 8a189b4c15c14abf688dbc50cbf397770f411344 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 16:41:16 +0100
Subject: [PATCH 24/45] Hide inline harvest form buttons if it is not editable
---
.../app/components/blocks/harvest/form.tsx | 72 ++++++++++---------
1 file changed, 37 insertions(+), 35 deletions(-)
diff --git a/fdm-app/app/components/blocks/harvest/form.tsx b/fdm-app/app/components/blocks/harvest/form.tsx
index b9e0bfdc1..7f09488f8 100644
--- a/fdm-app/app/components/blocks/harvest/form.tsx
+++ b/fdm-app/app/components/blocks/harvest/form.tsx
@@ -623,41 +623,43 @@ export function HarvestForm(props: HarvestFormDialogProps) {
className="grid lg:grid-cols-2 items-center gap-y-6 gap-x-8"
/>
-
-
- {form.formState.isSubmitting ||
- fetcher.state === "submitting" ? (
-
-
-
- ) : null}
- Verwijderen
-
-
- {form.formState.isSubmitting ? (
-
-
- Opslaan...
-
- ) : isHarvestUpdate ? (
- "Bijwerken"
- ) : (
- "Toevoegen"
- )}
-
-
+ {editable && (
+
+
+ {form.formState.isSubmitting ||
+ fetcher.state === "submitting" ? (
+
+
+
+ ) : null}
+ Verwijderen
+
+
+ {form.formState.isSubmitting ? (
+
+
+ Opslaan...
+
+ ) : isHarvestUpdate ? (
+ "Bijwerken"
+ ) : (
+ "Toevoegen"
+ )}
+
+
+ )}
From fb4dfa5dead73e9c9ca5ff4cffba11a5328de400 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Tue, 25 Nov 2025 16:52:36 +0100
Subject: [PATCH 25/45] Add disabled buttons to empty field and cultivation
overview tables
---
.../farm.$b_id_farm.$calendar.field._index.tsx | 12 +++++++-----
.../farm.$b_id_farm.$calendar.rotation._index.tsx | 12 +++++++-----
2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
index 0716c51dc..fdbcd6394 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
@@ -261,11 +261,13 @@ export default function FarmFieldIndex() {
:(
-
-
- Maak een perceel
-
-
+ {loaderData.farmWritePermission && (
+
+
+ Maak een perceel
+
+
+ )}
{/*
*/}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
index 1184d2589..ca6a1267b 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
@@ -381,11 +381,13 @@ export default function FarmRotationIndex() {
:(
-
-
- Maak een perceel
-
-
+ {loaderData.farmWritePermission && (
+
+
+ Maak een perceel
+
+
+ )}
>
) : (
From ab554d3d3d76fdff1fc130b6c957e7100548ddba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 13:49:53 +0100
Subject: [PATCH 26/45] Remove add button altogether in the field fertilizer
applications
---
.../blocks/fertilizer-applications/card.tsx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
index 9f06fd419..b0fab86bf 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
@@ -152,12 +152,14 @@ export function FertilizerApplicationCard({
open={isDialogOpen}
onOpenChange={handleDialogOpenChange}
>
-
-
-
- Toevoegen
-
-
+ {canCreateFertilizerApplication && (
+
+
+
+ Toevoegen
+
+
+ )}
From 73f143610c44e3eae5a145f9a66d8e1c1124b1a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 13:51:20 +0100
Subject: [PATCH 27/45] Add disabled buttons to the farm settings properties
route
---
.../farm.$b_id_farm.settings.properties.tsx | 42 +++++++++++++------
1 file changed, 30 insertions(+), 12 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
index ecaa41a09..64efcd9cc 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
-import { getFarm, updateFarm } from "@svenvw/fdm-core"
+import { getFarm, hasPermission, updateFarm } from "@svenvw/fdm-core"
import { useEffect } from "react"
import { Form } from "react-hook-form"
import {
@@ -80,10 +80,19 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
statusText: "Farm is not found",
})
}
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ { fallback: true },
+ )
// Return user information from loader
return {
farm: farm,
+ farmWritePermission: farmWritePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -145,7 +154,12 @@ export default function FarmSettingsPropertiesBlock() {
onSubmit={form.handleSubmit}
method="POST"
>
-
+
-
-
- {form.formState.isSubmitting && }
- Bijwerken
-
-
+ {loaderData.farmWritePermission && (
+
+
+ {form.formState.isSubmitting && (
+
+ )}
+ Bijwerken
+
+
+ )}
From 84e8095f76947ab69b820c2ddc66b9393d9808d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 13:59:37 +0100
Subject: [PATCH 28/45] Do not hide any buttons in the farm overview
---
fdm-app/app/routes/farm.$b_id_farm._index.tsx | 73 ++++++-------------
1 file changed, 21 insertions(+), 52 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm._index.tsx b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
index fef7b17e7..77b5858ec 100644
--- a/fdm-app/app/routes/farm.$b_id_farm._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm._index.tsx
@@ -1,11 +1,5 @@
import { cowHead } from "@lucide/lab"
-import {
- getFarm,
- getFarms,
- getFields,
- hasPermission,
- isAllowedToDeleteFarm,
-} from "@svenvw/fdm-core"
+import { getFarm, getFarms, getFields } from "@svenvw/fdm-core"
import {
ArrowRightLeft,
BookOpenText,
@@ -109,25 +103,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
})
- const farmSharePermission = hasPermission(
- fdm,
- "farm",
- "share",
- b_id_farm,
- session.principal_id,
- { fallback: true },
- )
- // Check if user is allowed to delete farm
- let canDeleteFarm = await isAllowedToDeleteFarm(
- fdm,
- session.principal_id,
- b_id_farm,
- )
- // Add temporary workaround (until implemented in fdm-core) so that advisors are not able to delete a farm
- if (canDeleteFarm && farm.roles.includes("advisor")) {
- canDeleteFarm = false
- }
-
// Return the farm ID and session info
return {
b_id_farm: b_id_farm,
@@ -136,8 +111,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
farmArea: Math.round(farmArea),
farmOptions: farmOptions,
roles: farm.roles,
- canDeleteFarm: canDeleteFarm,
- farmSharePermission: await farmSharePermission,
}
} catch (error) {
throw handleLoaderError(error)
@@ -476,30 +449,26 @@ export default function FarmDashboardIndex() {
Beweiding
- {loaderData.farmSharePermission && (
-
-
-
- Toegang
-
-
- )}
- {loaderData.canDeleteFarm && (
-
-
-
- Verwijderen
-
-
- )}
+
+
+
+ Toegang
+
+
+
+
+
+ Verwijderen
+
+
From 09688e63a0fec9aa656ea6798d36af3fd15630aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:10:20 +0100
Subject: [PATCH 29/45] Add disabled buttons to the farm derogation
---
.../farm.$b_id_farm.settings.derogation.tsx | 61 ++++++++++++-------
1 file changed, 40 insertions(+), 21 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
index c03a88750..11519db8e 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
@@ -1,5 +1,6 @@
import {
addDerogation,
+ hasPermission,
listDerogations,
removeDerogation,
} from "@svenvw/fdm-core"
@@ -40,7 +41,15 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
session.principal_id,
b_id_farm,
)
- return { b_id_farm, derogations }
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ { fallback: true },
+ )
+ return { b_id_farm, derogations, farmWritePermission }
} catch (error) {
throw handleLoaderError(error)
}
@@ -86,7 +95,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
}
export default function DerogationSettings() {
- const { derogations } = useLoaderData()
+ const { derogations, farmWritePermission } = useLoaderData()
const fetcher = useFetcher()
const years = getCalendarSelection()
@@ -99,9 +108,11 @@ export default function DerogationSettings() {
Derogatie
- Schakel derogatie in voor de jaren waarvoor dit bedrijf
- in aanmerking komt. Dit heeft invloed op de berekening
- van je gebruiksruimte.
+ {farmWritePermission
+ ? "Schakel derogatie in voor de jaren waarvoor dit bedrijf in aanmerking komt."
+ : "Hieronder staan de jaren waarin dit bedrijf derogatie in aanmerking heeft."}{" "}
+ Dit heeft invloed op de berekening van je
+ gebruiksruimte.
@@ -121,25 +132,33 @@ export default function DerogationSettings() {
{year}
-
+ {farmWritePermission ? (
+
+ {
+ fetcher.submit(
+ {
+ year: String(
+ year,
+ ),
+ hasDerogation:
+ String(
+ hasDerogation,
+ ),
+ },
+ {
+ method: "post",
+ },
+ )
+ }}
+ />
+
+ ) : (
{
- fetcher.submit(
- {
- year: String(
- year,
- ),
- hasDerogation:
- String(
- hasDerogation,
- ),
- },
- { method: "post" },
- )
- }}
/>
-
+ )}
)
From 3da135d5fb45396f21e799f0e140ba8006f47055 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:24:14 +0100
Subject: [PATCH 30/45] Add disabled buttons to the farm bio-certificate
settings
---
...id_farm.settings.organic-certification.tsx | 29 +++++++++++++------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
index 39fa32106..3074d94f9 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
@@ -1,6 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import {
addOrganicCertification,
+ hasPermission,
listOrganicCertifications,
removeOrganicCertification,
} from "@svenvw/fdm-core"
@@ -93,7 +94,15 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
// For now we expect that a farm can have only 1 certification
const organicCertification = organicCertifications[0]
- return { b_id_farm, organicCertification }
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ )
+
+ return { b_id_farm, organicCertification, farmWritePermission }
} catch (error) {
throw handleLoaderError(error)
}
@@ -155,7 +164,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
}
export default function OrganicCertificationSettings() {
- const { organicCertification } = useLoaderData()
+ const { organicCertification, farmWritePermission } =
+ useLoaderData()
const navigation = useNavigation()
const isDeleting =
@@ -309,15 +319,16 @@ export default function OrganicCertificationSettings() {
Dit bedrijf heeft geen bio-certificaat
- Als dit bedrijf wel een bio-certificaat heeft,
- kunt u deze toevoegen.
+ {farmWritePermission ? "Als dit bedrijf wel een bio-certificaat heeft, kunt u deze toevoegen." : "U hebt geen toestemming om een bio-certificaat voor dit bedrijf toe te voegen."}
-
-
- Voeg toe
-
-
+ {farmWritePermission && (
+
+
+ Voeg toe
+
+
+ )}
From ca3826dc59e14d215405b1884d640be669ee8513 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:30:29 +0100
Subject: [PATCH 31/45] Fix flaky getRolesOfPrincipalForResource test
---
fdm-core/src/authorization.test.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fdm-core/src/authorization.test.ts b/fdm-core/src/authorization.test.ts
index 9bb3b1595..cf78d0d1e 100644
--- a/fdm-core/src/authorization.test.ts
+++ b/fdm-core/src/authorization.test.ts
@@ -835,7 +835,9 @@ describe("Authorization Functions", () => {
farm_id,
organization_member_id,
)
- expect(roles).toEqual(["owner", "researcher"])
+ expect(roles.length).toBe(2)
+ expect(roles).toContain("owner")
+ expect(roles).toContain("researcher")
})
it("should throw error with invalid resource", async () => {
From 2ad7030425ce7d7f563f419303cd576de65ebefe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:40:06 +0100
Subject: [PATCH 32/45] Add missing effect dependencies in fertilizer
application card
---
.../components/blocks/fertilizer-applications/card.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
index b0fab86bf..a278b316d 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
@@ -125,7 +125,13 @@ export function FertilizerApplicationCard({
setIsDialogOpen(true)
}
}
- }, [savedFormValues, applicationToEdit, isDialogOpen])
+ }, [
+ savedFormValues,
+ applicationToEdit,
+ isDialogOpen,
+ canCreateFertilizerApplication,
+ canModifyFertilizerApplication,
+ ])
function handleDialogOpenChange(state: boolean) {
if (!state && params.b_id_farm && b_id_or_b_lu_catalogue) {
From 24ac7ab1c5efa5ebb38eeee4611d118f1fcb43c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:40:42 +0100
Subject: [PATCH 33/45] Update documentation for hasPermission
---
fdm-core/src/authorization.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fdm-core/src/authorization.ts b/fdm-core/src/authorization.ts
index a42aa099b..b630f8759 100644
--- a/fdm-core/src/authorization.ts
+++ b/fdm-core/src/authorization.ts
@@ -220,7 +220,8 @@ export async function checkPermission(
* @param resource_id - The unique identifier of the specific resource.
* @param principal_id - The principal identifier(s); supports a single ID or an array.
* @param options - Options to customize the behavior
- * @returns Resolves to true if the principal is permitted to perform the action. Returns undefined in case of error.
+ * @returns Resolves to true if the principal is permitted to perform the action.
+ * If an error occurs and `options.fallback` is provided, returns the fallback value; otherwise throws.
*/
export async function hasPermission(
fdm: FdmType,
From 6f464a12e3b602188093f1da4b63051176d6222a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:49:50 +0100
Subject: [PATCH 34/45] Add disabled buttons to the farm grazing intention
settings
---
....$b_id_farm.settings.grazing-intention.tsx | 67 +++++++++++++------
1 file changed, 45 insertions(+), 22 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
index d5c04db3f..56cdcff45 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
@@ -1,4 +1,8 @@
-import { getGrazingIntentions, setGrazingIntention } from "@svenvw/fdm-core"
+import {
+ getGrazingIntentions,
+ hasPermission,
+ setGrazingIntention,
+} from "@svenvw/fdm-core"
import {
type ActionFunctionArgs,
type LoaderFunctionArgs,
@@ -39,7 +43,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
session.principal_id,
b_id_farm,
)
- return { b_id_farm, grazingIntentions }
+ const farmWritePermission = await hasPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ session.principal_id,
+ )
+ return { b_id_farm, grazingIntentions, farmWritePermission }
} catch (error) {
throw handleLoaderError(error)
}
@@ -78,7 +89,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
}
export default function GrazingIntentionSettings() {
- const { grazingIntentions } = useLoaderData()
+ const { grazingIntentions, farmWritePermission } =
+ useLoaderData()
const fetcher = useFetcher()
const currentYear = new Date().getFullYear()
@@ -92,9 +104,10 @@ export default function GrazingIntentionSettings() {
Beweiding
- Geef hier aan of je voor een bepaald jaar hebt beweid of
- van plan bent te gaan beweiden. Dit heeft invloed op de
- berekeningen.
+ {farmWritePermission
+ ? "Geef hier aan of je voor een bepaald jaar hebt beweid of van plan bent te gaan beweiden."
+ : "Hieronder staan de jaren waarin beweiding gepland is of heeft plaatsgevonden."}{" "}
+ Dit heeft invloed op de berekeningen.
@@ -117,27 +130,37 @@ export default function GrazingIntentionSettings() {
{year}
-
+ {farmWritePermission ? (
+
+ {
+ fetcher.submit(
+ {
+ year: String(
+ year,
+ ),
+ hasGrazingIntention:
+ String(
+ hasGrazingIntention,
+ ),
+ },
+ {
+ method: "post",
+ },
+ )
+ }}
+ />
+
+ ) : (
{
- fetcher.submit(
- {
- year: String(
- year,
- ),
- hasGrazingIntention:
- String(
- hasGrazingIntention,
- ),
- },
- { method: "post" },
- )
- }}
/>
-
+ )}
)
From bcc2f04eac8f218770b0615abc58f479f2620579 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 14:52:58 +0100
Subject: [PATCH 35/45] Improve derogation text
---
fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
index 11519db8e..dc7545263 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
@@ -110,7 +110,7 @@ export default function DerogationSettings() {
{farmWritePermission
? "Schakel derogatie in voor de jaren waarvoor dit bedrijf in aanmerking komt."
- : "Hieronder staan de jaren waarin dit bedrijf derogatie in aanmerking heeft."}{" "}
+ : "Hieronder staan de jaren waarin dit bedrijf derogatie heeft."}{" "}
Dit heeft invloed op de berekening van je
gebruiksruimte.
From de0ae02cf50f0f120c52aa3084af05b90d94ef87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 15:04:06 +0100
Subject: [PATCH 36/45] Add missing fallback true
---
.../app/routes/farm.$b_id_farm.settings.grazing-intention.tsx | 1 +
.../routes/farm.$b_id_farm.settings.organic-certification.tsx | 1 +
2 files changed, 2 insertions(+)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
index 56cdcff45..6c7fa2cf6 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
@@ -49,6 +49,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id_farm,
session.principal_id,
+ { fallback: true },
)
return { b_id_farm, grazingIntentions, farmWritePermission }
} catch (error) {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
index 3074d94f9..b11c847d5 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
@@ -100,6 +100,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
"write",
b_id_farm,
session.principal_id,
+ { fallback: true },
)
return { b_id_farm, organicCertification, farmWritePermission }
From 05ecb4ae101034fbe5ae2ef37c72829deac25df9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 15:33:37 +0100
Subject: [PATCH 37/45] Fix various issues about the field harvests
---
.../blocks/cultivation/card-harvests.tsx | 33 ++--
.../app/components/blocks/harvest/form.tsx | 153 +++++++++---------
2 files changed, 92 insertions(+), 94 deletions(-)
diff --git a/fdm-app/app/components/blocks/cultivation/card-harvests.tsx b/fdm-app/app/components/blocks/cultivation/card-harvests.tsx
index ab75f6150..644098e38 100644
--- a/fdm-app/app/components/blocks/cultivation/card-harvests.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-harvests.tsx
@@ -24,9 +24,7 @@ export function CultivationHarvestsCard({
if (b_lu_harvestable === "multiple") {
canAddHarvest = true
}
- if (typeof editable !== "undefined") {
- canAddHarvest = editable
- }
+ canAddHarvest &&= editable
return (
@@ -35,22 +33,25 @@ export function CultivationHarvestsCard({
{b_lu_harvestable === "multiple" ? "Oogsten" : "Oogst"}
- {
- if (!canAddHarvest) {
- e.preventDefault()
- }
- }}
- className={!canAddHarvest ? "cursor-not-allowed" : ""}
+
- {
+ if (!canAddHarvest) {
+ e.preventDefault()
+ }
+ }}
+ className={
+ !canAddHarvest ? "cursor-not-allowed" : ""
+ }
>
Oogst toevoegen
-
-
+
+
diff --git a/fdm-app/app/components/blocks/harvest/form.tsx b/fdm-app/app/components/blocks/harvest/form.tsx
index 7f09488f8..44dbb6b87 100644
--- a/fdm-app/app/components/blocks/harvest/form.tsx
+++ b/fdm-app/app/components/blocks/harvest/form.tsx
@@ -459,7 +459,10 @@ function HarvestFormExplainer() {
return (
-
+
Waarom zie ik deze oogstparameters?
@@ -507,84 +510,78 @@ export function HarvestFormDialog(props: HarvestFormDialogProps) {
method="post"
action={action}
>
-
-
-
-
- {isHarvestUpdate
- ? "Oogst bijwerken"
- : "Oogst toevoegen"}
-
-
- {isHarvestUpdate
- ? "Werk de oogst bij van dit gewas. Vul de gegevens in, zodat deze gebruikt kunnen worden in de berekeningen."
- : "Voeg een oogst toe aan dit gewas. Vul de gegevens in, zodat deze gebruikt kunnen worden in de berekeningen."}
-
-
+
+
+
+ {isHarvestUpdate
+ ? "Oogst bijwerken"
+ : "Oogst toevoegen"}
+
+
+ {isHarvestUpdate
+ ? "Werk de oogst bij van dit gewas. Vul de gegevens in, zodat deze gebruikt kunnen worden in de berekeningen."
+ : "Voeg een oogst toe aan dit gewas. Vul de gegevens in, zodat deze gebruikt kunnen worden in de berekeningen."}
+
+
+
-
-
-
- {editable && (
-
- {fetcher.state === "submitting" ? (
-
-
-
- ) : null}
- Verwijderen
-
- )}
-
-
- Sluiten
-
-
- {editable && (
-
- {form.formState.isSubmitting ? (
-
-
- Opslaan...
-
- ) : isHarvestUpdate ? (
- "Bijwerken"
- ) : (
- "Toevoegen"
- )}
-
- )}
-
-
-
-
+
+
+
+
+ {editable && (
+
+ {fetcher.state === "submitting" ? (
+
+
+
+ ) : null}
+ Verwijderen
+
+ )}
+
+
+ Sluiten
+
+
+ {editable && (
+
+ {form.formState.isSubmitting ? (
+
+
+ Opslaan...
+
+ ) : isHarvestUpdate ? (
+ "Bijwerken"
+ ) : (
+ "Toevoegen"
+ )}
+
+ )}
+
+
+
From 0dca996b37345d4661592a9024f3508c3cc3a7c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 15:43:26 +0100
Subject: [PATCH 38/45] Fix import in farm.tsx
---
fdm-app/app/routes/farm.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fdm-app/app/routes/farm.tsx b/fdm-app/app/routes/farm.tsx
index 904ab5319..4547118e4 100644
--- a/fdm-app/app/routes/farm.tsx
+++ b/fdm-app/app/routes/farm.tsx
@@ -20,7 +20,7 @@ import { clientConfig } from "~/lib/config"
import { handleLoaderError } from "~/lib/error"
import { useCalendarStore } from "~/store/calendar"
import { useFarmStore } from "~/store/farm"
-import { fdm } from "../lib/fdm.server"
+import { fdm } from "~/lib/fdm.server"
export const meta: MetaFunction = () => {
return [
From d8dcd23aaef8cf878b97b338960b55c03da0bc06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 16:10:31 +0100
Subject: [PATCH 39/45] Add changeset
---
.changeset/dark-wombats-slide.md | 5 +++++
.changeset/jolly-pens-crash.md | 5 +++++
2 files changed, 10 insertions(+)
create mode 100644 .changeset/dark-wombats-slide.md
create mode 100644 .changeset/jolly-pens-crash.md
diff --git a/.changeset/dark-wombats-slide.md b/.changeset/dark-wombats-slide.md
new file mode 100644
index 000000000..12f70c6f1
--- /dev/null
+++ b/.changeset/dark-wombats-slide.md
@@ -0,0 +1,5 @@
+---
+"@svenvw/fdm-core": minor
+---
+
+Added the `hasPermission` function that returns if the principal has permission to perform a specific action on a resource without throwing exceptions or creating an audit log.
diff --git a/.changeset/jolly-pens-crash.md b/.changeset/jolly-pens-crash.md
new file mode 100644
index 000000000..8a5b1be95
--- /dev/null
+++ b/.changeset/jolly-pens-crash.md
@@ -0,0 +1,5 @@
+---
+"@svenvw/fdm-app": minor
+---
+
+Various UI elements are now hidden if the current logged-in user doesn't have permission to perform the actions that they represent.
From f4c2b6f76c13dd22cdd2d67b56cd4b8b1e3e8497 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 16:31:33 +0100
Subject: [PATCH 40/45] Add disabled buttons to the single field overview page
---
...b_id_farm.$calendar.field.$b_id.overview.tsx | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
index fce74bf66..38033c9bd 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
@@ -1,6 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import {
getField,
+ hasPermission,
listAvailableAcquiringMethods,
updateField,
} from "@svenvw/fdm-core"
@@ -41,6 +42,7 @@ import { clientConfig } from "~/lib/config"
import { handleActionError, handleLoaderError } from "~/lib/error"
import { fdm } from "~/lib/fdm.server"
import { extractFormValuesFromRequest } from "~/lib/form"
+import { cn } from "~/lib/utils"
export const meta: MetaFunction = () => {
return [
@@ -86,9 +88,19 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
})
}
+ const fieldWritePermission = await hasPermission(
+ fdm,
+ "field",
+ "write",
+ b_id,
+ session.principal_id,
+ { fallback: true },
+ )
+
// Return user information from loader
return {
field: field,
+ fieldWritePermission,
acquiringMethodOptions: listAvailableAcquiringMethods(),
}
} catch (error) {
@@ -233,7 +245,10 @@ export default function FarmFieldsOverviewBlock() {
{form.formState.isSubmitting && }
Bijwerken
From b888e929ceea29e2683d00242f38ca451f0349e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Wed, 26 Nov 2025 16:47:11 +0100
Subject: [PATCH 41/45] Get rid of the hasPermission function
---
fdm-core/src/authorization.test.ts | 69 +++++++++----------------
fdm-core/src/authorization.ts | 82 ++++++++----------------------
fdm-core/src/index.ts | 2 +-
3 files changed, 45 insertions(+), 108 deletions(-)
diff --git a/fdm-core/src/authorization.test.ts b/fdm-core/src/authorization.test.ts
index cf78d0d1e..f2e11a292 100644
--- a/fdm-core/src/authorization.test.ts
+++ b/fdm-core/src/authorization.test.ts
@@ -6,7 +6,6 @@ import {
checkPermission,
getRolesOfPrincipalForResource,
grantRole,
- hasPermission,
listPrincipalsForResource,
listResources,
resources,
@@ -137,6 +136,20 @@ describe("Authorization Functions", () => {
)
})
+ it("should not throw an error in non-strict mode if principal does not have the required role", async () => {
+ await expect(
+ checkPermission(
+ fdm,
+ "farm",
+ "read",
+ farm_id,
+ createId(),
+ "test",
+ false,
+ ),
+ ).resolves.toBe(false)
+ })
+
it("should grant access through the organization", async () => {
await grantRole(fdm, "farm", "owner", farm_id, organization_id)
const invitation_id = await inviteUserToOrganization(
@@ -261,14 +274,20 @@ describe("Authorization Functions", () => {
expect(auditLogs[0].action).toBe("read")
expect(auditLogs[0].allowed).toBe(false)
})
- })
- describe("hasPermission", () => {
- it("should not store the audit log", async () => {
+ it("should not store the audit log if non-strict", async () => {
const principal_id_new = createId()
await expect(
- hasPermission(fdm, "farm", "read", farm_id, principal_id_new),
+ checkPermission(
+ fdm,
+ "farm",
+ "read",
+ farm_id,
+ principal_id_new,
+ "test",
+ false,
+ ),
).resolves.toBe(false)
const auditLogs = await fdm
@@ -278,46 +297,6 @@ describe("Authorization Functions", () => {
.orderBy(desc(authZSchema.audit.audit_timestamp))
expect(auditLogs).toHaveLength(0)
})
-
- it("should return the fallback value on error when specified", async () => {
- await grantRole(fdm, "farm", "owner", farm_id, principal_id)
- await expect(
- hasPermission(
- fdm,
- // biome-ignore lint/suspicious/noExplicitAny: Used for testing validation
- "unknown_resource" as any,
- "read",
- farm_id,
- principal_id,
- { fallback: true },
- ),
- ).resolves.toBe(true)
- await expect(
- hasPermission(
- fdm,
- // biome-ignore lint/suspicious/noExplicitAny: Used for testing validation
- "unknown_resource" as any,
- "read",
- farm_id,
- principal_id,
- { fallback: false },
- ),
- ).resolves.toBe(false)
- })
-
- it("should throw any error encountered if the fallback value is not specified", async () => {
- await grantRole(fdm, "farm", "owner", farm_id, principal_id)
- await expect(
- hasPermission(
- fdm,
- // biome-ignore lint/suspicious/noExplicitAny: Used for testing validation
- "unknown_resource" as any,
- "read",
- farm_id,
- principal_id,
- ),
- ).rejects.toThrowError("Exception for hasPermission")
- })
})
describe("grantRole", () => {
diff --git a/fdm-core/src/authorization.ts b/fdm-core/src/authorization.ts
index b630f8759..8a82f6b78 100644
--- a/fdm-core/src/authorization.ts
+++ b/fdm-core/src/authorization.ts
@@ -142,7 +142,8 @@ export const permissions: Permission[] = [
*
* This function retrieves the valid roles for the specified action and resource, constructs the resource hierarchy,
* and iterates through the chain to verify if any level grants the required permission for the principal(s). It records
- * the permission check details in the audit log and throws an error if the permission is denied.
+ * the permission check details in the audit log and throws an error if the permission is denied. `strict` may be
+ * specified as false in order to disable the exception.
*
* @param fdm The FDM instance providing the connection to the database. The instance can be created with {@link createFdmServer}.
* @param resource - The type of resource being accessed.
@@ -150,6 +151,7 @@ export const permissions: Permission[] = [
* @param resource_id - The unique identifier of the specific resource.
* @param principal_id - The principal identifier(s); supports a single ID or an array.
* @param origin - The source origin used for audit logging the permission check.
+ * @param strict - When set to false, the function will not perform an audit log, or throw an exception if the user has no permission.
* @returns Resolves to true if the principal is permitted to perform the action.
*
* @throws {Error} When the principal does not have the required permission.
@@ -161,6 +163,7 @@ export async function checkPermission(
resource_id: string,
principal_id: PrincipalId,
origin: string,
+ strict = true,
) {
const start = performance.now()
try {
@@ -176,21 +179,23 @@ export async function checkPermission(
const granting_resource_id = permission?.granting_resource_id ?? ""
// Store check in audit
- await fdm.insert(authZSchema.audit).values({
- audit_id: createId(),
- audit_origin: origin,
- principal_id: principal_id,
- target_resource: resource,
- target_resource_id: resource_id,
- granting_resource: granting_resource,
- granting_resource_id: granting_resource_id,
- action: action,
- allowed: !!permission,
- duration: Math.round(performance.now() - start),
- })
+ if (strict) {
+ await fdm.insert(authZSchema.audit).values({
+ audit_id: createId(),
+ audit_origin: origin,
+ principal_id: principal_id,
+ target_resource: resource,
+ target_resource_id: resource_id,
+ granting_resource: granting_resource,
+ granting_resource_id: granting_resource_id,
+ action: action,
+ allowed: !!permission,
+ duration: Math.round(performance.now() - start),
+ })
- if (!permission) {
- throw new Error("Permission denied")
+ if (!permission) {
+ throw new Error("Permission denied")
+ }
}
return !!permission
@@ -209,53 +214,6 @@ export async function checkPermission(
}
}
-/**
- * Returns whether the principal has permission to perform the action in the given resource.
- *
- * If an action is actually going to be taken based on the result, you should use `checkPermission` instead.
- *
- * @param fdm The FDM instance providing the connection to the database. The instance can be created with {@link createFdmServer}.
- * @param resource - The type of resource being accessed.
- * @param action - The action the principal intends to perform.
- * @param resource_id - The unique identifier of the specific resource.
- * @param principal_id - The principal identifier(s); supports a single ID or an array.
- * @param options - Options to customize the behavior
- * @returns Resolves to true if the principal is permitted to perform the action.
- * If an error occurs and `options.fallback` is provided, returns the fallback value; otherwise throws.
- */
-export async function hasPermission(
- fdm: FdmType,
- resource: Resource,
- action: Action,
- resource_id: string,
- principal_id: PrincipalId,
- options?: {
- /** The value to return in case of error. Omit if you would like the return promise to be rejected instead. */
- fallback?: boolean
- },
-) {
- try {
- return !!(await getPermission(
- fdm,
- resource,
- action,
- resource_id,
- principal_id,
- ))
- } catch (err) {
- if (options && typeof options.fallback !== "undefined") {
- return options.fallback
- }
- const message = "Exception for hasPermission"
- throw handleError(err, message, {
- resource: resource,
- action: action,
- resource_id: resource_id,
- principal_id: principal_id,
- })
- }
-}
-
/**
* Gets the granting resource type and ID if the principal has permission to perform the action in the given resource.
*
diff --git a/fdm-core/src/index.ts b/fdm-core/src/index.ts
index 544487f37..1d87cb97e 100644
--- a/fdm-core/src/index.ts
+++ b/fdm-core/src/index.ts
@@ -22,7 +22,7 @@ export {
updateUserProfile,
} from "./authentication"
export type { FdmAuth } from "./authentication.d"
-export { checkPermission, hasPermission } from "./authorization"
+export { checkPermission } from "./authorization"
export type { PrincipalId } from "./authorization.d"
export {
getCachedCalculation,
From 4c8a4f0515cdf3ac32039e949c17b6af4b1e4c0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Thu, 27 Nov 2025 12:43:08 +0100
Subject: [PATCH 42/45] Use the extended checkPermission function in loaders
---
...d.cultivation.$b_lu.harvest.$b_id_harvesting.tsx | 7 ++++---
...farm.$calendar.field.$b_id.cultivation.$b_lu.tsx | 7 ++++---
...$b_id_farm.$calendar.field.$b_id.cultivation.tsx | 7 ++++---
...farm.$calendar.field.$b_id.fertilizer._index.tsx | 13 ++++++++-----
...rm.$b_id_farm.$calendar.field.$b_id.overview.tsx | 7 ++++---
...$b_id_farm.$calendar.field.$b_id.soil._index.tsx | 13 ++++++++-----
...rm.$calendar.field.$b_id.soil.analysis.$a_id.tsx | 7 ++++---
.../farm.$b_id_farm.$calendar.field.$b_id.tsx | 12 +++++++++---
.../farm.$b_id_farm.$calendar.field._index.tsx | 12 +++++++-----
.../farm.$b_id_farm.$calendar.rotation._index.tsx | 7 ++++---
.../routes/farm.$b_id_farm.fertilizers.$p_id.tsx | 7 ++++---
.../routes/farm.$b_id_farm.fertilizers._index.tsx | 7 ++++---
.../routes/farm.$b_id_farm.settings.derogation.tsx | 7 ++++---
.../farm.$b_id_farm.settings.grazing-intention.tsx | 7 ++++---
...rm.$b_id_farm.settings.organic-certification.tsx | 7 ++++---
.../routes/farm.$b_id_farm.settings.properties.tsx | 7 ++++---
16 files changed, 80 insertions(+), 54 deletions(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
index bf3175c2a..a4d76f6a0 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx
@@ -1,8 +1,8 @@
import {
+ checkPermission,
getCultivation,
getHarvest,
getParametersForHarvestCat,
- hasPermission,
removeHarvest,
updateHarvest,
} from "@svenvw/fdm-core"
@@ -88,13 +88,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
const session = await getSession(request)
const calendar = getCalendar(params)
- const harvestingWritePermission = hasPermission(
+ const harvestingWritePermission = checkPermission(
fdm,
"harvesting",
"write",
b_id_harvesting,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Get details of cultivation
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
index 0351208f7..7dc02700a 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx
@@ -1,11 +1,11 @@
import {
+ checkPermission,
type CultivationCatalogue,
getCultivation,
getCultivationsFromCatalogue,
getField,
getHarvests,
getParametersForHarvestCat,
- hasPermission,
removeCultivation,
updateCultivation,
} from "@svenvw/fdm-core"
@@ -100,13 +100,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
const timeframe = getTimeframe(params)
const calendar = getCalendar(params)
- const cultivationWritePermission = hasPermission(
+ const cultivationWritePermission = checkPermission(
fdm,
"cultivation",
"write",
b_lu,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Get details of field
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
index 26507c82f..4ade1f941 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx
@@ -1,10 +1,10 @@
import {
addCultivation,
+ checkPermission,
getCultivations,
getCultivationsFromCatalogue,
getField,
getHarvests,
- hasPermission,
removeCultivation,
} from "@svenvw/fdm-core"
import {
@@ -79,13 +79,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
// Get timeframe from calendar store
const timeframe = getTimeframe(params)
- const fieldWritePermission = await hasPermission(
+ const fieldWritePermission = await checkPermission(
fdm,
"field",
"write",
b_id,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Get details of field
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
index ae66a548f..c87ae4e49 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
@@ -1,11 +1,11 @@
import { calculateDose } from "@svenvw/fdm-calculator"
import {
addFertilizerApplication,
+ checkPermission,
getFertilizerApplications,
getFertilizerParametersDescription,
getFertilizers,
getField,
- hasPermission,
removeFertilizerApplication,
updateFertilizerApplication,
} from "@svenvw/fdm-core"
@@ -166,13 +166,15 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
calendar: getCalendar(params),
}
- const fieldWritePermission = hasPermission(
+ const pathname = new URL(request.url).pathname
+ const fieldWritePermission = checkPermission(
fdm,
"field",
"write",
b_id,
session.principal_id,
- { fallback: true },
+ pathname,
+ false,
)
const fertilizerApplicationWritePermissionsEntries = await Promise.all(
@@ -180,13 +182,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
async (app) =>
[
app.p_app_id,
- await hasPermission(
+ await checkPermission(
fdm,
"fertilizer_application",
"write",
app.p_app_id,
session.principal_id,
- { fallback: true },
+ pathname,
+ false,
),
] as [string, boolean],
),
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
index 38033c9bd..4845b88ee 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import {
+ checkPermission,
getField,
- hasPermission,
listAvailableAcquiringMethods,
updateField,
} from "@svenvw/fdm-core"
@@ -88,13 +88,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
})
}
- const fieldWritePermission = await hasPermission(
+ const fieldWritePermission = await checkPermission(
fdm,
"field",
"write",
b_id,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
index e734ee237..676d6d9cb 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
@@ -1,9 +1,9 @@
import {
+ checkPermission,
getCurrentSoilData,
getField,
getSoilAnalyses,
getSoilParametersDescription,
- hasPermission,
removeSoilAnalysis,
} from "@svenvw/fdm-core"
import { Plus } from "lucide-react"
@@ -96,25 +96,28 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
// Get soil parameter descriptions
const soilParameterDescription = getSoilParametersDescription()
- const fieldWritePermission = hasPermission(
+ const pathname = new URL(request.url).pathname
+ const fieldWritePermission = checkPermission(
fdm,
"field",
"write",
b_id,
session.principal_id,
- { fallback: true },
+ pathname,
+ false,
)
const soilAnalysisWritePermissionsEntries = await Promise.all(
soilAnalyses.map(async (analysis) => [
analysis.a_id,
- await hasPermission(
+ await checkPermission(
fdm,
"soil_analysis",
"write",
analysis.a_id,
session.principal_id,
- { fallback: true },
+ pathname,
+ false,
),
]),
)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
index 510f49865..df75e8b2d 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx
@@ -1,8 +1,8 @@
import {
+ checkPermission,
getField,
getSoilAnalysis,
getSoilParametersDescription,
- hasPermission,
updateSoilAnalysis,
} from "@svenvw/fdm-core"
import { ArrowLeft } from "lucide-react"
@@ -99,13 +99,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
(item: { parameter: string }) => soilAnalysis[item.parameter],
)
- const soilAnalysisWritePermission = await hasPermission(
+ const soilAnalysisWritePermission = await checkPermission(
fdm,
"soil_analysis",
"write",
a_id,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
index f1695b0e1..3216a7897 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx
@@ -1,4 +1,9 @@
-import { getFarms, getField, getFields, hasPermission } from "@svenvw/fdm-core"
+import {
+ checkPermission,
+ getFarms,
+ getField,
+ getFields,
+} from "@svenvw/fdm-core"
import {
data,
type LoaderFunctionArgs,
@@ -152,13 +157,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
},
]
- const fieldWritePermission = await hasPermission(
+ const fieldWritePermission = await checkPermission(
fdm,
"field",
"write",
b_id,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
if (fieldWritePermission) {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
index fdbcd6394..d59dd0c81 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
@@ -1,11 +1,11 @@
import {
+ checkPermission,
getCultivations,
getCurrentSoilData,
getFarms,
getFertilizerApplications,
getFertilizers,
getFields,
- hasPermission,
} from "@svenvw/fdm-core"
import {
data,
@@ -156,13 +156,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
(x) => x.parameter === "b_soiltype_agr",
)?.value ?? null
- const has_write_permission = await hasPermission(
+ const has_write_permission = await checkPermission(
fdm,
"field",
"write",
field.b_id,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
return {
b_id: field.b_id,
@@ -178,13 +179,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}),
)
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
index ca6a1267b..2f24bb5b7 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
@@ -1,5 +1,6 @@
import {
type CultivationCatalogue,
+ checkPermission,
getCultivations,
getCultivationsFromCatalogue,
getCurrentSoilData,
@@ -8,7 +9,6 @@ import {
getFertilizers,
getFields,
getHarvests,
- hasPermission,
} from "@svenvw/fdm-core"
import {
type LoaderFunctionArgs,
@@ -306,13 +306,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
cultivationCatalogue,
)
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
index 487655171..aae40d014 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
@@ -1,11 +1,11 @@
import { zodResolver } from "@hookform/resolvers/zod"
import {
+ checkPermission,
getFarm,
getFarms,
getFertilizer,
getFertilizerParametersDescription,
getFertilizers,
- hasPermission,
updateFertilizerFromCatalogue,
} from "@svenvw/fdm-core"
import {
@@ -113,13 +113,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
if (
editable &&
- !(await hasPermission(
+ !(await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
))
) {
editable = false
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
index 596d57776..e57fe4860 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
@@ -1,9 +1,9 @@
import {
+ checkPermission,
getFarm,
getFarms,
getFertilizerParametersDescription,
getFertilizers,
- hasPermission,
} from "@svenvw/fdm-core"
import {
data,
@@ -103,13 +103,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
})),
)
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Return user information from loader
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
index dc7545263..9e1dc027d 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx
@@ -1,6 +1,6 @@
import {
addDerogation,
- hasPermission,
+ checkPermission,
listDerogations,
removeDerogation,
} from "@svenvw/fdm-core"
@@ -41,13 +41,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
session.principal_id,
b_id_farm,
)
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
return { b_id_farm, derogations, farmWritePermission }
} catch (error) {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
index 6c7fa2cf6..0dc1bc5f2 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.grazing-intention.tsx
@@ -1,6 +1,6 @@
import {
+ checkPermission,
getGrazingIntentions,
- hasPermission,
setGrazingIntention,
} from "@svenvw/fdm-core"
import {
@@ -43,13 +43,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
session.principal_id,
b_id_farm,
)
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
return { b_id_farm, grazingIntentions, farmWritePermission }
} catch (error) {
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
index b11c847d5..91c6aa246 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import {
addOrganicCertification,
- hasPermission,
+ checkPermission,
listOrganicCertifications,
removeOrganicCertification,
} from "@svenvw/fdm-core"
@@ -94,13 +94,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
// For now we expect that a farm can have only 1 certification
const organicCertification = organicCertifications[0]
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
return { b_id_farm, organicCertification, farmWritePermission }
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
index 64efcd9cc..177f0c5f1 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
-import { getFarm, hasPermission, updateFarm } from "@svenvw/fdm-core"
+import { checkPermission, getFarm, updateFarm } from "@svenvw/fdm-core"
import { useEffect } from "react"
import { Form } from "react-hook-form"
import {
@@ -80,13 +80,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
statusText: "Farm is not found",
})
}
- const farmWritePermission = await hasPermission(
+ const farmWritePermission = await checkPermission(
fdm,
"farm",
"write",
b_id_farm,
session.principal_id,
- { fallback: true },
+ new URL(request.url).pathname,
+ false,
)
// Return user information from loader
From 0eec169a631b7739cd5fae997746290061268ead Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Thu, 27 Nov 2025 13:56:56 +0100
Subject: [PATCH 43/45] Make use of display: none and visibility: hidden for
consistency
---
.../blocks/cultivation/card-details.tsx | 46 +++---
.../blocks/fertilizer-applications/card.tsx | 21 ++-
.../blocks/fertilizer-applications/list.tsx | 87 +++++-----
.../app/components/blocks/fertilizer/form.tsx | 28 ++--
.../components/blocks/fertilizer/table.tsx | 19 ++-
.../app/components/blocks/fields/table.tsx | 93 +++++------
.../app/components/blocks/harvest/form.tsx | 153 +++++++++---------
.../app/components/blocks/rotation/table.tsx | 148 +++++++++--------
fdm-app/app/components/blocks/soil/form.tsx | 12 +-
fdm-app/app/components/blocks/soil/list.tsx | 44 ++---
...farm.$calendar.field.$b_id.soil._index.tsx | 39 +++--
...farm.$b_id_farm.$calendar.field._index.tsx | 18 ++-
...m.$b_id_farm.$calendar.rotation._index.tsx | 18 ++-
...id_farm.settings.organic-certification.tsx | 23 ++-
.../farm.$b_id_farm.settings.properties.tsx | 32 ++--
15 files changed, 416 insertions(+), 365 deletions(-)
diff --git a/fdm-app/app/components/blocks/cultivation/card-details.tsx b/fdm-app/app/components/blocks/cultivation/card-details.tsx
index 30b0b97c5..7bfc3cfba 100644
--- a/fdm-app/app/components/blocks/cultivation/card-details.tsx
+++ b/fdm-app/app/components/blocks/cultivation/card-details.tsx
@@ -22,6 +22,7 @@ import {
SelectTrigger,
SelectValue,
} from "~/components/ui/select"
+import { cn } from "~/lib/utils"
import {
CultivationDetailsFormSchema,
type CultivationDetailsFormSchemaType,
@@ -203,27 +204,30 @@ export function CultivationDetailsCard({
)}
/>
- {editable && (
-
-
- {form.formState.isSubmitting ||
- fetcher.state === "submitting" ? (
-
- ) : (
- "Bijwerken"
- )}
-
-
- )}
+
+
+ {form.formState.isSubmitting ||
+ fetcher.state === "submitting" ? (
+
+ ) : (
+ "Bijwerken"
+ )}
+
+
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
index a278b316d..b009e1afb 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx
@@ -15,6 +15,7 @@ import {
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog"
+import { cn } from "~/lib/utils"
import { FertilizerApplicationForm } from "./form"
import { FertilizerApplicationsList } from "./list"
import type { FertilizerApplication, FertilizerOption } from "./types.d"
@@ -158,14 +159,18 @@ export function FertilizerApplicationCard({
open={isDialogOpen}
onOpenChange={handleDialogOpenChange}
>
- {canCreateFertilizerApplication && (
-
-
-
- Toevoegen
-
-
- )}
+
+
+
+ Toevoegen
+
+
diff --git a/fdm-app/app/components/blocks/fertilizer-applications/list.tsx b/fdm-app/app/components/blocks/fertilizer-applications/list.tsx
index ac3f0e027..2dd9984e1 100644
--- a/fdm-app/app/components/blocks/fertilizer-applications/list.tsx
+++ b/fdm-app/app/components/blocks/fertilizer-applications/list.tsx
@@ -4,6 +4,7 @@ import { format } from "date-fns"
import { nl } from "date-fns/locale"
import { Circle, Diamond, Square, Trash, Triangle } from "lucide-react"
import { useFetcher } from "react-router"
+import { LoadingSpinner } from "~/components/custom/loadingspinner"
import { Button } from "~/components/ui/button"
import {
Empty,
@@ -26,7 +27,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from "~/components/ui/tooltip"
-import { LoadingSpinner } from "../../custom/loadingspinner"
+import { cn } from "~/lib/utils"
export function FertilizerApplicationsList({
fertilizerApplications,
@@ -124,49 +125,49 @@ export function FertilizerApplicationsList({
- {editable && (
-
-
-
-
-
+
+
+
+ {
+ if (
+ application.p_app_ids
+ ) {
+ handleDelete(
+ application.p_app_ids,
+ )
+ } else {
+ handleDelete([
+ application.p_app_id,
+ ])
}
- onClick={() => {
- if (
- application.p_app_ids
- ) {
- handleDelete(
- application.p_app_ids,
- )
- } else {
- handleDelete(
- [
- application.p_app_id,
- ],
- )
- }
- }}
- >
- {fetcher.state ===
- "submitting" ? (
-
- ) : (
-
- )}
-
-
-
- Verwijder
-
-
-
-
- )}
+ }}
+ >
+ {fetcher.state ===
+ "submitting" ? (
+
+ ) : (
+
+ )}
+
+
+
+ Verwijder
+
+
+
+
)
diff --git a/fdm-app/app/components/blocks/fertilizer/form.tsx b/fdm-app/app/components/blocks/fertilizer/form.tsx
index 3b20583c0..ed471faf0 100644
--- a/fdm-app/app/components/blocks/fertilizer/form.tsx
+++ b/fdm-app/app/components/blocks/fertilizer/form.tsx
@@ -22,6 +22,7 @@ import {
SelectTrigger,
SelectValue,
} from "~/components/ui/select"
+import { cn } from "~/lib/utils"
export interface FertilizerParameterDescriptionItem {
parameter: FertilizerParameters
@@ -258,19 +259,20 @@ export function FertilizerForm({
))}
- {editable && (
-
-
- {form.formState.isSubmitting
- ? "Meststof opslaan..."
- : "Meststof opslaan"}
-
-
- )}
+
+
+ {form.formState.isSubmitting
+ ? "Meststof opslaan..."
+ : "Meststof opslaan"}
+
+
diff --git a/fdm-app/app/components/blocks/fertilizer/table.tsx b/fdm-app/app/components/blocks/fertilizer/table.tsx
index 454e5a37b..e81c570e7 100644
--- a/fdm-app/app/components/blocks/fertilizer/table.tsx
+++ b/fdm-app/app/components/blocks/fertilizer/table.tsx
@@ -21,6 +21,7 @@ import {
TableHeader,
TableRow,
} from "~/components/ui/table"
+import { cn } from "~/lib/utils"
interface DataTableProps {
columns: ColumnDef[]
@@ -67,16 +68,14 @@ export function DataTable({
}
className="max-w-sm"
/>
- {canAddItem && (
-
-
-
-
- Meststof toevoegen
-
-
-
- )}
+
+
+
+
+ Meststof toevoegen
+
+
+
diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx
index 53e1cc0a4..ebbd6db2e 100644
--- a/fdm-app/app/components/blocks/fields/table.tsx
+++ b/fdm-app/app/components/blocks/fields/table.tsx
@@ -214,56 +214,57 @@ export function DataTable({
- {canAddItem && (
- <>
-
-
-
-
- {isFertilizerButtonDisabled ? (
-
-
- Bemesting
-
- ) : (
-
-
-
- Bemesting
-
-
- )}
-
-
-
- {fertilizerTooltipContent}
-
-
-
-
-
-
-
-
+
+
+
+
+ {isFertilizerButtonDisabled ? (
+
+
+ Bemesting
+
+ ) : (
+
- Nieuw perceel
+ Bemesting
-
-
- Voeg een nieuw perceel toe
-
-
-
- >
- )}
+ )}
+
+
+
+ {fertilizerTooltipContent}
+
+
+
+
+
+
+
+
+
+
+ Nieuw perceel
+
+
+
+
+ Voeg een nieuw perceel toe
+
+
+
diff --git a/fdm-app/app/components/blocks/harvest/form.tsx b/fdm-app/app/components/blocks/harvest/form.tsx
index 44dbb6b87..106d122b9 100644
--- a/fdm-app/app/components/blocks/harvest/form.tsx
+++ b/fdm-app/app/components/blocks/harvest/form.tsx
@@ -531,28 +531,28 @@ export function HarvestFormDialog(props: HarvestFormDialogProps) {
- {editable && (
-
- {fetcher.state === "submitting" ? (
-
-
-
- ) : null}
- Verwijderen
-
- )}
+
+ {fetcher.state === "submitting" ? (
+
+
+
+ ) : null}
+ Verwijderen
+
- {editable && (
-
- {form.formState.isSubmitting ? (
-
-
- Opslaan...
-
- ) : isHarvestUpdate ? (
- "Bijwerken"
- ) : (
- "Toevoegen"
- )}
-
- )}
+
+ {form.formState.isSubmitting ? (
+
+
+ Opslaan...
+
+ ) : isHarvestUpdate ? (
+ "Bijwerken"
+ ) : (
+ "Toevoegen"
+ )}
+
@@ -620,43 +619,43 @@ export function HarvestForm(props: HarvestFormDialogProps) {
className="grid lg:grid-cols-2 items-center gap-y-6 gap-x-8"
/>
- {editable && (
-
-
- {form.formState.isSubmitting ||
- fetcher.state === "submitting" ? (
-
-
-
- ) : null}
- Verwijderen
-
-
- {form.formState.isSubmitting ? (
-
-
- Opslaan...
-
- ) : isHarvestUpdate ? (
- "Bijwerken"
- ) : (
- "Toevoegen"
- )}
-
-
- )}
+
+
+ {form.formState.isSubmitting ||
+ fetcher.state === "submitting" ? (
+
+
+
+ ) : null}
+ Verwijderen
+
+
+ {form.formState.isSubmitting ? (
+
+
+ Opslaan...
+
+ ) : isHarvestUpdate ? (
+ "Bijwerken"
+ ) : (
+ "Toevoegen"
+ )}
+
+
diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx
index 9c9da23a9..fa4877077 100644
--- a/fdm-app/app/components/blocks/rotation/table.tsx
+++ b/fdm-app/app/components/blocks/rotation/table.tsx
@@ -278,81 +278,79 @@ export function DataTable
({
- {canAddItem && (
- <>
-
-
-
-
- {isFertilizerButtonDisabled ? (
-
-
- Bemesting
-
- ) : (
-
-
-
- Bemesting
-
-
- )}
-
-
-
- {fertilizerTooltipContent}
-
-
-
-
-
-
-
- {isHarvestButtonDisabled ? (
-
-
- Oogst toevoegen
-
- ) : harvestErrorMessage ? (
-
- notify.error(
- harvestErrorMessage,
- )
- }
- >
-
- Oogst toevoegen
-
- ) : (
-
-
-
- Oogst toevoegen
-
-
- )}
-
-
-
- {harvestTooltipContent}
-
-
-
- >
- )}
+
+
+
+
+ {isFertilizerButtonDisabled ? (
+
+
+ Bemesting
+
+ ) : (
+
+
+
+ Bemesting
+
+
+ )}
+
+
+
+ {fertilizerTooltipContent}
+
+
+
+
+
+
+
+ {isHarvestButtonDisabled ? (
+
+
+ Oogst toevoegen
+
+ ) : harvestErrorMessage ? (
+
+ notify.error(
+ harvestErrorMessage,
+ )
+ }
+ >
+
+ Oogst toevoegen
+
+ ) : (
+
+
+
+ Oogst toevoegen
+
+
+ )}
+
+
+
+ {harvestTooltipContent}
+
+
+
diff --git a/fdm-app/app/components/blocks/soil/form.tsx b/fdm-app/app/components/blocks/soil/form.tsx
index 4ecc888bb..29bbbe440 100644
--- a/fdm-app/app/components/blocks/soil/form.tsx
+++ b/fdm-app/app/components/blocks/soil/form.tsx
@@ -6,6 +6,7 @@ import { RemixFormProvider, useRemixForm } from "remix-hook-form"
import type { z } from "zod"
import { FormSchema } from "@/app/components/blocks/soil/formschema"
import type { SoilAnalysis } from "@/app/components/blocks/soil/types"
+import { DatePicker } from "~/components/custom/date-picker"
import { LoadingSpinner } from "~/components/custom/loadingspinner"
import { Button } from "~/components/ui/button"
import {
@@ -24,7 +25,7 @@ import {
SelectTrigger,
SelectValue,
} from "~/components/ui/select"
-import { DatePicker } from "../../custom/date-picker"
+import { cn } from "~/lib/utils"
export function SoilAnalysisForm(props: {
soilAnalysis: SoilAnalysis | undefined
@@ -226,7 +227,12 @@ export function SoilAnalysisForm(props: {
}
})}
- {editable &&
+
{form.formState.isSubmitting ? (
@@ -237,7 +243,7 @@ export function SoilAnalysisForm(props: {
"Opslaan"
)}
-
}
+
diff --git a/fdm-app/app/components/blocks/soil/list.tsx b/fdm-app/app/components/blocks/soil/list.tsx
index 9ee6984e0..2e6f123f8 100644
--- a/fdm-app/app/components/blocks/soil/list.tsx
+++ b/fdm-app/app/components/blocks/soil/list.tsx
@@ -72,28 +72,28 @@ export function SoilAnalysesList({
Bewerk
- {(canModifySoilAnalysis[analysis.a_id] ??
- true) ? (
- {
- handleDelete(analysis.a_id)
- }}
- >
- {fetcher.state === "submitting" ? (
-
-
- Verwijderen...
-
- ) : (
- "Verwijder"
- )}
-
- ) : null}
+ {
+ handleDelete(analysis.a_id)
+ }}
+ className={cn(
+ !canModifySoilAnalysis[analysis.a_id] ? "hidden" : "",
+ )}
+ >
+ {fetcher.state === "submitting" ? (
+
+
+ Verwijderen...
+
+ ) : (
+ "Verwijder"
+ )}
+
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
index 676d6d9cb..d1aadc1e5 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
@@ -25,6 +25,7 @@ import { getSession } from "~/lib/auth.server"
import { getTimeframe } from "~/lib/calendar"
import { handleActionError, handleLoaderError } from "~/lib/error"
import { fdm } from "~/lib/fdm.server"
+import { cn } from "~/lib/utils"
/**
* Loader function for the soil data page of a specific farm field.
@@ -165,14 +166,17 @@ export default function FarmFieldSoilOverviewBlock() {
Parameters
Analyses
- {loaderData.fieldWritePermission && (
-
-
-
- Bodemanalyse toevoegen
-
-
- )}
+
+
+
+ Bodemanalyse toevoegen
+
+
@@ -189,13 +193,18 @@ export default function FarmFieldSoilOverviewBlock() {
bodem bij te houden
- {loaderData.fieldWritePermission && (
-
-
- Bodemanalyse toevoegen
-
-
- )}
+
+
+ Bodemanalyse toevoegen
+
+
) : (
{
return [
@@ -263,13 +264,20 @@ export default function FarmFieldIndex() {
:(
- {loaderData.farmWritePermission && (
-
+
+
- Maak een perceel
+ Maak een perceel
-
- )}
+
+
{/*
*/}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
index 2f24bb5b7..ebeba99ae 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx
@@ -34,6 +34,7 @@ import { getCalendar, getTimeframe } from "~/lib/calendar"
import { clientConfig } from "~/lib/config"
import { handleLoaderError } from "~/lib/error"
import { fdm } from "~/lib/fdm.server"
+import { cn } from "~/lib/utils"
export const meta: MetaFunction = () => {
return [
@@ -382,13 +383,20 @@ export default function FarmRotationIndex() {
:(
- {loaderData.farmWritePermission && (
-
+
+
- Maak een perceel
+ Maak een perceel
-
- )}
+
+
>
) : (
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
index 91c6aa246..bdf442019 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
@@ -64,6 +64,7 @@ import { clientConfig } from "~/lib/config"
import { handleLoaderError } from "~/lib/error"
import { fdm } from "~/lib/fdm.server"
import { extractFormValuesFromRequest } from "~/lib/form"
+import { cn } from "~/lib/utils"
export const meta: MetaFunction = () => {
return [
@@ -321,16 +322,22 @@ export default function OrganicCertificationSettings() {
Dit bedrijf heeft geen bio-certificaat
- {farmWritePermission ? "Als dit bedrijf wel een bio-certificaat heeft, kunt u deze toevoegen." : "U hebt geen toestemming om een bio-certificaat voor dit bedrijf toe te voegen."}
+ {farmWritePermission
+ ? "Als dit bedrijf wel een bio-certificaat heeft, kunt u deze toevoegen."
+ : "U hebt geen toestemming om een bio-certificaat voor dit bedrijf toe te voegen."}
- {farmWritePermission && (
-
-
- Voeg toe
-
-
- )}
+
+
+
+ Voeg toe
+
+
+
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
index 177f0c5f1..08cbd1632 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
@@ -30,6 +30,7 @@ import { getSession } from "~/lib/auth.server"
import { handleActionError, handleLoaderError } from "~/lib/error"
import { fdm } from "~/lib/fdm.server"
import { extractFormValuesFromRequest } from "~/lib/form"
+import { cn } from "~/lib/utils"
const { isPostalCode } = validator
@@ -253,20 +254,23 @@ Wageningen"
- {loaderData.farmWritePermission && (
-
-
- {form.formState.isSubmitting && (
-
- )}
- Bijwerken
-
-
- )}
+
+
+ {form.formState.isSubmitting && }
+ Bijwerken
+
+
From 17575749303c1c155e678e666bef860802cd10bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Thu, 27 Nov 2025 14:49:51 +0100
Subject: [PATCH 44/45] Update changeset
---
.changeset/dark-wombats-slide.md | 2 +-
.changeset/jolly-pens-crash.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.changeset/dark-wombats-slide.md b/.changeset/dark-wombats-slide.md
index 12f70c6f1..80cc48679 100644
--- a/.changeset/dark-wombats-slide.md
+++ b/.changeset/dark-wombats-slide.md
@@ -2,4 +2,4 @@
"@svenvw/fdm-core": minor
---
-Added the `hasPermission` function that returns if the principal has permission to perform a specific action on a resource without throwing exceptions or creating an audit log.
+Added the optional `strict` parameter to the `checkPermission` function, which, when specified as false, disables audit logging, and throwing an exception if the principal has no permission.
diff --git a/.changeset/jolly-pens-crash.md b/.changeset/jolly-pens-crash.md
index 8a5b1be95..f817f18d5 100644
--- a/.changeset/jolly-pens-crash.md
+++ b/.changeset/jolly-pens-crash.md
@@ -2,4 +2,4 @@
"@svenvw/fdm-app": minor
---
-Various UI elements are now hidden if the current logged-in user doesn't have permission to perform the actions that they represent.
+Various UI elements are now hidden or grayed out if the current logged-in user doesn't have permission to perform the actions that they represent.
From d9754841a07268cc421ffc526fb28edf5b919a80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?=
Date: Thu, 27 Nov 2025 15:02:34 +0100
Subject: [PATCH 45/45] Fix typo
---
.../routes/farm.$b_id_farm.settings.organic-certification.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
index bdf442019..66ca969eb 100644
--- a/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
+++ b/fdm-app/app/routes/farm.$b_id_farm.settings.organic-certification.tsx
@@ -324,7 +324,7 @@ export default function OrganicCertificationSettings() {
{farmWritePermission
? "Als dit bedrijf wel een bio-certificaat heeft, kunt u deze toevoegen."
- : "U hebt geen toestemming om een bio-certificaat voor dit bedrijf toe te voegen."}
+ : "U heeft geen toestemming om een bio-certificaat voor dit bedrijf toe te voegen."}