diff --git a/.changeset/all-schools-lay.md b/.changeset/all-schools-lay.md new file mode 100644 index 000000000..ea0aaacda --- /dev/null +++ b/.changeset/all-schools-lay.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Add titles and descriptions to pages diff --git a/.changeset/fluffy-hotels-switch.md b/.changeset/fluffy-hotels-switch.md new file mode 100644 index 000000000..80f283263 --- /dev/null +++ b/.changeset/fluffy-hotels-switch.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Add a message at signin page that the app is still in development diff --git a/.changeset/warm-dryers-start.md b/.changeset/warm-dryers-start.md new file mode 100644 index 000000000..2a5866116 --- /dev/null +++ b/.changeset/warm-dryers-start.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Make fdm-app configurable for various settings, including the name diff --git a/fdm-app/.env.example b/fdm-app/.env.example index eb5d3597e..12563eca9 100644 --- a/fdm-app/.env.example +++ b/fdm-app/.env.example @@ -1,85 +1,90 @@ -# NodeJS Configuration -# Sets the environment for the application (development, production). -# Example: development, production +# ------------------------------------- +# General Application Configuration +# ------------------------------------- +# Sets the environment (development, production). Affects logging, error handling, etc. +# Required: Yes NODE_ENV= -# Secret key used to sign session cookies. -# Must be a strong, randomly generated string. Do not hardcode or commit this value to version control. Use a secure secret management system. -# Rotate this key periodically. Compromising this secret can allow attackers to hijack user sessions. -FDM_SESSION_SECRET= +# Public name of the application displayed in the UI. +# Required: Yes +VITE_FDM_NAME= -# DB configuration -# The hostname or IP address of the PostgreSQL database server. -POSTGRES_HOST= +# Port the application server will listen on. +# Required: Yes (Defaults may exist in deployment environment) +PORT= -# The port number on which the PostgreSQL database server is listening. Defaults to 5432 if not specified. -POSTGRES_PORT= +# ------------------------------------- +# Session Management +# ------------------------------------- +# Secret key used to sign session cookies. MUST be a strong, random string. +# Keep this secret and rotate periodically. +# Required: Yes +FDM_SESSION_SECRET= -# The name of the PostgreSQL database to connect to. +# ------------------------------------- +# Database Configuration (PostgreSQL) +# ------------------------------------- +# Required: Yes +POSTGRES_HOST= +POSTGRES_PORT=5432 # Default PostgreSQL port POSTGRES_DB= - -# The username used to authenticate with the PostgreSQL database server. POSTGRES_USER= - -# The password used to authenticate with the PostgreSQL database server. Ensure this is stored securely and not exposed in version control. POSTGRES_PASSWORD= -# Mapbox configuration -# API token for Mapbox services. Required to use Mapbox features. -# Obtain your token from the Mapbox website. Keep this token secure and do not expose it in version control. -MAPBOX_TOKEN= - -# Authentication configuration -# Secret key used BY better-auth +# ------------------------------------- +# Authentication (Better Auth & OAuth Providers) +# ------------------------------------- +# Secret key used BY the better-auth library. +# Required: Yes BETTER_AUTH_SECRET= -# URL of application used by better-auth +# Full base URL of this application (used for redirects by better-auth). +# Example: http://localhost:5173 or https://yourdomain.com +# Required: Yes BETTER_AUTH_URL= +# Google OAuth Credentials (Optional: Leave empty to disable Google Sign-In) +# Required: No GOOGLE_CLIENT_ID= - GOOGLE_CLIENT_SECRET= -# Microsoft OAuth2 client ID obtained from Azure Portal +# Microsoft OAuth Credentials (Optional: Leave empty to disable Microsoft Sign-In) +# Required: No MS_CLIENT_ID= - -# Microsoft OAuth2 client secret obtained from Azure Portal. Keep this secure and never commit to version control. MS_CLIENT_SECRET= -# URL of .fgb file that contains the fields available to be selected -AVAILABLE_FIELDS_URL= +# ------------------------------------- +# Map & Data Configuration +# ------------------------------------- +# Mapbox API token for displaying maps. +# Required: Yes (for map functionality) +MAPBOX_TOKEN= +# URL to the FlatGeobuf (.fgb) file containing selectable field geometries. +# Required: Yes (for field selection functionality) +AVAILABLE_FIELDS_URL= -# Sentry configuration -# Sentry organization +# ------------------------------------- +# Analytics & Error Tracking (Optional) +# ------------------------------------- +# To enable Sentry error tracking and performance monitoring, fill in ALL VITE_SENTRY_* variables below. +# Leave them empty to disable Sentry integration. +# Required: No VITE_SENTRY_ORG= - -# Sentry project VITE_SENTRY_PROJECT= - -# Sentry DSN VITE_SENTRY_DSN= - -# Sentry auth token +VITE_SENTRY_TRACE_SAMPLE_RATE=1.0 # Sample rate for performance monitoring (0.0 to 1.0) +VITE_SENTRY_REPLAY_SAMPLE_RATE=0.1 # Sample rate for session replay (0.0 to 1.0) +VITE_SENTRY_REPLAY_SAMPLE_RATE_ON_ERROR=1.0 # Sample rate for session replay when errors occur (0.0 to 1.0) +VITE_SENTRY_PROFILE_SAMPLE_RATE=1.0 # Sample rate for profiling (0.0 to 1.0) +VITE_SENTRY_SECURITY_REPORT_URI= # Used for CSP reporting + +# Sentry Auth Token (Required ONLY for uploading source maps during build, not for runtime) +# Required: No (for runtime) SENTRY_AUTH_TOKEN= -# Sentry trace sample rate -VITE_SENTRY_TRACE_SAMPLE_RATE= - -# Sentry replay sample rate -VITE_SENTRY_REPLAY_SAMPLE_RATE= - -# Sentry replay sample rate on error -VITE_SENTRY_REPLAY_SAMPLE_RATE_ON_ERROR= - -# Sentry profile sample rate -VITE_SENTRY_PROFILE_SAMPLE_RATE= - -# Sentry security report URI for CSP reporting -VITE_SENTRY_SECURITY_REPORT_URI= - -# Posthog configuration -# Posthog public key -VITE_PUBLIC_POSTHOG_KEY= -# Posthog host -VITE_PUBLIC_POSTHOG_HOST= \ No newline at end of file +# To enable PostHog product analytics, fill in BOTH variables below. +# Leave them empty to disable PostHog integration. +# Required: No +VITE_PUBLIC_POSTHOG_KEY= # Example: phc_... +VITE_PUBLIC_POSTHOG_HOST= # Example: https://eu.i.posthog.com diff --git a/fdm-app/README.md b/fdm-app/README.md index fe4301478..e6e780f5c 100644 --- a/fdm-app/README.md +++ b/fdm-app/README.md @@ -24,9 +24,25 @@ The `fdm-app` is a React application providing a user-friendly interface for vis pnpm add @svenvw/fdm-app ``` -3. **Configuration:** Set the required environment variables, including the database URL, API keys, and other relevant settings. Create a `.env` file in the root directory of your application and copy the values from the `.env.example` file. +3. **Configuration:** + Configure the application by setting environment variables. Create a `.env` file in the `fdm-app` directory by copying the provided `.env.example` file: -4. **Running the App:** + ```bash + cp .env.example .env + ``` + + Edit the `.env` file and provide values for the necessary variables. Key configuration areas include: + * **General:** Application name (`VITE_FDM_NAME`), environment (`NODE_ENV`). + * **Session:** A strong secret key (`FDM_SESSION_SECRET`). + * **Database:** Connection details for your PostgreSQL database. + * **Authentication:** Secrets and URLs for `better-auth` and optionally OAuth providers (Google, Microsoft). + * **Mapbox:** API token for map rendering (`MAPBOX_TOKEN`). + * **Data URLs:** Paths to external data files (`AVAILABLE_FIELDS_URL`). + * **Analytics (Optional):** Configuration for Sentry and/or PostHog. These services are disabled by default. To enable them, provide the relevant keys/DSNs as described in `.env.example`. + + Refer to the comments within the `.env.example` file for detailed explanations of each variable and whether it's required. **Never commit your `.env` file to version control.** + +4. **Running the App:** ```bash # Development mode pnpm dev diff --git a/fdm-app/app/components/blocks/farm.tsx b/fdm-app/app/components/blocks/farm.tsx index e13c9cebb..87eaa4d19 100644 --- a/fdm-app/app/components/blocks/farm.tsx +++ b/fdm-app/app/components/blocks/farm.tsx @@ -2,8 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { Form } from "react-router" import { RemixFormProvider, useRemixForm } from "remix-hook-form" import type { z } from "zod" - -import { Button } from "@/components/ui/button" +import { Button } from "~/components/ui/button" import { Card, CardContent, @@ -11,7 +10,7 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card" +} from "~/components/ui/card" import { FormControl, FormDescription, @@ -19,8 +18,8 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" +} from "~/components/ui/form" +import { Input } from "~/components/ui/input" import { LoadingSpinner } from "../custom/loadingspinner" export interface fertilizersListType { diff --git a/fdm-app/app/components/blocks/fields.tsx b/fdm-app/app/components/blocks/fields.tsx index aa89c08f4..987a94ddb 100644 --- a/fdm-app/app/components/blocks/fields.tsx +++ b/fdm-app/app/components/blocks/fields.tsx @@ -1,10 +1,11 @@ +import { useToast } from "@/hooks/use-toast" import type { FeatureCollection } from "geojson" +import { Check, ChevronsUpDown } from "lucide-react" import { useEffect, useState } from "react" import { Form, useNavigation } from "react-router" - -import { FieldMap } from "@/components/blocks/field-map" -// Components -import { Button } from "@/components/ui/button" +import { ClientOnly } from "remix-utils/client-only" +import { FieldMap } from "~/components/blocks/field-map" +import { Button } from "~/components/ui/button" import { Card, CardContent, @@ -12,7 +13,7 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card" +} from "~/components/ui/card" import { Command, CommandEmpty, @@ -20,25 +21,22 @@ import { CommandInput, CommandItem, CommandList, -} from "@/components/ui/command" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" +} from "~/components/ui/command" +import { Input } from "~/components/ui/input" +import { Label } from "~/components/ui/label" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" +} from "~/components/ui/popover" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select" -import { useToast } from "@/hooks/use-toast" -import { cn } from "@/lib/utils" -import { Check, ChevronsUpDown } from "lucide-react" -import { ClientOnly } from "remix-utils/client-only" +} from "~/components/ui/select" +import { cn } from "~/lib/utils" import { Skeleton } from "../ui/skeleton" interface CultivationOption { diff --git a/fdm-app/app/components/custom/atlas/atlas-panels.tsx b/fdm-app/app/components/custom/atlas/atlas-panels.tsx index 950026f2f..d24023d03 100644 --- a/fdm-app/app/components/custom/atlas/atlas-panels.tsx +++ b/fdm-app/app/components/custom/atlas/atlas-panels.tsx @@ -1,6 +1,13 @@ -import { LoadingSpinner } from "@/components/custom/loadingspinner" -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" -import { Button } from "@/components/ui/button" +import type { FeatureCollection } from "geojson" +import throttle from "lodash.throttle" +import { Check, Info } from "lucide-react" +import { useEffect, useState } from "react" +import { useMap } from "react-map-gl" +import type { MapBoxZoomEvent, MapEvent, MapMouseEvent } from "react-map-gl" +import { data, useFetcher } from "react-router" +import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert" +import { Button } from "~/components/ui/button" import { Card, CardContent, @@ -8,15 +15,8 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card" -import { cn } from "@/lib/utils" -import type { FeatureCollection } from "geojson" -import throttle from "lodash.throttle" -import { Check, Info } from "lucide-react" -import { useEffect, useState } from "react" -import { useMap } from "react-map-gl" -import type { MapBoxZoomEvent, MapEvent, MapMouseEvent } from "react-map-gl" -import { useFetcher } from "react-router" +} from "~/components/ui/card" +import { cn } from "~/lib/utils" export function FieldsPanelHover({ zoomLevelFields, diff --git a/fdm-app/app/components/custom/atlas/atlas-sources.tsx b/fdm-app/app/components/custom/atlas/atlas-sources.tsx index fdefd63c1..333b6b160 100644 --- a/fdm-app/app/components/custom/atlas/atlas-sources.tsx +++ b/fdm-app/app/components/custom/atlas/atlas-sources.tsx @@ -4,7 +4,7 @@ import throttle from "lodash.throttle" import { type Dispatch, type SetStateAction, useEffect, useState } from "react" import { Source, useMap } from "react-map-gl" import { generateFeatureClass } from "./atlas-functions" -import type { fieldsAvailableUrlType } from "./atlas.d" +import type { FieldsAvailableUrlType } from "./atlas.d" export function FieldsSourceNotClickable({ id, @@ -108,7 +108,7 @@ export function FieldsSourceAvailable({ children, }: { id: string - url: fieldsAvailableUrlType + url: FieldsAvailableUrlType zoomLevelFields: number children: JSX.Element }) { diff --git a/fdm-app/app/components/custom/atlas/atlas.d.tsx b/fdm-app/app/components/custom/atlas/atlas.d.tsx index f5a76b5c5..656643560 100644 --- a/fdm-app/app/components/custom/atlas/atlas.d.tsx +++ b/fdm-app/app/components/custom/atlas/atlas.d.tsx @@ -7,7 +7,7 @@ export interface MapFieldsProps { mapboxToken: string mapStyle: "mapbox://styles/mapbox/satellite-streets-v12" fieldsSelected: FeatureCollection | null - fieldsAvailableUrl: fieldsAvailableUrlType + fieldsAvailableUrl: FieldsAvailableUrlType fieldsSaved: FeatureCollection | null } diff --git a/fdm-app/app/components/custom/banner.tsx b/fdm-app/app/components/custom/banner.tsx index 10e5d6d14..c284f513f 100644 --- a/fdm-app/app/components/custom/banner.tsx +++ b/fdm-app/app/components/custom/banner.tsx @@ -1,7 +1,8 @@ -import { Button } from "@/components/ui/button" -import { Cookie, CookieIcon, X } from "lucide-react" +import { Cookie, X } from "lucide-react" import posthog from "posthog-js" import { useEffect, useState } from "react" +import { Button } from "~/components/ui/button" +import { clientConfig } from "~/lib/config" type ConsentType = "yes" | "no" | "undecided" @@ -36,7 +37,8 @@ export function Banner() { }, []) useEffect(() => { - if (consentGiven !== "undecided") { + // Set PostHog persistence based on consent, if PostHog is configured + if (clientConfig.analytics.posthog && consentGiven !== "undecided") { try { posthog.set_config({ persistence: @@ -116,7 +118,7 @@ export function Banner() {

- Cookies op FDM + {`Cookies op ${clientConfig.name}`}

@@ -132,9 +134,9 @@ export function Banner() {

- Wij gebruiken cookies enkel om FDM te + {`Wij gebruiken cookies enkel om ${clientConfig.name} te verbeteren, zodat we weten wat er goed en - fout gaat. + fout gaat.`}
Geen zorgen, we gebruiken ze niet voor advertenties en ook niet om je online te diff --git a/fdm-app/app/components/custom/combobox.tsx b/fdm-app/app/components/custom/combobox.tsx index e7b785297..0f69d5fca 100644 --- a/fdm-app/app/components/custom/combobox.tsx +++ b/fdm-app/app/components/custom/combobox.tsx @@ -1,6 +1,6 @@ +import { Check, ChevronsUpDown } from "lucide-react" import { type ReactNode, useMemo, useState } from "react" - -import { Button } from "@/components/ui/button" +import { Button } from "~/components/ui/button" import { Command, CommandEmpty, @@ -8,7 +8,7 @@ import { CommandInput, CommandItem, CommandList, -} from "@/components/ui/command" +} from "~/components/ui/command" import { FormControl, FormDescription, @@ -16,14 +16,13 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" +} from "~/components/ui/form" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" -import { cn } from "@/lib/utils" -import { Check, ChevronsUpDown } from "lucide-react" +} from "~/components/ui/popover" +import { cn } from "~/lib/utils" type optionType = { value: string diff --git a/fdm-app/app/components/custom/cultivation/form.tsx b/fdm-app/app/components/custom/cultivation/form.tsx index c41c575f7..504a3c84c 100644 --- a/fdm-app/app/components/custom/cultivation/form.tsx +++ b/fdm-app/app/components/custom/cultivation/form.tsx @@ -1,5 +1,15 @@ -import { Button } from "@/components/ui/button" -import { Calendar } from "@/components/ui/calendar" +import { zodResolver } from "@hookform/resolvers/zod" +import { format } from "date-fns/format" +import { nl } from "date-fns/locale/nl" +import { CalendarIcon } from "lucide-react" +import { useEffect } from "react" +import { Form } from "react-router" +import { RemixFormProvider, useRemixForm } from "remix-hook-form" +import type { z } from "zod" +import { Combobox } from "~/components/custom/combobox" +import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { Button } from "~/components/ui/button" +import { Calendar } from "~/components/ui/calendar" import { FormControl, FormDescription, @@ -7,23 +17,13 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" +} from "~/components/ui/form" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" -import { cn } from "@/lib/utils" -import { zodResolver } from "@hookform/resolvers/zod" -import { format } from "date-fns/format" -import { nl } from "date-fns/locale/nl" -import { CalendarIcon } from "lucide-react" -import { useEffect } from "react" -import { Form } from "react-router" -import { RemixFormProvider, useRemixForm } from "remix-hook-form" -import type { z } from "zod" -import { Combobox } from "../combobox" -import { LoadingSpinner } from "../loadingspinner" +} from "~/components/ui/popover" +import { cn } from "~/lib/utils" import { FormSchema } from "./schema" import type { CultivationsFormProps } from "./types" diff --git a/fdm-app/app/components/custom/cultivation/list.tsx b/fdm-app/app/components/custom/cultivation/list.tsx index 35ed9ce8a..c8ebaca67 100644 --- a/fdm-app/app/components/custom/cultivation/list.tsx +++ b/fdm-app/app/components/custom/cultivation/list.tsx @@ -1,8 +1,8 @@ -import { Button } from "@/components/ui/button" import { format } from "date-fns/format" import { Pencil, Trash2 } from "lucide-react" import { NavLink, useFetcher } from "react-router" -import { LoadingSpinner } from "../loadingspinner" +import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { Button } from "~/components/ui/button" import type { Cultivation } from "./types" interface Harvest { diff --git a/fdm-app/app/components/custom/error.tsx b/fdm-app/app/components/custom/error.tsx index 871bb1a4a..4206dc1e9 100644 --- a/fdm-app/app/components/custom/error.tsx +++ b/fdm-app/app/components/custom/error.tsx @@ -1,7 +1,7 @@ import { ArrowLeft, Copy, Home } from "lucide-react" import { useEffect, useState } from "react" import { NavLink } from "react-router" -import { Button } from "../ui/button" +import { Button } from "~/components/ui/button" /** * Displays a full-screen error block with tailored messaging and navigation options. diff --git a/fdm-app/app/components/custom/farm/farm-content.tsx b/fdm-app/app/components/custom/farm/farm-content.tsx index 575cae807..8f578f607 100644 --- a/fdm-app/app/components/custom/farm/farm-content.tsx +++ b/fdm-app/app/components/custom/farm/farm-content.tsx @@ -1,9 +1,9 @@ +import type { ReactNode } from "react" +import { Outlet } from "react-router" import { SidebarPage, type SidebarPageProps, -} from "@/components/custom/sidebar-page" -import type { ReactNode } from "react" -import { Outlet } from "react-router" +} from "~/components/custom/sidebar-page" interface FarmContentProps { sidebarItems?: SidebarPageProps["items"] diff --git a/fdm-app/app/components/custom/farm/farm-header.tsx b/fdm-app/app/components/custom/farm/farm-header.tsx index 060db55ee..ce5c41f3b 100644 --- a/fdm-app/app/components/custom/farm/farm-header.tsx +++ b/fdm-app/app/components/custom/farm/farm-header.tsx @@ -1,24 +1,23 @@ +import { ChevronDown } from "lucide-react" +import { NavLink } from "react-router" import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { Separator } from "@/components/ui/separator" -import { SidebarTrigger } from "@/components/ui/sidebar" - -import { Button } from "@/components/ui/button" +} from "~/components/ui/breadcrumb" +import { Button } from "~/components/ui/button" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" -import { useCalendarStore } from "@/store/calendar" -import { ChevronDown } from "lucide-react" -import { NavLink } from "react-router" +} from "~/components/ui/dropdown-menu" +import { Separator } from "~/components/ui/separator" +import { SidebarTrigger } from "~/components/ui/sidebar" +import { cn } from "~/lib/utils" +import { useCalendarStore } from "~/store/calendar" import type { FarmOptions, FertilizerOption, diff --git a/fdm-app/app/components/custom/farm/farm-pagination.tsx b/fdm-app/app/components/custom/farm/farm-pagination.tsx index bf34254e4..c8311d5b8 100644 --- a/fdm-app/app/components/custom/farm/farm-pagination.tsx +++ b/fdm-app/app/components/custom/farm/farm-pagination.tsx @@ -3,7 +3,7 @@ import { PaginationContent, PaginationItem, PaginationLink, -} from "@/components/ui/pagination" +} from "~/components/ui/pagination" import type { PaginationItems } from "./farm.d" interface PaginationLayoutProps { diff --git a/fdm-app/app/components/custom/farm/farm-title.tsx b/fdm-app/app/components/custom/farm/farm-title.tsx index e4946e7d2..4dc581312 100644 --- a/fdm-app/app/components/custom/farm/farm-title.tsx +++ b/fdm-app/app/components/custom/farm/farm-title.tsx @@ -1,4 +1,4 @@ -import { Separator } from "@/components/ui/separator" +import { Separator } from "~/components/ui/separator" interface FarmTitleProps { title: string diff --git a/fdm-app/app/components/custom/fertilizer-applications.tsx b/fdm-app/app/components/custom/fertilizer-applications.tsx index a104116a2..5aaa72066 100644 --- a/fdm-app/app/components/custom/fertilizer-applications.tsx +++ b/fdm-app/app/components/custom/fertilizer-applications.tsx @@ -1,6 +1,16 @@ -import { Combobox } from "@/components/custom/combobox" -import { Button } from "@/components/ui/button" -import { Calendar } from "@/components/ui/calendar" +import { zodResolver } from "@hookform/resolvers/zod" +import { format } from "date-fns" +import { CalendarIcon } from "lucide-react" +import { useEffect } from "react" +import { Form, useFetcher } from "react-router" +import { RemixFormProvider, useRemixForm } from "remix-hook-form" +import type { z } from "zod" +import { Combobox } from "~/components/custom/combobox" +import { FormSchema } from "~/components/custom/fertilizer-applications/formschema" +import type { FertilizerApplicationsFormProps } from "~/components/custom/fertilizer-applications/types.d" +import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { Button } from "~/components/ui/button" +import { Calendar } from "~/components/ui/calendar" import { FormControl, FormDescription, @@ -8,25 +18,15 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" +} from "~/components/ui/form" +import { Input } from "~/components/ui/input" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" -import { Separator } from "@/components/ui/separator" -import { cn } from "@/lib/utils" -import { zodResolver } from "@hookform/resolvers/zod" -import { format } from "date-fns" -import { CalendarIcon } from "lucide-react" -import { useEffect } from "react" -import { Form, useFetcher } from "react-router" -import { RemixFormProvider, useRemixForm } from "remix-hook-form" -import type { z } from "zod" -import { FormSchema } from "./fertilizer-applications/formschema" -import type { FertilizerApplicationsFormProps } from "./fertilizer-applications/types.d" -import { LoadingSpinner } from "./loadingspinner" +} from "~/components/ui/popover" +import { Separator } from "~/components/ui/separator" +import { cn } from "~/lib/utils" export function FertilizerApplicationsForm( props: FertilizerApplicationsFormProps, diff --git a/fdm-app/app/components/custom/fertilizer-applications/cards.tsx b/fdm-app/app/components/custom/fertilizer-applications/cards.tsx index 9c0dda288..84c3d4755 100644 --- a/fdm-app/app/components/custom/fertilizer-applications/cards.tsx +++ b/fdm-app/app/components/custom/fertilizer-applications/cards.tsx @@ -1,13 +1,13 @@ -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import type { Dose } from "@svenvw/fdm-calculator" +import { Lightbulb, Scale } from "lucide-react" +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip" -import { cn } from "@/lib/utils" -import type { Dose } from "@svenvw/fdm-calculator" -import { Lightbulb, Scale } from "lucide-react" +} from "~/components/ui/tooltip" +import { cn } from "~/lib/utils" import type { FertilizerApplicationsCardProps } from "./types.d" function FertilizerApplicationsCard({ diff --git a/fdm-app/app/components/custom/fertilizer-applications/form.tsx b/fdm-app/app/components/custom/fertilizer-applications/form.tsx index d87d42524..8874a1f25 100644 --- a/fdm-app/app/components/custom/fertilizer-applications/form.tsx +++ b/fdm-app/app/components/custom/fertilizer-applications/form.tsx @@ -1,5 +1,13 @@ -import { Button } from "@/components/ui/button" -import { Calendar } from "@/components/ui/calendar" +import { zodResolver } from "@hookform/resolvers/zod" +import { format } from "date-fns" +import { nl } from "date-fns/locale/nl" +import { CalendarIcon } from "lucide-react" +import { useEffect } from "react" +import { Form } from "react-hook-form" +import { RemixFormProvider, useRemixForm } from "remix-hook-form" +import type { z } from "zod" +import { Button } from "~/components/ui/button" +import { Calendar } from "~/components/ui/calendar" import { FormControl, FormDescription, @@ -7,22 +15,14 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" +} from "~/components/ui/form" +import { Input } from "~/components/ui/input" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" -import { cn } from "@/lib/utils" -import { zodResolver } from "@hookform/resolvers/zod" -import { format } from "date-fns" -import { nl } from "date-fns/locale/nl" -import { CalendarIcon } from "lucide-react" -import { useEffect } from "react" -import { Form } from "react-hook-form" -import { RemixFormProvider, useRemixForm } from "remix-hook-form" -import type { z } from "zod" +} from "~/components/ui/popover" +import { cn } from "~/lib/utils" import { Combobox } from "../combobox" import { LoadingSpinner } from "../loadingspinner" import { FormSchema } from "./formschema" diff --git a/fdm-app/app/components/custom/fertilizer-applications/list.tsx b/fdm-app/app/components/custom/fertilizer-applications/list.tsx index 585002496..fdefcf577 100644 --- a/fdm-app/app/components/custom/fertilizer-applications/list.tsx +++ b/fdm-app/app/components/custom/fertilizer-applications/list.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/components/ui/button" import { format } from "date-fns" +import { Button } from "~/components/ui/button" import { LoadingSpinner } from "../loadingspinner" import type { FertilizerApplication } from "./types.d" diff --git a/fdm-app/app/components/custom/fertilizer/column-header.tsx b/fdm-app/app/components/custom/fertilizer/column-header.tsx index dff88df81..27922207e 100644 --- a/fdm-app/app/components/custom/fertilizer/column-header.tsx +++ b/fdm-app/app/components/custom/fertilizer/column-header.tsx @@ -1,15 +1,14 @@ import type { Column } from "@tanstack/react-table" import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react" - -import { Button } from "@/components/ui/button" +import { Button } from "~/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" +} from "~/components/ui/dropdown-menu" +import { cn } from "~/lib/utils" interface DataTableColumnHeaderProps extends React.HTMLAttributes { diff --git a/fdm-app/app/components/custom/fertilizer/columns.tsx b/fdm-app/app/components/custom/fertilizer/columns.tsx index 1d228c4d1..7ac621530 100644 --- a/fdm-app/app/components/custom/fertilizer/columns.tsx +++ b/fdm-app/app/components/custom/fertilizer/columns.tsx @@ -1,13 +1,13 @@ -import { Badge } from "@/components/ui/badge" +import type { ColumnDef } from "@tanstack/react-table" +import { ArrowRight, Pencil, SquareArrowOutUpRight } from "lucide-react" +import { NavLink } from "react-router-dom" +import { Badge } from "~/components/ui/badge" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip" -import type { ColumnDef } from "@tanstack/react-table" -import { ArrowRight, Pencil, SquareArrowOutUpRight } from "lucide-react" -import { NavLink } from "react-router-dom" +} from "~/components/ui/tooltip" import { DataTableColumnHeader } from "./column-header" export type Fertilizer = { diff --git a/fdm-app/app/components/custom/fertilizer/form.tsx b/fdm-app/app/components/custom/fertilizer/form.tsx index 09c63da16..ccf9f0bab 100644 --- a/fdm-app/app/components/custom/fertilizer/form.tsx +++ b/fdm-app/app/components/custom/fertilizer/form.tsx @@ -1,6 +1,10 @@ -import { LoadingSpinner } from "@/components/custom/loadingspinner" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" +import type { UseFormReturn } from "react-hook-form" +import { Form } from "react-router" +import { RemixFormProvider } from "remix-hook-form" +import type { ZodType, z } from "zod" +import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { Badge } from "~/components/ui/badge" +import { Button } from "~/components/ui/button" import { Card, CardContent, @@ -8,26 +12,22 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card" +} from "~/components/ui/card" import { FormControl, FormDescription, FormField, FormItem, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" +} from "~/components/ui/form" +import { Input } from "~/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select" -import type { UseFormReturn } from "react-hook-form" -import { Form } from "react-router" -import { RemixFormProvider } from "remix-hook-form" -import type { ZodType, z } from "zod" +} from "~/components/ui/select" import type { Fertilizer } from "./columns" export function FertilizerForm({ diff --git a/fdm-app/app/components/custom/fertilizer/table.tsx b/fdm-app/app/components/custom/fertilizer/table.tsx index b2c3752d8..a0aea0003 100644 --- a/fdm-app/app/components/custom/fertilizer/table.tsx +++ b/fdm-app/app/components/custom/fertilizer/table.tsx @@ -1,13 +1,3 @@ -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table" import { type ColumnDef, type ColumnFiltersState, @@ -21,6 +11,16 @@ import { import { Plus } from "lucide-react" import { useState } from "react" import { NavLink } from "react-router" +import { Button } from "~/components/ui/button" +import { Input } from "~/components/ui/input" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "~/components/ui/table" interface DataTableProps { columns: ColumnDef[] diff --git a/fdm-app/app/components/custom/harvest/form.tsx b/fdm-app/app/components/custom/harvest/form.tsx index 5f6d5e3a1..96d3bbec4 100644 --- a/fdm-app/app/components/custom/harvest/form.tsx +++ b/fdm-app/app/components/custom/harvest/form.tsx @@ -1,5 +1,12 @@ -import { Button } from "@/components/ui/button" -import { Calendar } from "@/components/ui/calendar" +import { zodResolver } from "@hookform/resolvers/zod" +import { format } from "date-fns/format" +import { nl } from "date-fns/locale/nl" +import { CalendarIcon } from "lucide-react" +import { Form } from "react-hook-form" +import { RemixFormProvider, useRemixForm } from "remix-hook-form" +import type { z } from "zod" +import { Button } from "~/components/ui/button" +import { Calendar } from "~/components/ui/calendar" import { FormControl, FormDescription, @@ -7,21 +14,14 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" +} from "~/components/ui/form" +import { Input } from "~/components/ui/input" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" -import { cn } from "@/lib/utils" -import { zodResolver } from "@hookform/resolvers/zod" -import { format } from "date-fns/format" -import { nl } from "date-fns/locale/nl" -import { CalendarIcon } from "lucide-react" -import { Form } from "react-hook-form" -import { RemixFormProvider, useRemixForm } from "remix-hook-form" -import type { z } from "zod" +} from "~/components/ui/popover" +import { cn } from "~/lib/utils" import { LoadingSpinner } from "../loadingspinner" import { FormSchema } from "./schema" diff --git a/fdm-app/app/components/custom/harvest/list.tsx b/fdm-app/app/components/custom/harvest/list.tsx index 6a4bee7e8..0da3f76fc 100644 --- a/fdm-app/app/components/custom/harvest/list.tsx +++ b/fdm-app/app/components/custom/harvest/list.tsx @@ -1,7 +1,7 @@ -import { Button } from "@/components/ui/button" import { format } from "date-fns/format" import { Eye, Trash2 } from "lucide-react" import { NavLink, useFetcher } from "react-router" +import { Button } from "~/components/ui/button" import { LoadingSpinner } from "../loadingspinner" import type { Harvest, HarvestableType } from "./types" diff --git a/fdm-app/app/components/custom/loadingspinner.tsx b/fdm-app/app/components/custom/loadingspinner.tsx index 391ee3aae..ffc53039b 100644 --- a/fdm-app/app/components/custom/loadingspinner.tsx +++ b/fdm-app/app/components/custom/loadingspinner.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils" +import { cn } from "~/lib/utils" export interface ISVGProps extends React.SVGProps { size?: number diff --git a/fdm-app/app/components/custom/multi-select.tsx b/fdm-app/app/components/custom/multi-select.tsx index 54bf98374..af4b12510 100644 --- a/fdm-app/app/components/custom/multi-select.tsx +++ b/fdm-app/app/components/custom/multi-select.tsx @@ -10,8 +10,8 @@ import { } from "lucide-react" import * as React from "react" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" +import { Badge } from "~/components/ui/badge" +import { Button } from "~/components/ui/button" import { Command, CommandEmpty, @@ -20,13 +20,13 @@ import { CommandItem, CommandList, CommandSeparator, -} from "@/components/ui/command" +} from "~/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, -} from "@/components/ui/popover" -import { Separator } from "@/components/ui/separator" +} from "~/components/ui/popover" +import { Separator } from "~/components/ui/separator" import { cn } from "~/lib/utils" /** diff --git a/fdm-app/app/components/custom/sidebar-app.tsx b/fdm-app/app/components/custom/sidebar-app.tsx index 81a116399..26a0d880c 100644 --- a/fdm-app/app/components/custom/sidebar-app.tsx +++ b/fdm-app/app/components/custom/sidebar-app.tsx @@ -1,11 +1,37 @@ -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" +import * as Sentry from "@sentry/react" +import { + ArrowRightLeft, + BadgeCheck, + Calendar, + Check, + ChevronRight, + ChevronsUpDown, + Cookie, + GitPullRequestArrow, + House, + Languages, + LifeBuoy, + LogOut, + Map as MapIcon, + Scale, + Send, + Settings, + Shapes, + Sparkles, + Square, +} from "lucide-react" +import posthog from "posthog-js" +import { useEffect, useState } from "react" +import { Form, NavLink, useLocation } from "react-router" +import { toast } from "sonner" +import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar" +import { Badge } from "~/components/ui/badge" +import { Button } from "~/components/ui/button" import { Collapsible, CollapsibleContent, CollapsibleTrigger, -} from "@/components/ui/collapsible" +} from "~/components/ui/collapsible" import { DropdownMenu, DropdownMenuContent, @@ -14,7 +40,7 @@ import { DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" +} from "~/components/ui/dropdown-menu" import { Sidebar, SidebarContent, @@ -30,48 +56,24 @@ import { SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, -} from "@/components/ui/sidebar" -import { useIsMobile } from "@/hooks/use-mobile" -import { getCalendarSelection } from "@/lib/calendar" -import { useCalendarStore } from "@/store/calendar" -import { useFarmStore } from "@/store/farm" -import * as Sentry from "@sentry/react" -import { - ArrowRightLeft, - BadgeCheck, - Calendar, - Check, - ChevronRight, - ChevronsUpDown, - Cookie, - GitPullRequestArrow, - House, - Languages, - LifeBuoy, - LogOut, - Map as MapIcon, - Scale, - Send, - Settings, - Shapes, - Sparkles, - Sprout, - Square, -} from "lucide-react" -import posthog from "posthog-js" -import { useEffect, useState } from "react" -import { Form, NavLink, useLocation } from "react-router" -import { toast } from "sonner" +} from "~/components/ui/sidebar" +import { useIsMobile } from "~/hooks/use-mobile" +import { getCalendarSelection } from "~/lib/calendar" +import { clientConfig } from "~/lib/config" +import { useCalendarStore } from "~/store/calendar" +import { useFarmStore } from "~/store/farm" + +interface SideBarAppUserType { + id: string + name: string // Full name from session.user + email: string + image?: string | null | undefined + // Other properties from session.user might exist but are not needed here +} interface SideBarAppType { - user: { - firstname: string - surname: string - name: string - email: string - image: string | undefined - } - userName: string + user: SideBarAppUserType + userName: string // Display name, potentially different from user.name initials: string } @@ -140,20 +142,35 @@ export function SidebarApp(props: SideBarAppType) { const omBalanceLink = undefined const baatLink = undefined - try { - Sentry.setUser({ - fullName: user.name, - email: user.email, - }) - } catch (error) { - Sentry.captureException(error) + if (clientConfig.analytics.sentry) { + try { + Sentry.setUser({ + fullName: user.name, + email: user.email, + }) + } catch (error) { + Sentry.captureException(error) + } } - const [feedback, setFeedback] = useState() + + const [feedback, setFeedback] = useState | null>(null) const [isLoading, setIsLoading] = useState(true) useEffect(() => { - setFeedback(Sentry.getFeedback()) - setIsLoading(false) + try { + const feedbackInstance = Sentry.getFeedback() + if (feedbackInstance) { + setFeedback(feedbackInstance) + } else { + console.warn("Sentry.getFeedback() returned null or undefined.") + } + } catch (error) { + console.error("Failed to initialize Sentry feedback:", error) + } finally { + setIsLoading(false) + } }, []) if (isLoading) { @@ -161,6 +178,15 @@ export function SidebarApp(props: SideBarAppType) { } const openFeedbackForm = async () => { + if (!feedback || typeof feedback.createForm !== "function") { + console.error( + "Feedback object not available or missing createForm method.", + ) + toast.error( + "Feedback formulier is nog niet beschikbaar. Probeer het opnieuw.", + ) + return + } try { const form = await feedback.createForm() form.appendToDom() @@ -195,11 +221,13 @@ export function SidebarApp(props: SideBarAppType) { FDM

- FDM + + {clientConfig.name} +
@@ -448,18 +476,20 @@ export function SidebarApp(props: SideBarAppType) { Ondersteuning - - - - - Feedback - - - + {clientConfig.analytics.sentry ? ( + + + + + Feedback + + + + ) : null} @@ -475,7 +505,7 @@ export function SidebarApp(props: SideBarAppType) { > @@ -484,7 +514,7 @@ export function SidebarApp(props: SideBarAppType) {
- {`${user.firstname} ${user.surname}`} + {userName} {user.email} @@ -502,7 +532,6 @@ export function SidebarApp(props: SideBarAppType) {
- {/* */} {avatarInitials} @@ -567,7 +596,14 @@ export function SidebarApp(props: SideBarAppType) { -
-
- - - -
-
- -
-
- ) -} - -/** - * Processes the form submission to update field details. - * - * This function validates that the necessary URL parameters for the field and farm IDs are present. - * It extracts form data and session information from the incoming request, updates the field record, - * and, if applicable, updates the related cultivation data. If the submitted soil properties differ from - * the existing values, a new soil analysis entry is added. - * - * @param request - The HTTP request containing form submission and session data. - * @param params - An object with URL parameters including the field ID (b_id) and farm ID (b_id_farm). - * @returns A payload with a success message upon successful update. - * @throws {Error} If either the field ID or farm ID is missing. - */ -export async function action({ request, params }: ActionFunctionArgs) { - try { - const b_id = params.b_id - if (!b_id) { - throw new Error("missing: b_id") - } - const b_id_farm = params.b_id_farm - if (!b_id_farm) { - throw new Error("missing: b_id_farm") - } - - // Get the session - const session = await getSession(request) - - // Get timeframe from calendar store - const timeframe = getTimeframe(params) - - const formValues = await extractFormValuesFromRequest( - request, - FormSchema, - ) - - await updateField( - fdm, - session.principal_id, - b_id, - formValues.b_name, - undefined, - undefined, - undefined, - undefined, - undefined, - ) - - const cultivations = await getCultivations( - fdm, - session.principal_id, - b_id, - timeframe, - ) - if (cultivations && cultivations.length > 0) { - await updateCultivation( - fdm, - session.principal_id, - cultivations[0].b_lu, - formValues.b_lu_catalogue, - undefined, - undefined, - ) - - const currentSoilAnalysis = await getSoilAnalysis( - fdm, - session.principal_id, - b_id, - ) - const soilPropertiesChanged = - currentSoilAnalysis?.b_soiltype_agr !== - formValues.b_soiltype_agr || - currentSoilAnalysis?.b_gwl_class !== formValues.b_gwl_class || - currentSoilAnalysis?.a_p_al !== formValues.a_p_al || - currentSoilAnalysis?.a_p_cc !== formValues.a_p_cc || - currentSoilAnalysis?.a_som_loi !== formValues.a_som_loi - - if (soilPropertiesChanged) { - const currentYear = new Date().getFullYear() - const defaultDate = new Date(currentYear, 0, 1) - await addSoilAnalysis( - fdm, - session.principal_id, - defaultDate, - "user", - b_id, - 30, - defaultDate, - { - a_p_al: formValues.a_p_al, - a_p_cc: formValues.a_p_cc, - a_som_loi: formValues.a_som_loi, - b_soiltype_agr: formValues.b_soiltype_agr, - b_gwl_class: formValues.b_gwl_class, - }, - ) - } - - return dataWithSuccess("fields have been updated", { - message: `${formValues.b_name} is bijgewerkt! 🎉`, - }) - } - } catch (error) { - throw handleActionError(error) - } -} diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields._index.tsx index 9569309f6..5025f6343 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields._index.tsx @@ -1,10 +1,28 @@ -import { getSession } from "@/lib/auth.server" -import { getTimeframe } from "@/lib/calendar" -import { handleLoaderError } from "@/lib/error" -import { fdm } from "@/lib/fdm.server" -import { useCalendarStore } from "@/store/calendar" import { getFields } from "@svenvw/fdm-core" -import { type LoaderFunctionArgs, redirect } from "react-router" +import { + type LoaderFunctionArgs, + type MetaFunction, + redirect, +} from "react-router" +import { getSession } from "~/lib/auth.server" +import { getTimeframe } from "~/lib/calendar" +import { clientConfig } from "~/lib/config" +import { handleLoaderError } from "~/lib/error" +import { fdm } from "~/lib/fdm.server" + +// Meta +export const meta: MetaFunction = () => { + return [ + { + title: `Percelen beheren - Bedrijf toevoegen | ${clientConfig.name}`, + }, + { + name: "description", + content: + "Beheer de percelen van je bedrijf. Pas namen aan en bekijk perceelsinformatie.", + }, + ] +} /** * Loads the user's session and associated fields for a specified farm, redirecting to the route of the first field. diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx index 33c8017ba..4e977be2f 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx @@ -1,19 +1,3 @@ -import { SidebarPage } from "@/components/custom/sidebar-page" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { getSession } from "@/lib/auth.server" -import { getCalendar, getTimeframe } from "@/lib/calendar" -import { handleActionError, handleLoaderError } from "@/lib/error" -import { cn } from "@/lib/utils" -import { useCalendarStore } from "@/store/calendar" import { getCultivationsFromCatalogue, getFarm, @@ -28,13 +12,35 @@ import { data, } from "react-router" import { useLoaderData } from "react-router" -import { fdm } from "../lib/fdm.server" +import { SidebarPage } from "~/components/custom/sidebar-page" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "~/components/ui/breadcrumb" +import { Button } from "~/components/ui/button" +import { Separator } from "~/components/ui/separator" +import { SidebarInset, SidebarTrigger } from "~/components/ui/sidebar" +import { getSession } from "~/lib/auth.server" +import { getCalendar, getTimeframe } from "~/lib/calendar" +import { clientConfig } from "~/lib/config" +import { handleActionError, handleLoaderError } from "~/lib/error" +import { fdm } from "~/lib/fdm.server" +import { cn } from "~/lib/utils" // Meta export const meta: MetaFunction = () => { return [ - { title: "FDM App" }, - { name: "description", content: "Welcome to FDM!" }, + { + title: `Percelen beheren - Bedrijf toevoegen | ${clientConfig.name}`, + }, + { + name: "description", + content: + "Beheer de percelen van je bedrijf. Pas namen aan en bekijk perceelsinformatie.", + }, ] } diff --git a/fdm-app/app/routes/farm.create._index.tsx b/fdm-app/app/routes/farm.create._index.tsx index 9ab03eb20..ddbfa2aac 100644 --- a/fdm-app/app/routes/farm.create._index.tsx +++ b/fdm-app/app/routes/farm.create._index.tsx @@ -1,16 +1,3 @@ -import { Farm } from "@/components/blocks/farm" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { Separator } from "@/components/ui/separator" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { getSession } from "@/lib/auth.server" -import { handleActionError } from "@/lib/error" -import { extractFormValuesFromRequest } from "@/lib/form" import { PrincipalId, addFarm, @@ -27,13 +14,30 @@ import type { import { useLoaderData } from "react-router" import { redirectWithSuccess } from "remix-toast" import { z } from "zod" -import { fdm } from "../lib/fdm.server" +import { Farm } from "~/components/blocks/farm" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "~/components/ui/breadcrumb" +import { Separator } from "~/components/ui/separator" +import { SidebarInset, SidebarTrigger } from "~/components/ui/sidebar" +import { getSession } from "~/lib/auth.server" +import { clientConfig } from "~/lib/config" +import { handleActionError } from "~/lib/error" +import { fdm } from "~/lib/fdm.server" +import { extractFormValuesFromRequest } from "~/lib/form" // Meta export const meta: MetaFunction = () => { return [ - { title: "FDM App" }, - { name: "description", content: "Welcome to FDM!" }, + { title: `Bedrijf toevoegen | ${clientConfig.name}` }, + { + name: "description", + content: "Voeg een nieuw bedrijf toe.", + }, ] } diff --git a/fdm-app/app/routes/farm.tsx b/fdm-app/app/routes/farm.tsx index 1f355d869..0de954a50 100644 --- a/fdm-app/app/routes/farm.tsx +++ b/fdm-app/app/routes/farm.tsx @@ -1,10 +1,3 @@ -import { SidebarApp } from "@/components/custom/sidebar-app" -import { SidebarProvider } from "@/components/ui/sidebar" -import { SidebarInset } from "@/components/ui/sidebar" -import { auth, getSession } from "@/lib/auth.server" -import { handleActionError, handleLoaderError } from "@/lib/error" -import { useCalendarStore } from "@/store/calendar" -import { useFarmStore } from "@/store/farm" import posthog from "posthog-js" import { useEffect } from "react" import type { @@ -15,13 +8,25 @@ import type { import { redirect, useRoutes } from "react-router" import { useLoaderData, useMatches } from "react-router" import { Outlet } from "react-router-dom" +import { SidebarApp } from "~/components/custom/sidebar-app" +import { SidebarProvider } from "~/components/ui/sidebar" +import { SidebarInset } from "~/components/ui/sidebar" +import { auth, getSession } from "~/lib/auth.server" +import { clientConfig } from "~/lib/config" +import { handleActionError, handleLoaderError } from "~/lib/error" +import { useCalendarStore } from "~/store/calendar" +import { useFarmStore } from "~/store/farm" import Account from "./farm.account" import WhatsNew from "./farm.whats-new" export const meta: MetaFunction = () => { return [ - { title: "FDM App" }, - { name: "description", content: "Welcome to FDM!" }, + { title: `Dashboard | ${clientConfig.name}` }, + { + name: "description", + content: + "Beheer je bedrijfsgegevens, percelen en gewassen in één overzichtelijk dashboard.", + }, ] } @@ -100,8 +105,9 @@ export default function App() { setCalendar(initialCalendar) }, [initialCalendar, setCalendar]) + // Identify user if PostHog is configured useEffect(() => { - if (posthog && loaderData.user) { + if (clientConfig.analytics.posthog && loaderData.user) { posthog.identify(loaderData.user.id, { id: loaderData.user.id, email: loaderData.user.email, diff --git a/fdm-app/app/routes/farm.whats-new.tsx b/fdm-app/app/routes/farm.whats-new.tsx index eb9c712f4..ab7abef02 100644 --- a/fdm-app/app/routes/farm.whats-new.tsx +++ b/fdm-app/app/routes/farm.whats-new.tsx @@ -1,21 +1,36 @@ -import { FarmTitle } from "@/components/custom/farm/farm-title" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, -} from "@/components/ui/breadcrumb" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Separator } from "@/components/ui/separator" -import { SidebarTrigger } from "@/components/ui/sidebar" -import { getSession } from "@/lib/auth.server" -import { handleLoaderError } from "@/lib/error" import { formatDistanceToNow } from "date-fns" import { nl } from "date-fns/locale" import { Sparkles } from "lucide-react" import ReactMarkdown from "react-markdown" -import { type LoaderFunctionArgs, useLoaderData } from "react-router" +import { + type LoaderFunctionArgs, + type MetaFunction, + useLoaderData, +} from "react-router" import remarkGfm from "remark-gfm" +import { FarmTitle } from "~/components/custom/farm/farm-title" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, +} from "~/components/ui/breadcrumb" +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card" +import { Separator } from "~/components/ui/separator" +import { SidebarTrigger } from "~/components/ui/sidebar" +import { getSession } from "~/lib/auth.server" +import { clientConfig } from "~/lib/config" +import { handleLoaderError } from "~/lib/error" + +export const meta: MetaFunction = () => { + return [ + { title: `Wat is er nieuw? | ${clientConfig.name}` }, + { + name: "description", + content: `Blijf op de hoogte van de laatste ontwikkelingen en verbeteringen van ${clientConfig.name}.`, + }, + ] +} // Define the structure for a single update post export interface UpdatePost { @@ -26,12 +41,12 @@ export interface UpdatePost { isNew?: boolean } -// Sample data for update posts (replace with a database or CMS later) +// Data for update posts export const updatePosts: UpdatePost[] = [ { id: "update-1", - title: "Lancering MINAS2 🎉", - description: `MINAS2 is gelanceerd! Vanaf nu kun je bedrijven aanmaken, percelen toevoegen en bemestingen invullen. + title: `Lancering ${clientConfig.name} 🎉`, + description: `${clientConfig.name} is gelanceerd! Vanaf nu kun je bedrijven aanmaken, percelen toevoegen en bemestingen invullen. **Nieuwe features:** - Account aanmaken diff --git a/fdm-app/app/routes/signin.tsx b/fdm-app/app/routes/signin.tsx index 2e0654654..b68ec7e79 100644 --- a/fdm-app/app/routes/signin.tsx +++ b/fdm-app/app/routes/signin.tsx @@ -1,4 +1,10 @@ -import { Button } from "@/components/ui/button" +import { Check, Cookie, Info, MoveDown } from "lucide-react" +import type { LoaderFunctionArgs } from "react-router" +import { redirect } from "react-router" +import type { MetaFunction } from "react-router" +import { toast } from "sonner" +import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert" +import { Button } from "~/components/ui/button" import { Card, CardContent, @@ -6,15 +12,22 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card" -import { signIn } from "@/lib/auth-client" -import { auth } from "@/lib/auth.server" -import { handleLoaderError } from "@/lib/error" -import { cn } from "@/lib/utils" -import { Check, Cookie, MoveDown } from "lucide-react" -import type { LoaderFunctionArgs } from "react-router" -import { redirect } from "react-router" -import { toast } from "sonner" +} from "~/components/ui/card" +import { signIn } from "~/lib/auth-client" +import { auth } from "~/lib/auth.server" +import { clientConfig } from "~/lib/config" +import { handleLoaderError } from "~/lib/error" +import { cn } from "~/lib/utils" + +export const meta: MetaFunction = () => { + return [ + { title: `Aanmelden | ${clientConfig.name}` }, + { + name: "description", + content: `Meld je aan bij ${clientConfig.name} om toegang te krijgen tot je dashboard en je bedrijfsgegevens te beheren.`, + }, + ] +} /** * Checks for an existing user session and redirects authenticated users. @@ -59,7 +72,7 @@ export async function loader({ request }: LoaderFunctionArgs) { * @returns A React element representing the sign-in page. */ export default function SignIn() { - const handleSignInError = (provider: string, error: Error) => { + const handleSignInError = (provider: string, error: unknown) => { toast( `Er is helaas iets misgegaan bij het aanmelden met ${provider}. Probeer het opnieuw.`, ) @@ -82,19 +95,19 @@ export default function SignIn() {
FDM
- FDM + {clientConfig.name}
{/* End logo and title fix */}

- Maak een account aan bij FDM en krijg toegang tot: + {`Maak een account aan bij ${clientConfig.name} en krijg toegang tot:`}

@@ -157,6 +170,20 @@ export default function SignIn() {
+ + + +

+ Let op! +

+
+ +

+ {`${clientConfig.name} is nog in ontwikkeling. Functionaliteiten + kunnen nog ontbreken of veranderen.`} +

+
+
Aanmelden @@ -282,7 +309,8 @@ export default function SignIn() {
diff --git a/fdm-app/app/types/config.d.ts b/fdm-app/app/types/config.d.ts new file mode 100644 index 000000000..009ffd63d --- /dev/null +++ b/fdm-app/app/types/config.d.ts @@ -0,0 +1,55 @@ +export interface ServerConfig { + auth: { + fdm_session_secret: string + better_auth_secret: string + google?: { + clientSecret: string + } | null + microsoft?: { + clientSecret: string + } | null + } + database: { + password: string + user: string + database: string + host: string + port: number + } + integrations: { + mapbox: { + token: string + } + nmi?: { + api_key: string + } + } + analytics: { + sentry?: { + auth_token: string + } | null + } +} + +// Define the structure for client-safe configuration +export interface ClientConfig { + name: string + logo: string + logomark: string + analytics: { + sentry?: { + dsn: string + organization: string + project: string + trace_sample_rate: number + replay_sample_rate: number + replay_sample_rate_on_error: number + profile_sample_rate: number + security_report_uri: string + } | null + posthog?: { + key: string + host: string + } | null + } +} diff --git a/fdm-app/tsconfig.json b/fdm-app/tsconfig.json index d933a4b1c..7cb10fd38 100644 --- a/fdm-app/tsconfig.json +++ b/fdm-app/tsconfig.json @@ -27,7 +27,7 @@ "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { - "@/*": ["./app/*"], + "@/*": ["./*"], "~/*": ["./app/*"] }, "rootDirs": [".", "./.react-router/types"], diff --git a/fdm-app/vite.config.ts b/fdm-app/vite.config.ts index 9fba8b06b..229735913 100644 --- a/fdm-app/vite.config.ts +++ b/fdm-app/vite.config.ts @@ -3,17 +3,22 @@ import { sentryVitePlugin } from "@sentry/vite-plugin" import { defineConfig } from "vite" import tsconfigPaths from "vite-tsconfig-paths" +// Vite does not support loading config files from `/lib/config`... +const org = process.env.VITE_SENTRY_ORG +const authToken = process.env.SENTRY_AUTH_TOKEN +const project = process.env.VITE_SENTRY_PROJECT +let pluginSentry: ReturnType | undefined +if (org && authToken && project) { + pluginSentry = sentryVitePlugin({ + org: org, + authToken: authToken, + project: project, + telemetry: false, + }) +} + export default defineConfig({ - plugins: [ - reactRouter(), - tsconfigPaths(), - sentryVitePlugin({ - org: process.env.VITE_SENTRY_ORG, - authToken: process.env.SENTRY_AUTH_TOKEN, - project: process.env.VITE_SENTRY_PROJECT, - telemetry: false, - }), - ], + plugins: [reactRouter(), tsconfigPaths(), pluginSentry], define: { global: {}, },