Skip to content

Commit 4d34fca

Browse files
alwoodmits-gabo
andauthored
feat(SCS-541): Front-end for Provider Information section (#550)
* feat: add provider information management * refactor: requested changes from review --------- Co-authored-by: its-gabo <[email protected]>
1 parent 1012ba9 commit 4d34fca

File tree

13 files changed

+417
-8
lines changed

13 files changed

+417
-8
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
import { useTranslation } from "react-i18next";
3+
4+
import { ApiClient } from "~/api/api-client";
5+
import { companyInformationQueryOptions } from "~/api/queries/useCompanyInformation";
6+
import { queryClient } from "~/api/queryClient";
7+
import { useToast } from "~/components/ui/use-toast";
8+
9+
import type { UpdateCompanyInformationBody } from "~/api/generated-api";
10+
11+
export function useUpdateCompanyInformation() {
12+
const { toast } = useToast();
13+
const { t } = useTranslation();
14+
15+
return useMutation({
16+
mutationFn: async (data: UpdateCompanyInformationBody) => {
17+
const response = await ApiClient.api.settingsControllerUpdateCompanyInformation(data);
18+
return response.data;
19+
},
20+
onSuccess: () => {
21+
queryClient.invalidateQueries(companyInformationQueryOptions);
22+
toast({
23+
description: t("providerInformation.updateSuccess"),
24+
});
25+
},
26+
onError: () => {
27+
toast({
28+
description: t("providerInformation.updateError"),
29+
variant: "destructive",
30+
});
31+
},
32+
});
33+
}

apps/web/app/api/queries/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export {
1515
} from "./useStudentCourses";
1616
export { useAllUsers, useAllUsersSuspense, usersQueryOptions } from "./useUsers";
1717
export { useContentCreatorStatistics } from "./useContentCreatorStatistics";
18+
export { useCompanyInformation } from "./useCompanyInformation";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { queryOptions, useQuery } from "@tanstack/react-query";
2+
3+
import { ApiClient } from "~/api/api-client";
4+
5+
export const companyInformationQueryOptions = queryOptions({
6+
queryKey: ["company-information"],
7+
queryFn: async () => {
8+
const response = await ApiClient.api.settingsControllerGetCompanyInformation();
9+
return response.data;
10+
},
11+
staleTime: 1000 * 60 * 5,
12+
});
13+
14+
export function useCompanyInformation() {
15+
return useQuery(companyInformationQueryOptions);
16+
}

apps/web/app/assets/svgs/info.svg

Lines changed: 2 additions & 2 deletions
Loading

apps/web/app/components/Navigation/NavigationFooter.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,38 @@ export function NavigationFooter({ setIsMobileNavOpen }: NavigationFooterProps)
1717
const { t } = useTranslation();
1818

1919
return (
20-
<menu className="grid w-full grid-cols-2 gap-2 md:grid-cols-6 md:gap-4 2xl:flex 2xl:flex-col 2xl:gap-2 2xl:self-end">
21-
<li className="col-span-2 md:col-span-6 2xl:hidden">
20+
<menu className="grid w-full grid-cols-3 gap-2 md:grid-cols-6 md:gap-4 2xl:flex 2xl:flex-col 2xl:gap-2 2xl:self-end">
21+
<li className="col-span-3 md:col-span-6 2xl:hidden">
2222
<Separator className="bg-primary-200 2xl:h-px 3xl:my-2" />
2323
</li>
24-
<li className="col-span-2 md:col-span-3">
24+
<li className="col-span-1 md:col-span-2">
25+
<Tooltip>
26+
<TooltipTrigger className="w-full">
27+
<NavLink
28+
onClick={() => setIsMobileNavOpen(false)}
29+
to="/provider-information"
30+
className={({ isActive }) =>
31+
cn("flex w-full items-center gap-x-3 rounded-lg px-4 py-3.5 2xl:p-2", {
32+
"bg-primary-700 text-white": isActive,
33+
"bg-white text-neutral-900": !isActive,
34+
})
35+
}
36+
>
37+
<Icon name="Info" className="size-5" />
38+
<span className="capitalize 2xl:sr-only 3xl:not-sr-only">
39+
{t("navigationSideBar.providerInformation")}
40+
</span>
41+
</NavLink>
42+
</TooltipTrigger>
43+
<TooltipContent
44+
side="right"
45+
className="hidden 2xl:block 2xl:bg-neutral-950 2xl:capitalize 2xl:text-white 3xl:hidden"
46+
>
47+
{t("navigationSideBar.providerInformation")}
48+
</TooltipContent>
49+
</Tooltip>
50+
</li>
51+
<li className="col-span-1 md:col-span-2">
2552
<Tooltip>
2653
<TooltipTrigger className="w-full">
2754
<NavLink
@@ -35,7 +62,9 @@ export function NavigationFooter({ setIsMobileNavOpen }: NavigationFooterProps)
3562
}
3663
>
3764
<Icon name="Settings" className="size-6" />
38-
<span className="2xl:sr-only 3xl:not-sr-only">{t("navigationSideBar.settings")}</span>
65+
<span className="capitalize 2xl:sr-only 3xl:not-sr-only">
66+
{t("navigationSideBar.settings")}
67+
</span>
3968
</NavLink>
4069
</TooltipTrigger>
4170
<TooltipContent
@@ -49,7 +78,7 @@ export function NavigationFooter({ setIsMobileNavOpen }: NavigationFooterProps)
4978
<li className="hidden 2xl:block">
5079
<Separator className="bg-primary-200 2xl:h-px" />
5180
</li>
52-
<li className="col-span-2 md:col-span-3">
81+
<li className="col-span-1 md:col-span-2">
5382
<Tooltip>
5483
<TooltipTrigger className="w-full">
5584
<button

apps/web/app/config/routeAccessConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,5 @@ export const routeAccessConfig = createRouteConfig({
112112
"admin/lesson-items/new-text-block": ADMIN_AND_CONTENT_CREATOR,
113113
"admin/lesson-items/new-question": ADMIN_AND_CONTENT_CREATOR,
114114
"admin/lesson-items/:id": ADMIN_AND_CONTENT_CREATOR,
115+
"provider-information": ALL_ROLES,
115116
});

apps/web/app/locales/en/translation.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"common": {
33
"button": {
44
"save": "Save",
5+
"saving": "Saving...",
56
"delete": "Delete",
67
"create": "Create",
78
"cancel": "Cancel",
@@ -13,7 +14,8 @@
1314
"goBack": "Go back",
1415
"ignore": "Ignore",
1516
"clearAll": "Clear All",
16-
"validate": "Validate"
17+
"validate": "Validate",
18+
"edit": "Edit"
1719
},
1820
"tooltip": {
1921
"soon": "Cooming soon"
@@ -113,11 +115,34 @@
113115
"categories": "Categories",
114116
"users": "Users",
115117
"groups": "Groups",
118+
"providerInformation": "Provider Info",
116119
"browseCourses": "Browse Courses",
117120
"ariaLabels": {
118121
"goToAvailableCourses": "Go to available courses"
119122
}
120123
},
124+
"providerInformation": {
125+
"title": "Provider Information",
126+
"description": "Information about your company that will be visible to users",
127+
"editTitle": "Edit Provider Information",
128+
"companyName": "Company Name",
129+
"registeredAddress": "Registered Address",
130+
"taxNumber": "Tax Number (NIP)",
131+
"emailAddress": "Email Address",
132+
"courtRegisterNumber": "Court Register Number (KRS)",
133+
"noDataAvailable": "No provider information available",
134+
"createSuccess": "Provider information created successfully",
135+
"updateSuccess": "Provider information updated successfully",
136+
"createError": "Failed to create provider information",
137+
"updateError": "Failed to update provider information",
138+
"saveError": "Failed to save provider information",
139+
"validation": {
140+
"nipMustBe10Digits": "NIP must be 10 digits",
141+
"krsMustBe10Digits": "KRS must be 10 digits",
142+
"invalidEmailFormat": "Invalid email format"
143+
},
144+
"noCompanyInformationAvailable": "No company information available"
145+
},
121146
"changeUserLanguageView": {
122147
"header": "Change Language",
123148
"subHeader": "Select your preferred language",

apps/web/app/locales/pl/translation.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"common": {
33
"button": {
44
"save": "Zapisz",
5+
"saving": "Zapisywanie...",
56
"delete": "Usuń",
67
"create": "Utwórz",
78
"cancel": "Anuluj",
@@ -14,6 +15,7 @@
1415
"clearAll": "Wyczyść wszystko",
1516
"ignore": "Ignoruj",
1617
"validate": "Popraw błędy",
18+
"edit": "Edytuj",
1719
"continue": "Kontynuuj"
1820
},
1921
"tooltip": {
@@ -114,11 +116,34 @@
114116
"categories": "Kategorie",
115117
"users": "Użytkownicy",
116118
"groups": "Grupy",
119+
"providerInformation": "O dostawcy",
117120
"browseCourses": "Przeglądaj kursy",
118121
"ariaLabels": {
119122
"goToAvailableCourses": "Przejdź do dostępnych kursów"
120123
}
121124
},
125+
"providerInformation": {
126+
"title": "Informacje o Dostawcy",
127+
"description": "Informacje o Twojej firmie, które będą widoczne dla użytkowników",
128+
"editTitle": "Edytuj Informacje o Dostawcy",
129+
"companyName": "Nazwa firmy",
130+
"registeredAddress": "Adres siedziby",
131+
"taxNumber": "Numer NIP",
132+
"emailAddress": "Adres email",
133+
"courtRegisterNumber": "Numer KRS",
134+
"noDataAvailable": "Brak informacji o dostawcy",
135+
"createSuccess": "Informacje o dostawcy zostały utworzone",
136+
"updateSuccess": "Informacje o dostawcy zostały zaktualizowane",
137+
"createError": "Nie udało się utworzyć informacji o dostawcy",
138+
"updateError": "Nie udało się zaktualizować informacji o dostawcy",
139+
"saveError": "Nie udało się zapisać informacji o dostawcy",
140+
"validation": {
141+
"nipMustBe10Digits": "NIP musi mieć 10 cyfr",
142+
"krsMustBe10Digits": "KRS musi mieć 10 cyfr",
143+
"invalidEmailFormat": "Nieprawidłowy format email"
144+
},
145+
"noCompanyInformationAvailable": "Brak informacji o dostawcy"
146+
},
122147
"changeUserLanguageView": {
123148
"header": "Zmień język",
124149
"subHeader": "Wybierz preferowany język",
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useState } from "react";
2+
import { useTranslation } from "react-i18next";
3+
4+
import { useUpdateCompanyInformation } from "~/api/mutations/admin/useUpdateCompanyInformation";
5+
import { useCompanyInformation } from "~/api/queries";
6+
import { PageWrapper } from "~/components/PageWrapper";
7+
import { Button } from "~/components/ui/button";
8+
import { useUserRole } from "~/hooks/useUserRole";
9+
import { filterChangedData } from "~/utils/filterChangedData";
10+
11+
import Loader from "../common/Loader/Loader";
12+
13+
import { ProviderInformationCard, ProviderInformationEditCard } from "./components";
14+
15+
import type { UpdateCompanyInformationBody } from "~/api/generated-api";
16+
17+
export default function ProviderInformationPage() {
18+
const [isEditing, setIsEditing] = useState(false);
19+
const { t } = useTranslation();
20+
21+
const { isAdmin } = useUserRole();
22+
const { data: companyInfo, isLoading } = useCompanyInformation();
23+
const { mutate: updateCompanyInformation, isPending } = useUpdateCompanyInformation();
24+
25+
const handleSubmit = (data: UpdateCompanyInformationBody) => {
26+
const changedData = filterChangedData(data, companyInfo?.data || {});
27+
28+
if (!Object.keys(changedData).length) {
29+
return;
30+
}
31+
32+
updateCompanyInformation(changedData, {
33+
onSuccess: () => {
34+
setIsEditing(false);
35+
},
36+
});
37+
};
38+
39+
const handleCancel = () => setIsEditing(false);
40+
41+
if (isLoading) {
42+
return (
43+
<div className="grid size-full place-items-center">
44+
<Loader />
45+
</div>
46+
);
47+
}
48+
49+
return (
50+
<PageWrapper role="main">
51+
<div className="flex flex-col items-center gap-6">
52+
<section className="flex w-full max-w-[720px] justify-between">
53+
<h2 className="h5 md:h3 text-neutral-950">{t("providerInformation.title")}</h2>
54+
{isAdmin && (
55+
<Button variant="outline" onClick={() => setIsEditing(!isEditing)} disabled={isPending}>
56+
{isEditing ? t("common.button.cancel") : t("common.button.edit")}
57+
</Button>
58+
)}
59+
</section>
60+
61+
{isEditing ? (
62+
<ProviderInformationEditCard
63+
companyInformation={companyInfo?.data || null}
64+
onSubmit={handleSubmit}
65+
onCancel={handleCancel}
66+
isLoading={isPending}
67+
/>
68+
) : (
69+
<ProviderInformationCard companyInformation={companyInfo?.data || null} />
70+
)}
71+
</div>
72+
</PageWrapper>
73+
);
74+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useMemo } from "react";
2+
import { useTranslation } from "react-i18next";
3+
4+
import type { GetCompanyInformationResponse } from "~/api/generated-api";
5+
6+
type ProviderInformationCardProps = {
7+
companyInformation: GetCompanyInformationResponse["data"] | null;
8+
};
9+
10+
export const ProviderInformationCard = ({ companyInformation }: ProviderInformationCardProps) => {
11+
const { t } = useTranslation();
12+
13+
const isCompanyInformationProvided =
14+
companyInformation && Object.values(companyInformation).filter(Boolean).length > 0;
15+
16+
const providedCompanyInformation = useMemo(() => {
17+
return isCompanyInformationProvided ? Object.values(companyInformation).filter(Boolean) : [];
18+
}, [companyInformation, isCompanyInformationProvided]);
19+
20+
if (!isCompanyInformationProvided) {
21+
return (
22+
<section className="flex w-full max-w-[720px] flex-col gap-y-6 rounded-b-lg rounded-t-2xl bg-white p-6 drop-shadow">
23+
<p className="body-base text-neutral-600">
24+
{t("providerInformation.noCompanyInformationAvailable")}
25+
</p>
26+
</section>
27+
);
28+
}
29+
30+
return (
31+
<section className="flex w-full max-w-[720px] flex-col gap-y-6 rounded-b-lg rounded-t-2xl bg-white p-6 drop-shadow">
32+
<div className="flex flex-col gap-4">
33+
{providedCompanyInformation.map((value, index) => (
34+
<div key={index} className="flex flex-col gap-y-2">
35+
<span className="text-neutral-900">
36+
{t(`providerInformation.${Object.keys(companyInformation)[index]}`)}:
37+
</span>
38+
<span className="font-medium text-neutral-950">{value}</span>
39+
</div>
40+
))}
41+
</div>
42+
</section>
43+
);
44+
};

0 commit comments

Comments
 (0)