@@ -577,7 +577,6 @@ export default function ReviewPage({
h={16}
variant="ghost"
onClick={onDiscard}
- leftIcon={}
size="sm"
px={8}
mr={4}
@@ -585,11 +584,12 @@ export default function ReviewPage({
borderColor="sentiment.negativeDefault"
color="sentiment.negativeDefault"
>
+
{t("discard-all-changes")}
);
diff --git a/app/src/app/[lng]/[inventory]/preferences/transportation/TransportationPage.tsx b/app/src/app/[lng]/[inventory]/preferences/transportation/TransportationPage.tsx
index 16708c561..75dbbbd9e 100644
--- a/app/src/app/[lng]/[inventory]/preferences/transportation/TransportationPage.tsx
+++ b/app/src/app/[lng]/[inventory]/preferences/transportation/TransportationPage.tsx
@@ -13,7 +13,7 @@ export default function TransportationPage({ t }: { t: TFunction }) {
{t("which-transportation")}
{t("which-transportation-description")}
-
+
{TRANSPORTATION_ITEMS.map(({ id, icon }) => (
{t("which-waste")}
{t("which-waste-description")}
-
+
{WASTE_ITEMS.map(({ id, icon }) => (
-
-
-
+
+
+
{t("my-profile")}
-
-
+
+
{t("my-files")}
-
-
+
+
{t("my-inventories")}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/app/src/app/[lng]/auth/check-email/page.tsx b/app/src/app/[lng]/auth/check-email/page.tsx
index 165eb1b5b..e405610ab 100644
--- a/app/src/app/[lng]/auth/check-email/page.tsx
+++ b/app/src/app/[lng]/auth/check-email/page.tsx
@@ -1,7 +1,7 @@
"use client";
import { useTranslation } from "@/i18n/client";
-import { Link } from "@chakra-ui/next-js";
+import { Link } from "@chakra-ui/react";
import { Button, Heading, Text } from "@chakra-ui/react";
import NextLink from "next/link";
import { useSearchParams } from "next/navigation";
diff --git a/app/src/app/[lng]/auth/forgot-password/page.tsx b/app/src/app/[lng]/auth/forgot-password/page.tsx
index 285024306..4bd3b241b 100644
--- a/app/src/app/[lng]/auth/forgot-password/page.tsx
+++ b/app/src/app/[lng]/auth/forgot-password/page.tsx
@@ -62,7 +62,7 @@ export default function ForgotPassword({
-
{suggestions.map((suggestion, i) => (
@@ -607,7 +611,7 @@ export default function ChatBot({
fontWeight="400"
whiteSpace="nowrap"
display="inline-block"
- isDisabled={inputDisabled}
+ disabled={inputDisabled}
>
{suggestion.preview}
@@ -634,19 +638,21 @@ export default function ChatBot({
{inputDisabled ? (
}
colorScheme="red"
aria-label={t("stop-generation")}
- />
+ >
+
+
) : (
}
color="content.tertiary"
aria-label={t("send-message")}
- isDisabled={inputDisabled}
- />
+ disabled={inputDisabled}
+ >
+
+
)}
diff --git a/app/src/components/ChatBot/chat-popover.tsx b/app/src/components/ChatBot/chat-popover.tsx
index 5e2a309a5..8191782ee 100644
--- a/app/src/components/ChatBot/chat-popover.tsx
+++ b/app/src/components/ChatBot/chat-popover.tsx
@@ -1,16 +1,9 @@
"use client";
import {
- Button,
Icon,
IconButton,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverCloseButton,
- PopoverContent,
PopoverHeader,
- PopoverTrigger,
useDisclosure,
} from "@chakra-ui/react";
import React from "react";
@@ -19,6 +12,17 @@ import ChatBot from "./chat-bot";
import { useTranslation } from "@/i18n/client";
import { AskAiIcon } from "../icons";
+import {
+ PopoverBody,
+ PopoverCloseTrigger,
+ PopoverContent,
+ PopoverRoot,
+ PopoverTitle,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+
+import { Button } from "@/components/ui/button";
+
export default function ChatPopover({
lng = "en",
inventoryId,
@@ -27,34 +31,34 @@ export default function ChatPopover({
lng?: string;
inventoryId: string;
}) {
- const { isOpen, onOpen, onClose } = useDisclosure();
+ const { open, onOpen, onClose } = useDisclosure();
const inputRef = React.useRef(null);
const { t } = useTranslation(lng, "chat");
return (
<>
-
{
- state.elements.popper.style.zIndex = "9999";
- },
- },
- ]}
+ inputRef.current}
+ onOpenChange={onOpen}
+ positioning={{
+ placement: "top-end",
+ }}
+ // closeOnBlur={false}
+ // strategy="fixed"
+ // modifiers={[
+ // {
+ // name: "zIndex",
+ // enabled: true,
+ // phase: "write",
+ // fn: ({ state }) => {
+ // state.elements.popper.style.zIndex = "9999";
+ // },
+ // },
+ // ]}
>
-
+
}
className="fixed z-30 bottom-16 right-16"
fontSize="button.md"
fontStyle="normal"
@@ -63,7 +67,9 @@ export default function ChatPopover({
py="26px"
fontFamily="heading"
aria-label={t("ai-expert")}
+ variant="solid"
>
+ {/* */}
{t("ask-ai")}
@@ -77,7 +83,7 @@ export default function ChatPopover({
>
{t("ask-ai-expert")}
-
+
-
+
>
);
}
diff --git a/app/src/components/HomePage/ActionCardSmall.tsx b/app/src/components/HomePage/ActionCardSmall.tsx
index 85093d641..36ddb4446 100644
--- a/app/src/components/HomePage/ActionCardSmall.tsx
+++ b/app/src/components/HomePage/ActionCardSmall.tsx
@@ -13,7 +13,7 @@ const ActionCardSmall: React.FC
= ({
title,
}) => {
return (
- = ({
-
+
);
};
diff --git a/app/src/components/HomePage/ActionCards.tsx b/app/src/components/HomePage/ActionCards.tsx
index 277046b18..c6b261bfe 100644
--- a/app/src/components/HomePage/ActionCards.tsx
+++ b/app/src/components/HomePage/ActionCards.tsx
@@ -23,64 +23,64 @@ export function ActionCards({
}) {
return (
-
-
+
-
-
-
-
-
-
-
-
-
-
- add-data-to-inventory
-
-
-
-
- add-data-to-inventory-description
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+ add-data-to-inventory
+
+
+
+
+ add-data-to-inventory-description
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/app/src/components/HomePage/AddCollaboratorButton.tsx b/app/src/components/HomePage/AddCollaboratorButton.tsx
index 4fc0bd874..31fcdd455 100644
--- a/app/src/components/HomePage/AddCollaboratorButton.tsx
+++ b/app/src/components/HomePage/AddCollaboratorButton.tsx
@@ -7,7 +7,7 @@ import ActionCardSmall from "./ActionCardSmall";
export function AddCollaboratorButton({ lng }: { lng: string }) {
const {
- isOpen: isModalOpen,
+ open: isModalOpen,
onOpen: onModalOpen,
onClose: onModalClose,
} = useDisclosure();
diff --git a/app/src/components/HomePage/AddCollaboratorModal/AddCollaboratorsModal.tsx b/app/src/components/HomePage/AddCollaboratorModal/AddCollaboratorsModal.tsx
index 8a030dfab..95c90d82e 100644
--- a/app/src/components/HomePage/AddCollaboratorModal/AddCollaboratorsModal.tsx
+++ b/app/src/components/HomePage/AddCollaboratorModal/AddCollaboratorsModal.tsx
@@ -2,17 +2,9 @@ import React, { useState } from "react";
import {
Box,
Button,
- Checkbox,
CheckboxGroup,
- Divider,
+ Separator,
HStack,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
Text,
} from "@chakra-ui/react";
import type { TFunction } from "i18next";
@@ -28,8 +20,17 @@ import LabelLarge from "@/components/Texts/Label";
import MultipleEmailInput from "./MultipleEmailInput";
import { UseErrorToast, UseSuccessToast } from "@/hooks/Toasts";
import { useTranslation } from "@/i18n/client";
+import {
+ DialogRoot,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+} from "@/components/ui/dialog";
+import { Checkbox } from "@/components/ui/checkbox";
-const AddCollaboratorsModal = ({
+const AddCollaboratorsDialog = ({
lng,
isOpen,
onClose,
@@ -84,18 +85,17 @@ const AddCollaboratorsModal = ({
};
return (
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
@@ -104,7 +104,7 @@ const AddCollaboratorsModal = ({
key={city.cityId}
my={"24px"}
mx={"32px"}
- isChecked={selectedCities.includes(city.cityId)}
+ checked={selectedCities.includes(city.cityId)}
onChange={() => handleCityChange(city.cityId)}
>
{city.name}
@@ -121,11 +121,11 @@ const AddCollaboratorsModal = ({
-
-
+
+
-
-
-
-
+
+
+
+
);
};
-export default AddCollaboratorsModal;
+export default AddCollaboratorsDialog;
diff --git a/app/src/components/HomePage/AddCollaboratorModal/MultipleEmailInput.tsx b/app/src/components/HomePage/AddCollaboratorModal/MultipleEmailInput.tsx
index 3dea345ee..08307112e 100644
--- a/app/src/components/HomePage/AddCollaboratorModal/MultipleEmailInput.tsx
+++ b/app/src/components/HomePage/AddCollaboratorModal/MultipleEmailInput.tsx
@@ -3,15 +3,15 @@ import {
Box,
Input,
Tag,
- TagCloseButton,
- TagLabel,
- Wrap,
HStack,
- Divider,
+ Separator,
+ Icon,
+ Flex,
} from "@chakra-ui/react";
-import { InfoOutlineIcon } from "@chakra-ui/icons";
+import { MdInfoOutline } from "react-icons/md";
import type { TFunction } from "i18next";
import { BodyLarge, BodyMedium } from "@/components/Texts/Body";
+import { Field } from "@/components/ui/field";
interface MultipleEmailInputProps {
t: TFunction;
@@ -61,46 +61,48 @@ const MultipleEmailInput: React.FC = ({
return (
-
+
+
+
{error ? (
-
+
) : (
-
+
)}
-
-
+
+
{emails.map((email, index) => (
-
- handleRemoveEmail(email)}
- />
-
+
+ handleRemoveEmail(email)}
+ />
+
+
))}
-
+
);
};
diff --git a/app/src/components/HomePage/DownloadAndShareModals/DownloadButtons.tsx b/app/src/components/HomePage/DownloadAndShareModals/DownloadButtons.tsx
index 554d262a7..b7524b837 100644
--- a/app/src/components/HomePage/DownloadAndShareModals/DownloadButtons.tsx
+++ b/app/src/components/HomePage/DownloadAndShareModals/DownloadButtons.tsx
@@ -1,18 +1,11 @@
import type { TFunction } from "i18next";
-import {
- Badge,
- Box,
- Button,
- CloseButton,
- Spacer,
- Text,
- useToast,
-} from "@chakra-ui/react";
+import { Badge, Box, Button, Icon, Spacer, Text } from "@chakra-ui/react";
import React, { MouseEventHandler } from "react";
-import { InfoOutlineIcon } from "@chakra-ui/icons";
-import { MdCheckCircleOutline } from "react-icons/md";
+import { MdCheckCircleOutline, MdInfoOutline } from "react-icons/md";
import { FiDownload } from "react-icons/fi";
+import { Toaster, toaster } from "@/components/ui/toaster";
+
const DownloadButtons = ({
t,
lng,
@@ -39,8 +32,6 @@ const DownloadButtons = ({
ERROR = "error",
}
- const toast = useToast();
-
const showToast = (
title: string,
description: string,
@@ -51,62 +42,60 @@ const DownloadButtons = ({
) => {
// Replace previous toast notifications
if (duration == null) {
- toast.closeAll();
+ toaster.dismiss();
}
- const animatedGradientClass = `bg-gradient-to-l from-brand via-brand_light to-brand bg-[length:200%_auto] animate-gradient`;
- toast({
+ toaster.create({
description: t(description),
- status: status,
- duration: duration,
- isClosable: true,
- render: ({
- onClose,
- }: {
- onClose: MouseEventHandler;
- }) => (
-
-
- {status === "info" || status === "error" ? (
-
- ) : (
-
- )}
-
- {t(title)}
-
-
-
- {status === "error" && (
- handleDownload("csv")}
- fontWeight="600"
- fontSize="16px"
- letterSpacing="1.25px"
- >
- {t("try-again")}
-
- )}
-
-
- ),
+ type: status,
+ duration: duration!,
+ // render: ({
+ // onClose,
+ // }: {
+ // onClose: MouseEventHandler;
+ // }) => (
+ //
+ //
+ // {status === "info" || status === "error" ? (
+ //
+ // ) : (
+ //
+ // )}
+ //
+ // {t(title)}
+ //
+ //
+ //
+ // {status === "error" && (
+ // handleDownload("csv")}
+ // fontWeight="600"
+ // fontSize="16px"
+ // letterSpacing="1.25px"
+ // >
+ // {t("try-again")}
+ //
+ // )}
+ //
+ //
+ // ),
});
};
@@ -171,8 +160,7 @@ const DownloadButtons = ({
my="16px"
mx="24px"
variant="ghost"
- leftIcon={}
- isDisabled={!isAvailable}
+ disabled={!isAvailable}
style={{
backgroundColor: "white",
color: "black",
@@ -181,6 +169,7 @@ const DownloadButtons = ({
justifyContent="flex-start"
onClick={() => handleDownload(format)}
>
+
@@ -206,6 +195,7 @@ const DownloadButtons = ({
)}
))}
+
);
};
diff --git a/app/src/components/HomePage/DownloadAndShareModals/ModalDownloadReport.tsx b/app/src/components/HomePage/DownloadAndShareModals/ModalDownloadReport.tsx
index b048ff31f..e2c928e1c 100644
--- a/app/src/components/HomePage/DownloadAndShareModals/ModalDownloadReport.tsx
+++ b/app/src/components/HomePage/DownloadAndShareModals/ModalDownloadReport.tsx
@@ -1,20 +1,18 @@
-import {
- Center,
- Divider,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalHeader,
- ModalOverlay,
- Text,
-} from "@chakra-ui/react";
+import { Box, Center, Text } from "@chakra-ui/react";
import type { TFunction } from "i18next";
import React from "react";
import DownloadButtons from "./DownloadButtons";
import ModalPublishButtons from "./PublishButtons";
import { InventoryResponse } from "@/util/types";
+import {
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogRoot,
+} from "@/components/ui/dialog";
+
const ModalDownloadReport = ({
t,
lng,
@@ -35,19 +33,18 @@ const ModalDownloadReport = ({
inventory: InventoryResponse;
}) => {
return (
-
-
-
-
+
+
+
{t("download-and-share")}
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
);
};
diff --git a/app/src/components/HomePage/DownloadAndShareModals/ModalPublish.tsx b/app/src/components/HomePage/DownloadAndShareModals/ModalPublish.tsx
index f771928de..7a0db6f70 100644
--- a/app/src/components/HomePage/DownloadAndShareModals/ModalPublish.tsx
+++ b/app/src/components/HomePage/DownloadAndShareModals/ModalPublish.tsx
@@ -1,17 +1,4 @@
-import {
- Box,
- Button,
- Divider,
- HStack,
- Img,
- Modal,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
-} from "@chakra-ui/react";
+import { Box, Button, HStack, Image, Text } from "@chakra-ui/react";
import type { TFunction } from "i18next";
import { api } from "@/services/api";
import { useState } from "react";
@@ -19,18 +6,29 @@ import { UnpublishedView } from "@/components/HomePage/DownloadAndShareModals/Un
import { PublishedView } from "@/components/HomePage/DownloadAndShareModals/PublishedView";
import { InventoryResponse } from "@/util/types";
+import {
+ DialogRoot,
+ DialogContent,
+ DialogHeader,
+ DialogBody,
+ DialogFooter,
+} from "@/components/ui/dialog";
+
const ModalPublish = ({
t,
isPublishOpen,
+ //Todo: resolve close action
onPublishClose,
inventoryId,
inventory,
+ setModalOpen,
}: {
t: TFunction;
isPublishOpen: boolean;
onPublishClose: () => void;
inventoryId: string;
inventory: InventoryResponse;
+ setModalOpen: (open: boolean) => void;
}) => {
const [isAuthorized, setIsAuthorized] = useState(false);
@@ -44,12 +42,11 @@ const ModalPublish = ({
};
return (
-
-
-
-
+ setModalOpen(e.open)}>
+
+
-
-
-
-
- {!inventory.isPublic ? (
-
- setIsAuthorized((isAuth: boolean) => !isAuth)
- }
- />
- ) : (
-
- )}
-
+
+
+
+
+ {!inventory.isPublic ? (
+
+ setIsAuthorized((isAuth: boolean) => !isAuth)
+ }
+ />
+ ) : (
+
+ )}
+
+
-
-
-
+
+
+
);
};
diff --git a/app/src/components/HomePage/DownloadAndShareModals/PublishButtons.tsx b/app/src/components/HomePage/DownloadAndShareModals/PublishButtons.tsx
index 0a382b34b..6e89a1489 100644
--- a/app/src/components/HomePage/DownloadAndShareModals/PublishButtons.tsx
+++ b/app/src/components/HomePage/DownloadAndShareModals/PublishButtons.tsx
@@ -1,8 +1,15 @@
import type { TFunction } from "i18next";
-import { Badge, Button, HStack, Text, VStack } from "@chakra-ui/react";
-import { Img } from "@react-email/components";
-import { ChevronRightIcon } from "@chakra-ui/icons";
+import {
+ Badge,
+ Button,
+ HStack,
+ Text,
+ VStack,
+ Image,
+ Icon,
+} from "@chakra-ui/react";
import React from "react";
+import { BsChevronRight } from "react-icons/bs";
const PublishButtons = ({
t,
@@ -38,9 +45,7 @@ const PublishButtons = ({
my="24px"
mx="24px"
variant="ghost"
- leftIcon={
}
- rightIcon={}
- isDisabled={!isAvailable}
+ disabled={!isAvailable}
style={{
backgroundColor: "white",
color: "black",
@@ -51,6 +56,7 @@ const PublishButtons = ({
w="full"
onClick={onClick}
>
+
@@ -84,11 +90,12 @@ const PublishButtons = ({
overflow: "hidden",
textOverflow: "ellipsis",
}}
- align="left"
+ textAlign="left"
>
{t(`${title}-description`)}
+
))}
>
diff --git a/app/src/components/HomePage/DownloadAndShareModals/PublishedView.tsx b/app/src/components/HomePage/DownloadAndShareModals/PublishedView.tsx
index 968be7c16..ad5a149f6 100644
--- a/app/src/components/HomePage/DownloadAndShareModals/PublishedView.tsx
+++ b/app/src/components/HomePage/DownloadAndShareModals/PublishedView.tsx
@@ -2,11 +2,11 @@ import i18next, { TFunction } from "i18next";
import {
Box,
Button,
- Divider,
HStack,
- ModalBody,
Text,
VStack,
+ Link,
+ Icon,
} from "@chakra-ui/react";
import { InventoryResponse } from "@/util/types";
import type { Locale } from "date-fns";
@@ -14,8 +14,14 @@ import { enUS, pt, de, es } from "date-fns/locale";
import { formatDistance } from "date-fns";
import { toZonedTime } from "date-fns-tz";
import { BlueSubtitle } from "@/components/Texts/BlueSubtitle";
-import { ExternalLinkIcon } from "@chakra-ui/icons";
import "dotenv/config";
+import { FiExternalLink } from "react-icons/fi";
+import {
+ DialogRoot,
+ DialogBody,
+ DialogContent,
+ DialogFooter,
+} from "@/components/ui/dialog";
export function PublishedView({
inventoryId,
@@ -50,12 +56,12 @@ export function PublishedView({
const URL = `${window.location.protocol}//${window.location.host}/${lng}/public/${inventoryId}`;
return (
-
+
{t("public-city-inventory")}
{t("manage-public-inventory-description")}
-
+
@@ -66,18 +72,14 @@ export function PublishedView({
- }
- >
-
-
+
+
+
+
+
+
-
+
);
}
diff --git a/app/src/components/HomePage/DownloadAndShareModals/UnpublishedView.tsx b/app/src/components/HomePage/DownloadAndShareModals/UnpublishedView.tsx
index 4d65c945c..82742e003 100644
--- a/app/src/components/HomePage/DownloadAndShareModals/UnpublishedView.tsx
+++ b/app/src/components/HomePage/DownloadAndShareModals/UnpublishedView.tsx
@@ -1,5 +1,12 @@
import type { TFunction } from "i18next";
-import { Checkbox, ModalBody, Text } from "@chakra-ui/react";
+import { Text } from "@chakra-ui/react";
+import {
+ DialogRoot,
+ DialogBody,
+ DialogContent,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Checkbox } from "@/components/ui/checkbox";
export function UnpublishedView({
checked,
@@ -11,14 +18,14 @@ export function UnpublishedView({
t: TFunction;
}) {
return (
-
+
{t("make-public")}
{t("make-public-description")}
-
+
{t("i-authorize")}
-
+
);
}
diff --git a/app/src/components/HomePage/DownloadButton.tsx b/app/src/components/HomePage/DownloadButton.tsx
index 8bee110dc..d2e1f5e32 100644
--- a/app/src/components/HomePage/DownloadButton.tsx
+++ b/app/src/components/HomePage/DownloadButton.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { useDisclosure } from "@chakra-ui/react";
+import { Card, useDisclosure } from "@chakra-ui/react";
import { FiDownload } from "react-icons/fi";
import type { TFunction } from "i18next";
import ModalDownloadReport from "./DownloadAndShareModals/ModalDownloadReport";
@@ -22,13 +22,13 @@ const DownloadButton: React.FC = ({
t,
}) => {
const {
- isOpen: isDownloadShareOpen,
+ open: isDownloadShareOpen,
onOpen: onDownloadShareOpen,
onClose: onDownloadShareClose,
} = useDisclosure();
const {
- isOpen: isPublishOpen,
+ open: isPublishOpen,
onOpen: onPublishOpen,
onClose: onPublishClose,
} = useDisclosure();
@@ -46,6 +46,8 @@ const DownloadButton: React.FC = ({
cityLocode={city?.locode}
/>
{}}
t={t}
isPublishOpen={isPublishOpen}
onPublishClose={onPublishClose}
diff --git a/app/src/components/HomePage/Hero.tsx b/app/src/components/HomePage/Hero.tsx
index 933e89eec..0636a15fc 100644
--- a/app/src/components/HomePage/Hero.tsx
+++ b/app/src/components/HomePage/Hero.tsx
@@ -1,15 +1,20 @@
// only render map on the client
import dynamic from "next/dynamic";
-import { Box, Heading, Icon, Spinner, Text, Tooltip } from "@chakra-ui/react";
+import { Box, Heading, Icon, Spinner, Text } from "@chakra-ui/react";
import { CircleFlag } from "react-circle-flags";
import { InventorySelect } from "@/components/InventorySelect";
-import { MdArrowOutward, MdGroup, MdOutlineAspectRatio } from "react-icons/md";
-import { InfoOutlineIcon } from "@chakra-ui/icons";
+import {
+ MdArrowOutward,
+ MdGroup,
+ MdInfoOutline,
+ MdOutlineAspectRatio,
+} from "react-icons/md";
import { Trans } from "react-i18next/TransWithoutContext";
import { getShortenNumberUnit, shortenNumber } from "@/util/helpers";
import type { TFunction } from "i18next";
import type { PopulationAttributes } from "@/models/Population";
import type { InventoryResponse } from "@/util/types";
+import { Tooltip } from "@/components/ui/tooltip";
const CityMap = dynamic(() => import("@/components/CityMap"), { ssr: false });
@@ -97,13 +102,13 @@ export function Hero({
>
-
)}
{`Source: OpenClimate`}
@@ -167,9 +171,12 @@ export function Hero({
})}
>
}
- placement="bottom-start"
+ positioning={{
+ placement: "bottom-start",
+ }}
>
-
)}
{`Source: OpenClimate`}
>
}
- placement="bottom-start"
+ positioning={{
+ placement: "bottom-start",
+ }}
>
- router.push(`/onboarding`), 0);
}
}
- }, [isInventoryLoading, inventory, inventoryIdFromParam, lng, router]);
+ }, [isInventoryLoading, inventory, inventoryIdFromParam, language, router]);
// query API data
// TODO maybe rework this logic into one RTK query:
@@ -175,7 +166,6 @@ export default function HomePage({
}
h="48px"
aria-label="activity-button"
fontSize="button.md"
@@ -186,6 +176,7 @@ export default function HomePage({
)
}
>
+
{t("add-new-inventory")}
@@ -196,13 +187,15 @@ export default function HomePage({
lng={language}
t={t}
/>
-
-
- {[
- t("tab-emission-inventory-calculation-title"),
- t("tab-emission-inventory-results-title"),
- ]?.map((tab, index) => (
-
+
+
+ {["calculation", "report"].map((tab, index) => (
+
{t(tab)}
-
+
))}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
>
) : (
-
-
-
- {formatPercent(thirdPartyProgress)}%{" "}
- connect-third-party-data
-
+
+ }
+ >
+ {formatPercent(thirdPartyProgress)}%{" "}
+ connect-third-party-data
-
-
-
- {formatPercent(uploadedProgress)}%{" "}
- uploaded-data
-
+
+ }
+ >
+ {formatPercent(uploadedProgress)}%{" "}
+ uploaded-data
diff --git a/app/src/components/HomePage/InventoryPreferencesCard.tsx b/app/src/components/HomePage/InventoryPreferencesCard.tsx
index 3fe306c97..a25fb294a 100644
--- a/app/src/components/HomePage/InventoryPreferencesCard.tsx
+++ b/app/src/components/HomePage/InventoryPreferencesCard.tsx
@@ -24,7 +24,7 @@ export function InventoryPreferencesCard({
return (
<>
{shouldShowInventoryPreferences && (
-
+
{t("set-inventory-preferences")}
@@ -47,12 +47,13 @@ export function InventoryPreferencesCard({
}
onClick={() => setShouldShowInventoryPreferences(false)}
aria-label={"close"}
- />
+ >
+
+
-
+
)}
>
);
diff --git a/app/src/components/InventorySelect.tsx b/app/src/components/InventorySelect.tsx
index 515c6ef57..a08063e2f 100644
--- a/app/src/components/InventorySelect.tsx
+++ b/app/src/components/InventorySelect.tsx
@@ -1,16 +1,6 @@
import { api, useGetCitiesAndYearsQuery } from "@/services/api";
import type { CityAndYearsResponse } from "@/util/types";
-import {
- Center,
- Icon,
- IconButton,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- Spinner,
- Text,
-} from "@chakra-ui/react";
+import { Center, Icon, IconButton, Spinner, Text } from "@chakra-ui/react";
import { useRouter } from "next/navigation";
import {
MdAdd,
@@ -18,6 +8,8 @@ import {
MdLocationOn,
MdOutlineLocationOn,
} from "react-icons/md";
+import { MenuRoot, MenuContent, MenuItem, MenuTrigger } from "./ui/menu";
+import { Button } from "./ui/button";
export const InventorySelect = ({
currentInventoryId,
@@ -41,11 +33,20 @@ export const InventorySelect = ({
};
return (
-
+
+
);
};
diff --git a/app/src/components/Modals/activity-modal/activity-form-modal.tsx b/app/src/components/Modals/activity-modal/activity-form-modal.tsx
index 4a80318d6..fc5280570 100644
--- a/app/src/components/Modals/activity-modal/activity-form-modal.tsx
+++ b/app/src/components/Modals/activity-modal/activity-form-modal.tsx
@@ -1,22 +1,10 @@
"use client";
import { api, useUpdateActivityValueMutation } from "@/services/api";
-import {
- Box,
- Button,
- Modal,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
- useToast,
-} from "@chakra-ui/react";
+import { Box, Button, Text } from "@chakra-ui/react";
import { FC } from "react";
import { SubmitHandler } from "react-hook-form";
import { TFunction } from "i18next";
-import { CheckCircleIcon } from "@chakra-ui/icons";
import { getInputMethodology } from "@/util/helpers";
import type { SuggestedActivity } from "@/util/form-schema";
import ActivityModalBody, { Inputs } from "./activity-modal-body";
@@ -28,6 +16,16 @@ import useActivityForm, {
} from "@/hooks/activity-value-form/use-activity-form";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import useEmissionFactors from "@/hooks/activity-value-form/use-emission-factors";
+import { toaster } from "@/components/ui/toaster";
+import {
+ DialogBackdrop,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogRoot,
+} from "@/components/ui/dialog";
+import { MdCheckCircle } from "react-icons/md";
interface AddActivityModalProps {
isOpen: boolean;
@@ -44,6 +42,7 @@ interface AddActivityModalProps {
targetActivityValue?: ActivityValue;
inventoryValue?: InventoryValue | null;
resetSelectedActivityValue: () => void;
+ setAddActivityDialogOpen: Function;
}
const AddActivityModal: FC = ({
@@ -60,6 +59,7 @@ const AddActivityModal: FC = ({
referenceNumber,
targetActivityValue,
resetSelectedActivityValue,
+ setAddActivityDialogOpen,
}) => {
const {
fields,
@@ -106,8 +106,6 @@ const AddActivityModal: FC = ({
handleSubmit(onSubmit)();
};
- const toast = useToast();
-
const [createActivityValue, { isLoading }] =
api.useCreateActivityValueMutation();
@@ -229,26 +227,9 @@ const AddActivityModal: FC = ({
if (response.data) {
setHasActivityData(!hasActivityData);
- toast({
- status: "success",
+ toaster.success({
duration: 1200,
title: t("activity-value-success"),
- render: ({ title }) => (
-
-
- {title}
-
- ),
});
reset();
onClose();
@@ -260,15 +241,14 @@ const AddActivityModal: FC = ({
handleManalInputValidationError(errorData.error.issues);
} else {
const error = response.error as FetchBaseQueryError;
- toast({
- status: "error",
+ toaster.error({
title: errorData.error?.message || t("activity-value-error"),
});
}
}
};
- const closeModalFunc = () => {
+ const onCloseDialog = () => {
onClose();
resetSelectedActivityValue();
reset({
@@ -282,19 +262,20 @@ const AddActivityModal: FC = ({
return (
<>
- setAddActivityDialogOpen(e.open)}
+ onExitComplete={onCloseDialog}
>
-
-
+
- = ({
borderColor="border.neutral"
>
{edit ? t("update-emission-data") : t("add-emission-data")}
-
-
+
+
= ({
clearErrors={clearErrors}
setValue={setValue}
/>
- = ({
fontWeight="semibold"
fontSize="button.md"
type="submit"
- isLoading={isLoading || updateLoading}
+ loading={isLoading || updateLoading}
onClick={submit}
p={0}
m={0}
>
{edit ? t("update-emission-data") : t("add-emission-data")}
-
-
-
+
+
+
>
);
};
diff --git a/app/src/components/Modals/activity-modal/activity-modal-body.tsx b/app/src/components/Modals/activity-modal/activity-modal-body.tsx
index dfa1b9413..cb0946229 100644
--- a/app/src/components/Modals/activity-modal/activity-modal-body.tsx
+++ b/app/src/components/Modals/activity-modal/activity-modal-body.tsx
@@ -1,14 +1,12 @@
import {
Box,
- FormControl,
- FormLabel,
Grid,
+ Group,
Heading,
HStack,
+ Icon,
Input,
- InputGroup,
- ModalBody,
- Select,
+ SelectValueText,
Spinner,
Text,
Textarea,
@@ -16,7 +14,6 @@ import {
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
import BuildingTypeSelectInput from "../../building-select-input";
-import { InfoOutlineIcon, WarningIcon } from "@chakra-ui/icons";
import { TFunction } from "i18next";
import {
Control,
@@ -36,9 +33,17 @@ import { ExtraField, Methodology, SuggestedActivity } from "@/util/form-schema";
import { ActivityValue } from "@/models/ActivityValue";
import FormattedNumberInput from "@/components/formatted-number-input";
import PercentageBreakdownInput from "@/components/percentage-breakdown-input";
-import { RadioButton } from "@/components/radio-button";
import { EmissionFactorTypes } from "@/hooks/activity-value-form/use-emission-factors";
import DependentSelectInput from "@/components/dependent-select-input";
+import { DialogBody } from "@/components/ui/dialog";
+import { Field } from "@/components/ui/field";
+import { Radio, RadioGroup } from "@/components/ui/radio";
+
+import { MdInfoOutline, MdWarning } from "react-icons/md";
+import {
+ NativeSelectField,
+ NativeSelectRoot,
+} from "@/components/ui/native-select";
interface AddActivityModalBodyProps {
t: TFunction;
@@ -121,7 +126,7 @@ const ActivityModalBody = ({
control,
defaultValue: selectedActivity?.prefills?.[0].value,
});
- const { getRootProps, getRadioProps, value } = useRadioGroup(field);
+ const { getRootProps, getItemProps, value } = useRadioGroup(field);
let prefix = "";
const [isEmissionFactorInputDisabled, setIsEmissionFactorInputDisabled] =
@@ -172,7 +177,7 @@ const ActivityModalBody = ({
setIsEmissionFactorInputDisabled(true);
}
}
- }, [emissionsFactorTypes, emissionFactorTypeValue]);
+ }, [emissionsFactorTypes, emissionFactorTypeValue, setValue, t]);
const filteredFields = fields.filter((f) => {
return !(f.id.includes("-source") && f.type === "text");
@@ -183,38 +188,39 @@ const ActivityModalBody = ({
);
return (
-
+
) : null}
{methodology?.id.includes("direct-measure") && (
-
-
- {t("emissions-value-co2")}
-
+
@@ -551,7 +575,7 @@ const ActivityModalBody = ({
{(errors?.activity?.["CO2EmissionFactor"] as any) ? (
-
+
{t("emission-amount-form-error")}
@@ -559,22 +583,19 @@ const ActivityModalBody = ({
) : (
""
)}
-
-
-
- {t("emissions-value-n2o")}
-
+
+
@@ -583,7 +604,7 @@ const ActivityModalBody = ({
{(errors?.activity?.["N2OEmissionFactor"] as any) ? (
-
+
{t("emission-amount-form-error")}
@@ -591,22 +612,19 @@ const ActivityModalBody = ({
) : (
""
)}
-
-
-
- {t("emissions-value-ch4")}
-
+
+
@@ -615,7 +633,7 @@ const ActivityModalBody = ({
{(errors?.activity?.["CH4EmissionFactor"] as any) ? (
-
+
{t("emission-amount-form-error")}
@@ -623,7 +641,7 @@ const ActivityModalBody = ({
) : (
""
)}
-
+
)}
{!methodology?.id.includes("direct-measure") &&
@@ -636,8 +654,7 @@ const ActivityModalBody = ({
display="flex"
alignItems="center"
>
-
{t("emissions-factor-values")}
-
+
-
-
-
-
- {t("co2-emission-factor")}
-
+
+
+
{areEmissionFactorsLoading ? (
) : (
@@ -678,10 +694,10 @@ const ActivityModalBody = ({
)}
-
+
{(errors?.activity?.["CO2EmissionFactor"] as any) ? (
-
+
{t("emission-amount-form-error")}
@@ -695,28 +711,20 @@ const ActivityModalBody = ({
h="16px"
>
)}
-
-
-
- {t("n2o-emission-factor")}
-
+
+
{areEmissionFactorsLoading ? (
) : (
-
+
kg/
{methodology.id.includes("energy-consumption") ||
methodology.id.includes("electricity-consumption")
@@ -727,7 +735,7 @@ const ActivityModalBody = ({
{(errors?.activity?.["N2OEmissionFactor"] as any) ? (
-
+
{t("emission-amount-form-error")}
@@ -741,25 +749,22 @@ const ActivityModalBody = ({
h="16px"
>
)}
-
-
-
- {t("ch4-emission-factor")}
-
+
+
{areEmissionFactorsLoading ? (
) : (
@@ -773,7 +778,7 @@ const ActivityModalBody = ({
{(errors?.activity?.["CH4EmissionFactor"] as any) ? (
-
+
{t("emission-amount-form-error")}
@@ -787,91 +792,109 @@ const ActivityModalBody = ({
h="16px"
>
)}
-
- {" "}
+
+
>
)}
-
-
- {t("data-quality")}
-
- {errors.activity?.dataQuality ? (
-
-
- {t("data-quality-form-label")}
-
- ) : (
- ""
- )}
-
- {sourceField && (
-
- {t("data-source")}
-
-
+
+ (
+
-
+ h="full"
+ shadow="1dp"
+ >
+ {
+ field.onChange(e.target.value);
+ setValue("activity.dataQuality", e.target.value);
+ }}
+ >
+
+
+
+
+
+ )}
+ />
+ {errors.activity?.dataQuality ? (
+
+
+ {t("data-quality-form-label")}
+
+ ) : (
+ ""
+ )}
+
+ {sourceField && (
+
+
+
{(errors?.activity?.[sourceField.id] as any) ? (
-
+
{" "}
{errors?.activity?.[sourceField.id]?.message}{" "}
@@ -880,13 +903,13 @@ const ActivityModalBody = ({
) : (
""
)}
-
+
)}
-
- {t("data-comments")}
{errors.activity?.dataComments ? (
-
+
{" "}
{errors?.activity?.dataComments?.message}{" "}
@@ -925,11 +948,10 @@ const ActivityModalBody = ({
) : (
""
)}
-
+
-
-
+
{t("gwp-info-prefix")}{" "}
@@ -938,7 +960,7 @@ const ActivityModalBody = ({
-
+
);
};
diff --git a/app/src/components/Modals/add-file-data-modal.tsx b/app/src/components/Modals/add-file-data-dialog.tsx
similarity index 81%
rename from app/src/components/Modals/add-file-data-modal.tsx
rename to app/src/components/Modals/add-file-data-dialog.tsx
index e9b6e6417..d889a2da2 100644
--- a/app/src/components/Modals/add-file-data-modal.tsx
+++ b/app/src/components/Modals/add-file-data-dialog.tsx
@@ -1,23 +1,7 @@
import React, { FC, useEffect, useState } from "react";
-import {
- Box,
- Button,
- Checkbox,
- Divider,
- FormControl,
- FormLabel,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
- useToast,
-} from "@chakra-ui/react";
+import { Box, Icon, Separator, Text } from "@chakra-ui/react";
import DropdownSelectInput from "../dropdown-select-input";
-import { InfoIcon, InfoOutlineIcon } from "@chakra-ui/icons";
+
import {
DataStep,
SubSectorWithRelations,
@@ -32,12 +16,29 @@ import {
UserFileResponse,
UserInfoResponse,
} from "@/util/types";
-import { MdOutlineInsertDriveFile } from "react-icons/md";
+import { MdInfoOutline, MdOutlineInsertDriveFile } from "react-icons/md";
import { appendFileToFormData } from "@/util/helpers";
import { api, useAddUserFileMutation } from "@/services/api";
-interface AddFileDataModalProps {
+import {
+ DialogBackdrop,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogRoot,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { toaster } from "../ui/toaster";
+import { Button } from "../ui/button";
+import { Field } from "../ui/field";
+import { Checkbox } from "../ui/checkbox";
+
+interface AddFileDataDialogProps {
isOpen: boolean;
+ onOpenChange: (isOpen: boolean) => void;
onClose: () => void;
subsectors: SubSectorWithRelations[] | null;
t: TFunction;
@@ -64,7 +65,8 @@ const scopes = [
},
];
-const AddFileDataModal: FC = ({
+const AddFileDataDialog: FC = ({
+ onOpenChange,
isOpen,
onClose,
subsectors,
@@ -113,8 +115,6 @@ const AddFileDataModal: FC = ({
const cityId = inventoryData?.city.cityId!;
- const toast = useToast();
-
const onSubmit: SubmitHandler = async (data) => {
const base64FileString = await fileToBase64(uploadedFile);
const filename = uploadedFile.name;
@@ -138,17 +138,15 @@ const AddFileDataModal: FC = ({
await addUserFile({ formData, cityId }).then((res: any) => {
// show toast
if (res.error) {
- toast({
+ toaster.error({
title: t("file-upload-error"),
description: t("file-upload-error-description"),
- status: "error",
duration: 2000,
});
} else {
- toast({
+ toaster.success({
title: t("file-upload-success"),
description: t("file-upload-success"),
- status: "success",
duration: 2000,
});
@@ -180,10 +178,14 @@ const AddFileDataModal: FC = ({
};
return (
-
-
-
- onOpenChange(e.open)}
+ onExitComplete={onClose}
+ >
+
+
+ = ({
borderColor="border.neutral"
>
{t("file-context")}
-
-
-
+
+
+
= ({
>
{t("file-data-description")}
-
+
-
-
-
- {t("scopes")}
-
+
+ {t("scopes")}}>
{scopes.map((scope) => (
= ({
gap="8px"
>
+ onChange={(e: any) =>
handleSelectedScopes(scope.value, e.target.checked)
}
checked={selectedScopes.includes(scope.value)}
@@ -294,12 +299,12 @@ const AddFileDataModal: FC = ({
)}
-
+
-
-
+ = ({
justifyContent="space-around"
>
= ({
bg="interactive.secondary"
h="64px"
w="316px"
- isLoading={isLoading}
+ loading={isLoading}
onClick={handleSubmit(onSubmit)}
>
{t("upload")}
-
-
-
+
+
+
);
};
-export default AddFileDataModal;
+export default AddFileDataDialog;
diff --git a/app/src/components/Modals/change-methodology.tsx b/app/src/components/Modals/change-methodology.tsx
index ea0d228d7..845e2bee0 100644
--- a/app/src/components/Modals/change-methodology.tsx
+++ b/app/src/components/Modals/change-methodology.tsx
@@ -1,25 +1,21 @@
"use client";
-import {
- Box,
- Button,
- Icon,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
- useToast,
-} from "@chakra-ui/react";
+import { Box, Button, DialogHeader, Icon, Text } from "@chakra-ui/react";
import React, { FC } from "react";
import { TFunction } from "i18next";
-import { CheckCircleIcon } from "@chakra-ui/icons";
import { useDeleteAllActivityValuesMutation } from "@/services/api";
import { ChangeMethodologyIcon } from "../icons";
import { Trans } from "react-i18next";
+import { toaster } from "../ui/toaster";
+import {
+ DialogBackdrop,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogRoot,
+ DialogTitle,
+} from "../ui/dialog";
interface ChangeMethodologyProps {
isOpen: boolean;
@@ -28,6 +24,7 @@ interface ChangeMethodologyProps {
t: TFunction;
gpcReferenceNumber: string;
inventoryId: string;
+ setChangeMethodology: Function;
}
const ChangeMethodology: FC = ({
@@ -37,9 +34,8 @@ const ChangeMethodology: FC = ({
t,
gpcReferenceNumber,
inventoryId,
+ setChangeMethodology,
}) => {
- const toast = useToast();
-
const [deleteAllActivityValues, { isLoading: isDeleteAllLoading }] =
useDeleteAllActivityValuesMutation();
@@ -51,31 +47,13 @@ const ChangeMethodology: FC = ({
});
if (response.data) {
// TODO create toast wrapper for success state
- toast({
- status: "success",
+ toaster.success({
title: t("change-methodology-success"),
- render: ({ title }) => (
-
-
- {title}
-
- ),
});
onChangeClicked();
onClose();
} else {
- toast({
- status: "error",
+ toaster.error({
title: t("change-methodology-error"),
});
}
@@ -85,10 +63,14 @@ const ChangeMethodology: FC = ({
return (
<>
-
-
-
- setChangeMethodology(e.open)}
+ onExitComplete={onClose}
+ >
+
+
+ = ({
borderStyle="solid"
borderColor="border.neutral"
>
- {t("change-methodology")}
-
-
-
+ {t("change-methodology")}
+
+
+
= ({
-
-
+ = ({
= ({
>
{t("change-methodology")}
-
-
-
+
+
+
>
);
};
diff --git a/app/src/components/Modals/delete-activity-modal.tsx b/app/src/components/Modals/delete-activity-modal.tsx
index c020c08f7..f6c420264 100644
--- a/app/src/components/Modals/delete-activity-modal.tsx
+++ b/app/src/components/Modals/delete-activity-modal.tsx
@@ -2,26 +2,24 @@
import { ActivityValue } from "@/models/ActivityValue";
import { useDeleteActivityValueMutation } from "@/services/api";
-import {
- Modal,
- Button,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalHeader,
- ModalOverlay,
- Text,
- Box,
- Badge,
- ModalFooter,
- useToast,
-} from "@chakra-ui/react";
+import { Text, Box, Badge, DialogTitle, Icon } from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC } from "react";
import { Trans } from "react-i18next";
-import { CheckCircleIcon } from "@chakra-ui/icons";
import { FiTrash2 } from "react-icons/fi";
+import {
+ DialogBackdrop,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogRoot,
+} from "../ui/dialog";
+import { Button } from "../ui/button";
+import { MdCheckCircle } from "react-icons/md";
+import { toaster } from "../ui/toaster";
interface DeleteAllActivitiesModalProps {
isOpen: boolean;
@@ -30,6 +28,7 @@ interface DeleteAllActivitiesModalProps {
selectedActivityValue: ActivityValue;
resetSelectedActivityValue: () => void;
inventoryId: string;
+ setDeleteActivityDialogOpen: Function;
}
const DeleteActivityModal: FC = ({
@@ -39,8 +38,8 @@ const DeleteActivityModal: FC = ({
selectedActivityValue,
resetSelectedActivityValue,
inventoryId,
+ setDeleteActivityDialogOpen,
}) => {
- const toast = useToast();
const [deleteActivityValue, { isLoading }] = useDeleteActivityValueMutation();
// define the function to delete all activities
@@ -52,48 +51,27 @@ const DeleteActivityModal: FC = ({
});
if (response.data?.success) {
// TODO create toast wrapper for success state
- toast({
- status: "success",
+ toaster.success({
title: t("delete-activity-success"),
- render: ({ title }) => (
-
-
- {title}
-
- ),
});
onClose();
resetSelectedActivityValue();
} else {
- toast({
- status: "error",
+ toaster.error({
title: t("delete-activity-failed"),
});
}
};
return (
<>
- {
- onClose();
- resetSelectedActivityValue();
- }}
+ setDeleteActivityDialogOpen(e.open)}
>
-
-
-
+
+ = ({
borderStyle="solid"
borderColor="border.neutral"
>
- {t("delete-activity")}
-
-
-
+ {t("delete-activity")}
+
+
+
= ({
-
-
+ = ({
background="sentiment.negativeDefault"
paddingTop="16px"
data-testid="delete-activity-modal-confirm"
- isLoading={isLoading}
+ loading={isLoading}
onClick={handleDeleteActivity}
paddingBottom="16px"
px="24px"
@@ -184,9 +162,9 @@ const DeleteActivityModal: FC = ({
>
{t("delete-activity")}
-
-
-
+
+
+
>
);
};
diff --git a/app/src/components/Modals/delete-all-activities-modal.tsx b/app/src/components/Modals/delete-all-activities-modal.tsx
index 6b7de44d6..d192d6be8 100644
--- a/app/src/components/Modals/delete-all-activities-modal.tsx
+++ b/app/src/components/Modals/delete-all-activities-modal.tsx
@@ -6,26 +6,24 @@ import {
useDeleteActivityValueMutation,
useDeleteAllActivityValuesMutation,
} from "@/services/api";
-import {
- Modal,
- Button,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalHeader,
- ModalOverlay,
- Text,
- Box,
- Badge,
- ModalFooter,
- useToast,
-} from "@chakra-ui/react";
+import { Text, Box, Badge, Icon, DialogRoot } from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC } from "react";
import { Trans } from "react-i18next";
-import { CheckCircleIcon } from "@chakra-ui/icons";
import { FiTrash2 } from "react-icons/fi";
+import { toaster } from "../ui/toaster";
+import { MdCheckCircle } from "react-icons/md";
+import {
+ DialogBackdrop,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "../ui/dialog";
+import { Button } from "../ui/button";
interface DeleteAllActivitiesModalProps {
isOpen: boolean;
@@ -33,6 +31,7 @@ interface DeleteAllActivitiesModalProps {
t: TFunction;
inventoryId: string;
subsectorId: string;
+ setDeleteActivityAllDialogOpen: Function;
}
const DeleteAllActivitiesModal: FC = ({
@@ -41,11 +40,11 @@ const DeleteAllActivitiesModal: FC = ({
t,
inventoryId,
subsectorId,
+ setDeleteActivityAllDialogOpen,
}) => {
- const toast = useToast();
const [deleteAllActivityValues, { isLoading }] =
useDeleteAllActivityValuesMutation();
-
+ console.log(inventoryId, subsectorId);
// define the function to delete all activities
const handleDeleteAllActivities = async () => {
// call the delete all activities mutation
@@ -55,30 +54,12 @@ const DeleteAllActivitiesModal: FC = ({
});
if (response.data) {
// TODO create toast wrapper for success state
- toast({
- status: "success",
+ toaster.success({
title: t("all-activities-deleted"),
- render: ({ title }) => (
-
-
- {title}
-
- ),
});
onClose();
} else {
- toast({
- status: "error",
+ toaster.error({
title: t("delete-all-activities-failed"),
});
}
@@ -86,10 +67,14 @@ const DeleteAllActivitiesModal: FC = ({
return (
<>
-
-
-
- setDeleteActivityAllDialogOpen(e.open)}
+ onExitComplete={onClose}
+ >
+
+
+ = ({
borderStyle="solid"
borderColor="border.neutral"
>
- {t("delete-all-activities")}
-
-
-
+ {t("delete-all-activities")}
+
+
+
= ({
-
-
+ = ({
w="472px"
background="sentiment.negativeDefault"
paddingTop="16px"
- isLoading={isLoading}
+ loading={isLoading}
paddingBottom="16px"
px="24px"
letterSpacing="widest"
@@ -179,9 +164,9 @@ const DeleteAllActivitiesModal: FC = ({
>
{t("delete-all-activities")}
-
-
-
+
+
+
>
);
};
diff --git a/app/src/components/Modals/delete-city-modal.tsx b/app/src/components/Modals/delete-city-modal.tsx
index f01161880..89b668c86 100644
--- a/app/src/components/Modals/delete-city-modal.tsx
+++ b/app/src/components/Modals/delete-city-modal.tsx
@@ -1,39 +1,35 @@
"use client";
-import {
- Badge,
- Box,
- Button,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
- useToast,
-} from "@chakra-ui/react";
+import { Badge, Box, Button, Text, Icon } from "@chakra-ui/react";
import React, { FC, useState } from "react";
import { FiTrash2 } from "react-icons/fi";
import PasswordInput from "../password-input";
import { SubmitHandler, useForm } from "react-hook-form";
import { TFunction } from "i18next";
-import { InfoOutlineIcon } from "@chakra-ui/icons";
-import { UserAttributes } from "@/models/User";
+import { MdInfoOutline } from "react-icons/md";
import { api } from "@/services/api";
-import { CityAttributes } from "@/models/City";
-import { MdCheckCircleOutline } from "react-icons/md";
+import type { CityAttributes } from "@/models/City";
+
+import {
+ DialogRoot,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+} from "@/components/ui/dialog";
-interface DeleteCityModalProps {
+import { toaster } from "@/components/ui/toaster";
+
+interface DeleteCityDialogProps {
isOpen: boolean;
onClose: any;
cityData: CityAttributes;
t: TFunction;
}
-const DeleteCityModal: FC = ({
+const DeleteCityDialog: FC = ({
isOpen,
onClose,
cityData,
@@ -50,7 +46,6 @@ const DeleteCityModal: FC = ({
const { data: token } = api.useGetVerifcationTokenQuery({});
const [removeCity] = api.useRemoveCityMutation();
const [isPasswordCorrect, setIsPasswordCorrect] = useState(true);
- const toast = useToast();
const onSubmit: SubmitHandler<{ password: string }> = async (data) => {
await requestPasswordConfirm({
@@ -63,38 +58,9 @@ const DeleteCityModal: FC = ({
}).then((res: any) => {
onClose();
setIsPasswordCorrect(true);
- toast({
+ toaster.success({
description: t("city-deleted"),
- status: "success",
duration: 5000,
- isClosable: true,
- render: () => (
-
-
-
-
-
- {t("city-deleted")}
-
-
-
- ),
});
});
} else {
@@ -105,10 +71,9 @@ const DeleteCityModal: FC = ({
return (
<>
-
-
-
-
+
+ = ({
borderColor="border.neutral"
>
{t("remove-city")}
-
-
-
+
+
+
= ({
w="365px"
gap="6px"
>
-
+
{isPasswordCorrect ? (
= ({
-
-
+ = ({
>
{t("remove-city")}
-
-
-
+
+
+
>
);
};
-export default DeleteCityModal;
+export default DeleteCityDialog;
diff --git a/app/src/components/Modals/delete-file-modal.tsx b/app/src/components/Modals/delete-file-modal.tsx
index 022c05f83..b1e79b25b 100644
--- a/app/src/components/Modals/delete-file-modal.tsx
+++ b/app/src/components/Modals/delete-file-modal.tsx
@@ -2,33 +2,30 @@
import { UserFileAttributes } from "@/models/UserFile";
import { api } from "@/services/api";
-import {
- Modal,
- Button,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalHeader,
- ModalOverlay,
- Text,
- Box,
- Badge,
- ModalFooter,
-} from "@chakra-ui/react";
+import { Button, Text, Box, Badge } from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC } from "react";
import { Trans } from "react-i18next";
import { FiTrash2 } from "react-icons/fi";
-interface DeleteFileModalProps {
+import {
+ DialogRoot,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+} from "@/components/ui/dialog";
+
+interface DeleteFileDialogProps {
isOpen: boolean;
onClose: any;
fileData: UserFileAttributes | undefined;
t: TFunction;
}
-const DeleteFileModal: FC = ({
+const DeleteFileDialog: FC = ({
isOpen,
onClose,
fileData,
@@ -45,103 +42,98 @@ const DeleteFileModal: FC = ({
}
};
return (
- <>
-
-
-
-
+
+
+ {t("delete-file")}
+
+
+
+
- {t("delete-file")}
-
-
-
-
+
+
+
-
-
-
-
-
- Are you sure you want to{" "}
-
- permanently delete
- {" "}
- this file from the city's repository?
-
-
-
+
+ Are you sure you want to{" "}
+ permanently delete{" "}
+ this file from the city's repository?
+
+
-
-
+
+
+ onDeleteFile()}
+ p={0}
+ m={0}
>
- onDeleteFile()}
- p={0}
- m={0}
- >
- {t("delete-file")}
-
-
-
-
- >
+ {t("delete-file")}
+
+
+
+
);
};
-export default DeleteFileModal;
+export default DeleteFileDialog;
diff --git a/app/src/components/Modals/delete-inventory-modal.tsx b/app/src/components/Modals/delete-inventory-modal.tsx
index 734d91302..67a868d6d 100644
--- a/app/src/components/Modals/delete-inventory-modal.tsx
+++ b/app/src/components/Modals/delete-inventory-modal.tsx
@@ -1,46 +1,39 @@
"use client";
-import {
- Badge,
- Box,
- Button,
- FormControl,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
- useToast,
-} from "@chakra-ui/react";
+import { Badge, Box, Button, Text, Icon, DialogFooter } from "@chakra-ui/react";
import React, { FC, useState } from "react";
-
import { FiTrash2 } from "react-icons/fi";
-import PasswordInput from "../password-input";
+import { MdOutlineInfo } from "react-icons/md";
import { SubmitHandler, useForm } from "react-hook-form";
import { Trans } from "react-i18next/TransWithoutContext";
import { TFunction } from "i18next";
-import { InfoOutlineIcon } from "@chakra-ui/icons";
-import { UserAttributes } from "@/models/User";
+
+import {
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogRoot,
+} from "@/components/ui/dialog";
+import { Field } from "@/components/ui/field";
+import { toaster } from "@/components/ui/toaster";
+import PasswordInput from "@/components/password-input";
+
+import type { UserAttributes } from "@/models/User";
import { api } from "@/services/api";
-import { MdCheckCircleOutline } from "react-icons/md";
-interface DeleteInventoryModalProps {
+interface DeleteInventoryDialogProps {
isOpen: boolean;
onClose: any;
userData: UserAttributes;
t: TFunction;
- lng: string;
inventoryId: string;
}
-const DeleteInventoryModal: FC = ({
+const DeleteInventoryDialog: FC = ({
isOpen,
onClose,
userData,
- lng,
inventoryId,
t,
}) => {
@@ -48,7 +41,6 @@ const DeleteInventoryModal: FC = ({
handleSubmit,
register,
formState: { errors },
- setValue,
reset,
} = useForm<{ password: string }>();
const [requestPasswordConfirm] = api.useRequestVerificationMutation();
@@ -57,7 +49,6 @@ const DeleteInventoryModal: FC = ({
});
const [deleteInventory, { isLoading }] = api.useDeleteInventoryMutation();
const [isPasswordCorrect, setIsPasswordCorrect] = useState(true);
- const toast = useToast();
const onSubmit: SubmitHandler<{ password: string }> = async (data) => {
await requestPasswordConfirm({
@@ -67,42 +58,13 @@ const DeleteInventoryModal: FC = ({
if (res.data?.comparePassword) {
await deleteInventory({
inventoryId,
- }).then((res: any) => {
+ }).then((_res: any) => {
reset();
onClose();
setIsPasswordCorrect(true);
- toast({
+ toaster.success({
description: t("inventory-deleted"),
- status: "success",
duration: 5000,
- isClosable: true,
- render: () => (
-
-
-
-
-
- {t("inventory-deleted")}
-
-
-
- ),
});
});
} else {
@@ -113,11 +75,10 @@ const DeleteInventoryModal: FC = ({
return (
<>
-
-
-
+
+
-
-
+
+
>
);
};
-export default DeleteInventoryModal;
+export default DeleteInventoryDialog;
diff --git a/app/src/components/Modals/delete-user-modal.tsx b/app/src/components/Modals/delete-user-modal.tsx
index cb7bbc0d1..f603da44e 100644
--- a/app/src/components/Modals/delete-user-modal.tsx
+++ b/app/src/components/Modals/delete-user-modal.tsx
@@ -2,32 +2,28 @@
import { UseErrorToast, UseSuccessToast } from "@/hooks/Toasts";
import { api } from "@/services/api";
-import {
- Badge,
- Box,
- Button,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
-} from "@chakra-ui/react";
+import { Badge, Box, Button, Text } from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC } from "react";
import { FiTrash2 } from "react-icons/fi";
+import {
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogRoot,
+} from "@/components/ui/dialog";
-interface DeleteUserModalProps {
+interface DeleteUserDialogProps {
isOpen: boolean;
onClose: any;
cityInviteId: string;
t: TFunction;
}
-const DeleteUserModal: FC = ({
+const DeleteUserDialog: FC = ({
isOpen,
onClose,
cityInviteId,
@@ -58,10 +54,9 @@ const DeleteUserModal: FC = ({
};
return (
<>
-
-
-
-
+
+ = ({
fontFamily="heading"
>
{t("remove-user")}
-
-
-
+
+
+
= ({
-
-
+ = ({
>
{t("remove-user")}
-
-
-
+
+
+
>
);
};
-export default DeleteUserModal;
+export default DeleteUserDialog;
diff --git a/app/src/components/Modals/update-user-modal.tsx b/app/src/components/Modals/update-user-modal.tsx
index 7c29d2031..37f5be48b 100644
--- a/app/src/components/Modals/update-user-modal.tsx
+++ b/app/src/components/Modals/update-user-modal.tsx
@@ -1,33 +1,32 @@
"use client";
-import {
- Box,
- Button,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
-} from "@chakra-ui/react";
+import { Box, Button } from "@chakra-ui/react";
import React, { FC, useEffect, useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
-import FormInput from "../form-input";
-import FormSelectInput from "../form-select-input";
-import { api } from "@/services/api";
import { TFunction } from "i18next";
+
+import { api } from "@/services/api";
import { GetUserCityInvitesResponseUserData, Roles } from "@/util/types";
import { UseErrorToast, UseSuccessToast } from "@/hooks/Toasts";
+import FormInput from "@/components/form-input";
+import FormSelectInput from "@/components/form-select-input";
+import {
+ DialogRoot,
+ DialogBody,
+ DialogCloseTrigger,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+} from "@/components/ui/dialog";
-interface UpdateUserModalProps {
+interface UpdateUserDialogProps {
isOpen: boolean;
onClose: any;
t: TFunction;
userData: GetUserCityInvitesResponseUserData;
}
-const UpdateUserModal: FC = ({
+const UpdateUserDialog: FC = ({
isOpen,
onClose,
t,
@@ -85,10 +84,9 @@ const UpdateUserModal: FC = ({
return (
<>
-
-
-
-
+
+ = ({
borderColor="border.neutral"
>
{t("edit-user")}
-
-
-
+
+
+
-
-
+ = ({
>
{t("save-changes")}
-
-
-
+
+
+
>
);
};
-export default UpdateUserModal;
+export default UpdateUserDialog;
diff --git a/app/src/components/MultiSelectInput.tsx b/app/src/components/MultiSelectInput.tsx
index 273310e7b..04f6255d5 100644
--- a/app/src/components/MultiSelectInput.tsx
+++ b/app/src/components/MultiSelectInput.tsx
@@ -1,10 +1,13 @@
import React, { useEffect, useState } from "react";
import Select, { components, MultiValueProps, OptionProps } from "react-select";
-import { Checkbox, Box, Text, CloseButton } from "@chakra-ui/react";
+import { Box, Icon, Text } from "@chakra-ui/react";
import { TFunction } from "i18next";
-import { WarningIcon } from "@chakra-ui/icons";
+
import { Control, Controller } from "react-hook-form";
import { Inputs } from "@/components/Modals/activity-modal/activity-modal-body";
+import { CloseButton } from "./ui/close-button";
+import { Checkbox } from "./ui/checkbox";
+import { MdWarning } from "react-icons/md";
interface MultiSelectInputProps {
title: string;
@@ -59,7 +62,7 @@ const CustomOption = (props: OptionProps) => {
>
{data.label}
@@ -115,7 +118,6 @@ const MultiSelectWithCheckbox = ({
return (
{error ? (
-
+
{error?.message}
) : null}
diff --git a/app/src/components/NotAvailable.tsx b/app/src/components/NotAvailable.tsx
index 9de2a6e6e..991c1d7b8 100644
--- a/app/src/components/NotAvailable.tsx
+++ b/app/src/components/NotAvailable.tsx
@@ -1,10 +1,10 @@
import { Box, Text } from "@chakra-ui/layout";
import Image from "next/image";
import { Button, Heading } from "@chakra-ui/react";
-import { ArrowForwardIcon } from "@chakra-ui/icons";
import React from "react";
import { useTranslation } from "@/i18n/client";
-import "dotenv/config";
+import { MdArrowForward } from "react-icons/md";
+import NextLink from "next/link";
const NotAvailable = ({ lng }: { lng: string }) => {
const { t } = useTranslation(lng, "inventory-not-found");
@@ -47,17 +47,14 @@ const NotAvailable = ({ lng }: { lng: string }) => {
>
{t("emission-mist")}
- }
>
- {t("go-to-citycatalyst")}
-
+
+ {t("go-to-citycatalyst")}
+
+
+
);
diff --git a/app/src/components/SegmentedProgress.tsx b/app/src/components/SegmentedProgress.tsx
index 72ad781f8..de4edde33 100644
--- a/app/src/components/SegmentedProgress.tsx
+++ b/app/src/components/SegmentedProgress.tsx
@@ -4,12 +4,7 @@ import {
Box,
Flex,
Table,
- TableContainer,
- Tbody,
- Td,
Text,
- Tooltip,
- Tr,
useToken,
VStack,
} from "@chakra-ui/react";
@@ -19,6 +14,8 @@ import {
toKebabCase,
} from "@/util/helpers";
+import { Tooltip } from "@/components/ui/tooltip";
+
export type SegmentedProgressValues =
| number
| { name: string; percentage: number; value: bigint };
@@ -50,60 +47,58 @@ export function SegmentedProgress({
: v,
);
const tooltipContent = (
-
-
-
- {normalizedValues.map((value, index) => (
-
-
-
- {t(toKebabCase(value.name))}
-
- |
-
-
- {value.percentage.toFixed(1)}%
-
- |
-
- {convertKgToTonnes(value.value)}
- |
-
- ))}
-
-
-
- {capitalizeFirstLetter(t("total"))}
+
+
+ {normalizedValues.map((value, index) => (
+
+
+
+ {t(toKebabCase(value.name))}
- |
- |
-
-
- {convertKgToTonnes(total!)}
+
+
+
+ {value.percentage.toFixed(1)}%
- |
-
-
-
-
+
+
+ {convertKgToTonnes(value.value)}
+
+
+ ))}
+
+
+
+ {capitalizeFirstLetter(t("total"))}
+
+
+
+
+
+ {convertKgToTonnes(total!)}
+
+
+
+
+
);
const progressBars = (
{normalizedValues.map((value, i) => (
))}
@@ -142,7 +137,7 @@ export function SegmentedProgress({
py={1}
px={2}
marginRight={2}
- borderRadius="full"
+ borderRadius="10px"
bg="base.light"
>
@@ -150,7 +145,7 @@ export function SegmentedProgress({
width={3}
height={3}
bg={colors[i]}
- borderRadius="full"
+ borderRadius="10px"
mx={2}
my={1}
/>
diff --git a/app/src/components/Tabs/Activity/activity-accordion.tsx b/app/src/components/Tabs/Activity/activity-accordion.tsx
index 3eadf6e7a..b7c689c17 100644
--- a/app/src/components/Tabs/Activity/activity-accordion.tsx
+++ b/app/src/components/Tabs/Activity/activity-accordion.tsx
@@ -1,36 +1,33 @@
import { ActivityValue } from "@/models/ActivityValue";
import { convertKgToTonnes } from "@/util/helpers";
-import { AddIcon } from "@chakra-ui/icons";
import {
Accordion,
- AccordionButton,
- AccordionIcon,
- AccordionItem,
- AccordionPanel,
Box,
Icon,
IconButton,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverContent,
- PopoverTrigger,
Table,
- TableContainer,
- Tag,
TagLabel,
- Tbody,
- Td,
Text,
- Th,
- Thead,
- Tr,
} from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC, useMemo } from "react";
-import { MdModeEditOutline, MdMoreVert } from "react-icons/md";
+import { MdAdd, MdModeEditOutline, MdMoreVert } from "react-icons/md";
import { FiTrash2 } from "react-icons/fi";
import { ExtraField, findMethodology, Methodology } from "@/util/form-schema";
+import { Tag } from "@/components/ui/tag";
+import {
+ PopoverArrow,
+ PopoverBody,
+ PopoverContent,
+ PopoverRoot,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import {
+ AccordionItem,
+ AccordionItemContent,
+ AccordionItemTrigger,
+ AccordionRoot,
+} from "@/components/ui/accordion";
import { useParams } from "next/navigation";
import { REGIONALLOCALES } from "@/util/constants";
@@ -154,7 +151,7 @@ const ActivityAccordion: FC = ({
return {
activityGroups,
};
- }, [activityData, methodology]);
+ }, [activityData, methodology, referenceNumber]);
// let extraFields = (methodology as Methodology)?.activities?.[0]?.[
// "extra-fields"
@@ -193,149 +190,197 @@ const ActivityAccordion: FC = ({
title: string,
) => {
return (
-
-
+
-
-
- {filteredFields?.length! > 0 && (
-
- {t(filteredFields[0].id)}
- |
- )}
- {t("data-quality")} |
- {t(sourceField as string)} |
- {t(title)} |
- {t("emissions")} |
- |
-
-
-
- {list?.map((activity: any, i: number) => {
- return (
-
- {filteredFields?.length! > 0 && (
- {t(activity.activityData?.[filteredFields[0].id])} |
- )}
-
-
- {t(activity?.metadata?.dataQuality)}
-
- |
-
- {activity?.activityData[sourceField as string]}
- |
-
- {parseFloat(activity?.activityData[title])}{" "}
- {t(activity?.activityData[title + "-unit"])}
- |
-
- {convertKgToTonnes(
- activity?.co2eq,
- null,
- REGIONALLOCALES[lng as string],
- )}
- |
-
-
-
- }
- aria-label="more-icon"
- variant="ghost"
- color="content.tertiary"
- />
-
-
+ {filteredFields?.length! > 0 && (
+
+ {t(filteredFields[0].id)}
+
+ )}
+
+ {t("data-quality")}
+
+
+ {t(sourceField as string)}
+
+
+ {t(title)}
+
+
+ {t("emissions")}
+
+
+
+
+
+ {list?.map((activity: any, i: number) => {
+ return (
+
+ {filteredFields?.length! > 0 && (
+
+ {t(activity.activityData?.[filteredFields[0].id])}
+
+ )}
+
+
+ {t(activity?.metadata?.dataQuality)}
+
+
+
+ {activity?.activityData[sourceField as string]}
+
+
+ {parseFloat(activity?.activityData[title])}{" "}
+ {t(activity?.activityData[title + "-unit"])}
+
+ {convertKgToTonnes(activity?.co2eq)}
+
+
+
+
-
-
- onEditActivity(activity)}
+
+
+
+
+
+ onEditActivity(activity)}
+ >
+
+
-
-
- {t("update-activity")}
-
-
- onDeleteActivity(activity)}
+ {t("update-activity")}
+
+
+ onDeleteActivity(activity)}
+ >
+
+
-
-
- {t("delete-activity")}
-
-
-
-
-
- |
-
- );
- })}
-
-
-
+ {t("delete-activity")}
+
+
+
+
+
+
+
+ );
+ })}
+
+
);
};
@@ -345,114 +390,113 @@ const ActivityAccordion: FC = ({
Object.keys(activityGroups)
.sort()
.map((key) => (
-
+
-
-
+
+
+
+ {key.includes(",")
+ ? t(`mixed${activityGroups[key].tag}`)
+ : t(key)}
+
+
+ {activityGroups[key]?.activityData.length}{" "}
+ {t("activities-added")}
+
+
+ {/*Todo find a way to sum all consumptions regardless of their units*/}
+ {/**/}
+ {/* {t(title)}: */}
+ {/* 0M gallons*/}
+ {/**/}
+
-
- {key.includes(",")
- ? t(`mixed${activityGroups[key].tag}`)
- : t(key)}
-
-
- {activityGroups[key]?.activityData.length}{" "}
- {t("activities-added")}
+ {t("emissions")}:
+
+ {" "}
+ {convertKgToTonnes(
+ activityGroups[key].activityData?.reduce(
+ (acc, curr) => acc + BigInt(curr.co2eq as bigint),
+ 0n,
+ ),
+ null,
+ REGIONALLOCALES[lng as string],
+ )}{" "}
- {/*Todo find a way to sum all consumptions regardless of their units*/}
- {/**/}
- {/* {t(title)}: */}
- {/* 0M gallons*/}
- {/**/}
-
-
+ {
+ e.stopPropagation();
+ showActivityModal();
+ }}
+ _hover={{ bg: "none" }}
+ aria-label="add-activity"
>
-
- {t("emissions")}:
-
-
- {" "}
- {convertKgToTonnes(
- activityGroups[key].activityData?.reduce(
- (acc, curr) =>
- acc + BigInt(curr.co2eq as bigint),
- 0n,
- ),
- null,
- REGIONALLOCALES[lng as string],
- )}{" "}
-
-
-
- {
- e.stopPropagation();
- showActivityModal();
- }}
- _hover={{ bg: "none" }}
- aria-label="add-activity"
- icon={
-
- }
+
-
+
+
+
-
-
-
-
+
+
+
+
{renderTable(
activityGroups[key].activityData as ActivityValue[],
activityGroups[key].filteredFields as ExtraField[],
activityGroups[key].sourceField as string,
activityGroups[key].title as string,
)}
-
+
-
+
))
) : (
= ({
}
}, [inventoryValue]);
+ console.log(referenceNumber);
+
return (
<>
-
+
= ({
)}
= ({
}
aria-label="edit"
variant="ghost"
color="content.tertiary"
- />
+ >
+
+
@@ -382,7 +384,7 @@ const ActivityTab: FC = ({
)}
>
)}
-
+
>
);
};
diff --git a/app/src/components/Tabs/Activity/direct-measure-table.tsx b/app/src/components/Tabs/Activity/direct-measure-table.tsx
index 787de0b42..d417d81e3 100644
--- a/app/src/components/Tabs/Activity/direct-measure-table.tsx
+++ b/app/src/components/Tabs/Activity/direct-measure-table.tsx
@@ -1,39 +1,34 @@
import { ActivityValue } from "@/models/ActivityValue";
import { convertKgToTonnes } from "@/util/helpers";
import {
- Accordion,
- AccordionButton,
- AccordionIcon,
- AccordionItem,
- AccordionPanel,
Box,
Icon,
IconButton,
- Popover,
PopoverArrow,
PopoverBody,
- PopoverContent,
PopoverTrigger,
Table,
- Tag,
TagLabel,
- Tbody,
- Td,
Text,
- Th,
- Thead,
- Tr,
} from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC, useMemo } from "react";
import { FiTrash2 } from "react-icons/fi";
-import { MdModeEditOutline, MdMoreVert } from "react-icons/md";
+import { MdAdd, MdModeEditOutline, MdMoreVert } from "react-icons/md";
import {
DirectMeasure,
ExtraField,
MANUAL_INPUT_HIERARCHY,
} from "@/util/form-schema";
-import { AddIcon } from "@chakra-ui/icons";
+
+import { PopoverContent, PopoverRoot } from "@/components/ui/popover";
+import { Tag } from "@/components/ui/tag";
+import {
+ AccordionItem,
+ AccordionItemContent,
+ AccordionItemTrigger,
+ AccordionRoot,
+} from "@/components/ui/accordion";
import { useParams } from "next/navigation";
import { REGIONALLOCALES } from "@/util/constants";
@@ -85,85 +80,148 @@ const DirectMeasureTable: FC = ({
const renderTable = (list: ActivityValue[]) => {
return (
-
-
-
+
+
+
{filteredFields.length > 0 && (
- {t(filteredFields[0].id)} |
+
+ {t(filteredFields[0].id)}
+
)}
- {t("data-quality")} |
- {t(sourceField as string)} |
-
+
+ {t("data-quality")}
+
+
+ {t(sourceField as string)}
+
+
{t("co2-emissions")}
- |
-
+
+
{t("n2o-emissions")}
- |
-
+
+
{t("ch4-emissions")}
- |
- |
-
-
-
+
+
+
+
+
{list?.map((activity: ActivityValue, i: number) => {
const dataQuality = activity?.metadata?.dataQuality;
return (
-
+
{filteredFields.length > 0 && (
-
{t(activity?.activityData?.[filteredFields[0].id])}
- |
+
)}
-
-
+
+
{t(dataQuality!)}
- |
-
-
+
+
+
{activity?.activityData?.[sourceField as string]}
- |
-
+
+
{/*Direct measure entries are collected in tonnes by default*/}
{convertKgToTonnes(
activity?.activityData?.co2_amount * 1000,
"CO2e",
REGIONALLOCALES[lng as string],
)}
- |
-
+
+
{convertKgToTonnes(
activity?.activityData?.n2o_amount * 1000,
"N2O",
REGIONALLOCALES[lng as string],
)}
- |
-
+
+
{convertKgToTonnes(
activity?.activityData?.ch4_amount * 1000,
"CH4",
REGIONALLOCALES[lng as string],
)}
- |
-
-
+
+
+
}
aria-label="more-icon"
variant="ghost"
color="content.tertiary"
- />
+ >
+
+
= ({
-
- |
-
+
+
+
);
})}
-
-
+
+
);
};
@@ -244,97 +302,91 @@ const DirectMeasureTable: FC = ({
Object.keys(activityGroups)
.sort()
.map((key) => (
-
+
-
-
+
+
+
+ {key.includes(",") ? t(`mixed${tag}`) : t(key)}
+
+
+ {activityGroups[key]?.length} {t("activities-added")}
+
+
+
-
- {key.includes(",") ? t(`mixed${tag}`) : t(key)}
-
-
- {activityGroups[key]?.length} {t("activities-added")}
+ {t("emissions")}:
+
+ {" "}
+ {convertKgToTonnes(
+ activityGroups[key]?.reduce(
+ (acc, curr) => acc + BigInt(curr.co2eq as bigint),
+ 0n,
+ ),
+ )}{" "}
-
-
+ {
+ e.stopPropagation();
+ showActivityModal();
+ }}
+ _hover={{ bg: "none" }}
+ aria-label="add-activity"
>
-
- {t("emissions")}:
-
-
- {" "}
- {convertKgToTonnes(
- activityGroups[key]?.reduce(
- (acc, curr) =>
- acc + BigInt(curr.co2eq as bigint),
- 0n,
- ),
- null,
- REGIONALLOCALES[lng as string],
- )}{" "}
-
-
-
- {
- e.stopPropagation();
- showActivityModal();
- }}
- _hover={{ bg: "none" }}
- aria-label="add-activity"
- icon={
-
- }
+
-
+
-
-
-
-
+
+
+
{renderTable(activityGroups[key])}
-
+
-
+
))
) : (
{
+ setOpenChangeMethodology(true);
+ };
+
+ // Add Activity Dialog
+ const [openActivityDataDialog, setAddActivityDataDialogOpen] =
+ useState(false);
+ const handleActivityAddDataDialog = () => {
+ setAddActivityDataDialogOpen(true);
+ };
+
+ // Delete Activity Dialog
+ const [openActivityDeleteDialog, setActivityDeleteDataDialogOpen] =
+ useState(false);
+ const handleDeleteActivityDataDialog = () => {
+ setActivityDeleteDataDialogOpen(true);
+ };
+
+ // Delete all activities dialog
+ const [openActivityDeleteAllDialog, setActivityDeleteAllDataDialogOpen] =
+ useState(false);
+ const handleDeleteAllActivityDataDialog = () => {
+ setActivityDeleteAllDataDialogOpen(true);
+ };
+
const changeMethodologyFunc = () => {
changeMethodology();
onChangeMethodologyClose();
@@ -105,9 +139,9 @@ const EmissionDataSection = ({
onDeleteActivityModalClose();
};
- const handleActivityAdded = (suggestedActivity: SuggestedActivity) => {
+ const handleActivityAdded = (suggestedActivity?: SuggestedActivity) => {
setSelectedActivity(suggestedActivity);
- onAddActivityModalOpen();
+ setAddActivityDataDialogOpen(true);
};
const onDeleteActivity = (activity: ActivityValue) => {
@@ -117,7 +151,7 @@ const EmissionDataSection = ({
const onEditActivity = (activity: ActivityValue) => {
setSelectedActivityValue(activity);
- onAddActivityModalOpen();
+ setAddActivityDataDialogOpen(true);
};
const { lng } = useParams();
@@ -152,7 +186,7 @@ const EmissionDataSection = ({
>
) : (
-
handleActivityAdded()}
data-testid="add-emission-data-button"
title={t("add-emission-data")}
- leftIcon={}
h="48px"
aria-label="activity-button"
fontSize="button.md"
gap="8px"
>
+
{t("add-emission-data-btn")}
-
+
)}
>
);
@@ -231,29 +265,31 @@ const EmissionDataSection = ({
getInputMethodology(methodology?.id!)) !== "direct-measure" && (
}
h="48px"
aria-label="activity-button"
fontSize="button.md"
gap="8px"
>
+
{t("add-emission-data")}
)}
-
+
}
aria-label="more-icon"
variant="ghost"
color="content.tertiary"
- />
+ >
+
+
-
-
-
+
+
+ {" "}
+
{t("change-methodology")}
@@ -291,7 +328,7 @@ const EmissionDataSection = ({
cursor: "pointer",
}}
className="group"
- onClick={onDeleteActivitiesModalOpen}
+ onClick={handleDeleteAllActivityDataDialog}
>
{t("delete-all-activities")}
@@ -310,7 +348,7 @@ const EmissionDataSection = ({
)}
-
+
@@ -325,18 +363,18 @@ const EmissionDataSection = ({
t={t}
referenceNumber={refNumberWithScope}
activityData={activityValues}
- onDeleteActivity={onDeleteActivity}
+ onDeleteActivity={handleDeleteActivityDataDialog}
onEditActivity={onEditActivity}
- showActivityModal={onAddActivityModalOpen}
+ showActivityModal={handleActivityAdded}
/>
) : (
)}
@@ -376,7 +414,7 @@ const EmissionDataSection = ({
setSelectedActivityValue(undefined)}
+ setAddActivityDialogOpen={setAddActivityDataDialogOpen}
/>
setSelectedActivityValue(undefined)}
+ setDeleteActivityDialogOpen={setActivityDeleteDataDialogOpen}
/>
>
diff --git a/app/src/components/Tabs/Activity/external-data-section.tsx b/app/src/components/Tabs/Activity/external-data-section.tsx
index c5fa605f7..81bb49273 100644
--- a/app/src/components/Tabs/Activity/external-data-section.tsx
+++ b/app/src/components/Tabs/Activity/external-data-section.tsx
@@ -10,10 +10,8 @@ import {
SimpleGrid,
Tag,
TagLabel,
- TagLeftIcon,
Text,
useDisclosure,
- useToast,
} from "@chakra-ui/react";
import React, { useState } from "react";
import { TFunction } from "i18next";
@@ -27,6 +25,7 @@ import { SourceDrawer } from "@/app/[lng]/[inventory]/data/[step]/SourceDrawer";
import type { DataSourceWithRelations } from "@/app/[lng]/[inventory]/data/[step]/types";
import { api } from "@/services/api";
import { convertKgToTonnes } from "@/util/helpers";
+import { toaster } from "@/components/ui/toaster";
const ExternalDataSection = ({
t,
@@ -35,13 +34,12 @@ const ExternalDataSection = ({
t: TFunction;
inventoryValue: InventoryValue;
}) => {
- const toast = useToast();
const source = inventoryValue.dataSource;
const [disconnectThirdPartyData, { isLoading: isDisconnectLoading }] =
api.useDisconnectThirdPartyDataMutation();
const {
- isOpen: isSourceDrawerOpen,
+ open: isSourceDrawerOpen,
onClose: onSourceDrawerClose,
onOpen: onSourceDrawerOpen,
} = useDisclosure();
@@ -56,10 +54,8 @@ const ExternalDataSection = ({
const handleMouseLeave = () => setHovered(false);
const buttonContent = hovered ? t("disconnect-data") : t("data-connected");
-
const buttonIcon = !hovered ? : null;
-
- const variant = hovered ? "danger" : "solidPrimary";
+ const buttonColorScheme = hovered ? "danger" : "primary"; // TODO create these color palletes
const onDisconnectThirdPartyData = async (
_source: DataSourceWithRelations,
@@ -68,8 +64,7 @@ const ExternalDataSection = ({
inventoryId: inventoryValue.inventoryId,
datasourceId: inventoryValue.datasourceId,
});
- toast({
- status: "error",
+ toaster.error({
title: t("disconnected-data-source"),
});
};
@@ -116,8 +111,8 @@ const ExternalDataSection = ({
-
-
+
{/* TODO add icon to DataSource */}
-
+
{getTranslationFromDict(source.datasetName)}
-
-
-
+
+
+
+
+
{t("data-quality")}: {t("quality-" + source.dataQuality)}
-
-
+
+
{source.subCategory?.scope && (
-
-
+
+
+
+
{t("scope")}: {source.subCategory.scope.scopeName}
-
+
)}
-
+
{getTranslationFromDict(source.datasetDescription) ||
getTranslationFromDict(source.methodologyDescription)}
@@ -166,18 +157,19 @@ const ExternalDataSection = ({
{t("see-more-details")}
onDisconnectThirdPartyData(source)}
- isLoading={isDisconnectLoading}
+ loading={isDisconnectLoading}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
- leftIcon={buttonIcon as React.JSX.Element}
>
+ {buttonIcon}
{buttonContent}
-
+
= ({
const {
register,
handleSubmit,
- reset,
- watch,
- setError,
- clearErrors,
- setFocus,
setValue,
control,
- getValues,
formState: { errors },
} = useForm<{
reason: string;
@@ -66,15 +61,7 @@ const ScopeUnavailable: FC = ({
rules: { required: t("option-required") },
});
- const {
- getRootProps,
- getRadioProps,
- value: selectedReason,
- setValue: setSelectedReason,
- } = useRadioGroup({
- defaultValue: reason || "",
- onChange: (value) => setValue("reason", value), // Update reason in React Hook Form on selection
- });
+ const [selectedReason, setSelectedReason] = React.useState();
useEffect(() => {
if (reason) {
@@ -83,6 +70,11 @@ const ScopeUnavailable: FC = ({
}
}, [reason, setSelectedReason, setValue]);
+ const handleSelectedValue = (value: string) => {
+ setSelectedReason(value);
+ setValue("reason", value);
+ };
+
const formSubmitHandler = async (data: {
reason: string;
justification: string;
@@ -126,48 +118,51 @@ const ScopeUnavailable: FC = ({
>
{t("select-reason")}
-
-
-
- {t("reason-NO")}
-
-
- {t("reason-NE")}
-
-
- {t("reason-C")}
-
-
- {t("reason-IE")}
-
+
+ handleSelectedValue(e.value)}
+ colorPalette="interactive.secondary"
+ >
+
+
+ {t("reason-NO")}
+
+
+ {t("reason-NE")}
+
+
+ {t("reason-C")}
+
+
+ {t("reason-IE")}
+
- {errors?.reason ? (
-
-
- {errors?.reason.message}
-
- ) : null}
-
-
+ {errors?.reason ? (
+
+
+ {errors?.reason.message}
+
+ ) : null}
+
+
+
= ({
/>
{errors?.justification ? (
-
+
{errors?.justification.message}
) : null}
@@ -197,7 +192,7 @@ const ScopeUnavailable: FC = ({
p="16px"
mt="24px"
onClick={submit}
- isLoading={isLoading}
+ loading={isLoading}
>
{t("save-changes")}
diff --git a/app/src/components/Tabs/Activity/select-methodology.tsx b/app/src/components/Tabs/Activity/select-methodology.tsx
index 086972d60..c0dfe6dc1 100644
--- a/app/src/components/Tabs/Activity/select-methodology.tsx
+++ b/app/src/components/Tabs/Activity/select-methodology.tsx
@@ -65,7 +65,7 @@ const SelectMethodology = ({
>
{t("select-methodology")}
-
+
{(methodologies || []).map(
({ id, disabled, activities, inputRequired, ...rest }) => (
= ({
formState: { errors, isSubmitting },
} = useForm();
const [setCurrentUserData] = useSetCurrentUserDataMutation();
- const toast = useToast();
const onSubmit: SubmitHandler = async (data) => {
await setCurrentUserData({
@@ -34,37 +33,9 @@ const AccountDetailsTabPanel: FC = ({
email: data.email,
role: data.role,
}).then(() =>
- toast({
+ toaster.success({
description: t("user-details-updated"),
- status: "success",
duration: 5000,
- isClosable: true,
- render: () => (
-
-
-
-
- {t("user-details-updated")}
-
-
-
- ),
}),
);
};
@@ -103,7 +74,7 @@ const AccountDetailsTabPanel: FC = ({
-
+
{t("invite")}
diff --git a/app/src/components/Tabs/MyProfileTab/ManageCitiesTabPanel.tsx b/app/src/components/Tabs/MyProfileTab/ManageCitiesTabPanel.tsx
index ec3027b9a..4b3ac944f 100644
--- a/app/src/components/Tabs/MyProfileTab/ManageCitiesTabPanel.tsx
+++ b/app/src/components/Tabs/MyProfileTab/ManageCitiesTabPanel.tsx
@@ -4,23 +4,17 @@ import {
Button,
IconButton,
List,
- ListItem,
+ Text,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverTrigger,
Table,
- TableContainer,
- Tbody,
- Td,
- Text,
- Th,
- Thead,
- Tr,
useDisclosure,
+ Icon,
} from "@chakra-ui/react";
-import { AddIcon } from "@chakra-ui/icons";
+import { MdAdd } from "react-icons/md";
import { FiTrash2 } from "react-icons/fi";
import { MdDomain, MdMoreVert, MdOutlineFileDownload } from "react-icons/md";
import NextLink from "next/link";
@@ -38,7 +32,7 @@ const ManageCitiesTabPanel: FC = ({ t }) => {
api.useGetCitiesAndYearsQuery();
const {
- isOpen: isCityDeleteModalOpen,
+ open: isCityDeleteModalOpen,
onOpen: onCityDeleteModalOpen,
onClose: onCityDeleteModalClose,
} = useDisclosure();
@@ -63,7 +57,6 @@ const ManageCitiesTabPanel: FC = ({ t }) => {
}
type="submit"
h="48px"
w="auto"
@@ -76,144 +69,148 @@ const ManageCitiesTabPanel: FC = ({ t }) => {
fontWeight="semibold"
fontSize="button.md"
>
+
{t("add-city")}
-
-
-
-
- {t("city-name")} |
- {t("state-province")} |
- {t("country")} |
- {t("last-updated")} |
-
-
-
- {citiesAndYears?.map(({ city }) => (
-
-
-
-
- {city.name}
-
- |
- {city.region} |
- {city.country} |
-
+
+ {t("city-name")}
+ {t("state-province")}
+ {t("country")}
+
+ {t("last-updated")}
+
+
+
+
+ {citiesAndYears?.map(({ city }) => (
+
+
+
-
- {city?.lastUpdated &&
- new Date(city.lastUpdated).toLocaleDateString()}
-
-
-
- }
- />
-
-
+ {city.name}
+
+
+ {city.region}
+ {city.country}
+
+
+ {city?.lastUpdated &&
+ new Date(city.lastUpdated).toLocaleDateString()}
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
- {t("download-city-data")}
-
-
- {
- setCityData(city);
- onCityDeleteModalOpen();
- }}
+ {t("download-city-data")}
+
+
+ {
+ setCityData(city);
+ onCityDeleteModalOpen();
+ }}
+ >
+
+
-
-
- {t("remove-city")}
-
-
-
-
-
-
- |
-
- ))}
-
-
-
+ {t("remove-city")}
+
+
+
+
+
+
+
+
+ ))}
+
+
{!!cityData && (
) => {
- setSelectedRowId(row.original.id);
- setIsModalOpen(true);
- };
+ const subTableColumns: Column[] = useMemo(() => {
+ const handleDeleteClick = (row: Row) => {
+ setSelectedRowId(row.original.id);
+ setIsModalOpen(true);
+ };
- const handleResetClick = (row: Row) => {
- setSelectedRowId(row.original.id);
- resetUserInvite({ cityInviteId: row.original.id });
- if (error) {
- showErrorToast();
- } else {
- showSuccessToast();
- }
- };
+ const handleResetClick = (row: Row) => {
+ setSelectedRowId(row.original.id);
+ resetUserInvite({ cityInviteId: row.original.id });
+ if (error) {
+ showErrorToast();
+ } else {
+ showSuccessToast();
+ }
+ };
- const subTableColumns: Column[] = useMemo(() => {
const getTextAndBorderColor = (value: CityInviteStatus) => {
switch (value) {
case CityInviteStatus.ACCEPTED:
- return theme.colors.sentiment.positiveDefault;
+ return "sentiment.positiveDefault";
case CityInviteStatus.PENDING:
- return theme.colors.sentiment.warningDefault;
+ return "sentiment.warningDefault";
default:
- return theme.colors.interactive.control;
+ return "interactive.control";
}
};
const getBackgroundColor = (value: CityInviteStatus) => {
switch (value) {
case CityInviteStatus.ACCEPTED:
- return theme.colors.sentiment.positiveOverlay;
+ return "sentiment.positiveOverlay";
case CityInviteStatus.PENDING:
- return theme.colors.sentiment.warningOverlay;
+ return "sentiment.warningOverlay";
default:
- return theme.colors.background.neutral;
+ return "background.neutral";
}
};
return [
{
Header: () => (
-
+
),
id: "spacer",
accessor: () => {},
@@ -101,7 +99,6 @@ const ManageUsersSubTable = React.memo(function SubTable({
id: "status",
Cell: ({ value }) => (
{value}
@@ -133,33 +130,33 @@ const ManageUsersSubTable = React.memo(function SubTable({
row.original.status === CityInviteStatus.EXPIRED ? (
handleResetClick(row)}
- icon={
-
- }
aria-label="edit"
variant="ghost"
color="content.tertiary"
- />
+ >
+
+
) : (
handleDeleteClick(row)}
- icon={
-
- }
aria-label="edit"
variant="ghost"
color="content.tertiary"
- />
+ >
+
+
),
},
];
- }, [theme]);
+ }, [error, resetUserInvite, showErrorToast, showSuccessToast]);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({
@@ -173,7 +170,7 @@ const ManageUsersSubTable = React.memo(function SubTable({
{...getTableProps()}
style={{
width: "100%",
- backgroundColor: theme.colors.background.alternativeLight,
+ backgroundColor: "background.alternativeLight",
}}
>
@@ -184,7 +181,7 @@ const ManageUsersSubTable = React.memo(function SubTable({
{...column.getHeaderProps()}
key={column.id}
style={{
- backgroundColor: theme.colors.background.alternativeLight,
+ backgroundColor: "background.alternativeLight",
borderTop: "1px solid border.overlay",
borderBottom: "1px solid border.overlay",
padding: "8px",
diff --git a/app/src/components/Tabs/MyProfileTab/ManageUsersTabPanel.tsx b/app/src/components/Tabs/MyProfileTab/ManageUsersTabPanel.tsx
index 7ede1acee..ffabc4402 100644
--- a/app/src/components/Tabs/MyProfileTab/ManageUsersTabPanel.tsx
+++ b/app/src/components/Tabs/MyProfileTab/ManageUsersTabPanel.tsx
@@ -3,20 +3,29 @@ import React, { FC, useEffect, useState } from "react";
import {
Box,
Center,
- CircularProgress,
+ ProgressCircle,
HStack,
Input,
- InputGroup,
- InputLeftElement,
Select,
+ Icon,
+ createListCollection,
} from "@chakra-ui/react";
import { api } from "@/services/api";
import { GetUserCityInvitesResponse } from "@/util/types";
import ManageUsersTable from "./ManageUsersTable";
-import { SearchIcon } from "@chakra-ui/icons";
+import { MdSearch } from "react-icons/md";
import { TitleMedium } from "@/components/Texts/Title";
import { AddCollaboratorButtonSmall } from "./AddCollaboratorButtonSmall";
import { useTranslation } from "@/i18n/client";
+import { InputGroup } from "@/components/ui/input-group";
+import {
+ SelectContent,
+ SelectItem,
+ SelectLabel,
+ SelectRoot,
+ SelectTrigger,
+ SelectValueText,
+} from "@/components/ui/select";
interface ManageUsersProps {
lng: string;
@@ -33,6 +42,14 @@ const ManageUsersTabPanel: FC = ({ lng }) => {
Array
>([]);
+ const roleCollection = createListCollection({
+ items: [
+ { label: t("all"), value: "all" },
+ { label: t("admin"), value: "admin" },
+ { label: t("contributor"), value: "contributor" },
+ ],
+ });
+
useEffect(() => {
if (cityInvites) {
const result = cityInvites.filter((invite: any) => {
@@ -76,16 +93,18 @@ const ManageUsersTabPanel: FC = ({ lng }) => {
borderWidth="1px"
borderStyle="solid"
borderColor="border.neutral"
+ startElement={
+
+ }
>
-
-
-
= ({ lng }) => {
onChange={(e) => setFilterTerm(e.target.value)}
/>
-
+
+
+
+
+
+ {roleCollection.items.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
>
) : (
-
+
+
+
+
+
+
+
)}
>
diff --git a/app/src/components/Tabs/MyProfileTab/ManageUsersTable.tsx b/app/src/components/Tabs/MyProfileTab/ManageUsersTable.tsx
index 331fe0df4..75fd51a11 100644
--- a/app/src/components/Tabs/MyProfileTab/ManageUsersTable.tsx
+++ b/app/src/components/Tabs/MyProfileTab/ManageUsersTable.tsx
@@ -12,16 +12,17 @@ import {
GetUserCityInvitesResponse,
GetUserCityInvitesResponseUserData,
} from "@/util/types";
-import { MdOutlineMode } from "react-icons/md";
+import { MdChevronLeft, MdChevronRight, MdOutlineMode } from "react-icons/md";
import {
- ChevronDownIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
-} from "@chakra-ui/icons";
-import { Button, IconButton, useTheme } from "@chakra-ui/react";
+ HiMiniChevronDown,
+ HiMiniChevronRight,
+ HiMiniChevronLeft,
+} from "react-icons/hi2";
+import { Button, IconButton, Icon } from "@chakra-ui/react";
import ManageUsersSubTable from "./ManageUsersSubTable";
import type { TFunction } from "i18next";
import UpdateUserModal from "@/components/Modals/update-user-modal";
+import { Toaster } from "@chakra-ui/react";
interface GroupedInvites {
name: string;
@@ -41,7 +42,6 @@ const ManageUsersTable = ({
cityInvites: GetUserCityInvitesResponse[];
t: TFunction;
}) => {
- const theme = useTheme();
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedUser, setSelectedUser] =
useState(null);
@@ -119,9 +119,9 @@ const ManageUsersTable = ({
).getToggleRowExpandedProps()}
>
{(row as ExtendedRow).isExpanded ? (
-
+
) : (
-
+
)}
),
@@ -163,27 +163,23 @@ const ManageUsersTable = ({
Cell: ({ row }) => (
handleEditClick(row)}
- icon={
-
- }
aria-label="edit"
variant="ghost"
color="content.tertiary"
- />
+ >
+
+
),
},
],
- [theme, t],
+ [t],
);
const renderRowSubComponent = React.useCallback(
({ row }: { row: ExtendedRow }) => (
-
+
),
- [theme, t],
+ [t],
);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
@@ -264,7 +260,12 @@ const ManageUsersTable = ({
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 0))}
disabled={currentPage === 0}
>
-
+
{currentPage + 1}/{Math.ceil(data.length / itemsPerPage)}
@@ -280,7 +281,12 @@ const ManageUsersTable = ({
}
disabled={(currentPage + 1) * itemsPerPage >= data.length}
>
-
+
{selectedUser && (
diff --git a/app/src/components/Tabs/MyProfileTab/index.tsx b/app/src/components/Tabs/MyProfileTab/index.tsx
index 20ed6eb00..ca8985c15 100644
--- a/app/src/components/Tabs/MyProfileTab/index.tsx
+++ b/app/src/components/Tabs/MyProfileTab/index.tsx
@@ -1,14 +1,6 @@
"use client";
-import {
- Box,
- Tab,
- TabList,
- TabPanel,
- TabPanels,
- Tabs,
- Text,
-} from "@chakra-ui/react";
+import { Box, Tabs, Text } from "@chakra-ui/react";
import { FC } from "react";
import { UserAttributes } from "@/models/User";
import { TFunction } from "i18next";
@@ -24,170 +16,172 @@ interface MyProfileTabProps {
export const MyProfileTab: FC = ({ t, lng, userInfo }) => {
return (
-
-
-
-
- {t("my-profile")}
-
-
- {t("my-profile-sub-title")}
-
-
-
-
+
+
+ {t("my-profile")}
+
+
+ {t("my-profile-sub-title")}
+
+
+
+
+
+
+ {t("account-details")}
+
+
+ {t("users")}
+
+
+ {t("city")}
+
+
+
+
-
-
+
{t("account-details")}
-
-
- {t("users")}
-
-
- {t("city")}
-
-
-
-
-
+
-
-
- {t("account-details")}
-
-
- {t("my-profile-sub-title")}
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {t("my-profile-sub-title")}
+
+
+
+
+
+
+
+
+
+
+
-
+
);
};
diff --git a/app/src/components/Tabs/my-files-tab.tsx b/app/src/components/Tabs/my-files-tab.tsx
index 1dc46c7b3..a055a4d7f 100644
--- a/app/src/components/Tabs/my-files-tab.tsx
+++ b/app/src/components/Tabs/my-files-tab.tsx
@@ -1,56 +1,40 @@
"use client";
import {
- Avatar,
Badge,
Box,
- Button,
- Checkbox,
+ Icon,
IconButton,
- Input,
- InputGroup,
- InputLeftElement,
List,
- ListItem,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
- PopoverTrigger,
- Select,
- Tab,
- TabList,
- TabPanel,
- TabPanels,
- Table,
- TableContainer,
Tabs,
- Tbody,
- Td,
+ Table,
Text,
- Th,
- Thead,
- Tr,
useDisclosure,
+ PopoverTrigger,
} from "@chakra-ui/react";
import React, { FC, useEffect, useMemo, useState } from "react";
-import { CheckIcon, ChevronRightIcon, SearchIcon } from "@chakra-ui/icons";
import {
MdMoreVert,
MdOutlineCheck,
MdOutlineFileDownload,
MdOutlineFolder,
+ MdCheck,
+ MdChevronRight,
+ MdSearch,
} from "react-icons/md";
import { FiTrash2 } from "react-icons/fi";
import { FaFileCsv } from "react-icons/fa";
-import { CityData } from "@/app/[lng]/[inventory]/settings/page";
import { Session } from "next-auth";
import DeleteFileModal from "@/components/Modals/delete-file-modal";
-import { TFunction } from "i18next";
+import { t, TFunction } from "i18next";
import { UserAttributes } from "@/models/User";
import { UserFileAttributes } from "@/models/UserFile";
@@ -65,7 +49,7 @@ interface MyFilesTabProps {
userInfo: UserAttributes | any;
lng: string;
userFiles: UserFileAttributes[] | any;
- inventory: InventoryResponse
+ inventory: InventoryResponse;
}
const MyFilesTab: FC = ({
@@ -75,9 +59,8 @@ const MyFilesTab: FC = ({
lng,
userInfo,
userFiles,
- inventory
+ inventory,
}) => {
-
const getYears = userFiles?.map((item: any): number => {
const date = new Date(item.lastUpdated);
return date.getFullYear();
@@ -103,7 +86,7 @@ const MyFilesTab: FC = ({
const filteredData = filterDataByYear(userFiles, selectedYear);
const {
- isOpen: isFileDeleteModalOpen,
+ open: isFileDeleteModalOpen,
onOpen: onFileDeleteModalOpen,
onClose: onFileDeleteModalClose,
} = useDisclosure();
@@ -112,7 +95,7 @@ const MyFilesTab: FC = ({
return (
<>
-
+
= ({
>
{t("city")}
-
-
-
+
+
+ {inventory?.city.name}
+
+
+
+
+
+
+
+
+ {inventory?.city.name}
+
+
+
+
+ {
+ setIsYearSelected(false);
+ setSelectedYear(null);
}}
>
- {inventory?.city.name}
-
-
-
-
-
+
-
-
-
-
- {inventory?.city.name}
-
-
-
-
- {
- setIsYearSelected(false);
- setSelectedYear(null);
- }}
- >
- {t("all-inventory-years")}{" "}
-
-
+ {" "}
+ {selectedYear}
+ >
+ )}
+
+
+
+ {!isYearSelected ? (
+
+
+
+
+ {t("inventory-year")}
+
+ {t("files")}
+
+ {t("last-updated")}
+
+
+
+
- {isYearSelected && (
- <>
- {" "}
- {selectedYear}
- >
- )}
-
-
-
- {!isYearSelected ? (
-
-
-
-
- {t("inventory-year")} |
- {t("files")} |
- {t("last-updated")} |
-
-
- (
+
+ {
+ setIsYearSelected(true);
+ setSelectedYear(year);
+ }}
+ display="flex"
+ gap="16px"
+ alignItems="center"
+ _hover={{
+ textDecoration: "underline",
+ cursor: "pointer",
+ color: "content.link",
+ }}
>
- {years.map((year: any) => (
-
- {
- setIsYearSelected(true);
- setSelectedYear(year);
- }}
- display="flex"
- gap="16px"
- alignItems="center"
- _hover={{
- textDecoration: "underline",
- cursor: "pointer",
- color: "content.link",
- }}
- >
-
-
-
- {year}
- |
+
+
+
+ {year}
+
-
- {filterDataByYear(userFiles, year).length}{" "}
- {filterDataByYear(userFiles, year).length ==
- 0
- ? "files"
- : "file"}
- |
+
+ {filterDataByYear(userFiles, year).length}{" "}
+ {filterDataByYear(userFiles, year).length == 0
+ ? "files"
+ : "file"}
+
- 21 Sept, 2023 |
-
- ))}
-
-
-
- ) : (
-
-
-
-
- {t("name")} |
- {t("sector")} |
- {t("status")} |
- {t("last-updated")} |
-
-
- 21 Sept, 2023
+
+ ))}
+
+
+ ) : (
+
+
+
+ {t("name")}
+ {t("sector")}
+ {t("status")}
+
+ {t("last-updated")}
+
+
+
+
+ {filteredData.map((file: any) => (
+
+
+
+
+
+
+
+ {file.file.fileName}
+
+
+
+ {file.sector}
+
+
+ {t(`${file.status}`)}
+
+
+
- {filteredData.map((file: any) => (
-
-
-
-
-
-
-
- {file.file.fileName}
-
-
- |
- {file.sector} |
-
-
- {t(`${file.status}`)}
-
- |
- {file.lastUpdated as any}
+
+
+
- {file.lastUpdated as any}
-
-
- }
- >
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
-
- {t("download-file")}
-
-
-
- {
- setFileData(file);
- onFileDeleteModalOpen();
- }}
- >
-
-
- {t("delete-file")}
-
-
-
-
-
- {t("mark-as-completed")}
-
-
-
-
-
-
- |
-
- ))}
-
-
-
- )}
-
-
-
-
+
+ {t("download-file")}
+
+
+
+ {
+ setFileData(file);
+ onFileDeleteModalOpen();
+ }}
+ >
+
+
+ {t("delete-file")}
+
+
+
+
+
+ {t("mark-as-completed")}
+
+
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+
+
-
+
= ({
cities,
defaultCityId,
}) => {
- const [tabIndex, setTabIndex] = useState(0);
const [cityId, setCityId] = useState(defaultCityId);
const [inventoryId, setInventoryId] = useState("");
@@ -71,7 +60,7 @@ const MyInventoriesTab: FC = ({
api.useGetInventoriesQuery({ cityId: cityId! }, { skip: !cityId });
const {
- isOpen: isInventoryDeleteModalOpen,
+ open: isInventoryDeleteModalOpen,
onOpen: onInventoryDeleteModalOpen,
onClose: onInventoryDeleteModalClose,
} = useDisclosure();
@@ -85,287 +74,287 @@ const MyInventoriesTab: FC = ({
return (
<>
-
-
-
-
- {t("my-inventories")}
-
-
- {t("my-inventories-sub-title")}
-
-
+
+
+
+ {t("my-inventories")}
+
+
+ {t("my-inventories-sub-title")}
+
+
-
-
- {t("city")}
-
- setTabIndex(index)}
- >
-
- {cities?.map((city: CityAttributes) => (
- setCityId(city?.cityId!)}
- sx={{
- w: "223px",
- justifyContent: "left",
- h: "52px",
- letterSpacing: "wide",
- color: "content.secondary",
- lineHeight: "20px",
- fontStyle: "normal",
- fontSize: "label.lg",
- fontWeight: "medium",
- fontFamily: "heading",
- }}
- _selected={{
- color: "content.link",
- fontSize: "label.lg",
- fontWeight: "medium",
- backgroundColor: "background.neutral",
- borderRadius: "8px",
- borderWidth: "1px",
- borderStyle: "solid",
- borderColor: "content.link",
- }}
- >
- {city.name}
-
- ))}
-
+
+
+ {t("city")}
+
+
+
+ {cities?.map((city: CityAttributes) => (
+ setCityId(city?.cityId!)}
+ style={{
+ width: "223px",
+ justifyContent: "left",
+ height: "52px",
+ letterSpacing: "wide",
+ color: "content.secondary",
+ lineHeight: "20px",
+ fontStyle: "normal",
+ fontSize: "label.lg",
+ fontWeight: "medium",
+ fontFamily: "heading",
+ }}
+ _selected={{
+ color: "content.link",
+ fontSize: "label.lg",
+ fontWeight: "medium",
+ backgroundColor: "background.neutral",
+ borderRadius: "8px",
+ borderWidth: "1px",
+ borderStyle: "solid",
+ borderColor: "content.link",
+ }}
+ >
+ {city.name}
+
+ ))}
+
-
- {cities?.map((city: CityAttributes) => (
- setCityId(city.cityId)}
- key={city.cityId}
- display="flex"
- flexDirection="column"
- gap="24px"
- borderRadius="8px"
+ {cities?.map((city: CityAttributes) => (
+ setCityId(city.cityId)}
+ key={city.cityId}
+ value={city.cityId}
+ display="flex"
+ flexDirection="column"
+ gap="24px"
+ borderRadius="8px"
+ >
+
+
+
+
+ {city.name}
+
+
+
+
+
-
-
-
-
- {city.name}
-
-
-
-
-
- {t("all-inventory-years")}
-
-
-
-
-
-
-
- |
- {t("inventory-year")} |
- {t("status")} |
- {t("last-updated")} |
-
-
-
- {inventories?.map(
- (inventory: InventoryAttributes) => (
-
-
-
-
-
- |
-
- {inventory.year}
- |
-
- {/* TODO */}
- {/* generate status from progress API */}
-
- |
- {/* TODO remove hardcoded date https://openearth.atlassian.net/browse/ON-3350 */}
- 21 Sept, 2023 |
-
-
-
- }
- >
-
-
-
-
-
-
-
+ {t("all-inventory-years")}
+
+
+
+
+
+
+
+
+ {t("inventory-year")}
+
+ {t("status")}
+
+ {t("last-updated")}
+
+
+
+
+ {inventories?.map((inventory: InventoryAttributes) => (
+
+
+
+
+
+
+
+ {inventory.year}
+
+
+ {/* TODO */}
+ {/* generate status from progress API */}
+
+
+
+
+
+
+ {/* TODO remove hardcoded date https://openearth.atlassian.net/browse/ON-3350 */}
+ 21 Sept, 2023
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- {t("download-csv")}
-
-
- {
- setInventoryId(
- inventory.inventoryId,
- );
- onInventoryDeleteModalOpen();
- }}
- >
-
-
- {t("delete-inventory")}
-
-
-
-
-
-
- |
-
- ),
- )}
-
-
-
-
-
- ))}
-
-
-
+
+ {t("download-csv")}
+
+
+ {
+ setInventoryId(inventory.inventoryId);
+ onInventoryDeleteModalOpen();
+ }}
+ >
+
+
+ {t("delete-inventory")}
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ ))}
+
-
+
>
diff --git a/app/src/components/Texts/BlueSubtitle.tsx b/app/src/components/Texts/BlueSubtitle.tsx
index 736bca946..96ce58e1d 100644
--- a/app/src/components/Texts/BlueSubtitle.tsx
+++ b/app/src/components/Texts/BlueSubtitle.tsx
@@ -1,7 +1,5 @@
import { TFunction } from "i18next";
-import { undefined } from "zod";
import { Text } from "@chakra-ui/react";
-import { Trans } from "react-i18next/TransWithoutContext";
export const BlueSubtitle = ({
t,
@@ -16,7 +14,7 @@ export const BlueSubtitle = ({
fontWeight="semibold"
lineHeight="24"
my={4}
- textColor={"blue"}
+ color="blue"
>
{t(text).toUpperCase()}
diff --git a/app/src/components/Texts/Body.tsx b/app/src/components/Texts/Body.tsx
index e84c15c42..72cb784d3 100644
--- a/app/src/components/Texts/Body.tsx
+++ b/app/src/components/Texts/Body.tsx
@@ -10,7 +10,7 @@ export const BodyXLarge = ({ text, ...props }: BodyProps) => (
fontSize="body.xl"
fontWeight="regular"
lineHeight="32"
- textColor={"content.tertiary"}
+ color="content.tertiary"
{...props}
>
{text}
@@ -24,7 +24,7 @@ export const BodyLarge = ({ text, ...props }: BodyProps) => (
fontSize="body.lg"
fontWeight="regular"
lineHeight="24"
- textColor={"content.tertiary"}
+ color="content.tertiary"
{...props}
>
{text}
@@ -37,7 +37,7 @@ export const BodyMedium = ({ text, ...props }: BodyProps) => (
fontSize="body.md"
fontWeight="regular"
lineHeight="20"
- textColor={"content.tertiary"}
+ color="content.tertiary"
{...props}
>
{text}
diff --git a/app/src/components/Texts/Button.tsx b/app/src/components/Texts/Button.tsx
index 9f8a9c92c..ed07c9e84 100644
--- a/app/src/components/Texts/Button.tsx
+++ b/app/src/components/Texts/Button.tsx
@@ -6,7 +6,7 @@ export const ButtonSmall = (props: TextProps) => (
fontSize="button.sm"
fontWeight="semibold"
lineHeight="16px"
- textColor={"content.secondary"}
+ color="content.secondary"
{...props}
>
{props.children}
@@ -19,7 +19,7 @@ export const ButtonMedium = (props: TextProps) => (
fontSize="button.md"
fontWeight="semibold"
lineHeight="16px"
- textColor={"content.secondary"}
+ color="content.secondary"
{...props}
>
{props.children}
diff --git a/app/src/components/Texts/Headline.tsx b/app/src/components/Texts/Headline.tsx
index 030fd3d7c..4292d4b3b 100644
--- a/app/src/components/Texts/Headline.tsx
+++ b/app/src/components/Texts/Headline.tsx
@@ -10,7 +10,7 @@ export const HeadlineSmall = ({ text, ...props }: HeadlineProps) => (
fontSize="headline.sm"
fontWeight="semibold"
lineHeight="32px"
- textColor={"base.dark"}
+ color="base.dark"
{...props}
>
{text}
diff --git a/app/src/components/Texts/Title.tsx b/app/src/components/Texts/Title.tsx
index e34b1b23a..5b1d8c664 100644
--- a/app/src/components/Texts/Title.tsx
+++ b/app/src/components/Texts/Title.tsx
@@ -10,7 +10,7 @@ export const TitleLarge = ({ text, ...props }: TitleProps) => (
fontSize="title.lg"
fontWeight="semibold"
lineHeight="28"
- textColor={"content.secondary"}
+ color="content.secondary"
{...props}
>
{text}
@@ -23,7 +23,7 @@ export const TitleMedium = ({ ...props }: HeadingProps) => (
fontSize="title.md"
fontWeight="semibold"
lineHeight="24"
- textColor={"content.secondary"}
+ color="content.secondary"
{...props}
>
{props.children}
diff --git a/app/src/components/building-select-input.tsx b/app/src/components/building-select-input.tsx
index 4bf5e6e36..f515b5290 100644
--- a/app/src/components/building-select-input.tsx
+++ b/app/src/components/building-select-input.tsx
@@ -1,18 +1,17 @@
-import { Box, Select, Text } from "@chakra-ui/react";
+import { Box, Icon, NativeSelectRoot, Text } from "@chakra-ui/react";
import React, { FC, use, useEffect, useRef, useState } from "react";
import {
Control,
Controller,
- FieldError,
- FieldErrors,
UseFormRegister,
UseFormSetValue,
} from "react-hook-form";
import { Inputs } from "./Modals/activity-modal/activity-modal-body";
-import { WarningIcon } from "@chakra-ui/icons";
import { TFunction } from "i18next";
import type { SuggestedActivity } from "@/util/form-schema";
import MultiSelectInput from "@/components/MultiSelectInput";
+import { MdWarning } from "react-icons/md";
+import { NativeSelectField } from "./ui/native-select";
interface BuildingTypeSelectInputProps {
title: string;
@@ -52,7 +51,7 @@ const BuildingTypeSelectInput: FC = ({
setSelectedActivityValue(prefilledValue);
setValue(activity as any, prefilledValue);
}
- }, [prefilledValue]);
+ }, [activity, prefilledValue, setValue]);
if (multiselect) {
return (
@@ -71,9 +70,8 @@ const BuildingTypeSelectInput: FC = ({
const error = activity.split(".").reduce((acc, key) => acc?.[key], errors);
return (
-
+
= ({
rules={{ required: required === false ? false : t("option-required") }}
render={({ field }) => {
return (
-
+ {
+ field.onChange(e.currentTarget.value);
+ setValue(activity as any, e.currentTarget.value);
+ }}
+ value={field.value}
+ >
+ {options?.map((item: string) => (
+
+ ))}
+
+
);
}}
/>
{error ? (
-
+
{error?.message}
) : (
diff --git a/app/src/components/custom-radio-buttons.tsx b/app/src/components/custom-radio-buttons.tsx
deleted file mode 100644
index e21e2c9b5..000000000
--- a/app/src/components/custom-radio-buttons.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Box, Icon, useRadio, UseRadioProps } from "@chakra-ui/react";
-import { InventoryButtonCheckIcon } from "./icons";
-
-interface CustomRadioProps extends UseRadioProps {
- children: React.ReactNode;
-}
-
-export function CustomRadioButtons(props: CustomRadioProps) {
- const { getInputProps, getRadioProps } = useRadio(props);
-
- return (
-
-
-
- {props.isChecked ? : ""}
- {props.children}
-
-
- );
-}
diff --git a/app/src/components/dependent-select-input.tsx b/app/src/components/dependent-select-input.tsx
index cd3aebaf2..99ce0b9c0 100644
--- a/app/src/components/dependent-select-input.tsx
+++ b/app/src/components/dependent-select-input.tsx
@@ -1,8 +1,10 @@
import { ExtraField } from "@/util/form-schema";
-import { Box, Select, Text } from "@chakra-ui/react";
+import { Box, Icon, NativeSelectField, Text } from "@chakra-ui/react";
import { useWatch } from "react-hook-form";
-import { WarningIcon } from "@chakra-ui/icons";
import React from "react";
+import { SelectRoot, SelectTrigger, SelectValueText } from "./ui/select";
+import { MdWarning } from "react-icons/md";
+import { NativeSelectRoot } from "./ui/native-select";
const DependentSelectInput = ({
field,
@@ -30,11 +32,13 @@ const DependentSelectInput = ({
const fieldId = field.id;
return (
-
+
+ {field.dependentOptions?.[dependentValue]?.map((option) => (
+
+ ))}
+
+
{errors?.activity?.[fieldId] ? (
-
+
{errors?.activity?.[fieldId]?.message}
) : (
diff --git a/app/src/components/dropdown-select-input.tsx b/app/src/components/dropdown-select-input.tsx
index 3af7b564a..93b729d73 100644
--- a/app/src/components/dropdown-select-input.tsx
+++ b/app/src/components/dropdown-select-input.tsx
@@ -1,26 +1,31 @@
import React, { useEffect, useState } from "react";
import {
Box,
- Tag,
- TagLabel,
- TagCloseButton,
- Checkbox,
List,
ListItem,
Text,
Input,
- FormLabel,
+ TagLabel,
+ IconButton,
+ Icon,
+ Fieldset,
+ CheckboxGroup,
} from "@chakra-ui/react";
-import { MdArrowDropDown, MdArrowDropUp } from "react-icons/md";
+import { MdArrowDropDown, MdArrowDropUp, MdClose } from "react-icons/md";
import { SubSectorWithRelations } from "@/app/[lng]/[inventory]/data/[step]/types";
import {
UseFormRegister,
UseFormSetValue,
UseFormWatch,
+ useController,
useForm,
} from "react-hook-form";
-import { FileData } from "./Modals/add-file-data-modal";
+import { FileData } from "./Modals/add-file-data-dialog";
import { TFunction } from "i18next";
+import { Tag } from "./ui/tag";
+import { Checkbox } from "./ui/checkbox";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
interface DropdownSelectProps {
subsectors: SubSectorWithRelations[] | null;
@@ -38,33 +43,30 @@ const DropdownSelectInput: React.FC = ({
register,
}) => {
const [selectedItems, setSelectedItems] = useState([]);
+ const [showDropdown, setShowDropdown] = useState(false);
- const handleCheckboxChange = (
- event: React.ChangeEvent,
- value: string,
- ) => {
- if (event.target.checked) {
- setSelectedItems([...selectedItems, value]);
+ const handleShowdropDown = () => {
+ setShowDropdown((prev) => !prev);
+ };
+
+ const handleCheckboxChange = (checked: boolean, value: string) => {
+ if (checked) {
+ console.log(value);
+ setSelectedItems((prev) => [...prev, value]);
} else {
- setSelectedItems(selectedItems.filter((item) => item !== value));
+ setSelectedItems((prev) => prev.filter((item) => item !== value));
}
};
useEffect(() => {
- const subsectorValues = selectedItems.slice().join(",");
- setValue("subsectors", subsectorValues);
- }, [setValue, selectedItems]);
+ setValue("subsectors", selectedItems.join(","));
+ }, [selectedItems, setValue]);
const handleRemoveItem = (item: string) => {
- setSelectedItems(
- selectedItems.filter((selectedItem) => selectedItem !== item),
- );
+ setSelectedItems((prev) => prev.filter((selected) => selected !== item));
};
- const [showDropdown, setShowDropdown] = useState(false);
- const handleShowdropDown = () => {
- setShowDropdown((prev) => !prev);
- };
+ console.log(subsectors);
return (
@@ -95,22 +97,28 @@ const DropdownSelectInput: React.FC = ({
{t("select-placeholder")}
)}
- {selectedItems.map((item) => (
+ {selectedItems.map((item, i) => (
- {item}
- {t(item)}{" "}
+ handleRemoveItem(item)}
color="content.alternative"
+ boxSize="16px"
/>
))}
@@ -136,41 +144,37 @@ const DropdownSelectInput: React.FC = ({
py="16px"
zIndex="50"
>
-
- {subsectors?.map((subsector) => (
-
-
- handleCheckboxChange(e, subsector.subsectorName!)
- }
- />
-
- {subsector.subsectorName}
-
-
- ))}
-
+
+
+
+
+ {subsectors?.map((subsector) => {
+ return (
+
+ handleCheckboxChange(e, subsector.subsectorName!)
+ }
+ >
+
+ {t(subsector?.subsectorName!)}
+
+
+ );
+ })}
+
+
+
+
)}
diff --git a/app/src/components/email-input.tsx b/app/src/components/email-input.tsx
index 8a4d2af96..51e940386 100644
--- a/app/src/components/email-input.tsx
+++ b/app/src/components/email-input.tsx
@@ -1,13 +1,8 @@
import { emailPattern } from "@/util/validation";
-import { WarningIcon } from "@chakra-ui/icons";
-import {
- FormControl,
- FormErrorMessage,
- FormLabel,
- Input,
- Text,
-} from "@chakra-ui/react";
+import { Icon, Input, Text } from "@chakra-ui/react";
import { FieldError } from "react-hook-form";
+import { Fieldset } from "@chakra-ui/react";
+import { Field } from "@/components/ui/field";
export default function EmailInput({
children,
@@ -27,8 +22,7 @@ export default function EmailInput({
disabled?: boolean;
}) {
return (
-
- {name}
+
{children}
- {error && (
-
-
-
- {error.message}
-
-
- )}
-
+
);
}
diff --git a/app/src/components/file-input.tsx b/app/src/components/file-input.tsx
index d59d6f596..b8cf7b13d 100644
--- a/app/src/components/file-input.tsx
+++ b/app/src/components/file-input.tsx
@@ -1,13 +1,12 @@
-import { Box, FormLabel, Heading, Input, Text, VStack } from "@chakra-ui/react";
+import { Box, Heading, Input, Text, VStack } from "@chakra-ui/react";
import { TFunction } from "i18next";
-import React, {
- useState,
- DragEvent,
- ChangeEvent,
- Dispatch,
- SetStateAction,
-} from "react";
+import React, { useState, DragEvent, Dispatch, SetStateAction } from "react";
import { FiUpload } from "react-icons/fi";
+import { Field } from "./ui/field";
+import {
+ FileUploadDropzone,
+ FileUploadRoot,
+} from "@/components/ui/file-upload";
interface FileUploadProps {
onFileSelect: (file: File) => void; // Define a type for the onFileSelect prop
@@ -33,7 +32,7 @@ const FileInput: React.FC = ({
setDragging(false);
};
- const handleDrop = (e: DragEvent) => {
+ const handleDrop = (e: any) => {
e.preventDefault();
setDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
@@ -42,10 +41,12 @@ const FileInput: React.FC = ({
}
};
- const handleChange = (e: ChangeEvent) => {
- if (e.target.files && e.target.files[0]) {
- onFileSelect(e.target.files[0]);
- setUploadedFile(e.target.files[0]);
+ const handleChange = (e: any) => {
+ console.log(e.acceptedFiles);
+ console.log(e.acceptedFiles[0]);
+ if (e.acceptedFiles && e.acceptedFiles[0]) {
+ onFileSelect(e.acceptedFiles[0]);
+ setUploadedFile(e.acceptedFiles[0]);
}
};
@@ -65,53 +66,63 @@ const FileInput: React.FC = ({
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
-
-
-
-
-
-
-
- handleChange(e)}
+ border="none"
+ onDragOver={handleDragOver}
+ onDragLeave={handleDragLeave}
+ >
+
- {t("click-to-upload")}
- {" "}
-
- {t("drag-and-drop")}
-
-
-
- {t("csv-or-json")}
-
-
-
+
+
+
+
+
+ {t("click-to-upload")}
+ {" "}
+
+ {t("drag-and-drop")}
+
+
+
+ {t("csv-or-json")}
+
+
+ }
+ datatest-id="file-upload"
+ />
+
);
};
diff --git a/app/src/components/form-input.tsx b/app/src/components/form-input.tsx
index d532db7a3..c426f8b05 100644
--- a/app/src/components/form-input.tsx
+++ b/app/src/components/form-input.tsx
@@ -1,8 +1,9 @@
"use client";
-import { Box, FormControl, FormLabel, Input, Text } from "@chakra-ui/react";
-import React, { FC, LegacyRef, useEffect, useRef, useState } from "react";
+import { Input, Text } from "@chakra-ui/react";
+import React, { FC, useEffect, useState } from "react";
import { FieldError } from "react-hook-form";
+import { Field } from "@/components/ui/field";
interface FormInputProps {
label: string;
@@ -34,16 +35,21 @@ const FormInput: FC = ({
};
return (
-
-
- {label}
-
+
+ {label}
+
+ }
+ >
= ({
{error.message}
)}
-
+
);
};
diff --git a/app/src/components/form-select-input.tsx b/app/src/components/form-select-input.tsx
index 4ba1d9ebf..f2b0068be 100644
--- a/app/src/components/form-select-input.tsx
+++ b/app/src/components/form-select-input.tsx
@@ -1,6 +1,7 @@
import { Box, Select, Text } from "@chakra-ui/react";
import React, { FC } from "react";
import { FieldError } from "react-hook-form";
+import { NativeSelectField, NativeSelectRoot } from "./ui/native-select";
interface FormInputProps {
label: string;
@@ -24,7 +25,6 @@ const FormSelectInput: FC = ({
return (
= ({
>
{label}
-
+ onInputChange(e)}
+ disabled={isDisabled}
+ >
+
+
+
+
);
};
diff --git a/app/src/components/form-select-organization.tsx b/app/src/components/form-select-organization.tsx
index aa0afe5d1..ee2c1c29e 100644
--- a/app/src/components/form-select-organization.tsx
+++ b/app/src/components/form-select-organization.tsx
@@ -1,6 +1,7 @@
-import { Box, Select, Text } from "@chakra-ui/react";
+import { Box, NativeSelectField, Select, Text } from "@chakra-ui/react";
import React, { FC } from "react";
import { FieldError } from "react-hook-form";
+import { NativeSelectRoot } from "./ui/native-select";
interface FormInputProps {
label: string;
@@ -24,7 +25,6 @@ const FormSelectOrganization: FC = ({
return (
= ({
>
{label}
-
+ onInputChange(e)}
+ >
+
+
+
+
);
};
diff --git a/app/src/components/formatted-number-input.tsx b/app/src/components/formatted-number-input.tsx
index a4c9b5105..77e918cda 100644
--- a/app/src/components/formatted-number-input.tsx
+++ b/app/src/components/formatted-number-input.tsx
@@ -1,21 +1,21 @@
import React from "react";
import { Control, Controller, useWatch } from "react-hook-form";
+import { Group, InputAddon, NumberInput } from "@chakra-ui/react";
+import { useParams } from "next/navigation";
import {
- InputGroup,
- InputRightAddon,
- NumberInput,
NumberInputField,
NumberInputProps,
-} from "@chakra-ui/react";
-import { useParams } from "next/navigation";
+ NumberInputRoot,
+} from "./ui/number-input";
import { REGIONALLOCALES } from "@/util/constants";
+import { InputGroup } from "./ui/input-group";
interface FormattedNumberInputProps extends NumberInputProps {
control: Control;
name: string;
setError?: Function;
clearErrors?: Function;
- defaultValue?: number;
+ defaultValue?: string | undefined;
isDisabled?: boolean;
placeholder?: string;
children?: React.ReactNode;
@@ -24,6 +24,8 @@ interface FormattedNumberInputProps extends NumberInputProps {
testId?: string;
localization?: string;
t: Function;
+ max?: number;
+ min?: number;
}
function FormattedNumberInput({
@@ -32,7 +34,7 @@ function FormattedNumberInput({
id,
testId,
name,
- defaultValue = 0,
+ defaultValue = "0",
isDisabled = false,
children,
placeholder,
@@ -128,16 +130,23 @@ function FormattedNumberInput({
},
}}
render={({ field }) => (
-
-
+ {
- field.onChange(valueAsString);
+ shadow="1dp"
+ w="full"
+ borderRightRadius={children ? 0 : "md"} // Adjust border radius
+ bgColor={isDisabled ? "background.neutral" : "base.light"}
+ pos="relative"
+ zIndex={3}
+ min={min}
+ max={max}
+ onValueChange={(valueAsString: any) => {
+ const parsedValue = parse(valueAsString.value);
+ field.onChange(parsedValue);
}}
- isValidCharacter={isCharacterValid}
- onBlur={(e) => {
+ onBlur={(e: any) => {
e.preventDefault();
const parsedValue = parse(e.target.value);
field.onChange(parseFloat(parsedValue));
@@ -145,25 +154,16 @@ function FormattedNumberInput({
{...rest}
>
-
+
{children && (
-
{children}
-
+
)}
-
+
)}
/>
);
diff --git a/app/src/components/icons.tsx b/app/src/components/icons.tsx
index 17a12fbd9..5c45963aa 100644
--- a/app/src/components/icons.tsx
+++ b/app/src/components/icons.tsx
@@ -21,7 +21,13 @@ export const DataAlertIcon = (props: any) => (
export const WorldSearchIcon = (props: any) => (
@@ -1205,3 +1211,63 @@ export const HeatIcon = () => {
);
};
+
+export const MissingDataIcon = () => {
+ return (
+
+ );
+};
+
+export const NoDatasourcesIcon = () => {
+ return (
+
+ );
+};
diff --git a/app/src/components/loading-state.tsx b/app/src/components/loading-state.tsx
index 5455b7aef..8b899a670 100644
--- a/app/src/components/loading-state.tsx
+++ b/app/src/components/loading-state.tsx
@@ -1,41 +1,87 @@
import { Box, Card } from "@chakra-ui/react";
-const LoadingState = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
+const LoadingState = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
- export default LoadingState;
\ No newline at end of file
+export default LoadingState;
diff --git a/app/src/components/missing-inventory.tsx b/app/src/components/missing-inventory.tsx
index df8b8d1ae..9739396a0 100644
--- a/app/src/components/missing-inventory.tsx
+++ b/app/src/components/missing-inventory.tsx
@@ -1,11 +1,16 @@
import { Box, Link, Text } from "@chakra-ui/layout";
import Image from "next/image";
-import { Button, CircularProgress, Heading } from "@chakra-ui/react";
-import { ArrowForwardIcon } from "@chakra-ui/icons";
+import { Heading } from "@chakra-ui/react";
import React, { useEffect } from "react";
import { useTranslation } from "@/i18n/client";
import { useRouter } from "next/navigation";
import { api } from "@/services/api";
+import { MdArrowForward } from "react-icons/md";
+import { Button } from "@/components/ui/button";
+import {
+ ProgressCircleRing,
+ ProgressCircleRoot,
+} from "@/components/ui/progress-circle";
const MissingInventory = ({ lng }: { lng: string }) => {
const { data: userInfo, isLoading: isUserInfoLoading } =
@@ -78,10 +83,9 @@ const MissingInventory = ({ lng }: { lng: string }) => {
h="48px"
px="24px"
fontSize="body.md"
- isLoading={isUserInfoLoading}
- rightIcon={}
+ loading={isUserInfoLoading}
>
- {t("goto-dashboard")}
+ {t("goto-dashboard")}
@@ -93,7 +97,9 @@ const MissingInventory = ({ lng }: { lng: string }) => {
justifyContent="center"
className="flex w-full relative h-[100vh] z-10"
>
-
+
+
+
);
};
diff --git a/app/src/components/navigation-bar.tsx b/app/src/components/navigation-bar.tsx
index c1bec5279..b350090ba 100644
--- a/app/src/components/navigation-bar.tsx
+++ b/app/src/components/navigation-bar.tsx
@@ -2,31 +2,26 @@
import { useTranslation } from "@/i18n/client";
import { languages } from "@/i18n/settings";
-import { TriangleDownIcon, TriangleUpIcon } from "@chakra-ui/icons";
-import {
- Avatar,
- Box,
- Button,
- Divider,
- Heading,
- Icon,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- Text,
-} from "@chakra-ui/react";
+import { Box, Heading, Icon, Link, Separator, Text } from "@chakra-ui/react";
import i18next from "i18next";
import { signOut, useSession } from "next-auth/react";
import Image from "next/image";
-import NextLink from "next/link";
+
import { CircleFlag } from "react-circle-flags";
import { FiSettings } from "react-icons/fi";
import { MdLogout } from "react-icons/md";
import Cookies from "js-cookie";
-import { useParams } from "next/navigation";
+import { useParams, useRouter } from "next/navigation";
import { api } from "@/services/api";
-import { useEffect } from "react";
+import {
+ MenuContent,
+ MenuItem,
+ MenuRoot,
+ MenuTrigger,
+} from "@/components/ui/menu";
+import { Avatar, AvatarGroup } from "@/components/ui/avatar";
+
+import { Button } from "@/components/ui/button";
function countryFromLanguage(language: string) {
return language == "en" ? "us" : language;
@@ -80,12 +75,13 @@ export function NavigationBar({
const { data: userInfo, isLoading: isUserInfoLoading } =
api.useGetUserInfoQuery();
const currentInventoryId = userInfo?.defaultInventoryId;
+ const router = useRouter();
return (
-
+
-
-
-
+
+
+
{t("title")}
-
+
{showNav && !isPublic && (
<>
{" "}
-
-
+
+
{t("dashboard")}
-
-
+
{t("learning-hub")}
-
-
+
+
>
)}
-
- {!isPublic && status === "authenticated" && session.user && (
-
+
+ {!isPublic && status === "authenticated" && session.user && (
+
+ : }
+ className="whitespace-nowrap normal-case"
+ >
+
- }
- rightIcon={isOpen ? : }
- className="whitespace-nowrap normal-case"
- _hover={{
- bg: "#FFF2",
- }}
- _active={{
- bg: "#FFF3",
- }}
- >
-
- {session.user?.name}
-
-
-
+ {session.user?.name}
+
+
+
+
+
-
+ router.push(
+ `/${inventory ? inventory : currentInventoryId}/settings`,
+ )
+ }
>
-
-
+ {t("settings")}
+
+
-
- >
+
+
)}
-
- )}
+
+
);
}
diff --git a/app/src/components/password-input.tsx b/app/src/components/password-input.tsx
index 9cfbc0e13..388b37cbf 100644
--- a/app/src/components/password-input.tsx
+++ b/app/src/components/password-input.tsx
@@ -1,21 +1,10 @@
-import { ViewIcon, ViewOffIcon, WarningIcon } from "@chakra-ui/icons";
-import {
- Button,
- FormControl,
- FormErrorMessage,
- FormLabel,
- Input,
- InputGroup,
- InputRightElement,
- List,
- ListIcon,
- ListItem,
- Text,
-} from "@chakra-ui/react";
+import { Box, List, ListIndicator, Text } from "@chakra-ui/react";
import { useState } from "react";
import { FieldError } from "react-hook-form";
import { CheckListIcon, CloseListIcon } from "./icons";
import { TFunction } from "i18next";
+import { Field } from "@/components/ui/field";
+import { PasswordInput as ChakraPasswordInput } from "@/components/ui/password-input";
export default function PasswordInput({
children,
@@ -38,78 +27,53 @@ export default function PasswordInput({
shouldValidate?: boolean;
watchPassword?: string;
}) {
- const [showPassword, setShowPassword] = useState(false);
- const handlePasswordVisibility = () => setShowPassword(!showPassword);
-
// Password checks
const password = watchPassword || "";
const hasLowercase = /[a-z]/.test(password);
const hasMinLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasNumber = /\d/.test(password);
- const hasSpecial = /[^A-Za-z0-9]/.test(password);
- // overal validity
- const isPasswordValid =
- hasMinLength && hasLowercase && hasUppercase && hasNumber && hasSpecial;
return (
-
- {name}
-
-
- value.length >= 8 || t("password-min-length"),
- hasUpperCase: (value: string) =>
- /[A-Z]/.test(value) || t("password-upper-case"),
- hasLowerCase: (value: string) =>
- /[a-z]/.test(value) || t("password-lower-case"),
- hasNumber: (value: string) =>
- /[0-9]/.test(value) || t("password-number"),
- }
- : undefined,
- })}
- />
-
-
- {showPassword ? (
-
- ) : (
-
- )}
-
-
-
- {children}
+
+
+ value.length >= 8 || t("password-min-length"),
+ hasUpperCase: (value: string) =>
+ /[A-Z]/.test(value) || t("password-upper-case"),
+ hasLowerCase: (value: string) =>
+ /[a-z]/.test(value) || t("password-lower-case"),
+ hasNumber: (value: string) =>
+ /[0-9]/.test(value) || t("password-number"),
+ }
+ : undefined,
+ })}
+ />
+
+ {children}
{/* Password Checklist */}
{shouldValidate && (
-
-
-
+
+
+ >
+ {hasMinLength ? : }
+
{t("password-min-length-check", { length: 8 })}
-
-
-
+
+
+ >
+ {hasUppercase ? : }
+
{t("password-upper-case-check")}
-
-
-
+
+
+ >
+ {hasLowercase ? : }
+
{t("password-lower-case-check")}
-
-
-
+
+
+ >
+ {hasNumber ? : }
+
{t("password-number-check")}
-
-
- )}
- {error && (
-
-
-
- {t(error.message || "")}
-
-
+
+
)}
-
+
);
}
diff --git a/app/src/components/percentage-breakdown-input.tsx b/app/src/components/percentage-breakdown-input.tsx
index c6fbef4ed..f23fd4d78 100644
--- a/app/src/components/percentage-breakdown-input.tsx
+++ b/app/src/components/percentage-breakdown-input.tsx
@@ -1,21 +1,12 @@
"use client";
-import { ChevronDownIcon, ChevronUpIcon } from "@chakra-ui/icons";
import {
- FormControl,
- FormErrorMessage,
- FormLabel,
+ Box,
+ Group,
HStack,
Icon,
Input,
- InputGroup,
- InputRightAddon,
- InputRightElement,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverContent,
- PopoverTrigger,
+ InputAddon,
Text,
} from "@chakra-ui/react";
import React, { FC, useMemo } from "react";
@@ -34,6 +25,16 @@ import {
WoodIcon,
} from "./icons";
import type { TFunction } from "i18next";
+import { Field } from "./ui/field";
+import {
+ PopoverArrow,
+ PopoverBody,
+ PopoverContent,
+ PopoverRoot,
+ PopoverTrigger,
+} from "./ui/popover";
+import { GoChevronDown, GoChevronUp } from "react-icons/go";
+import { InputGroup } from "./ui/input-group";
const categoryIconMapping: Record = {
"waste-type-municipal-solid-waste": MunicipalSolidWasteIcon,
@@ -128,28 +129,40 @@ const PercentageBreakdownInput: FC = ({
return `${t(category ?? "")} ${value}%`;
})
.join(", ");
- // breakdownCategories
- }, [breakDownValues, t]);
+ }, [breakDownValues, breakdownCategories, t]);
+
+ // Todo: get the popover state and implement the popover logic
+ const [isOpen, setIsOpen] = React.useState(false);
return (
-
-
- {label}
-
-
- {({ isOpen, onClose }) => (
- <>
-
-
+
+
+
+
+
+
+ {isOpen ? (
+
+ ) : (
+
+ )}
+
+ }
+ >
= ({
borderColor: "content.link",
}}
/>
-
- {isOpen ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {breakdownCategories.map((category) => (
-
-
-
- {t(category)}
-
-
- {
- setValue(
- `activity.${id}.${category}`,
- e.target.value,
- );
- }}
- shadow="1dp"
- name={id}
- borderRadius="4px"
- border="inputBox"
- background={background}
- color={
- isDisabled ? "content.tertiary" : "content.secondary"
- }
- placeholder="0"
- w="116px"
- px={4}
- py={3}
- min={0}
- max={100}
- defaultValue={0}
- borderWidth={error ? "1px" : 0}
- borderColor={error ? "sentiment.negativeDefault" : ""}
- bgColor="base.light"
- _focus={{
- borderWidth: "1px",
- shadow: "none",
- borderColor: "content.link",
- }}
- />
- %
-
-
- ))}
-
+
+
+
+
+
+ {breakdownCategories.map((category) => (
+
+
+
-
- {t("total")}
-
-
- {totalPercent as number}%
-
-
-
-
- >
- )}
-
- {error?.message && (
- {t(error.message)}
- )}
-
+ {t(category)}
+
+
+ %
+ {
+ setValue(`activity.${id}.${category}`, e.target.value);
+ }}
+ shadow="1dp"
+ name={id}
+ borderRadius="4px"
+ border="inputBox"
+ background={background}
+ color={
+ isDisabled ? "content.tertiary" : "content.secondary"
+ }
+ placeholder="0"
+ w="116px"
+ px={4}
+ py={3}
+ min={0}
+ max={100}
+ defaultValue={0}
+ borderWidth={error ? "1px" : 0}
+ borderColor={error ? "sentiment.negativeDefault" : ""}
+ bgColor="base.light"
+ _focus={{
+ borderWidth: "1px",
+ shadow: "none",
+ borderColor: "content.link",
+ }}
+ />
+
+
+ ))}
+
+
+ {t("total")}
+
+
+ {totalPercent as number}%
+
+
+
+
+
+ {error?.message && {t(error.message)}}
+
);
};
diff --git a/app/src/components/radio-button.tsx b/app/src/components/radio-button.tsx
deleted file mode 100644
index 972bb015e..000000000
--- a/app/src/components/radio-button.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Box, Flex, Icon, Text, useRadio } from "@chakra-ui/react";
-import { MdCheck } from "react-icons/md";
-
-export function RadioButton(props: any) {
- const { getInputProps, getRadioProps, state } = useRadio(props);
-
- const input = getInputProps();
- const checkbox = getRadioProps();
-
- return (
-
-
-
-
- {state.isChecked && }
- {props.children}
-
-
-
- );
-}
diff --git a/app/src/components/selector.tsx b/app/src/components/selector.tsx
index 6dc7eaeeb..985d4a45a 100644
--- a/app/src/components/selector.tsx
+++ b/app/src/components/selector.tsx
@@ -1,6 +1,7 @@
import type { TFunction } from "i18next";
import React from "react";
-import { Select, Text } from "@chakra-ui/react";
+import { Text } from "@chakra-ui/react";
+import { NativeSelectField, NativeSelectRoot } from "./ui/native-select";
export function Selector({
value,
@@ -13,41 +14,32 @@ export function Selector({
t: TFunction;
onChange: (event: React.ChangeEvent) => void;
}) {
- const selectStyles = {
- fontFamily: "Poppins",
- fontSize: "button.md",
- fontWeight: 600,
- lineHeight: "16px",
- letterSpacing: "1.25px",
- textTransform: "uppercase",
- };
-
return (
-
+
+ {options.map((opt) => (
+
+ ))}
+
+
);
}
diff --git a/app/src/components/steps/add-inventory-details-step.tsx b/app/src/components/steps/add-inventory-details-step.tsx
index 14b36baa4..479cf2b24 100644
--- a/app/src/components/steps/add-inventory-details-step.tsx
+++ b/app/src/components/steps/add-inventory-details-step.tsx
@@ -9,21 +9,26 @@ import { Inputs } from "../../app/[lng]/onboarding/setup/page";
import { useEffect } from "react";
import {
Box,
- FormControl,
- FormErrorMessage,
+ createListCollection,
Heading,
HStack,
- InputGroup,
- InputRightElement,
+ Icon,
Link,
- Select,
Text,
- useRadioGroup,
- UseRadioGroupProps,
} from "@chakra-ui/react";
-import { CheckIcon, WarningIcon } from "@chakra-ui/icons";
+import { MdCheck, MdWarning } from "react-icons/md";
import { Trans } from "react-i18next";
-import { CustomRadioButtons } from "@/components/custom-radio-buttons";
+import { CustomRadio, RadioGroup } from "@/components/ui/custom-radio";
+import { InputGroup } from "@/components/ui/input-group";
+import {
+ SelectContent,
+ SelectItem,
+ SelectLabel,
+ SelectRoot,
+ SelectTrigger,
+ SelectValueText,
+} from "@/components/ui/select";
+import { Field } from "@/components/ui/field";
export default function SetInventoryDetailsStep({
t,
@@ -49,31 +54,14 @@ export default function SetInventoryDetailsStep({
useEffect(() => {
setValue("inventoryGoal", "gpc_basic");
setValue("globalWarmingPotential", "ar6");
- }, []);
- const {
- getRootProps: inventoryGoalRootProps,
- getRadioProps: getInventoryGoalRadioProps,
- } = useRadioGroup({
- name: "inventoryGoal",
- defaultValue: "gpc_basic",
- onChange: (value: string) => {
- setValue("inventoryGoal", value!);
- },
- } as UseRadioGroupProps);
-
- // Handle global warming potential Radio Input
- // Set default global warming potential form value
- const { getRootProps: GWPRootProps, getRadioProps: getGWPRadioProps } =
- useRadioGroup({
- name: "globalWarmingPotential",
- defaultValue: "ar6",
- onChange: (value: string) => {
- setValue("globalWarmingPotential", value!);
- },
- } as UseRadioGroupProps);
+ }, [setValue]);
- const inventoryGoalGroup = inventoryGoalRootProps();
- const gwpGroup = GWPRootProps();
+ const yearsCollection = createListCollection({
+ items: years.map((year) => ({
+ label: year.toString(),
+ value: year,
+ })),
+ });
return (
@@ -125,52 +113,62 @@ export default function SetInventoryDetailsStep({
-
-
-
+ }
+ >
+
+ )
+ }
+ >
+
- {years.map((year: number, i: number) => (
-
- ))}
-
-
- {!!year && (
-
+
+
- )}
-
+
+
+ {yearsCollection.items.map(
+ (year: { label: string; value: number }, i: number) => (
+
+ {year.label}
+
+ ),
+ )}
+
+
-
-
-
- {errors.year && errors.year.message}
-
-
-
+
@@ -228,34 +226,28 @@ export default function SetInventoryDetailsStep({
}}
render={({ field }) => (
<>
-
- {inventoryGoalOptions.map((value) => {
- const radioProps = getInventoryGoalRadioProps({ value });
- return (
-
- {t(value)}
-
- );
- })}
-
+ field.onChange(e.value)}
+ >
+
+ {inventoryGoalOptions.map((value) => {
+ return (
+
+ {t(value)}
+
+ );
+ })}
+
+
>
)}
/>
-
-
+
{errors.inventoryGoal && errors.inventoryGoal.message}
-
+
@@ -314,9 +306,6 @@ export default function SetInventoryDetailsStep({
- {/* TODO:
- only enable ar6 and disable ar5 until we have the feature
- */}
(
- <>
-
+ field.onChange(e.value)}
+ >
+
{globalWarmingPotential.map((value) => {
- const radioProps = getGWPRadioProps({ value });
return (
-
+
{t(value)}
-
+
);
})}
- >
+
)}
/>
diff --git a/app/src/components/steps/add-population-data-step.tsx b/app/src/components/steps/add-population-data-step.tsx
index 385a17959..f7811a626 100644
--- a/app/src/components/steps/add-population-data-step.tsx
+++ b/app/src/components/steps/add-population-data-step.tsx
@@ -11,19 +11,26 @@ import { useEffect, useState } from "react";
import { findClosestYear } from "@/util/helpers";
import {
Box,
- FormControl,
- FormErrorIcon,
- FormErrorMessage,
+ createListCollection,
+ Group,
Heading,
HStack,
- InputGroup,
- InputRightElement,
- Select,
+ Icon,
+ InputAddon,
Text,
} from "@chakra-ui/react";
import FormattedThousandsNumberInput from "@/app/[lng]/onboarding/setup/FormattedThousandsNumberInput";
-import { InfoOutlineIcon } from "@chakra-ui/icons";
-import Checkmark from "./chekmark";
+import { MdCheck, MdErrorOutline, MdInfoOutline } from "react-icons/md";
+import { Field } from "@/components/ui/field";
+import {
+ SelectContent,
+ SelectItem,
+ SelectLabel,
+ SelectRoot,
+ SelectTrigger,
+ SelectValueText,
+} from "@/components/ui/select";
+import { InputGroup } from "../ui/input-group";
export default function SetPopulationDataStep({
t,
@@ -81,7 +88,7 @@ export default function SetPopulationDataStep({
setValue("cityPopulationYear", population.year);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [cityData, year, setValue]);
+ }, [cityData, year, numberOfYearsDisplayed, setValue]);
useEffect(() => {
if (regionData && year) {
@@ -97,7 +104,7 @@ export default function SetPopulationDataStep({
setValue("regionPopulation", population.population);
setValue("regionPopulationYear", population.year);
}
- }, [regionData, year, setValue]);
+ }, [regionData, year, numberOfYearsDisplayed, setValue]);
useEffect(() => {
if (countryData && year) {
@@ -129,7 +136,7 @@ export default function SetPopulationDataStep({
setValue("totalCountryEmissions", emissions);
}
}
- }, [countryData, year, setValue]);
+ }, [countryData, year, numberOfYearsDisplayed, setValue]);
const cityPopulation = watch("cityPopulation");
const cityPopulationYear = watch("cityPopulationYear");
@@ -140,6 +147,10 @@ export default function SetPopulationDataStep({
const countryPopulation = watch("countryPopulation");
const countryPopulationYear = watch("countryPopulationYear");
+ const yearsCollection = createListCollection({
+ items: years.map((year) => ({ label: year.toString(), value: year })),
+ });
+
return (
-
-
-
+
+
+
+
+ {errors.countryPopulation?.message}
+
+ )
+ }
+ >
name="countryPopulation"
control={control}
@@ -209,7 +230,7 @@ export default function SetPopulationDataStep({
letterSpacing="wide"
/>
-
+
- {errors.countryPopulation && (
-
-
- {errors.countryPopulation.message}
-
- )}
-
+
-
-
-
-
-
-
+
+
+
+
+
+ {yearsCollection.items.map(
+ (year: { label: string; value: number }, i: number) => (
+
+ {year.label}
+
+ ),
+ )}
+
+
-
+
@@ -292,9 +315,17 @@ export default function SetPopulationDataStep({
{t("region-or-province")}
-
-
-
+
+
+
+
+ {errors.regionPopulation?.message}
+
+ }
+ >
name="regionPopulation"
control={control}
@@ -314,48 +345,55 @@ export default function SetPopulationDataStep({
fontSize="body.lg"
letterSpacing="wide"
/>
- {errors.regionPopulation && (
-
-
- {errors.regionPopulation.message}
-
- )}
-
+
-
-
-
-
-
-
+
+
+
+
+
+ {yearsCollection.items.map(
+ (year: { label: string; value: number }, i: number) => (
+
+ {year.label}
+
+ ),
+ )}
+
+
-
+
@@ -365,11 +403,11 @@ export default function SetPopulationDataStep({
borderBottomWidth="2px"
borderColor="border.overlay"
>
-
-
-
-
+
+
+
+
+ {errors.cityPopulation.message}
+
+ )
+ }
+ >
name="cityPopulation"
control={control}
@@ -404,50 +452,56 @@ export default function SetPopulationDataStep({
fontSize="body.lg"
letterSpacing="wide"
/>
- {errors.cityPopulation && (
-
-
- {errors.cityPopulation.message}
-
- )}
-
+
-
-
-
-
-
-
-
-
+ )
+ }
+ >
+
+ setValue("cityPopulationYear", e.value[0])
+ }
+ >
+
+
+
+
+
+ {yearsCollection.items.map(
+ (year: { label: string; value: number }, i: number) => (
+
+ {year.label}
+
+ ),
+ )}
+
+
+
-
+
);
diff --git a/app/src/components/steps/chekmark.tsx b/app/src/components/steps/chekmark.tsx
deleted file mode 100644
index 58c9e4e92..000000000
--- a/app/src/components/steps/chekmark.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { CheckIcon } from "@chakra-ui/icons";
-import React, { FC } from "react";
-
-interface CheckmarkProps {
- condition: boolean;
-}
-
-const Checkmark: FC = ({ condition }) => {
- return condition ? : null;
-};
-
-export default Checkmark;
diff --git a/app/src/components/steps/confirm-city-data-step.tsx b/app/src/components/steps/confirm-city-data-step.tsx
index cfc00c3eb..541fd13b9 100644
--- a/app/src/components/steps/confirm-city-data-step.tsx
+++ b/app/src/components/steps/confirm-city-data-step.tsx
@@ -26,7 +26,7 @@ export default function ConfirmStep({
}) {
return (
-
+
{t("confirm-heading")}
@@ -35,7 +35,7 @@ export default function ConfirmStep({
-
-
+
);
diff --git a/app/src/components/steps/select-city-steps.tsx b/app/src/components/steps/select-city-steps.tsx
index 7d71bea56..aae0bee55 100644
--- a/app/src/components/steps/select-city-steps.tsx
+++ b/app/src/components/steps/select-city-steps.tsx
@@ -7,7 +7,7 @@ import {
import { TFunction } from "i18next";
import { OCCityAttributes } from "@/util/types";
import { useAppDispatch } from "@/lib/hooks";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useState } from "react";
import { set } from "@/features/city/openclimateCitySlice";
import {
useGetCityQuery,
@@ -18,32 +18,25 @@ import { findClosestYear } from "@/util/helpers";
import {
Box,
Card,
- FormControl,
- FormErrorMessage,
- FormLabel,
Heading,
Icon,
Input,
- InputGroup,
- InputLeftElement,
- InputRightElement,
+ InputAddon,
Link,
Text,
- useOutsideClick,
} from "@chakra-ui/react";
-import {
- CheckIcon,
- InfoOutlineIcon,
- SearchIcon,
- WarningIcon,
-} from "@chakra-ui/icons";
-import RecentSearches from "@/components/recent-searches";
+import { MdCheck, MdInfoOutline, MdSearch, MdWarning } from "react-icons/md";
import Image from "next/image";
import dynamic from "next/dynamic";
import { NoResultsIcon } from "../icons";
import { useSearchParams } from "next/navigation";
import { Trans } from "react-i18next";
+import RecentSearches from "@/components/recent-searches";
+import { useOutsideClick } from "@/lib/use-outside-click";
+import { Field } from "@/components/ui/field";
+import { InputGroup } from "../ui/input-group";
+
const CityMap = dynamic(() => import("@/components/CityMap"), { ssr: false });
export default function SelectCityStep({
@@ -152,7 +145,7 @@ export default function SelectCityStep({
area: 0,
});
}
- }, [CCCityData]);
+ }, [CCCityData, setValue, setOcCityData]);
// react to API data changes and different year selections
useEffect(() => {
@@ -246,11 +239,10 @@ export default function SelectCityStep({
// using useOutsideClick instead of onBlur input attribute
// to fix clicking city dropdown entries not working
- const cityInputRef = useRef(null);
- useOutsideClick({
- ref: cityInputRef,
- handler: () => setTimeout(() => setOnInputClicked(false), 0),
- });
+ // const cityInputRef = useRef(null);
+ const cityInputRef = useOutsideClick(() =>
+ setTimeout(() => setOnInputClicked(false), 0),
+ );
return (
@@ -277,215 +269,216 @@ export default function SelectCityStep({
-
-
+ }
+ label={t("city")}
+ data-testId="setup-city-input-label"
>
-
-
-
- setOnInputClicked(true)}
- onFocus={() => setOnInputClicked(true)}
- />
-
- {isCityNew && (
-
- )}
-
-
- {onInputClicked && (
-
+ }
+ endElement={
+ isCityNew && (
+
+ )
+ }
>
- {!isLoading && !cityInputQuery && }
- {isLoading && Fetching Cities...
}
- {isSuccess &&
- cities &&
- cities.map((city: OCCityAttributes) => {
- return (
- handleSetCity(city)}
- key={city.actor_id}
- className="h-[72px] py-3 w-full flex flex-col justify-center group px-4 hover:bg-[#2351DC] transition-all duration-150 cursor-pointer"
- >
+ setOnInputClicked(true)}
+ onFocus={() => setOnInputClicked(true)}
+ />
+
+ {onInputClicked && (
+
+ {!isLoading && !cityInputQuery && }
+ {isLoading && Fetching Cities...
}
+ {isSuccess &&
+ cities &&
+ cities.map((city: OCCityAttributes) => {
+ return (
+ handleSetCity(city)}
+ key={city.actor_id}
+ className="h-[72px] py-3 w-full flex flex-col justify-center group px-4 hover:bg-[#2351DC] transition-all duration-150 cursor-pointer"
+ >
+
+ {city.name}
+
+
+ {renderParentPath(city.root_path_geo)}
+
+
+ );
+ })}
+ {isSuccess && cities.length == 0 && (
+
+
+
+
+
- {city.name}
+ {t("no-results")}
- {renderParentPath(city.root_path_geo)}
+ {t("no-results-details")}
- );
- })}
- {isSuccess && cities.length == 0 && (
-
-
-
-
-
- {t("no-results")}
-
-
+ )}
+
+ {ocCityData ? (
+
+
+
+
+
+
+
- {t("no-results-details")}
-
-
-
- )}
+ Contact Us
+
+
+
+
- )}
-
-
-
- {errors.city && errors.city.message}
-
-
-
- {ocCityData ? (
-
-
-
-
-
-
-
- Contact Us
-
-
-
+
+
+
+ {t("unselected-city-boundary-heading")}
+
+
+ {t("unselected-city-boundary-description")}
+
+
-
- ) : (
-
-
-
-
- {t("unselected-city-boundary-heading")}
-
-
- {t("unselected-city-boundary-description")}
-
-
-
- )}
-
-
+ )}
+
+
+
);
diff --git a/app/src/components/ui/accordion.tsx b/app/src/components/ui/accordion.tsx
new file mode 100644
index 000000000..0a23e4e50
--- /dev/null
+++ b/app/src/components/ui/accordion.tsx
@@ -0,0 +1,47 @@
+import { Accordion, HStack, Icon } from "@chakra-ui/react";
+import * as React from "react";
+import { LuChevronDown } from "react-icons/lu";
+
+interface AccordionItemTriggerProps extends Accordion.ItemTriggerProps {
+ indicatorPlacement?: "start" | "end";
+}
+
+export const AccordionItemTrigger = React.forwardRef<
+ HTMLButtonElement,
+ AccordionItemTriggerProps
+>(function AccordionItemTrigger(props, ref) {
+ const { children, indicatorPlacement = "end", ...rest } = props;
+ return (
+
+ {indicatorPlacement === "start" && (
+
+
+
+ )}
+
+ {children}
+
+ {indicatorPlacement === "end" && (
+
+
+
+ )}
+
+ );
+});
+
+interface AccordionItemContentProps extends Accordion.ItemContentProps {}
+
+export const AccordionItemContent = React.forwardRef<
+ HTMLDivElement,
+ AccordionItemContentProps
+>(function AccordionItemContent(props, ref) {
+ return (
+
+
+
+ );
+});
+
+export const AccordionRoot = Accordion.Root;
+export const AccordionItem = Accordion.Item;
diff --git a/app/src/components/ui/avatar.tsx b/app/src/components/ui/avatar.tsx
new file mode 100644
index 000000000..cd84664ae
--- /dev/null
+++ b/app/src/components/ui/avatar.tsx
@@ -0,0 +1,74 @@
+"use client"
+
+import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react"
+import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react"
+import * as React from "react"
+
+type ImageProps = React.ImgHTMLAttributes
+
+export interface AvatarProps extends ChakraAvatar.RootProps {
+ name?: string
+ src?: string
+ srcSet?: string
+ loading?: ImageProps["loading"]
+ icon?: React.ReactElement
+ fallback?: React.ReactNode
+}
+
+export const Avatar = React.forwardRef(
+ function Avatar(props, ref) {
+ const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
+ props
+ return (
+
+
+ {fallback}
+
+
+ {children}
+
+ )
+ },
+)
+
+interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
+ name?: string
+ icon?: React.ReactElement
+}
+
+const AvatarFallback = React.forwardRef(
+ function AvatarFallback(props, ref) {
+ const { name, icon, children, ...rest } = props
+ return (
+
+ {children}
+ {name != null && children == null && <>{getInitials(name)}>}
+ {name == null && children == null && (
+ {icon}
+ )}
+
+ )
+ },
+)
+
+function getInitials(name: string) {
+ const names = name.trim().split(" ")
+ const firstName = names[0] != null ? names[0] : ""
+ const lastName = names.length > 1 ? names[names.length - 1] : ""
+ return firstName && lastName
+ ? `${firstName.charAt(0)}${lastName.charAt(0)}`
+ : firstName.charAt(0)
+}
+
+interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {}
+
+export const AvatarGroup = React.forwardRef(
+ function AvatarGroup(props, ref) {
+ const { size, variant, borderless, ...rest } = props
+ return (
+
+
+
+ )
+ },
+)
diff --git a/app/src/components/ui/breadcrumb.tsx b/app/src/components/ui/breadcrumb.tsx
new file mode 100644
index 000000000..960e769af
--- /dev/null
+++ b/app/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,40 @@
+import { Breadcrumb, type SystemStyleObject } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface BreadcrumbRootProps extends Breadcrumb.RootProps {
+ separator?: React.ReactNode
+ separatorGap?: SystemStyleObject["gap"]
+}
+
+export const BreadcrumbRoot = React.forwardRef<
+ HTMLDivElement,
+ BreadcrumbRootProps
+>(function BreadcrumbRoot(props, ref) {
+ const { separator, separatorGap, children, ...rest } = props
+
+ const validChildren = React.Children.toArray(children).filter(
+ React.isValidElement,
+ )
+
+ return (
+
+
+ {validChildren.map((child, index) => {
+ const last = index === validChildren.length - 1
+ return (
+
+ {child}
+ {!last && (
+ {separator}
+ )}
+
+ )
+ })}
+
+
+ )
+})
+
+export const BreadcrumbLink = Breadcrumb.Link
+export const BreadcrumbCurrentLink = Breadcrumb.CurrentLink
+export const BreadcrumbEllipsis = Breadcrumb.Ellipsis
diff --git a/app/src/components/ui/button.tsx b/app/src/components/ui/button.tsx
new file mode 100644
index 000000000..20f5f7892
--- /dev/null
+++ b/app/src/components/ui/button.tsx
@@ -0,0 +1,40 @@
+import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react";
+import {
+ AbsoluteCenter,
+ Button as ChakraButton,
+ Span,
+ Spinner,
+} from "@chakra-ui/react";
+import * as React from "react";
+
+interface ButtonLoadingProps {
+ loading?: boolean;
+ loadingText?: React.ReactNode;
+}
+
+export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
+
+export const Button = React.forwardRef(
+ function Button(props, ref) {
+ const { loading, disabled, loadingText, children, ...rest } = props;
+ return (
+
+ {loading && !loadingText ? (
+ <>
+
+
+
+ {children}
+ >
+ ) : loading && loadingText ? (
+ <>
+
+ {loadingText}
+ >
+ ) : (
+ children
+ )}
+
+ );
+ },
+);
diff --git a/app/src/components/ui/checkbox.tsx b/app/src/components/ui/checkbox.tsx
new file mode 100644
index 000000000..2a27c2ffb
--- /dev/null
+++ b/app/src/components/ui/checkbox.tsx
@@ -0,0 +1,25 @@
+import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface CheckboxProps extends ChakraCheckbox.RootProps {
+ icon?: React.ReactNode
+ inputProps?: React.InputHTMLAttributes
+ rootRef?: React.Ref
+}
+
+export const Checkbox = React.forwardRef(
+ function Checkbox(props, ref) {
+ const { icon, children, inputProps, rootRef, ...rest } = props
+ return (
+
+
+
+ {icon || }
+
+ {children != null && (
+ {children}
+ )}
+
+ )
+ },
+)
diff --git a/app/src/components/ui/close-button.tsx b/app/src/components/ui/close-button.tsx
new file mode 100644
index 000000000..94af48859
--- /dev/null
+++ b/app/src/components/ui/close-button.tsx
@@ -0,0 +1,17 @@
+import type { ButtonProps } from "@chakra-ui/react"
+import { IconButton as ChakraIconButton } from "@chakra-ui/react"
+import * as React from "react"
+import { LuX } from "react-icons/lu"
+
+export type CloseButtonProps = ButtonProps
+
+export const CloseButton = React.forwardRef<
+ HTMLButtonElement,
+ CloseButtonProps
+>(function CloseButton(props, ref) {
+ return (
+
+ {props.children ?? }
+
+ )
+})
diff --git a/app/src/components/ui/color-mode.tsx b/app/src/components/ui/color-mode.tsx
new file mode 100644
index 000000000..1f619db19
--- /dev/null
+++ b/app/src/components/ui/color-mode.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import type { IconButtonProps } from "@chakra-ui/react";
+import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react";
+import { ThemeProvider, useTheme } from "next-themes";
+import type { ThemeProviderProps } from "next-themes";
+import * as React from "react";
+import { LuMoon, LuSun } from "react-icons/lu";
+
+export interface ColorModeProviderProps extends ThemeProviderProps {}
+
+export function ColorModeProvider(props: ColorModeProviderProps) {
+ return (
+
+ );
+}
+
+export function useColorMode() {
+ const { resolvedTheme, setTheme } = useTheme();
+ const toggleColorMode = () => {
+ setTheme(resolvedTheme === "light" ? "dark" : "light");
+ };
+ return {
+ colorMode: resolvedTheme,
+ setColorMode: setTheme,
+ toggleColorMode,
+ };
+}
+
+export function useColorModeValue(light: T, dark: T) {
+ const { colorMode } = useColorMode();
+ return colorMode === "light" ? light : dark;
+}
+
+export function ColorModeIcon() {
+ const { colorMode } = useColorMode();
+ return colorMode === "light" ? : ;
+}
+
+interface ColorModeButtonProps extends Omit {}
+
+export const ColorModeButton = React.forwardRef<
+ HTMLButtonElement,
+ ColorModeButtonProps
+>(function ColorModeButton(props, ref) {
+ const { toggleColorMode } = useColorMode();
+ return (
+ }>
+
+
+
+
+ );
+});
diff --git a/app/src/components/ui/custom-radio.tsx b/app/src/components/ui/custom-radio.tsx
new file mode 100644
index 000000000..d79bc58d4
--- /dev/null
+++ b/app/src/components/ui/custom-radio.tsx
@@ -0,0 +1,59 @@
+import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react";
+import * as React from "react";
+
+import { Box, Icon } from "@chakra-ui/react";
+import { InventoryButtonCheckIcon } from "../icons";
+
+export interface RadioProps extends ChakraRadioGroup.ItemProps {
+ rootRef?: React.Ref;
+ inputProps?: React.InputHTMLAttributes;
+}
+
+export const RadioGroup = ChakraRadioGroup.Root;
+
+export const CustomRadio = React.forwardRef(
+ function Radio(props, ref) {
+ const { children, inputProps, rootRef, ...rest } = props;
+
+ return (
+
+
+
+
+ {inputProps?.checked && }
+ {children && (
+ {children}
+ )}
+
+
+ );
+ },
+);
diff --git a/app/src/components/ui/dialog.tsx b/app/src/components/ui/dialog.tsx
new file mode 100644
index 000000000..5913453ed
--- /dev/null
+++ b/app/src/components/ui/dialog.tsx
@@ -0,0 +1,64 @@
+import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react";
+import { CloseButton } from "./close-button";
+import * as React from "react";
+
+interface DialogContentProps extends ChakraDialog.ContentProps {
+ portalled?: boolean;
+ portalRef?: React.RefObject;
+ backdrop?: boolean;
+}
+
+export const DialogContent = React.forwardRef<
+ HTMLDivElement,
+ DialogContentProps
+>(function DialogContent(props, ref) {
+ const {
+ children,
+ portalled = true,
+ portalRef,
+ backdrop = true,
+ ...rest
+ } = props;
+
+ return (
+
+ {backdrop && }
+
+
+ {children}
+
+
+
+ );
+});
+
+export const DialogCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraDialog.CloseTriggerProps
+>(function DialogCloseTrigger(props, ref) {
+ return (
+
+
+ {props.children}
+
+
+ );
+});
+
+export const DialogRoot = ChakraDialog.Root;
+export const DialogFooter = ChakraDialog.Footer;
+export const DialogHeader = ChakraDialog.Header;
+export const DialogBody = ChakraDialog.Body;
+export const DialogBackdrop = ChakraDialog.Backdrop;
+export const DialogTitle = ChakraDialog.Title;
+export const DialogDescription = ChakraDialog.Description;
+export const DialogTrigger = ChakraDialog.Trigger;
+export const DialogActionTrigger = ChakraDialog.ActionTrigger;
diff --git a/app/src/components/ui/drawer.tsx b/app/src/components/ui/drawer.tsx
new file mode 100644
index 000000000..ccb96c80a
--- /dev/null
+++ b/app/src/components/ui/drawer.tsx
@@ -0,0 +1,52 @@
+import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface DrawerContentProps extends ChakraDrawer.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+ offset?: ChakraDrawer.ContentProps["padding"]
+}
+
+export const DrawerContent = React.forwardRef<
+ HTMLDivElement,
+ DrawerContentProps
+>(function DrawerContent(props, ref) {
+ const { children, portalled = true, portalRef, offset, ...rest } = props
+ return (
+
+
+
+ {children}
+
+
+
+ )
+})
+
+export const DrawerCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraDrawer.CloseTriggerProps
+>(function DrawerCloseTrigger(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const DrawerTrigger = ChakraDrawer.Trigger
+export const DrawerRoot = ChakraDrawer.Root
+export const DrawerFooter = ChakraDrawer.Footer
+export const DrawerHeader = ChakraDrawer.Header
+export const DrawerBody = ChakraDrawer.Body
+export const DrawerBackdrop = ChakraDrawer.Backdrop
+export const DrawerDescription = ChakraDrawer.Description
+export const DrawerTitle = ChakraDrawer.Title
+export const DrawerActionTrigger = ChakraDrawer.ActionTrigger
diff --git a/app/src/components/ui/field.tsx b/app/src/components/ui/field.tsx
new file mode 100644
index 000000000..50c0cc0a4
--- /dev/null
+++ b/app/src/components/ui/field.tsx
@@ -0,0 +1,41 @@
+import { Field as ChakraField } from "@chakra-ui/react";
+import * as React from "react";
+
+export interface FieldProps extends Omit {
+ label?: React.ReactNode;
+ helperText?: React.ReactNode;
+ errorText?: React.ReactNode;
+ optionalText?: React.ReactNode;
+ labelColor?: string;
+}
+
+export const Field = React.forwardRef(
+ function Field(props, ref) {
+ const {
+ label,
+ children,
+ helperText,
+ errorText,
+ optionalText,
+ labelColor = "content.tertiary",
+ ...rest
+ } = props;
+ return (
+
+ {label && (
+
+ {label}
+
+
+ )}
+ {children}
+ {helperText && (
+ {helperText}
+ )}
+ {errorText && (
+ {errorText}
+ )}
+
+ );
+ },
+);
diff --git a/app/src/components/ui/file-upload.tsx b/app/src/components/ui/file-upload.tsx
new file mode 100644
index 000000000..28f5c8bd7
--- /dev/null
+++ b/app/src/components/ui/file-upload.tsx
@@ -0,0 +1,174 @@
+"use client";
+
+import type { ButtonProps, RecipeProps } from "@chakra-ui/react";
+import {
+ Button,
+ FileUpload as ChakraFileUpload,
+ Icon,
+ IconButton,
+ Span,
+ Text,
+ useFileUploadContext,
+ useRecipe,
+} from "@chakra-ui/react";
+import * as React from "react";
+import { LuFile, LuUpload, LuX } from "react-icons/lu";
+
+export interface FileUploadRootProps extends ChakraFileUpload.RootProps {
+ inputProps?: React.InputHTMLAttributes;
+}
+
+export const FileUploadRoot = React.forwardRef<
+ HTMLInputElement,
+ FileUploadRootProps
+>(function FileUploadRoot(props, ref) {
+ const { children, inputProps, ...rest } = props;
+ return (
+
+
+ {children}
+
+ );
+});
+
+export interface FileUploadDropzoneProps
+ extends ChakraFileUpload.DropzoneProps {
+ label: React.ReactNode;
+ description?: React.ReactNode;
+ children?: React.ReactNode;
+}
+
+export const FileUploadDropzone = React.forwardRef<
+ HTMLInputElement,
+ FileUploadDropzoneProps
+>(function FileUploadDropzone(props, ref) {
+ const { children, label, description, ...rest } = props;
+ return (
+
+
+ {label}
+ {description && {description}}
+
+ {children}
+
+ );
+});
+
+interface VisibilityProps {
+ showSize?: boolean;
+ clearable?: boolean;
+}
+
+interface FileUploadItemProps extends VisibilityProps {
+ file: File;
+}
+
+const FileUploadItem = React.forwardRef(
+ function FileUploadItem(props, ref) {
+ const { file, showSize, clearable } = props;
+ return (
+
+
+
+
+
+
+
+ {showSize ? (
+
+
+
+
+ ) : (
+
+ )}
+
+ {clearable && (
+
+
+
+
+
+ )}
+
+ );
+ },
+);
+
+interface FileUploadListProps
+ extends VisibilityProps,
+ ChakraFileUpload.ItemGroupProps {
+ files?: File[];
+}
+
+export const FileUploadList = React.forwardRef<
+ HTMLUListElement,
+ FileUploadListProps
+>(function FileUploadList(props, ref) {
+ const { showSize, clearable, files, ...rest } = props;
+
+ const fileUpload = useFileUploadContext();
+ const acceptedFiles = files ?? fileUpload.acceptedFiles;
+
+ if (acceptedFiles.length === 0) return null;
+
+ return (
+
+ {acceptedFiles.map((file) => (
+
+ ))}
+
+ );
+});
+
+type Assign = Omit & U;
+
+interface FileInputProps extends Assign> {
+ placeholder?: React.ReactNode;
+}
+
+export const FileInput = React.forwardRef(
+ function FileInput(props, ref) {
+ const inputRecipe = useRecipe({ key: "input" });
+ const [recipeProps, restProps] = inputRecipe.splitVariantProps(props);
+ const { placeholder = "Select file(s)", ...rest } = restProps;
+ return (
+
+
+
+ {({ acceptedFiles }) => {
+ if (acceptedFiles.length === 1) {
+ return {acceptedFiles[0].name};
+ }
+ if (acceptedFiles.length > 1) {
+ return {acceptedFiles.length} files;
+ }
+ return {placeholder};
+ }}
+
+
+
+ );
+ },
+);
+
+export const FileUploadLabel = ChakraFileUpload.Label;
+export const FileUploadClearTrigger = ChakraFileUpload.ClearTrigger;
+export const FileUploadTrigger = ChakraFileUpload.Trigger;
diff --git a/app/src/components/ui/input-group.tsx b/app/src/components/ui/input-group.tsx
new file mode 100644
index 000000000..5d8fb32ad
--- /dev/null
+++ b/app/src/components/ui/input-group.tsx
@@ -0,0 +1,53 @@
+import type { BoxProps, InputElementProps } from "@chakra-ui/react"
+import { Group, InputElement } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface InputGroupProps extends BoxProps {
+ startElementProps?: InputElementProps
+ endElementProps?: InputElementProps
+ startElement?: React.ReactNode
+ endElement?: React.ReactNode
+ children: React.ReactElement
+ startOffset?: InputElementProps["paddingStart"]
+ endOffset?: InputElementProps["paddingEnd"]
+}
+
+export const InputGroup = React.forwardRef(
+ function InputGroup(props, ref) {
+ const {
+ startElement,
+ startElementProps,
+ endElement,
+ endElementProps,
+ children,
+ startOffset = "6px",
+ endOffset = "6px",
+ ...rest
+ } = props
+
+ const child =
+ React.Children.only>(children)
+
+ return (
+
+ {startElement && (
+
+ {startElement}
+
+ )}
+ {React.cloneElement(child, {
+ ...(startElement && {
+ ps: `calc(var(--input-height) - ${startOffset})`,
+ }),
+ ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
+ ...children.props,
+ })}
+ {endElement && (
+
+ {endElement}
+
+ )}
+
+ )
+ },
+)
diff --git a/app/src/components/ui/menu.tsx b/app/src/components/ui/menu.tsx
new file mode 100644
index 000000000..eab6f3d10
--- /dev/null
+++ b/app/src/components/ui/menu.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react";
+import * as React from "react";
+import { LuCheck, LuChevronRight } from "react-icons/lu";
+
+interface MenuContentProps extends ChakraMenu.ContentProps {
+ portalled?: boolean;
+ portalRef?: React.RefObject;
+}
+
+export const MenuContent = React.forwardRef(
+ function MenuContent(props, ref) {
+ const { portalled = true, portalRef, ...rest } = props;
+ return (
+
+
+
+
+
+ );
+ },
+);
+
+export const MenuArrow = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.ArrowProps
+>(function MenuArrow(props, ref) {
+ return (
+
+
+
+ );
+});
+
+export const MenuCheckboxItem = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.CheckboxItemProps
+>(function MenuCheckboxItem(props, ref) {
+ return (
+
+
+
+
+ {props.children}
+
+ );
+});
+
+export const MenuRadioItem = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.RadioItemProps
+>(function MenuRadioItem(props, ref) {
+ const { children, ...rest } = props;
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+});
+
+export const MenuItemGroup = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.ItemGroupProps
+>(function MenuItemGroup(props, ref) {
+ const { title, children, ...rest } = props;
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+ {children}
+
+ );
+});
+
+export interface MenuTriggerItemProps extends ChakraMenu.ItemProps {
+ startIcon?: React.ReactNode;
+}
+
+export const MenuTriggerItem = React.forwardRef<
+ HTMLDivElement,
+ MenuTriggerItemProps
+>(function MenuTriggerItem(props, ref) {
+ const { startIcon, children, ...rest } = props;
+ return (
+
+ {startIcon}
+ {children}
+
+
+ );
+});
+
+export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup;
+export const MenuContextTrigger = ChakraMenu.ContextTrigger;
+export const MenuRoot = ChakraMenu.Root;
+export const MenuSeparator = ChakraMenu.Separator;
+
+export const MenuItem = ChakraMenu.Item;
+export const MenuItemText = ChakraMenu.ItemText;
+export const MenuItemCommand = ChakraMenu.ItemCommand;
+export const MenuTrigger = ChakraMenu.Trigger;
diff --git a/app/src/components/ui/native-select.tsx b/app/src/components/ui/native-select.tsx
new file mode 100644
index 000000000..9e6ebf501
--- /dev/null
+++ b/app/src/components/ui/native-select.tsx
@@ -0,0 +1,57 @@
+"use client"
+
+import { NativeSelect as Select } from "@chakra-ui/react"
+import * as React from "react"
+
+interface NativeSelectRootProps extends Select.RootProps {
+ icon?: React.ReactNode
+}
+
+export const NativeSelectRoot = React.forwardRef<
+ HTMLDivElement,
+ NativeSelectRootProps
+>(function NativeSelect(props, ref) {
+ const { icon, children, ...rest } = props
+ return (
+
+ {children}
+ {icon}
+
+ )
+})
+
+interface NativeSelectItem {
+ value: string
+ label: string
+ disabled?: boolean
+}
+
+interface NativeSelectField extends Select.FieldProps {
+ items?: Array
+}
+
+export const NativeSelectField = React.forwardRef<
+ HTMLSelectElement,
+ NativeSelectField
+>(function NativeSelectField(props, ref) {
+ const { items: itemsProp, children, ...rest } = props
+
+ const items = React.useMemo(
+ () =>
+ itemsProp?.map((item) =>
+ typeof item === "string" ? { label: item, value: item } : item,
+ ),
+ [itemsProp],
+ )
+
+ return (
+
+ {children}
+ {items?.map((item) => (
+
+ ))}
+
+ )
+})
diff --git a/app/src/components/ui/number-input.tsx b/app/src/components/ui/number-input.tsx
new file mode 100644
index 000000000..4a6500f80
--- /dev/null
+++ b/app/src/components/ui/number-input.tsx
@@ -0,0 +1,24 @@
+import { NumberInput as ChakraNumberInput } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface NumberInputProps extends ChakraNumberInput.RootProps {}
+
+export const NumberInputRoot = React.forwardRef<
+ HTMLDivElement,
+ NumberInputProps
+>(function NumberInput(props, ref) {
+ const { children, ...rest } = props
+ return (
+
+ {children}
+
+
+
+
+
+ )
+})
+
+export const NumberInputField = ChakraNumberInput.Input
+export const NumberInputScrubber = ChakraNumberInput.Scrubber
+export const NumberInputLabel = ChakraNumberInput.Label
diff --git a/app/src/components/ui/password-input.tsx b/app/src/components/ui/password-input.tsx
new file mode 100644
index 000000000..0c608a947
--- /dev/null
+++ b/app/src/components/ui/password-input.tsx
@@ -0,0 +1,148 @@
+"use client"
+
+import type {
+ ButtonProps,
+ GroupProps,
+ InputProps,
+ StackProps,
+} from "@chakra-ui/react"
+import {
+ Box,
+ HStack,
+ IconButton,
+ Input,
+ Stack,
+ mergeRefs,
+ useControllableState,
+} from "@chakra-ui/react"
+import * as React from "react"
+import { LuEye, LuEyeOff } from "react-icons/lu"
+import { InputGroup } from "./input-group"
+
+export interface PasswordVisibilityProps {
+ defaultVisible?: boolean
+ visible?: boolean
+ onVisibleChange?: (visible: boolean) => void
+ visibilityIcon?: { on: React.ReactNode; off: React.ReactNode }
+}
+
+export interface PasswordInputProps
+ extends InputProps,
+ PasswordVisibilityProps {
+ rootProps?: GroupProps
+}
+
+export const PasswordInput = React.forwardRef<
+ HTMLInputElement,
+ PasswordInputProps
+>(function PasswordInput(props, ref) {
+ const {
+ rootProps,
+ defaultVisible,
+ visible: visibleProp,
+ onVisibleChange,
+ visibilityIcon = { on: , off: },
+ ...rest
+ } = props
+
+ const [visible, setVisible] = useControllableState({
+ value: visibleProp,
+ defaultValue: defaultVisible || false,
+ onChange: onVisibleChange,
+ })
+
+ const inputRef = React.useRef(null)
+
+ return (
+ {
+ if (rest.disabled) return
+ if (e.button !== 0) return
+ e.preventDefault()
+ setVisible(!visible)
+ }}
+ >
+ {visible ? visibilityIcon.off : visibilityIcon.on}
+
+ }
+ {...rootProps}
+ >
+
+
+ )
+})
+
+const VisibilityTrigger = React.forwardRef(
+ function VisibilityTrigger(props, ref) {
+ return (
+
+ )
+ },
+)
+
+interface PasswordStrengthMeterProps extends StackProps {
+ max?: number
+ value: number
+}
+
+export const PasswordStrengthMeter = React.forwardRef<
+ HTMLDivElement,
+ PasswordStrengthMeterProps
+>(function PasswordStrengthMeter(props, ref) {
+ const { max = 4, value, ...rest } = props
+
+ const percent = (value / max) * 100
+ const { label, colorPalette } = getColorPalette(percent)
+
+ return (
+
+
+ {Array.from({ length: max }).map((_, index) => (
+
+ ))}
+
+ {label && {label}}
+
+ )
+})
+
+function getColorPalette(percent: number) {
+ switch (true) {
+ case percent < 33:
+ return { label: "Low", colorPalette: "red" }
+ case percent < 66:
+ return { label: "Medium", colorPalette: "orange" }
+ default:
+ return { label: "High", colorPalette: "green" }
+ }
+}
diff --git a/app/src/components/ui/popover.tsx b/app/src/components/ui/popover.tsx
new file mode 100644
index 000000000..3320659db
--- /dev/null
+++ b/app/src/components/ui/popover.tsx
@@ -0,0 +1,59 @@
+import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface PopoverContentProps extends ChakraPopover.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+}
+
+export const PopoverContent = React.forwardRef<
+ HTMLDivElement,
+ PopoverContentProps
+>(function PopoverContent(props, ref) {
+ const { portalled = true, portalRef, ...rest } = props
+ return (
+
+
+
+
+
+ )
+})
+
+export const PopoverArrow = React.forwardRef<
+ HTMLDivElement,
+ ChakraPopover.ArrowProps
+>(function PopoverArrow(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const PopoverCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraPopover.CloseTriggerProps
+>(function PopoverCloseTrigger(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const PopoverTitle = ChakraPopover.Title
+export const PopoverDescription = ChakraPopover.Description
+export const PopoverFooter = ChakraPopover.Footer
+export const PopoverHeader = ChakraPopover.Header
+export const PopoverRoot = ChakraPopover.Root
+export const PopoverBody = ChakraPopover.Body
+export const PopoverTrigger = ChakraPopover.Trigger
diff --git a/app/src/components/ui/progress-circle.tsx b/app/src/components/ui/progress-circle.tsx
new file mode 100644
index 000000000..2d430cbcf
--- /dev/null
+++ b/app/src/components/ui/progress-circle.tsx
@@ -0,0 +1,37 @@
+import type { SystemStyleObject } from "@chakra-ui/react"
+import {
+ AbsoluteCenter,
+ ProgressCircle as ChakraProgressCircle,
+} from "@chakra-ui/react"
+import * as React from "react"
+
+interface ProgressCircleRingProps extends ChakraProgressCircle.CircleProps {
+ trackColor?: SystemStyleObject["stroke"]
+ cap?: SystemStyleObject["strokeLinecap"]
+}
+
+export const ProgressCircleRing = React.forwardRef<
+ SVGSVGElement,
+ ProgressCircleRingProps
+>(function ProgressCircleRing(props, ref) {
+ const { trackColor, cap, color, ...rest } = props
+ return (
+
+
+
+
+ )
+})
+
+export const ProgressCircleValueText = React.forwardRef<
+ HTMLDivElement,
+ ChakraProgressCircle.ValueTextProps
+>(function ProgressCircleValueText(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const ProgressCircleRoot = ChakraProgressCircle.Root
diff --git a/app/src/components/ui/provider.tsx b/app/src/components/ui/provider.tsx
new file mode 100644
index 000000000..26acfe15a
--- /dev/null
+++ b/app/src/components/ui/provider.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { ChakraProvider } from "@chakra-ui/react";
+import { ColorModeProvider, type ColorModeProviderProps } from "./color-mode";
+import { appTheme } from "@/lib/theme/app-theme";
+
+export function Provider(props: ColorModeProviderProps, value: any) {
+ return (
+
+
+
+ );
+}
diff --git a/app/src/components/ui/radio.tsx b/app/src/components/ui/radio.tsx
new file mode 100644
index 000000000..365e1d2dd
--- /dev/null
+++ b/app/src/components/ui/radio.tsx
@@ -0,0 +1,24 @@
+import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react";
+import * as React from "react";
+
+export interface RadioProps extends ChakraRadioGroup.ItemProps {
+ rootRef?: React.Ref;
+ inputProps?: React.InputHTMLAttributes;
+}
+
+export const Radio = React.forwardRef(
+ function Radio(props, ref) {
+ const { children, inputProps, rootRef, ...rest } = props;
+ return (
+
+
+
+ {children && (
+ {children}
+ )}
+
+ );
+ },
+);
+
+export const RadioGroup = ChakraRadioGroup.Root;
diff --git a/app/src/components/ui/select.tsx b/app/src/components/ui/select.tsx
new file mode 100644
index 000000000..d2b927294
--- /dev/null
+++ b/app/src/components/ui/select.tsx
@@ -0,0 +1,143 @@
+"use client";
+
+import type { CollectionItem } from "@chakra-ui/react";
+import { Select as ChakraSelect, Portal } from "@chakra-ui/react";
+import { CloseButton } from "./close-button";
+import * as React from "react";
+
+interface SelectTriggerProps extends ChakraSelect.ControlProps {
+ clearable?: boolean;
+}
+
+export const SelectTrigger = React.forwardRef<
+ HTMLButtonElement,
+ SelectTriggerProps
+>(function SelectTrigger(props, ref) {
+ const { children, clearable, ...rest } = props;
+ return (
+
+ {children}
+
+ {clearable && }
+
+
+
+ );
+});
+
+const SelectClearTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraSelect.ClearTriggerProps
+>(function SelectClearTrigger(props, ref) {
+ return (
+
+
+
+ );
+});
+
+interface SelectContentProps extends ChakraSelect.ContentProps {
+ portalled?: boolean;
+ portalRef?: React.RefObject;
+}
+
+export const SelectContent = React.forwardRef<
+ HTMLDivElement,
+ SelectContentProps
+>(function SelectContent(props, ref) {
+ const { portalled = true, portalRef, ...rest } = props;
+ return (
+
+
+
+
+
+ );
+});
+
+export const SelectItem = React.forwardRef<
+ HTMLDivElement,
+ ChakraSelect.ItemProps
+>(function SelectItem(props, ref) {
+ const { item, children, ...rest } = props;
+ return (
+
+ {children}
+
+
+ );
+});
+
+interface SelectValueTextProps
+ extends Omit {
+ children?(items: CollectionItem[]): React.ReactNode;
+}
+
+export const SelectValueText = React.forwardRef<
+ HTMLSpanElement,
+ SelectValueTextProps
+>(function SelectValueText(props, ref) {
+ const { children, ...rest } = props;
+ return (
+
+
+ {(select) => {
+ const items = select.selectedItems;
+ if (items.length === 0) return props.placeholder;
+ if (children) return children(items);
+ if (items.length === 1)
+ return select.collection.stringifyItem(items[0]);
+ return `${items.length} selected`;
+ }}
+
+
+ );
+});
+
+export const SelectRoot = React.forwardRef<
+ HTMLDivElement,
+ ChakraSelect.RootProps
+>(function SelectRoot(props, ref) {
+ return (
+
+ {props.asChild ? (
+ props.children
+ ) : (
+ <>
+
+ {props.children}
+ >
+ )}
+
+ );
+}) as ChakraSelect.RootComponent;
+
+interface SelectItemGroupProps extends ChakraSelect.ItemGroupProps {
+ label: React.ReactNode;
+}
+
+export const SelectItemGroup = React.forwardRef<
+ HTMLDivElement,
+ SelectItemGroupProps
+>(function SelectItemGroup(props, ref) {
+ const { children, label, ...rest } = props;
+ return (
+
+ {label}
+ {children}
+
+ );
+});
+
+export const SelectLabel = ChakraSelect.Label;
+export const SelectItemText = ChakraSelect.ItemText;
diff --git a/app/src/components/ui/slider.tsx b/app/src/components/ui/slider.tsx
new file mode 100644
index 000000000..e3eaf5230
--- /dev/null
+++ b/app/src/components/ui/slider.tsx
@@ -0,0 +1,82 @@
+import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react";
+import * as React from "react";
+
+export interface SliderProps extends ChakraSlider.RootProps {
+ marks?: Array;
+ label?: React.ReactNode;
+ showValue?: boolean;
+}
+
+export const Slider = React.forwardRef(
+ function Slider(props, ref) {
+ const { marks: marksProp, label, showValue, ...rest } = props;
+ const value = props.defaultValue ?? props.value;
+
+ const marks = marksProp?.map((mark) => {
+ if (typeof mark === "number") return { value: mark, label: undefined };
+ return mark;
+ });
+
+ const hasMarkLabel = !!marks?.some((mark) => mark.label);
+
+ return (
+
+ {label && !showValue && (
+ {label}
+ )}
+ {label && showValue && (
+
+ {label}
+
+
+ )}
+
+
+
+
+
+
+
+
+ );
+ },
+);
+
+function SliderThumbs(props: { value?: number[] }) {
+ const { value } = props;
+ return (
+
+ {(_, index) => (
+
+
+
+ )}
+
+ );
+}
+
+interface SliderMarksProps {
+ marks?: Array;
+}
+
+const SliderMarks = React.forwardRef(
+ function SliderMarks(props, ref) {
+ const { marks } = props;
+ if (!marks?.length) return null;
+
+ return (
+
+ {marks.map((mark, index) => {
+ const value = typeof mark === "number" ? mark : mark.value;
+ const label = typeof mark === "number" ? undefined : mark.label;
+ return (
+
+
+ {label}
+
+ );
+ })}
+
+ );
+ },
+);
diff --git a/app/src/components/ui/switch.tsx b/app/src/components/ui/switch.tsx
new file mode 100644
index 000000000..a677ca2e5
--- /dev/null
+++ b/app/src/components/ui/switch.tsx
@@ -0,0 +1,39 @@
+import { Switch as ChakraSwitch } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface SwitchProps extends ChakraSwitch.RootProps {
+ inputProps?: React.InputHTMLAttributes
+ rootRef?: React.Ref
+ trackLabel?: { on: React.ReactNode; off: React.ReactNode }
+ thumbLabel?: { on: React.ReactNode; off: React.ReactNode }
+}
+
+export const Switch = React.forwardRef(
+ function Switch(props, ref) {
+ const { inputProps, children, rootRef, trackLabel, thumbLabel, ...rest } =
+ props
+
+ return (
+
+
+
+
+ {thumbLabel && (
+
+ {thumbLabel?.on}
+
+ )}
+
+ {trackLabel && (
+
+ {trackLabel.on}
+
+ )}
+
+ {children != null && (
+ {children}
+ )}
+
+ )
+ },
+)
diff --git a/app/src/components/ui/tag.tsx b/app/src/components/ui/tag.tsx
new file mode 100644
index 000000000..728250f17
--- /dev/null
+++ b/app/src/components/ui/tag.tsx
@@ -0,0 +1,39 @@
+import { Tag as ChakraTag } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface TagProps extends ChakraTag.RootProps {
+ startElement?: React.ReactNode
+ endElement?: React.ReactNode
+ onClose?: VoidFunction
+ closable?: boolean
+}
+
+export const Tag = React.forwardRef(
+ function Tag(props, ref) {
+ const {
+ startElement,
+ endElement,
+ onClose,
+ closable = !!onClose,
+ children,
+ ...rest
+ } = props
+
+ return (
+
+ {startElement && (
+ {startElement}
+ )}
+ {children}
+ {endElement && (
+ {endElement}
+ )}
+ {closable && (
+
+
+
+ )}
+
+ )
+ },
+)
diff --git a/app/src/components/ui/toaster.tsx b/app/src/components/ui/toaster.tsx
new file mode 100644
index 000000000..a2b882cc6
--- /dev/null
+++ b/app/src/components/ui/toaster.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import {
+ Toaster as ChakraToaster,
+ Portal,
+ Spinner,
+ Stack,
+ Toast,
+ createToaster,
+} from "@chakra-ui/react";
+
+export const toaster = createToaster({
+ placement: "bottom-end",
+ pauseOnPageIdle: true,
+});
+
+export const Toaster = () => {
+ return (
+
+
+ {(toast) => (
+
+ {toast.type === "loading" ? (
+
+ ) : (
+
+ )}
+
+ {toast.title && {toast.title}}
+ {toast.description && (
+ {toast.description}
+ )}
+
+ {toast.action && (
+ {toast.action.label}
+ )}
+ {toast.meta?.closable && }
+
+ )}
+
+
+ );
+};
diff --git a/app/src/components/ui/tooltip.tsx b/app/src/components/ui/tooltip.tsx
new file mode 100644
index 000000000..ea71654f0
--- /dev/null
+++ b/app/src/components/ui/tooltip.tsx
@@ -0,0 +1,46 @@
+import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react";
+import * as React from "react";
+
+export interface TooltipProps extends ChakraTooltip.RootProps {
+ showArrow?: boolean;
+ portalled?: boolean;
+ portalRef?: React.RefObject;
+ content: React.ReactNode;
+ contentProps?: ChakraTooltip.ContentProps;
+ disabled?: boolean;
+}
+
+export const Tooltip = React.forwardRef(
+ function Tooltip(props, ref) {
+ const {
+ showArrow,
+ children,
+ disabled,
+ portalled,
+ content,
+ contentProps,
+ portalRef,
+ ...rest
+ } = props;
+
+ if (disabled) return children;
+
+ return (
+
+ {children}
+
+
+
+ {showArrow && (
+
+
+
+ )}
+ {content}
+
+
+
+
+ );
+ },
+);
diff --git a/app/src/components/wizard-steps.tsx b/app/src/components/wizard-steps.tsx
deleted file mode 100644
index 3f3d2245b..000000000
--- a/app/src/components/wizard-steps.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import {
- Box,
- Step,
- StepIcon,
- StepIndicator,
- StepNumber,
- StepSeparator,
- StepStatus,
- StepTitle,
- Stepper,
- useBreakpointValue,
-} from "@chakra-ui/react";
-
-interface WizardStep {
- title: string;
-}
-
-export default function WizardSteps({
- steps,
- currentStep,
- onSelect = () => {},
-}: {
- steps: WizardStep[];
- currentStep: number;
- onSelect?: (selectedStep: number) => void;
-}) {
- const orientation: "horizontal" | "vertical" | undefined = useBreakpointValue(
- { base: "vertical", md: "horizontal" },
- { fallback: "md" },
- );
- const gap: "0" | undefined = useBreakpointValue(
- { base: "0", md: undefined },
- { fallback: "md" },
- );
-
- return (
-
- {steps.map((step, index) => (
- onSelect(index)}>
-
- }
- incomplete={}
- active={}
- />
-
-
-
- {step.title}
-
-
-
-
- ))}
-
- );
-}
diff --git a/app/src/hooks/Toasts.tsx b/app/src/hooks/Toasts.tsx
index c53fdf077..9372f7e9b 100644
--- a/app/src/hooks/Toasts.tsx
+++ b/app/src/hooks/Toasts.tsx
@@ -1,42 +1,17 @@
-import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons";
-import { Box, Text, useToast } from "@chakra-ui/react";
+import { toaster } from "@/components/ui/toaster";
export function UseSuccessToast(props: {
title: string;
description: string;
text: string;
}) {
- const toast = useToast();
const { title, description, text } = props;
const showSuccessToast = () => {
- return toast({
+ return toaster.success({
title,
description,
- status: "success",
duration: 3000,
- isClosable: true,
- position: "top",
- render: () => (
-
-
-
- {text}
-
-
- ),
+ placement: "top",
});
};
@@ -48,37 +23,13 @@ export function UseErrorToast(props: {
description: string;
text: string;
}) {
- const toast = useToast();
const { title, description, text } = props;
const showErrorToast = () => {
- return toast({
+ return toaster.error({
title,
description,
- status: "error",
duration: 6000,
- isClosable: true,
- position: "top",
- render: () => (
-
-
-
- {text}
-
-
- ),
+ placement: "top",
});
};
diff --git a/app/src/hooks/activity-value-form/use-activity-validation.tsx b/app/src/hooks/activity-value-form/use-activity-validation.tsx
index a84138d6e..5ec7643ba 100644
--- a/app/src/hooks/activity-value-form/use-activity-validation.tsx
+++ b/app/src/hooks/activity-value-form/use-activity-validation.tsx
@@ -1,4 +1,4 @@
-import { Box, CloseButton, Text, useToast } from "@chakra-ui/react";
+import { Box, Text } from "@chakra-ui/react";
import { Trans } from "react-i18next";
import {
ManualInputValidationErrorCodes,
@@ -7,6 +7,7 @@ import {
import { TFunction } from "i18next";
import { UseFormSetError, UseFormSetFocus } from "react-hook-form";
import { Inputs } from "@/components/Modals/activity-modal/activity-modal-body";
+import { toaster } from "@/components/ui/toaster";
const useActivityValueValidation = ({
t,
@@ -17,8 +18,6 @@ const useActivityValueValidation = ({
setError: UseFormSetError;
setFocus: UseFormSetFocus;
}) => {
- const toast = useToast();
-
const handleManalInputValidationError = (
error: ManualValidationErrorDetails,
) => {
@@ -83,28 +82,14 @@ const useActivityValueValidation = ({
default:
break;
}
- toast({
- status: "error",
- render: ({ onClose }) => (
-
-
-
- {key}
-
-
-
-
+ toaster.error({
+ duration: 6000,
+ meta: { closable: true },
+ title: (
+
+ {key}
+
),
- isClosable: true,
});
};
diff --git a/app/src/hooks/useAuthToast.tsx b/app/src/hooks/useAuthToast.tsx
index 8082bb4a7..4ca32d2f4 100644
--- a/app/src/hooks/useAuthToast.tsx
+++ b/app/src/hooks/useAuthToast.tsx
@@ -1,39 +1,37 @@
-import { CheckCircleIcon } from "@chakra-ui/icons";
-import { Box, Text, useToast } from "@chakra-ui/react";
+import { Box, Text } from "@chakra-ui/react";
import { TFunction } from "i18next";
+import { toaster } from "@/components/ui/toaster";
export function useAuthToast(t: TFunction) {
- const toast = useToast();
-
const showLoginSuccessToast = () => {
- return toast({
+ return toaster.create({
title: t("verified-toast-title"),
description: t("verified-toast-description"),
- status: "success",
+ type: "success",
duration: 3000,
- isClosable: true,
- position: "top",
- render: () => (
-
-
-
- {t("logged-in-successful")}
-
-
- ),
+ placement: "top",
+ // Todo: add custom styles to toaster
+ // render: () => (
+ //
+ //
+ //
+ // {t("logged-in-successful")}
+ //
+ //
+ // )
});
};
diff --git a/app/src/lib/app-theme.ts b/app/src/lib/app-theme.ts
index c4031649b..ab29cb56d 100644
--- a/app/src/lib/app-theme.ts
+++ b/app/src/lib/app-theme.ts
@@ -1,4 +1,25 @@
-import { extendTheme, theme } from "@chakra-ui/react";
+import {
+ defineConfig,
+ defaultBaseConfig,
+ createSystem,
+} from "@chakra-ui/react";
+
+import { buttonRecipe } from "./theme/recipes/button.recipe";
+import { separatorRecipe } from "./theme/recipes/separator.recipe";
+import {
+ accordionRecipe,
+ cardRecipe,
+ formRecipe,
+ headingRecipe,
+ linkRecipe,
+ progressRecipe,
+ switchRecipe,
+ tabsRecipe,
+ tagRecipe,
+ textareaRecipe,
+ textRecipe,
+ tooltipRecipe,
+} from "./theme/recipes";
export enum SectorColors {
I = "#5785F4",
@@ -14,572 +35,249 @@ export enum SubSectorColors {
"V.3" = "#149037",
}
-export const appTheme = extendTheme({
- colors: {
- brand: {
- primary: "#001EA7",
- secondary: "#2351DC",
- },
-
- content: {
- primary: "#00001F",
- secondary: "#00001F",
- tertiary: "#4B4C63",
- link: "#2351DC",
- alternative: "#001EA7",
- },
-
- sectors: {
- I: SectorColors.I,
- II: SectorColors.II,
- III: SectorColors.III,
- IV: SectorColors.IV,
- V: SectorColors.V,
- },
-
- semantic: {
- success: "#24BE00",
- successOverlay: "#EFFDE5",
- warning: "#C98300",
- warningOverlay: "#FEF8E1",
- danger: "#F23D33",
- dangerOverlay: "#FFEAEE",
- info: "#2351DC",
- },
-
- base: {
- light: "#FFFFFF",
- dark: "#00001F",
- },
-
- border: {
- neutral: "#D7D8FA",
- overlay: "#E6E7FF",
- },
- divider: {
- neutral: "#F0F0F0",
- },
-
- background: {
- default: "#FFFFFF",
- neutral: "#E8EAFB",
- alternative: "#EFFDE5",
- overlay: "#C5CBF5",
- transparentGrey: "rgba(232, 234, 251, 0.20)",
- backgroundLight: "#FAFAFA",
- backgroundGreyFlat: "#FAFBFE",
- backgroundLoading: "#E8EAFB",
- alternativeLight: "#F9FAFE",
- },
-
- interactive: {
- primary: "#008600",
- primaryLight: "#61c261",
- accent: "#5FE500",
- secondary: "#2351DC",
- tertiary: "#24BE00",
- quaternary: "#F17105",
- control: "#7A7B9A",
- connected: "#FA7200",
- },
-
- sentiment: {
- positiveOverlay: "#EFFDE5",
- positiveLight: "#f0f7eb",
- positiveDark: "#b9cfa9",
- positiveDefault: "#24BE00",
- warningDefault: "#C98300",
- warningOverlay: "#FEF8E1",
- negativeDefault: "#F23D33",
- negativeOverlay: "#FFEAEE",
- },
-
- brandScheme: {
- 100: "#C5CBF5",
- 500: "#2351DC",
- },
-
- body: "#232640",
- },
-
- fonts: {
- heading: "var(--font-poppins)",
- body: "var(--font-opensans)",
- },
-
- fontSizes: {
- display: {
- xl: "140px",
- lg: "57px",
- md: "45px",
- sm: "36px",
- },
-
- headline: {
- lg: "32px",
- md: "28px",
- sm: "24px",
- },
-
- title: {
- lg: "22px",
- md: "16px",
- sm: "14px",
- },
-
- label: {
- lg: "14px",
- md: "12px",
- sm: "11px",
- },
-
- body: {
- xl: "22px",
- lg: "16px",
- md: "14px",
- sm: "12px",
- },
-
- button: {
- lg: "20px",
- md: "14px",
- sm: "12px",
- },
-
- caption: "12px",
- overline: "10px",
- },
-
- fontWeights: {
- hairline: 100,
- thin: 200,
- light: 300,
- regular: 400,
- medium: 500,
- semibold: 600,
- bold: 700,
- },
-
- lineHeights: {
- normal: "normal",
- none: 1,
- "64": "64px",
- "52": "52px",
- "44": "44px",
- "40": "40px",
- "36": "36px",
- "32": "32px",
- "28": "28px",
- "24": "24px",
- "20": "20px",
- "16": "16px",
- },
-
- letterSpacing: {
- normal: 0,
- wide: "0.5px",
- wider: "1.25px",
- widest: "1.5px",
- },
-
- spacing: {
- xs: "4px",
- s: "8px",
- m: "16px",
- l: "24px",
- xl: "32px",
- xxl: "40px",
- "xxl-2": "48px",
- "xxl-3": "56px",
- "xxl-4": "64px",
- "xxl-5": "72px",
- "xxl-6": "80px",
- },
-
- shadows: {
- "1dp": "0px 1px 2px -1px #0000001A, 0px 1px 3px 0px #00001F1A",
- "2dp": "0px 2px 4px -2px #0000001A, 0px 4px 6px -1px #0000001A",
- "4dp": "0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A",
- "8dp": "0px 8px 10px -6px #0000001A, 0px 20px 25px -5px #0000001A",
- "12dp": "0px 25px 50px -12px #00000040",
- },
+const customConfig = defineConfig({
+ theme: {
+ tokens: {
+ colors: {
+ brand: {
+ primary: { value: "#001EA7" },
+ secondary: { value: "#2351DC" },
+ },
- borderRadius: {
- full: "50%",
- minimal: "4px",
- rounded: "8px",
- "rounded-xl": "16px",
- "rounded-xxl": "20px",
- },
- borders: {
- inputBox: " 1px solid #D7D8FB",
- },
- /*
- breakpoints: {
- xs: "360px",
- sm: "600px",
- md: "905px",
- lg: "1240px",
- xl: "1440px",
- },
- */
+ content: {
+ primary: { value: "#00001F" },
+ secondary: { value: "#00001F" },
+ tertiary: { value: "#4B4C63" },
+ link: { value: "#2351DC" },
+ alternative: { value: "#001EA7" },
+ },
- components: {
- Button: {
- baseStyle: {
- textTransform: "uppercase",
- borderRadius: 50,
- fontFamily: "var(--font-poppins)",
- letterSpacing: "1.25px",
- lineHeight: "16px",
- },
- variants: {
- outline: {
- border: "2px solid",
- borderColor: "interactive.secondary",
- color: "interactive.secondary",
- _hover: {
- borderColor: "#5a7be0",
- color: "#5a7be0",
- },
- _active: {
- borderColor: "#899ee0",
- color: "#899ee0",
- },
- _loading: {
- opacity: 0.8,
- },
+ sectors: {
+ I: { value: SectorColors.I },
+ II: { value: SectorColors.II },
+ III: { value: SectorColors.III },
+ IV: { value: SectorColors.IV },
+ V: { value: SectorColors.V },
},
- solid: {
- bg: "interactive.secondary",
- color: "white",
- _hover: {
- bg: "#5a7be0",
- },
- _active: {
- bg: "#899ee0",
- },
- _loading: {
- bg: "background.overlay",
- color: "content.link",
- _hover: {
- bg: "#5a7be0",
- color: "base.light",
- },
- },
+
+ semantic: {
+ success: { value: "#24BE00" },
+ successOverlay: { value: "#EFFDE5" },
+ warning: { value: "#C98300" },
+ warningOverlay: { value: "#FEF8E1" },
+ danger: { value: "#F23D33" },
+ dangerOverlay: { value: "#FFEAEE" },
+ info: { value: "#2351DC" },
},
- danger: {
- bg: "sentiment.negativeDefault", // #F23D33
- color: "white",
- _hover: {
- bg: "#FF5F5F",
- },
- _active: {
- bg: "#E3241A",
- },
- _loading: {
- bg: "semantic.dangerOverlay",
- color: "base.dark",
- _hover: {
- bg: "#E3241A",
- color: "base.light",
- },
- },
+
+ base: {
+ light: { value: "#FFFFFF" },
+ dark: { value: "#00001F" },
},
- solidPrimary: {
- ...theme.components.Button.variants?.solid,
- bg: "sentiment.positiveOverlay",
- color: "interactive.primary",
- _hover: {
- bg: "sentiment.positiveLight",
- color: "interactive.primaryLight",
- },
- _active: {
- bg: "sentiment.positiveDark",
- color: "sentiment.positiveOverlay",
- },
- _loading: {
- opacity: 0.8,
- bg: "sentiment.positiveLight",
- },
+
+ border: {
+ neutral: { value: "#D7D8FA" },
+ overlay: { value: "#E6E7FF" },
},
- ghost: {
- color: "content.link",
+ divider: {
+ neutral: { value: "#F0F0F0" },
},
- lightGhost: {
- color: "base.light",
- _hover: {
- bg: "background.transparentGrey",
- color: "base.light",
+
+ background: {
+ default: { value: "#FFFFFF" },
+ neutral: { value: "#E8EAFB" },
+ alternative: { value: "#EFFDE5" },
+ overlay: { value: "#C5CBF5" },
+ transparentGrey: {
+ value: "rgba(232 }, 234, 251, 0.20)",
+ backgroundLight: { value: "#FAFAFA" },
+ backgroundGreyFlat: { value: "#FAFBFE" },
+ backgroundLoading: { value: "#E8EAFB" },
+ alternativeLight: { value: "#F9FAFE" },
},
- },
- solidIcon: {
- bgColor: "background.neutral",
- color: "interactive.secondary",
- _hover: {
- color: "white",
- bg: "#5a7be0",
+
+ interactive: {
+ primary: { value: "#008600" },
+ primaryLight: { value: "#61c261" },
+ accent: { value: "#5FE500" },
+ secondary: { value: "#2351DC" },
+ tertiary: { value: "#24BE00" },
+ quaternary: { value: "#F17105" },
+ control: { value: "#7A7B9A" },
+ connected: { value: "#FA7200" },
},
- _active: {
- bg: "#899ee0",
+
+ sentiment: {
+ positiveOverlay: { value: "#EFFDE5" },
+ positiveLight: { value: "#f0f7eb" },
+ positiveDark: { value: "#b9cfa9" },
+ positiveDefault: { value: "#24BE00" },
+ warningDefault: { value: "#C98300" },
+ warningOverlay: { value: "#FEF8E1" },
+ negativeDefault: { value: "#F23D33" },
+ negativeOverlay: { value: "#FFEAEE" },
},
- _loading: {
- opacity: 0.8,
- bg: "background.neutral",
+
+ brandScheme: {
+ 100: { value: "#C5CBF5" },
+ 500: { value: "#2351DC" },
},
},
+
+ body: { value: "#232640" },
},
- },
- Link: {
- baseStyle: {
- color: "brand.secondary",
- },
- },
- Card: {
- baseStyle: {
- container: {
- borderRadius: 8,
- px: 6,
- py: 8,
- },
+
+ fonts: {
+ heading: { value: "var(--font-poppins)" },
+ body: { value: "var(--font-opensans)" },
},
- },
- Tag: {
- variants: {
- brand: {
- container: {
- px: 3,
- py: 1,
- borderRadius: "full",
- borderColor: "background.neutral",
- borderWidth: 1,
- color: "background.neutral",
- },
- label: {
- color: "content.secondary",
- fontFamily: "heading",
- fontSize: "14px",
- lineHeight: "20px",
- letterSpacing: "0.5px",
- borderWidth: 0,
- mt: -0.5,
- },
- },
- filled: {
- container: {
- px: 4,
- py: 1,
- borderRadius: "full",
- bgColor: "background.neutral",
- },
- label: {
- color: "content.alternative",
- },
+
+ fontSizes: {
+ display: {
+ xl: { value: "140px" },
+ lg: { value: "57px" },
+ md: { value: "45px" },
+ sm: { value: "36px" },
},
- success: {
- container: {
- px: 4,
- py: 1,
- borderRadius: "full",
- borderWidth: 1,
- borderColor: "sentiment.positiveDefault",
- bgColor: "sentiment.positiveOverlay",
- color: "sentiment.positiveDefault",
- fontWeight: 500,
- },
- label: {
- color: "sentiment.positiveDefault",
- },
+
+ headline: {
+ lg: { value: "32px" },
+ md: { value: "28px" },
+ sm: { value: "24px" },
},
- warning: {
- container: {
- px: 4,
- py: 1,
- borderRadius: "full",
- borderWidth: 1,
- borderColor: "sentiment.warningDefault",
- bgColor: "sentiment.warningOverlay",
- color: "sentiment.warningDefault",
- },
- label: {
- color: "sentiment.warningDefault",
- },
+
+ title: {
+ lg: { value: "22px" },
+ md: { value: "16px" },
+ sm: { value: "14px" },
},
- low: {
- container: {
- bgColor: "sentiment.warningOverlay",
- borderColor: "sentiment.warningDefault",
- borderWidth: 1,
- borderRadius: "full",
- },
- label: {
- color: "sentiment.warningDefault",
- fontWeight: "medium",
- },
+
+ label: {
+ lg: { value: "14px" },
+ md: { value: "12px" },
+ sm: { value: "11px" },
},
- medium: {
- container: {
- bgColor: "background.neutral",
- borderColor: "content.link",
- borderWidth: 1,
- borderRadius: "full",
- },
- label: {
- color: "content.link",
- fontWeight: "medium",
- },
+
+ body: {
+ xl: { value: "22px" },
+ lg: { value: "16px" },
+ md: { value: "14px" },
+ sm: { value: "12px" },
},
- high: {
- container: {
- bgColor: "sentiment.positiveOverlay",
- borderColor: "interactive.tertiary",
- borderWidth: 1,
- borderRadius: "full",
- },
- label: {
- color: "interactive.tertiary",
- fontWeight: "medium",
- },
+
+ button: {
+ lg: { value: "20px" },
+ md: { value: "14px" },
+ sm: { value: "12px" },
},
+
+ caption: { value: "12px" },
+ overline: { value: "10px" },
},
- defaultProps: {
- variant: "brand",
- },
- },
- Tooltip: {
- baseStyle: {
- bg: "content.secondary",
- color: "base.light",
- px: 4,
- py: 2,
- borderRadius: "lg",
+
+ fontWeights: {
+ hairline: { value: 100 },
+ thin: { value: 200 },
+ light: { value: 300 },
+ regular: { value: 400 },
+ medium: { value: 500 },
+ semibold: { value: 600 },
+ bold: { value: 700 },
},
- },
- Tabs: {
- variants: {
- line: {
- tab: {
- borderColor: "#E6E7FF",
- _selected: {
- color: "interactive.secondary",
- borderColor: "interactive.secondary",
- fontWeight: "bold",
- },
- },
- },
+
+ lineHeights: {
+ normal: { value: "normal" },
+ none: { value: 1 },
+ "64": { value: "64px" },
+ "52": { value: "52px" },
+ "44": { value: "44px" },
+ "40": { value: "40px" },
+ "36": { value: "36px" },
+ "32": { value: "32px" },
+ "28": { value: "28px" },
+ "24": { value: "24px" },
+ "20": { value: "20px" },
+ "16": { value: "16px" },
},
- },
- Accordion: {
- variants: {
- brand: {
- container: {
- borderRadius: "8px",
- bgColor: "background.transparentGrey",
- borderWidth: 0,
- px: 4,
- py: 4,
- mb: 6,
- },
- button: {
- borderRadius: "8px",
- },
- },
+
+ letterSpacings: {
+ normal: { value: 0 },
+ wide: { value: "0.5px" },
+ wider: { value: "1.25px" },
+ widest: { value: "1.5px" },
},
- defaultProps: {
- variant: "brand",
+
+ spacing: {
+ xs: { value: "4px" },
+ s: { value: "8px" },
+ m: { value: "16px" },
+ l: { value: "24px" },
+ xl: { value: "32px" },
+ xxl: { value: "40px" },
+ "xxl-2": { value: "48px" },
+ "xxl-3": { value: "56px" },
+ "xxl-4": { value: "64px" },
+ "xxl-5": { value: "72px" },
+ "xxl-6": { value: "80px" },
},
- },
- Progress: {
- baseStyle: {
- filledTrack: {
- bg: "#24BE00",
+
+ shadows: {
+ "1dp": {
+ value: "0px 1px 2px -1px #0000001A, 0px 1px 3px 0px #00001F1A",
},
- },
- },
- Form: {
- variants: {
- brand: {
- container: {
- label: {
- fontFamily: "heading",
- fontWeight: "500",
- lineHeight: "20px",
- letterSpacing: "0.5px",
- fontSize: "14px",
- mb: 4,
- },
- },
+ "2dp": {
+ value: "0px 2px 4px -2px #0000001A, 0px 4px 6px -1px #0000001A",
},
- },
- defaultProps: {
- variant: "brand",
- },
- },
- Text: {
- variants: {
- spaced: {
- fontWeight: "medium",
- lineHeight: "20",
- letterSpacing: "wide",
+ "4dp": {
+ value: "0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A",
},
- card: {
- fontSize: "label.lg",
- fontWeight: "medium",
- color: "content.secondary",
- textTransform: "none",
- whiteSpace: "normal",
- textAlign: "left",
+ "8dp": {
+ value: "0px 8px 10px -6px #0000001A, 0px 20px 25px -5px #0000001A",
},
+ "12dp": { value: "0px 25px 50px -12px #00000040" },
},
- },
- Heading: {
- sizes: {
- lg: {
- fontSize: "24px",
- lineHeight: "32px",
- fontWeight: 600,
- },
+
+ borders: {
+ inputBox: { value: " 1px solid #D7D8FB" },
},
- },
- Switch: {
- variants: {
- brand: {
- track: {
- bg: "background.overlay",
- _checked: {
- bg: "content.link",
- },
- },
- container: {
- mb: "0 !important",
- },
- },
+ /*
+ borderRadius: {
+ full: { value: "50%" },
+ minimal: { value: "4px" },
+ rounded: { value: "8px" },
+ "rounded-xl": { value: "16px" },
+ "rounded-xxl": { value: "20px" },
},
- defaultProps: {
- variant: "brand",
+ */
+ /*
+ breakpoints: {
+ xs: "360px",
+ sm: "600px",
+ md: "905px",
+ lg: "1240px",
+ xl: "1440px",
},
+ */
},
- Textarea: {
- variants: {
- brand: {
- borderWidth: "1px",
- borderRadius: "16px",
- resize: "none",
- borderColor: "border.neutral",
- _invalid: {
- background: "sentiment.negativeOverlay",
- borderWidth: "2px",
- borderColor: "sentiment.negativeDefault",
- },
- _focus: {
- borderWidth: "2px",
- borderColor: "#3182ce",
- },
- },
- },
- defaultProps: {
- variant: "brand",
- },
+
+ recipes: {
+ button: buttonRecipe,
+ link: linkRecipe,
+ tag: tagRecipe,
+ card: cardRecipe,
+ tooltip: tooltipRecipe,
+ tab: tabsRecipe,
+ accordion: accordionRecipe,
+ progress: progressRecipe,
+ form: formRecipe,
+ text: textRecipe,
+ heading: headingRecipe,
+ switch: switchRecipe,
+ textarea: textareaRecipe,
+ seperator: separatorRecipe,
},
},
});
+
+const system = createSystem(defaultBaseConfig, customConfig);
+export default system;
diff --git a/app/src/lib/theme/app-theme.ts b/app/src/lib/theme/app-theme.ts
new file mode 100644
index 000000000..ffa5e4094
--- /dev/null
+++ b/app/src/lib/theme/app-theme.ts
@@ -0,0 +1,269 @@
+import { createSystem, defaultConfig } from "@chakra-ui/react";
+import {
+ accordionRecipe,
+ cardRecipe,
+ formRecipe,
+ headingRecipe,
+ linkRecipe,
+ progressRecipe,
+ switchRecipe,
+ tabsRecipe,
+ tagRecipe,
+ textareaRecipe,
+ textRecipe,
+ tooltipRecipe,
+} from "./recipes";
+import { buttonRecipe } from "./recipes/button.recipe";
+import { separatorRecipe } from "./recipes/separator.recipe";
+
+export enum SectorColors {
+ I = "#5785F4",
+ II = "#DF2222",
+ III = "#F28C37",
+ IV = "#2DD05B",
+ V = "#C6C61D",
+}
+
+export enum SubSectorColors {
+ "V.1" = "#38A857",
+ "V.2" = "#0E5221",
+ "V.3" = "#149037",
+}
+
+export const appTheme = createSystem(defaultConfig, {
+ theme: {
+ recipes: {
+ button: buttonRecipe,
+ link: linkRecipe,
+ tag: tagRecipe,
+ card: cardRecipe,
+ tooltip: tooltipRecipe,
+ tab: tabsRecipe,
+ accordion: accordionRecipe,
+ progress: progressRecipe,
+ form: formRecipe,
+ text: textRecipe,
+ heading: headingRecipe,
+ switch: switchRecipe,
+ textarea: textareaRecipe,
+ seperator: separatorRecipe,
+ },
+ tokens: {
+ colors: {
+ brand: {
+ primary: { value: "#001EA7" },
+ secondary: { value: "#2351DC" },
+ },
+
+ content: {
+ primary: { value: "#00001F" },
+ secondary: { value: "#00001F" },
+ tertiary: { value: "#4B4C63" },
+ link: { value: "#2351DC" },
+ alternative: { value: "#001EA7" },
+ },
+
+ sectors: {
+ I: { value: SectorColors.I },
+ II: { value: SectorColors.II },
+ III: { value: SectorColors.III },
+ IV: { value: SectorColors.IV },
+ V: { value: SectorColors.V },
+ },
+
+ semantic: {
+ success: { value: "#24BE00" },
+ successOverlay: { value: "#EFFDE5" },
+ warning: { value: "#C98300" },
+ warningOverlay: { value: "#FEF8E1" },
+ danger: { value: "#F23D33" },
+ dangerOverlay: { value: "#FFEAEE" },
+ info: { value: "#2351DC" },
+ },
+
+ base: {
+ light: { value: "#FFFFFF" },
+ dark: { value: "#00001F" },
+ },
+
+ border: {
+ neutral: { value: "#D7D8FA" },
+ overlay: { value: "#E6E7FF" },
+ },
+ divider: {
+ neutral: { value: "#F0F0F0" },
+ },
+
+ background: {
+ default: { value: "#FFFFFF" },
+ neutral: { value: "#E8EAFB" },
+ alternative: { value: "#EFFDE5" },
+ overlay: { value: "#C5CBF5" },
+ transparentGrey: { value: "rgba(232, 234, 251, 0.20)" },
+ backgroundLight: { value: "#FAFAFA" },
+ backgroundGreyFlat: { value: "#FAFBFE" },
+ backgroundLoading: { value: "#E8EAFB" },
+ },
+
+ interactive: {
+ primary: { value: "#008600" },
+ primaryLight: { value: "#61c261" },
+ accent: { value: "#5FE500" },
+ secondary: { value: "#2351DC" },
+ tertiary: { value: "#24BE00" },
+ quaternary: { value: "#F17105" },
+ control: { value: "#7A7B9A" },
+ connected: { value: "#FA7200" },
+ },
+
+ sentiment: {
+ positiveOverlay: { value: "#EFFDE5" },
+ positiveLight: { value: "#f0f7eb" },
+ positiveDark: { value: "#b9cfa9" },
+ positiveDefault: { value: "#24BE00" },
+ warningDefault: { value: "#C98300" },
+ warningOverlay: { value: "#FEF8E1" },
+ negativeDefault: { value: "#F23D33" },
+ negativeOverlay: { value: "#FFEAEE" },
+ },
+
+ brandScheme: {
+ 100: { value: "#C5CBF5" },
+ 500: { value: "#2351DC" },
+ },
+
+ body: { value: "#232640" },
+ },
+
+ fonts: {
+ heading: { value: "var(--font-poppins)" },
+ body: { value: "var(--font-opensans)" },
+ },
+
+ fontSizes: {
+ display: {
+ xl: { value: "140px" },
+ lg: { value: "57px" },
+ md: { value: "45px" },
+ sm: { value: "36px" },
+ },
+
+ headline: {
+ lg: { value: "32px" },
+ md: { value: "28px" },
+ sm: { value: "24px" },
+ },
+
+ title: {
+ lg: { value: "22px" },
+ md: { value: "16px" },
+ sm: { value: "14px" },
+ },
+
+ label: {
+ lg: { value: "14px" },
+ md: { value: "12px" },
+ sm: { value: "11px" },
+ },
+
+ body: {
+ xl: { value: "22px" },
+ lg: { value: "16px" },
+ md: { value: "14px" },
+ sm: { value: "12px" },
+ },
+
+ button: {
+ lg: { value: "20px" },
+ md: { value: "14px" },
+ sm: { value: "12px" },
+ },
+
+ caption: { value: "12px" },
+ overline: { value: "10px" },
+ },
+
+ fontWeights: {
+ hairline: { value: 100 },
+ thin: { value: 200 },
+ light: { value: 300 },
+ regular: { value: 400 },
+ medium: { value: 500 },
+ semibold: { value: 600 },
+ bold: { value: 700 },
+ },
+
+ lineHeights: {
+ normal: { value: "normal" },
+ none: { value: 1 },
+ "64": { value: "64px" },
+ "52": { value: "52px" },
+ "44": { value: "44px" },
+ "40": { value: "40px" },
+ "36": { value: "36px" },
+ "32": { value: "32px" },
+ "28": { value: "28px" },
+ "24": { value: "24px" },
+ "20": { value: "20px" },
+ "16": { value: "16px" },
+ },
+
+ letterSpacings: {
+ normal: { value: 0 },
+ wide: { value: "0.5px" },
+ wider: { value: "1.25px" },
+ widest: { value: "1.5px" },
+ },
+
+ spacing: {
+ xs: { value: "4px" },
+ s: { value: "8px" },
+ m: { value: "16px" },
+ l: { value: "24px" },
+ xl: { value: "32px" },
+ xxl: { value: "40px" },
+ "xxl-2": { value: "48px" },
+ "xxl-3": { value: "56px" },
+ "xxl-4": { value: "64px" },
+ "xxl-5": { value: "72px" },
+ "xxl-6": { value: "80px" },
+ },
+
+ shadows: {
+ "1dp": {
+ value: "0px 1px 2px -1px #0000001A, 0px 1px 3px 0px #00001F1A",
+ },
+ "2dp": {
+ value: "0px 2px 4px -2px #0000001A, 0px 4px 6px -1px #0000001A",
+ },
+ "4dp": {
+ value: "0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A",
+ },
+ "8dp": {
+ value: "0px 8px 10px -6px #0000001A, 0px 20px 25px -5px #0000001A",
+ },
+ "12dp": { value: "0px 25px 50px -12px #00000040" },
+ },
+
+ radii: {
+ full: { value: "50%" },
+ minimal: { value: "4px" },
+ rounded: { value: "8px" },
+ "rounded-xl": { value: "16px" },
+ "rounded-xxl": { value: "20px" },
+ },
+ borders: {
+ inputBox: { value: "1px solid #D7D8FB" },
+ },
+ /*
+ breakpoints: {
+ xs: "360px",
+ sm: "600px",
+ md: "905px",
+ lg: "1240px",
+ xl: "1440px",
+ },
+ */
+ },
+ },
+});
diff --git a/app/src/lib/theme/recipes/button.recipe.ts b/app/src/lib/theme/recipes/button.recipe.ts
new file mode 100644
index 000000000..90b07bbc1
--- /dev/null
+++ b/app/src/lib/theme/recipes/button.recipe.ts
@@ -0,0 +1,126 @@
+import { defineRecipe } from "@chakra-ui/react";
+
+// define a recipe for the button
+export const buttonRecipe = defineRecipe({
+ // base styles for the button
+ className: "chakra-button",
+ base: {
+ textTransform: "uppercase",
+ borderRadius: 50,
+ fontFamily: "var(--font-poppins)",
+ letterSpacing: "1.25px",
+ lineHeight: "16px",
+ display: "flex",
+ fontWeight: "bold",
+ },
+
+ // variants for the button
+ variants: {
+ variant: {
+ outline: {
+ border: "2px solid",
+ borderColor: "interactive.secondary",
+ color: "interactive.secondary",
+ _hover: {
+ borderColor: "#5a7be0",
+ color: "#5a7be0",
+ },
+ _active: {
+ borderColor: "#899ee0",
+ color: "#899ee0",
+ },
+ _loading: {
+ opacity: 0.8,
+ },
+ },
+ solid: {
+ bg: "interactive.secondary",
+ color: "white",
+ _hover: {
+ bg: "#5a7be0",
+ },
+ _active: {
+ bg: "#899ee0",
+ },
+ _loading: {
+ bg: "background.overlay",
+ color: "content.link",
+ _hover: {
+ bg: "#5a7be0",
+ color: "base.light",
+ },
+ },
+ },
+ danger: {
+ bg: "sentiment.negativeDefault", // #F23D33
+ color: "white",
+ _hover: {
+ bg: "#FF5F5F",
+ },
+ _active: {
+ bg: "#E3241A",
+ },
+ _loading: {
+ bg: "semantic.dangerOverlay",
+ color: "base.dark",
+ _hover: {
+ bg: "#E3241A",
+ color: "base.light",
+ },
+ },
+ },
+ solidPrimary: {
+ bg: "sentiment.positiveOverlay",
+ color: "interactive.primary",
+ _hover: {
+ bg: "sentiment.positiveLight",
+ color: "interactive.primaryLight",
+ },
+ _active: {
+ bg: "sentiment.positiveDark",
+ color: "sentiment.positiveOverlay",
+ },
+ _loading: {
+ opacity: 0.8,
+ bg: "sentiment.positiveLight",
+ },
+ },
+ ghost: {
+ color: "content.link",
+ bg: "green.100",
+ },
+ lightGhost: {
+ color: "base.light",
+ outline: "none",
+ border: "none",
+ _hover: {
+ bg: "background.transparentGrey",
+ color: "base.light",
+ },
+ _active: {
+ outline: "none",
+ border: "none",
+ },
+ },
+ solidIcon: {
+ bgColor: "background.neutral",
+ color: "interactive.secondary",
+ _hover: {
+ color: "white",
+ bg: "#5a7be0",
+ },
+ _active: {
+ bg: "#899ee0",
+ },
+ _loading: {
+ opacity: 0.8,
+ bg: "background.neutral",
+ },
+ },
+ },
+ size: {
+ sm: { padding: "4", fontSize: "12px" },
+ lg: { padding: "8", fontSize: "24px" },
+ },
+ },
+});
diff --git a/app/src/lib/theme/recipes/index.ts b/app/src/lib/theme/recipes/index.ts
new file mode 100644
index 000000000..042d70b6a
--- /dev/null
+++ b/app/src/lib/theme/recipes/index.ts
@@ -0,0 +1,281 @@
+/**
+ * This file contains all the recipes that are used to define the styles of the components.
+ */
+
+import { defineRecipe } from "@chakra-ui/react";
+
+// define a recipe for the link (anchor tag)
+export const linkRecipe = defineRecipe({
+ base: {
+ color: "brand.secondary",
+ },
+});
+
+// define a recipe for the tag
+export const tagRecipe = defineRecipe({
+ base: {},
+
+ variants: {
+ brand: {
+ container: {
+ px: 3,
+ py: 1,
+ borderRadius: "full",
+ borderColor: "background.neutral",
+ borderWidth: 1,
+ color: "background.neutral",
+ },
+ label: {
+ color: "content.secondary",
+ fontFamily: "heading",
+ fontSize: "14px",
+ lineHeight: "20px",
+ letterSpacing: "0.5px",
+ borderWidth: 0,
+ mt: -0.5,
+ },
+ },
+ filled: {
+ container: {
+ px: 4,
+ py: 1,
+ borderRadius: "full",
+ bgColor: "background.neutral",
+ },
+ label: {
+ color: "content.alternative",
+ },
+ },
+ success: {
+ container: {
+ px: 4,
+ py: 1,
+ borderRadius: "full",
+ borderWidth: 1,
+ borderColor: "sentiment.positiveDefault",
+ bgColor: "sentiment.positiveOverlay",
+ color: "sentiment.positiveDefault",
+ fontWeight: 500,
+ },
+ label: {
+ color: "sentiment.positiveDefault",
+ },
+ },
+ warning: {
+ container: {
+ px: 4,
+ py: 1,
+ borderRadius: "full",
+ borderWidth: 1,
+ borderColor: "sentiment.warningDefault",
+ bgColor: "sentiment.warningOverlay",
+ color: "sentiment.warningDefault",
+ },
+ label: {
+ color: "sentiment.warningDefault",
+ },
+ },
+ low: {
+ container: {
+ bgColor: "sentiment.warningOverlay",
+ borderColor: "sentiment.warningDefault",
+ borderWidth: 1,
+ borderRadius: "full",
+ },
+ label: {
+ color: "sentiment.warningDefault",
+ fontWeight: "medium",
+ },
+ },
+ medium: {
+ container: {
+ bgColor: "background.neutral",
+ borderColor: "content.link",
+ borderWidth: 1,
+ borderRadius: "full",
+ },
+ label: {
+ color: "content.link",
+ fontWeight: "medium",
+ },
+ },
+ high: {
+ container: {
+ bgColor: "sentiment.positiveOverlay",
+ borderColor: "interactive.tertiary",
+ borderWidth: 1,
+ borderRadius: "full",
+ },
+ label: {
+ color: "interactive.tertiary",
+ fontWeight: "medium",
+ },
+ },
+ },
+ defaultVariants: {
+ brand: "label",
+ },
+});
+
+// define a recipe for the card
+export const cardRecipe = defineRecipe({
+ base: {
+ borderRadius: 8,
+ px: 6,
+ py: 8,
+ },
+});
+
+// define a recipe for the tooltip
+export const tooltipRecipe = defineRecipe({
+ base: {
+ bg: "content.secondary",
+ color: "base.light",
+ borderRadius: 8,
+ px: 4,
+ py: 2,
+ },
+});
+
+// define a recipe for the tabs
+export const tabsRecipe = defineRecipe({
+ variants: {
+ true: {
+ line: {
+ tab: {
+ borderColor: "#E6E7FF",
+ _selected: {
+ color: "interactive.secondary",
+ borderColor: "interactive.secondary",
+ fontWeight: "bold",
+ },
+ },
+ },
+ },
+ },
+});
+
+// define a recipe for the accordion
+export const accordionRecipe = defineRecipe({
+ variants: {
+ brand: {
+ container: {
+ borderRadius: 8,
+ },
+ button: {
+ borderRadius: "8px",
+ },
+ },
+ },
+ defaultVariants: {
+ brand: "container",
+ },
+});
+
+// define a recipe for the progress
+export const progressRecipe = defineRecipe({
+ base: {
+ bg: "#24BE00",
+ },
+});
+
+// define a recipe for the Form
+export const formRecipe = defineRecipe({
+ variants: {
+ brand: {
+ container: {
+ label: {
+ fontFamily: "heading",
+ fontWeight: "500",
+ lineHeight: "20px",
+ letterSpacing: "0.5px",
+ fontSize: "14px",
+ mb: 4,
+ },
+ },
+ },
+ },
+ defaultVariants: {
+ brand: "container",
+ },
+});
+
+// define a recipe for the text
+export const textRecipe = defineRecipe({
+ variants: {
+ spaced: {
+ true: {
+ fontWeight: "medium",
+ lineHeight: "20",
+ letterSpacing: "wide",
+ },
+ },
+ card: {
+ true: {
+ fontSize: "label.lg",
+ fontWeight: "medium",
+ color: "content.secondary",
+ textTransform: "none",
+ whiteSpace: "normal",
+ textAlign: "left",
+ },
+ },
+ },
+});
+
+// define a recipe for the Heading
+export const headingRecipe = defineRecipe({
+ variants: {
+ lg: {
+ true: {
+ fontSize: "24px",
+ fontWeight: "bold",
+ lineHeight: "32px",
+ },
+ },
+ },
+ defaultVariants: {
+ lg: true,
+ },
+});
+
+// define a recipe for the Switch
+export const switchRecipe = defineRecipe({
+ variants: {
+ brand: {
+ track: {
+ borderRadius: "full",
+ bg: "background.neutral",
+ _checked: {
+ bg: "interactive.secondary",
+ },
+ },
+ container: {
+ mb: "0 !important",
+ },
+ },
+ },
+ defaultVariants: {
+ brand: "track",
+ },
+});
+
+// define a recipe for the Textarea
+export const textareaRecipe = defineRecipe({
+ variants: {
+ brand: {
+ container: {
+ bg: "background.neutral",
+ borderColor: "background.neutral",
+ borderRadius: 8,
+ color: "content.secondary",
+ _focus: {
+ borderColor: "interactive.secondary",
+ },
+ },
+ },
+ },
+ defaultVariants: {
+ brand: "container",
+ },
+});
diff --git a/app/src/lib/theme/recipes/separator.recipe.ts b/app/src/lib/theme/recipes/separator.recipe.ts
new file mode 100644
index 000000000..f78e2c3ac
--- /dev/null
+++ b/app/src/lib/theme/recipes/separator.recipe.ts
@@ -0,0 +1,49 @@
+import { defineRecipe } from "@chakra-ui/react";
+
+export const separatorRecipe = defineRecipe({
+ className: "chakra-separator",
+ base: {
+ display: "block",
+ borderColor: "green",
+ },
+ variants: {
+ variant: {
+ solid: {
+ borderStyle: "solid",
+ },
+ dashed: {
+ borderStyle: "dashed",
+ },
+ dotted: {
+ borderStyle: "dotted",
+ },
+ },
+ orientation: {
+ vertical: {
+ borderInlineStartWidth: "var(--separator-thickness)",
+ },
+ horizontal: {
+ borderTopWidth: "var(--separator-thickness)",
+ },
+ },
+ size: {
+ xs: {
+ "--separator-thickness": "0.5px",
+ },
+ sm: {
+ "--separator-thickness": "1px",
+ },
+ md: {
+ "--separator-thickness": "2px",
+ },
+ lg: {
+ "--separator-thickness": "3px",
+ },
+ },
+ },
+ defaultVariants: {
+ size: "sm",
+ variant: "solid",
+ orientation: "horizontal",
+ },
+});
diff --git a/app/src/lib/use-outside-click.ts b/app/src/lib/use-outside-click.ts
new file mode 100644
index 000000000..9fa5c8bed
--- /dev/null
+++ b/app/src/lib/use-outside-click.ts
@@ -0,0 +1,23 @@
+import { useEffect, useRef } from "react";
+
+export const useOutsideClick = (callback: () => void) => {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent | TouchEvent) => {
+ if (ref.current && !ref.current.contains(event.target as Node)) {
+ callback();
+ }
+ };
+
+ document.addEventListener("mouseup", handleClickOutside);
+ document.addEventListener("touchend", handleClickOutside);
+
+ return () => {
+ document.removeEventListener("mouseup", handleClickOutside);
+ document.removeEventListener("touchend", handleClickOutside);
+ };
+ }, [callback]);
+
+ return ref;
+};
diff --git a/app/src/stories/WizardSteps.stories.ts b/app/src/stories/WizardSteps.stories.ts
deleted file mode 100644
index 374e8b667..000000000
--- a/app/src/stories/WizardSteps.stories.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import WizardSteps from "../components/wizard-steps";
-import { Meta, StoryObj } from "@storybook/react";
-
-const meta: Meta = {
- title: "CityCatalyst/WizardSteps",
- component: WizardSteps,
- tags: ["onboarding", "wizard"],
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- args: {
- steps: [
- { title: "Setting up your inventory" },
- { title: "Confirm City's Information" },
- ],
- currentStep: 0,
- },
-};
diff --git a/app/tsconfig.json b/app/tsconfig.json
index 251f23e18..43498eeba 100644
--- a/app/tsconfig.json
+++ b/app/tsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"target": "ES2020",
+
"lib": [
"dom",
"dom.iterable",
@@ -12,8 +13,8 @@
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "node",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",