@c-rex/components 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/package.json +1 -18
  2. package/src/carousel/carousel.tsx +6 -5
  3. package/src/check-article-lang.tsx +8 -4
  4. package/src/favorites/bookmark-button.tsx +26 -11
  5. package/src/favorites/favorite-button.tsx +67 -21
  6. package/src/info/info-table.tsx +60 -38
  7. package/src/info/set-available-versions.tsx +19 -0
  8. package/src/navbar/language-switcher/content-language-switch.tsx +36 -36
  9. package/src/navbar/language-switcher/ui-language-switch.tsx +5 -6
  10. package/src/navbar/navbar.tsx +6 -2
  11. package/src/restriction-menu/restriction-menu.tsx +2 -3
  12. package/src/results/generic/table-result-list.tsx +1 -1
  13. package/src/results/utils.ts +9 -2
  14. package/src/stores/favorites-store.ts +21 -21
  15. package/src/stores/language-store.ts +2 -31
  16. package/src/article/article-action-bar.analysis.md +0 -15
  17. package/src/article/article-action-bar.stories.tsx +0 -15
  18. package/src/article/article-content.analysis.md +0 -15
  19. package/src/article/article-content.stories.tsx +0 -21
  20. package/src/autocomplete.analysis.md +0 -17
  21. package/src/breadcrumb.analysis.md +0 -15
  22. package/src/carousel/carousel.analysis.md +0 -17
  23. package/src/check-article-lang.analysis.md +0 -15
  24. package/src/directoryNodes/tree-of-content.analysis.md +0 -14
  25. package/src/directoryNodes/tree-of-content.stories.tsx +0 -22
  26. package/src/documents/result-list.analysis.md +0 -14
  27. package/src/documents/result-list.stories.tsx +0 -19
  28. package/src/favorites/bookmark-button.analysis.md +0 -17
  29. package/src/favorites/bookmark-button.stories.tsx +0 -19
  30. package/src/favorites/favorite-button.analysis.md +0 -18
  31. package/src/favorites/favorite-button.stories.tsx +0 -22
  32. package/src/icons/file-icon.analysis.md +0 -14
  33. package/src/icons/file-icon.stories.tsx +0 -19
  34. package/src/icons/flag-icon.analysis.md +0 -14
  35. package/src/icons/flag-icon.stories.tsx +0 -25
  36. package/src/icons/loading.analysis.md +0 -14
  37. package/src/icons/loading.stories.tsx +0 -21
  38. package/src/info/info-table.analysis.md +0 -15
  39. package/src/info/shared.analysis.md +0 -14
  40. package/src/info/stories/info-table.stories.tsx +0 -31
  41. package/src/info/stories/shared.stories.tsx +0 -24
  42. package/src/navbar/language-switcher/content-language-switch.analysis.md +0 -15
  43. package/src/navbar/language-switcher/shared.analysis.md +0 -14
  44. package/src/navbar/language-switcher/ui-language-switch.analysis.md +0 -15
  45. package/src/navbar/navbar.analysis.md +0 -14
  46. package/src/navbar/settings.analysis.md +0 -14
  47. package/src/navbar/sign-in-out-btns.analysis.md +0 -14
  48. package/src/navbar/stories/navbar.stories.tsx +0 -31
  49. package/src/navbar/stories/settings.stories.tsx +0 -15
  50. package/src/navbar/stories/sign-in-out-btns.stories.tsx +0 -15
  51. package/src/navbar/stories/user-menu.stories.tsx +0 -20
  52. package/src/navbar/user-menu.analysis.md +0 -14
  53. package/src/page-wrapper.analysis.md +0 -14
  54. package/src/render-article.analysis.md +0 -15
  55. package/src/renditions/file-download.analysis.md +0 -14
  56. package/src/renditions/file-download.stories.tsx +0 -19
  57. package/src/renditions/html.analysis.md +0 -17
  58. package/src/renditions/html.stories.tsx +0 -19
  59. package/src/renditions/image/container.analysis.md +0 -15
  60. package/src/renditions/image/container.stories.tsx +0 -19
  61. package/src/renditions/image/rendition.analysis.md +0 -14
  62. package/src/renditions/image/rendition.stories.tsx +0 -19
  63. package/src/restriction-menu/restriction-menu-container.analysis.md +0 -14
  64. package/src/restriction-menu/restriction-menu-item.analysis.md +0 -14
  65. package/src/restriction-menu/restriction-menu.analysis.md +0 -17
  66. package/src/results/analysis/cards.analysis.md +0 -14
  67. package/src/results/analysis/dialog-filter.analysis.md +0 -17
  68. package/src/results/analysis/empty.analysis.md +0 -14
  69. package/src/results/analysis/filter-navbar.analysis.md +0 -16
  70. package/src/results/analysis/pagination.analysis.md +0 -14
  71. package/src/results/analysis/table-with-images.analysis.md +0 -15
  72. package/src/results/analysis/table.analysis.md +0 -15
  73. package/src/results/filter-sidebar/index.analysis.md +0 -14
  74. package/src/results/generic/table-result-list.analysis.md +0 -15
  75. package/src/results/generic/table-result-list.stories.tsx +0 -21
  76. package/src/results/stories/cards.stories.tsx +0 -66
  77. package/src/results/stories/dialog-filter.stories.tsx +0 -20
  78. package/src/results/stories/empty.stories.tsx +0 -25
  79. package/src/results/stories/filter-navbar.stories.tsx +0 -19
  80. package/src/results/stories/filter-sidebar.stories.tsx +0 -20
  81. package/src/results/stories/pagination.stories.tsx +0 -24
  82. package/src/results/stories/table-with-images.stories.tsx +0 -19
  83. package/src/results/stories/table.stories.tsx +0 -78
  84. package/src/search-input.analysis.md +0 -15
  85. package/src/share-button.analysis.md +0 -19
  86. package/src/stories/autocomplete.stories.tsx +0 -20
  87. package/src/stories/breadcrumb.stories.tsx +0 -93
  88. package/src/stories/check-article-lang.stories.tsx +0 -22
  89. package/src/stories/render-article.stories.tsx +0 -19
  90. package/src/stories/search-input.stories.tsx +0 -21
  91. package/src/stories/share-button.stories.tsx +0 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-rex/components",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "files": [
5
5
  "src"
6
6
  ],
@@ -179,31 +179,19 @@
179
179
  }
180
180
  },
181
181
  "scripts": {
182
- "storybook": "storybook dev -p 6006",
183
- "build-storybook": "storybook build",
184
182
  "lint": "eslint .",
185
183
  "lint:fix": "eslint . --fix"
186
184
  },
187
185
  "devDependencies": {
188
186
  "@c-rex/eslint-config": "*",
189
187
  "@c-rex/typescript-config": "*",
190
- "@chromatic-com/storybook": "^3.2.6",
191
- "@storybook/addon-essentials": "^8.6.12",
192
- "@storybook/addon-onboarding": "^8.6.12",
193
- "@storybook/blocks": "^8.6.12",
194
- "@storybook/nextjs": "^8.6.12",
195
- "@storybook/react": "^8.6.12",
196
- "@storybook/test": "^8.6.12",
197
188
  "@turbo/gen": "^2.4.4",
198
189
  "@types/node": "^22.13.10",
199
190
  "@types/react": "19.0.10",
200
191
  "@types/react-dom": "19.0.4",
201
192
  "autoprefixer": "^10.4.21",
202
193
  "eslint": "^9.23.0",
203
- "eslint-plugin-storybook": "^0.12.0",
204
194
  "postcss": "^8.5.3",
205
- "storybook": "^8.6.12",
206
- "style-loader": "^4.0.0",
207
195
  "tailwindcss": "^3.4.17",
208
196
  "typescript": "latest"
209
197
  },
@@ -226,10 +214,5 @@
226
214
  "react-icons": "^5.5.0",
227
215
  "tailwindcss-animate": "^1.0.7",
228
216
  "zustand": "^5.0.8"
229
- },
230
- "eslintConfig": {
231
- "extends": [
232
- "plugin:storybook/recommended"
233
- ]
234
217
  }
235
218
  }
@@ -322,9 +322,10 @@ const DefaultRenderCarouselItem: FC<{
322
322
  const itemType = getType(item.class);
323
323
  const language = getLanguage(item.languages)
324
324
  const countryCode = language.split("-")[1] || "";
325
+ const link = linkPattern.replace("{shortId}", item.shortId!);
325
326
 
326
327
  return (
327
- <div className="p-2 flex flex-1">
328
+ <Link href={link} className="group p-2 flex flex-1">
328
329
  <Card className="p-4 flex-1 justify-between relative">
329
330
  <Badge className="absolute -top-2 -right-2">{t(itemType.toLowerCase())}</Badge>
330
331
 
@@ -336,17 +337,17 @@ const DefaultRenderCarouselItem: FC<{
336
337
  />
337
338
  )}
338
339
 
339
- <Link href={linkPattern.replace("{id}", item.shortId!)} className="hover:underline text-lg font-semibold flex-1">
340
+ <span className="group-hover:underline text-lg font-semibold flex-1">
340
341
  {title}
341
- </Link>
342
+ </span>
342
343
 
343
344
  <div className="flex justify-between w-full">
344
- <span className="w-8 block border">
345
+ <span className="w-8 block">
345
346
  <Flag countryCode={countryCode} />
346
347
  </span>
347
348
  <span className="text-gray-400">{date || item.revision}</span>
348
349
  </div>
349
350
  </Card>
350
- </div>
351
+ </Link>
351
352
  );
352
353
  };
@@ -14,11 +14,15 @@ interface Props {
14
14
  export const CheckArticleLangToast: FC<Props> = ({ availableVersions }) => {
15
15
  const t = useTranslations();
16
16
  const { setAvailableVersions } = useAppConfig()
17
+ const searchLanguage = useSearchSettingsStore((state) => state.language);
17
18
 
18
19
  useEffect(() => {
19
20
  setAvailableVersions(availableVersions)
20
- const searchLanguage = useSearchSettingsStore.getState().language;
21
- const activeArticle = availableVersions.filter((item) => item.active)[0]
21
+ }, [availableVersions, setAvailableVersions])
22
+
23
+ useEffect(() => {
24
+ if (!searchLanguage) return;
25
+ const activeArticle = availableVersions.find((item) => item.active)
22
26
 
23
27
  if (activeArticle == undefined || activeArticle.lang == searchLanguage) return
24
28
 
@@ -26,7 +30,7 @@ export const CheckArticleLangToast: FC<Props> = ({ availableVersions }) => {
26
30
  if (articleAvailable != undefined) {
27
31
  articleAvailableInToast(articleAvailable.lang, articleAvailable.link)
28
32
  }
29
- }, [])
33
+ }, [availableVersions, searchLanguage])
30
34
 
31
35
  const articleAvailableInToast = (lang: string, link: string) => {
32
36
  toast(t('toast.read', { lang }), {
@@ -40,4 +44,4 @@ export const CheckArticleLangToast: FC<Props> = ({ availableVersions }) => {
40
44
  }
41
45
 
42
46
  return null
43
- }
47
+ }
@@ -1,12 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { ComponentProps, FC, ReactNode, useEffect, useState } from "react";
4
- import { Button, ButtonProps } from "@c-rex/ui/button";
3
+ import { ComponentProps, FC } from "react";
4
+ import { Button } from "@c-rex/ui/button";
5
5
  import { Trash } from "lucide-react";
6
6
  import { cn } from "@c-rex/utils";
7
7
  import Link from "next/link";
8
8
  import { useFavoritesStore } from "../stores/favorites-store";
9
9
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@c-rex/ui/dialog";
10
+ import { useTranslations } from "next-intl";
10
11
  import {
11
12
  Table,
12
13
  TableBody,
@@ -15,20 +16,22 @@ import {
15
16
  } from "@c-rex/ui/table"
16
17
  import { FaRegBookmark } from "react-icons/fa6";
17
18
 
19
+ const EMPTY_TOPICS: { id: string; label: string; color: string }[] = [];
20
+
18
21
  type BookmarkProps = {
19
22
  shortId: string;
23
+ linkPattern?: string;
20
24
  triggerVariant?: ComponentProps<typeof Button>["variant"];
21
25
  }
22
26
 
23
27
  export const BookmarkButton: FC<BookmarkProps> = ({
24
28
  shortId,
29
+ linkPattern = `/topics/{id}/pages`,
25
30
  triggerVariant = "outline"
26
31
  }) => {
27
- const [markersList, setMarkersList] = useState<Array<{ id: string; label: string; color: string }>>([]);
28
-
29
- useEffect(() => {
30
- setMarkersList(useFavoritesStore.getState().documents[shortId]?.topics || []);
31
- }, [shortId]);
32
+ const t = useTranslations("bookmarks");
33
+ const removeFavoriteTopic = useFavoritesStore((state) => state.unfavoriteTopic);
34
+ const markersList = useFavoritesStore((state) => state.documents[shortId]?.topics) ?? EMPTY_TOPICS;
32
35
  return (
33
36
  <Dialog>
34
37
  <DialogTrigger asChild>
@@ -47,25 +50,37 @@ export const BookmarkButton: FC<BookmarkProps> = ({
47
50
  </DialogTrigger>
48
51
  <DialogContent>
49
52
  <DialogHeader>
50
- <DialogTitle>Bookmarks</DialogTitle>
53
+ <DialogTitle>{t("title")}</DialogTitle>
51
54
  <DialogDescription>
52
- Manage your bookmarks here
55
+ {t("description")}
53
56
  </DialogDescription>
54
57
  </DialogHeader>
55
58
  <Table>
56
59
  <TableBody>
60
+ {markersList.length === 0 && (
61
+ <TableRow>
62
+ <TableCell colSpan={3} className="text-center">
63
+ {t("empty")}
64
+ </TableCell>
65
+ </TableRow>
66
+ )}
67
+
57
68
  {markersList.map((item) => (
58
69
  <TableRow key={item.id} className="min-h-12">
59
70
  <TableCell>
60
71
  <FaRegBookmark className={cn("w-5", `text-${item.color}`)} />
61
72
  </TableCell>
62
73
  <TableCell>
63
- <Link href={`${window.location.origin}/topics/${item.id}`}>
74
+ <Link href={linkPattern.replace("{id}", item.id)} className="hover:underline">
64
75
  {item.label}
65
76
  </Link>
66
77
  </TableCell>
67
78
  <TableCell>
68
- <Button variant="destructive" size="icon">
79
+ <Button
80
+ variant="destructive"
81
+ size="icon"
82
+ onClick={() => removeFavoriteTopic(shortId, item.id)}
83
+ >
69
84
  <Trash className="w-5 hover:text-red-600 cursor-pointer" />
70
85
  </Button>
71
86
  </TableCell>
@@ -6,6 +6,8 @@ import { FaStar, FaRegStar } from "react-icons/fa";
6
6
  import { useFavoritesStore } from "../stores/favorites-store";
7
7
  import { MARKER_COLORS, RESULT_TYPES } from "@c-rex/constants";
8
8
  import { ResultTypes } from "@c-rex/types";
9
+ import { Loader2 } from "lucide-react";
10
+ import { toast } from "sonner";
9
11
 
10
12
  export const FavoriteButton: FC<{ id: string, type: ResultTypes, label: string }> = ({ id, type, label }) => {
11
13
  const addFavoriteTopic = useFavoritesStore((state) => state.favoriteTopic);
@@ -17,58 +19,102 @@ export const FavoriteButton: FC<{ id: string, type: ResultTypes, label: string }
17
19
  const favoriteList = useFavoritesStore((state) => state.favorites);
18
20
  const isFavorite = favoriteList.find((fav) => fav.id === id);
19
21
  const [documentData, setDocumentData] = useState<{ id: string, label: string }>({ id, label });
22
+ const [isLoading, setIsLoading] = useState(false);
20
23
 
21
24
  useEffect(() => {
22
25
  if (type === RESULT_TYPES.TOPIC) {
23
- getTopicDocumentData(id);
26
+ void getTopicDocumentData(id).catch(() => {
27
+ // Lazy retry on user action.
28
+ });
24
29
  }
25
- }, []);
30
+ }, [id, type]);
26
31
 
27
- const addFavorite = async (id: string) => {
32
+ const ensureDocumentData = async (topicId: string): Promise<{ id: string, label: string }> => {
33
+ if (type !== RESULT_TYPES.TOPIC) {
34
+ return { id, label };
35
+ }
36
+
37
+ if (documentData.id !== topicId) {
38
+ return documentData;
39
+ }
40
+
41
+ return await getTopicDocumentData(topicId);
42
+ };
43
+
44
+ const addFavorite = async (targetId: string) => {
28
45
  if (type === RESULT_TYPES.DOCUMENT) {
29
- addFavoriteDocument(id, label);
46
+ addFavoriteDocument(targetId, label);
30
47
  return;
31
48
  }
32
49
 
33
- const length = favoriteDocumentList[documentData.id]?.topics.length || 0;
50
+ const docData = await ensureDocumentData(targetId);
51
+ const length = favoriteDocumentList[docData.id]?.topics.length || 0;
34
52
  const color = MARKER_COLORS[length] || MARKER_COLORS[MARKER_COLORS.length - 1] as string;
35
53
 
36
- addFavoriteTopic(documentData.id, id, label, color);
37
- }
54
+ addFavoriteTopic(docData.id, targetId, label, color);
55
+ };
38
56
 
39
- const removeFavorite = (id: string) => {
57
+ const removeFavorite = async (targetId: string) => {
40
58
  if (type === RESULT_TYPES.DOCUMENT) {
41
- removeFavoriteDocument(id);
59
+ removeFavoriteDocument(targetId);
42
60
  return;
43
61
  }
44
62
 
45
- removeFavoriteTopic(documentData.id, id);
46
- }
47
-
48
- const getTopicDocumentData = async (topicId: string): Promise<void> => {
63
+ const docData = await ensureDocumentData(targetId);
64
+ removeFavoriteTopic(docData.id, targetId);
65
+ };
49
66
 
67
+ const getTopicDocumentData = async (topicId: string): Promise<{ id: string, label: string }> => {
50
68
  const response = await fetch(`/api/information-units/document-by-topic-id?shortId=${topicId}`, {
51
69
  method: "GET"
52
70
  });
53
71
 
54
- if (!response.ok) throw new Error("Failed to fetch document by topic id")
72
+ if (!response.ok) {
73
+ throw new Error("Failed to fetch document by topic id");
74
+ }
55
75
 
56
76
  const { documentId, label } = await response.json();
57
-
58
- setDocumentData({ id: documentId, label });
59
- }
77
+ const data = { id: documentId, label };
78
+ setDocumentData(data);
79
+ return data;
80
+ };
81
+
82
+ const handleToggle = async () => {
83
+ if (isLoading) return;
84
+ setIsLoading(true);
85
+
86
+ try {
87
+ if (isFavorite) {
88
+ await removeFavorite(id);
89
+ } else {
90
+ await addFavorite(id);
91
+ }
92
+ } catch {
93
+ toast.error("Não foi possível atualizar os favoritos.");
94
+ } finally {
95
+ setIsLoading(false);
96
+ }
97
+ };
60
98
 
61
99
  if (isFavorite) {
62
100
  return (
63
- <Button variant="ghost" size="icon" onClick={() => removeFavorite(id)}>
64
- <FaStar className="!h-5 !w-5 color-primary" />
101
+ <Button variant="ghost" size="icon" onClick={handleToggle} disabled={isLoading}>
102
+ {isLoading ? (
103
+ <Loader2 className="!h-5 !w-5 animate-spin" />
104
+ ) : (
105
+ <FaStar className="!h-5 !w-5 color-primary" />
106
+ )}
65
107
  </Button>
66
108
  );
67
109
  }
68
110
 
69
111
  return (
70
- <Button variant="ghost" size="icon" onClick={() => addFavorite(id)}>
71
- <FaRegStar className="!h-5 !w-5" />
112
+ <Button variant="ghost" size="icon" onClick={handleToggle} disabled={isLoading}>
113
+ {isLoading ? (
114
+ <Loader2 className="!h-5 !w-5 animate-spin" />
115
+ ) : (
116
+ <FaRegStar className="!h-5 !w-5" />
117
+ )}
72
118
  </Button>
73
119
  )
74
120
  }
@@ -20,40 +20,79 @@ import { Flag } from "../icons/flag-icon";
20
20
  import { Button } from "@c-rex/ui/button";
21
21
  import { FileIcon } from "../icons/file-icon";
22
22
  import { BookmarkButton } from "../favorites/bookmark-button";
23
- import { InformationUnitsGetAll } from "../generated/server-components";
23
+ import { informationUnitsGetAllServer } from "@c-rex/services/server-requests";
24
24
  import { getTranslations } from "next-intl/server";
25
25
  import { EN_LANG } from "@c-rex/constants";
26
26
  import { getFileRenditions } from "../results/utils";
27
27
  import Link from "next/link";
28
28
  import { processDataToLabelValuePairs } from "@c-rex/utils";
29
+ import { AvailableVersionsInterface } from "@c-rex/interfaces";
30
+ import { SetAvailableVersions } from "./set-available-versions";
29
31
 
30
32
  type Props = {
31
33
  title: string;
32
- linkPath: "topics" | "documents";
34
+ linkPattern: string;
33
35
  data: InformationUnitModel;
34
36
  excludeKeys?: string[];
35
37
  showBookmarkButton?: boolean;
38
+ saveVersionsInContext?: boolean;
36
39
  }
37
40
 
38
41
  export const InfoTable: FC<Props> = async ({
39
42
  title,
40
43
  data,
41
- linkPath = "topics",
44
+ linkPattern,
42
45
  showBookmarkButton = false,
46
+ saveVersionsInContext = false
43
47
  }) => {
44
- const t = await getTranslations();
48
+ const versionBaseShortId = data.versionOf?.shortId ?? null;
49
+ const [t, informationUnits] = await Promise.all([
50
+ getTranslations(),
51
+ versionBaseShortId
52
+ ? informationUnitsGetAllServer({
53
+ Restrict: [`versionOf.shortId=${versionBaseShortId}`],
54
+ Fields: ["renditions", "class", "languages", "labels"],
55
+ })
56
+ : Promise.resolve({ items: [] }),
57
+ ])
45
58
  const files = getFileRenditions({ renditions: data.renditions! });
59
+ const versionsByShortId = new Map<string, { shortId: string; language: string }>();
60
+
61
+ if (data.shortId && data.languages?.[0]) {
62
+ versionsByShortId.set(data.shortId, {
63
+ shortId: data.shortId,
64
+ language: data.languages[0],
65
+ });
66
+ }
67
+
68
+ informationUnits.items.forEach((item) => {
69
+ if (!item.shortId || !item.languages?.[0]) return;
70
+ versionsByShortId.set(item.shortId, {
71
+ shortId: item.shortId,
72
+ language: item.languages[0],
73
+ });
74
+ });
75
+
76
+ const availableVersions = Array.from(versionsByShortId.values())
77
+ .map((item) => ({
78
+ shortId: item.shortId,
79
+ active: item.shortId === data.shortId,
80
+ lang: item.language,
81
+ country: item.language.split("-")[1] ?? item.language,
82
+ link: linkPattern.replace("{shortId}", item.shortId),
83
+ }))
84
+ .sort((a, b) => a.lang.localeCompare(b.lang)) as AvailableVersionsInterface[];
46
85
 
47
86
  return (
48
87
  <Card className="p-0 !pt-4">
49
88
  <CardHeader>
50
89
  <CardTitle className="text-lg flex justify-between items-end">
51
90
  {title}
52
-
53
91
  {showBookmarkButton && <BookmarkButton shortId={data.shortId!} />}
54
92
  </CardTitle>
55
93
  </CardHeader>
56
94
  <CardContent className="space-y-3 !p-0">
95
+ {saveVersionsInContext && <SetAvailableVersions versions={availableVersions} />}
57
96
  <Table>
58
97
  <TableBody>
59
98
  {processDataToLabelValuePairs(data).map((item) => (
@@ -67,39 +106,22 @@ export const InfoTable: FC<Props> = async ({
67
106
  </TableRow>
68
107
  ))}
69
108
 
70
- {/*check if has versionOF */}
71
- <InformationUnitsGetAll
72
- queryParams={{
73
- Restrict: [`versionOf.shortId=${data.versionOf?.shortId}`],
74
- Fields: ["renditions", "class", "languages", "labels"]
75
- }}
76
- render={(data, error) => {
77
- if (error) {
78
- return JSON.stringify(error);
79
- }
80
-
81
- return (
82
- <TableRow className="min-h-12">
83
- <TableCell className="font-medium w-28 pl-4">
84
- <h4 className="text-sm font-medium">{t("availableIn")}</h4>
85
- </TableCell>
86
- <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
87
- {data.items.map((item) => {
88
- const language = item.languages?.[0] ?? "NO LANGUAGE";
89
- const country = language.split("-")[1]!;
90
- return (
91
- <span className="w-8 block border" key={item.shortId}>
92
- <Link href={`/${linkPath}/${item.shortId}/content`} title={language}>
93
- <Flag countryCode={country} />
94
- </Link>
95
- </span>
96
- )
97
- })}
98
- </TableCell>
99
- </TableRow>
100
- )
101
- }}
102
- />
109
+ <TableRow className="min-h-12">
110
+ <TableCell className="font-medium w-28 pl-4">
111
+ <h4 className="text-sm font-medium">{t("availableIn")}</h4>
112
+ </TableCell>
113
+ <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
114
+ {availableVersions.map((item) => {
115
+ return (
116
+ <span className="w-8 block border" key={item.shortId}>
117
+ <Link href={item.link} title={item.lang}>
118
+ <Flag countryCode={item.country} />
119
+ </Link>
120
+ </span>
121
+ )
122
+ })}
123
+ </TableCell>
124
+ </TableRow>
103
125
 
104
126
  {Object.keys(files).length > 0 && (
105
127
  <TableRow className="min-h-12">
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { AvailableVersionsInterface } from "@c-rex/interfaces";
5
+ import { useAppConfig } from "@c-rex/contexts/config-provider";
6
+
7
+ type Props = {
8
+ versions: AvailableVersionsInterface[];
9
+ };
10
+
11
+ export const SetAvailableVersions = ({ versions }: Props) => {
12
+ const { setAvailableVersions } = useAppConfig();
13
+
14
+ useEffect(() => {
15
+ setAvailableVersions(versions);
16
+ }, [setAvailableVersions, versions]);
17
+
18
+ return null;
19
+ };
@@ -1,8 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { startTransition } from "react";
3
+ import { startTransition, useCallback, useMemo } from "react";
4
4
  import { SharedLanguageSwitch } from "./shared";
5
- import { BLOG_TYPE_AND_LINK, DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
6
5
  import { useQueryState } from "nuqs"
7
6
  import { useAppConfig } from "@c-rex/contexts/config-provider";
8
7
  import { useTranslations } from "next-intl";
@@ -16,26 +15,36 @@ type Props = {
16
15
 
17
16
  export const ContentLanguageSwitch = ({ contentLangDefault }: Props) => {
18
17
  const t = useTranslations();
19
- const contentLang = useSearchSettingsStore.getState().language || contentLangDefault;
20
- const updatePreferences = useSearchSettingsStore.getState().updatePreferences;
21
-
18
+ const contentLang = useSearchSettingsStore((state) => state.language) || contentLangDefault;
19
+ const updatePreferences = useSearchSettingsStore((state) => state.updatePreferences);
20
+ const availableLanguages = useLanguageStore((state) => state.availableLanguages);
22
21
  const { availableVersions } = useAppConfig()
23
22
 
24
- const availableLanguagesAndCountries = () => {
25
- const aux = useLanguageStore.getState().availableLanguages;
26
- let result = aux.map(item => ({ ...item, link: "#" }))
27
- if (availableVersions == null) return result
28
23
 
29
- result = aux.map(item => {
30
- const availableVersion = availableVersions.find(version => version.lang === item.value)
24
+ const normalizeLang = (lang: string) => lang.trim().toLowerCase();
25
+
26
+ const findVersionByLocale = useCallback((locale: string) => {
27
+ if (!availableVersions || availableVersions.length === 0) return undefined;
28
+ const normalizedLocale = normalizeLang(locale);
29
+
30
+ return availableVersions.find((item) => normalizeLang(item.lang) === normalizedLocale)
31
+ ?? availableVersions.find((item) => {
32
+ const langCode = normalizeLang(item.lang).split("-")[0];
33
+ const localeCode = normalizedLocale.split("-")[0];
34
+ return langCode === localeCode;
35
+ });
36
+ }, [availableVersions]);
37
+
38
+ const availableLanguagesAndCountries = useMemo(() => {
39
+ return availableLanguages.map((item) => {
40
+ const availableVersion = findVersionByLocale(item.value);
31
41
  return {
32
42
  ...item,
33
- link: availableVersion?.link ?? "#"
34
- }
35
- })
43
+ link: availableVersion?.link ?? "#",
44
+ };
45
+ });
46
+ }, [availableLanguages, availableVersions]);
36
47
 
37
- return result;
38
- }
39
48
  const [queryLanguage, setContentLanguage] = useQueryState('language', {
40
49
  history: 'push',
41
50
  shallow: false,
@@ -44,34 +53,25 @@ export const ContentLanguageSwitch = ({ contentLangDefault }: Props) => {
44
53
  const changeContentLanguage = (locale: string) => {
45
54
  startTransition(() => {
46
55
  updatePreferences({ language: locale })
47
-
48
56
  if (queryLanguage != null) {
49
57
  setContentLanguage(locale)
58
+ return;
50
59
  }
51
60
 
52
- //TODO en: needs to be fixed as it's not working
53
-
54
61
  const currentPath = window.location.pathname;
55
- const isTopicOrBlogOrDocument = (
56
- currentPath.includes(TOPICS_TYPE_AND_LINK) ||
57
- currentPath.includes(BLOG_TYPE_AND_LINK) ||
58
- currentPath.includes(DOCUMENTS_TYPE_AND_LINK)
59
- )
62
+ const isArticlePage = currentPath.includes("/pages");
63
+ if (!isArticlePage) {
64
+ window.location.reload();
65
+ return;
66
+ };
60
67
 
61
- if (!isTopicOrBlogOrDocument) {
62
- setTimeout(() => window.location.reload(), 5);
68
+ const targetVersion = findVersionByLocale(locale);
69
+ if (targetVersion?.link) {
70
+ window.location.href = targetVersion.link;
63
71
  return;
64
72
  }
65
73
 
66
- if (availableVersions !== null) {
67
- const filteredList = availableVersions.filter((item) => item.lang === locale)
68
-
69
- if (filteredList.length > 0 && filteredList[0]) {
70
- window.location.href = filteredList[0].link as string;
71
- } else {
72
- articleNotAvailableToast()
73
- }
74
- }
74
+ articleNotAvailableToast()
75
75
  });
76
76
  };
77
77
 
@@ -84,7 +84,7 @@ export const ContentLanguageSwitch = ({ contentLangDefault }: Props) => {
84
84
 
85
85
  return (
86
86
  <SharedLanguageSwitch
87
- availableLanguagesAndCountries={availableLanguagesAndCountries()}
87
+ availableLanguagesAndCountries={availableLanguagesAndCountries}
88
88
  changeLanguage={changeContentLanguage}
89
89
  selected={contentLang}
90
90
  />
@@ -5,11 +5,12 @@ import { SharedLanguageSwitch } from "./shared";
5
5
  import { UI_LANG_KEY, UI_LANG_OPTIONS } from "@c-rex/constants";
6
6
  import { getCountryCodeByLang, } from "@c-rex/utils";
7
7
  import { setCookie } from "@c-rex/utils/cookies";
8
- import { useLanguageStore } from "../../stores/language-store";
8
+ import { useLocale } from "next-intl";
9
+ import { useRouter } from "next/navigation";
9
10
 
10
11
  export const UILanguageSwitch: FC = () => {
11
- const uiLang = useLanguageStore((state) => state.uiLang);
12
- const setUiLang = useLanguageStore.getState().setUiLang;
12
+ const uiLang = useLocale().toLowerCase();
13
+ const router = useRouter();
13
14
  const UILanguages = UI_LANG_OPTIONS.map((lang) => {
14
15
  const langCode = lang.split("-")[0] as string;
15
16
  return {
@@ -21,10 +22,8 @@ export const UILanguageSwitch: FC = () => {
21
22
 
22
23
  const setUILanguage = (locale: string) => {
23
24
  startTransition(() => {
24
- setUiLang(locale)
25
25
  setCookie(UI_LANG_KEY, locale, { httpOnly: false });
26
-
27
- //window.location.reload()
26
+ router.refresh();
28
27
  });
29
28
  }
30
29
 
@@ -1,4 +1,4 @@
1
- import { ComponentProps, FC } from "react";
1
+ import { FC } from "react";
2
2
  import Link from "next/link";
3
3
  import { SignInBtn } from "./sign-in-out-btns";
4
4
  import { getServerSession } from "next-auth";
@@ -118,7 +118,11 @@ export const NavBar: FC<NavBarProps> = async ({
118
118
  <div className="flex gap-2">
119
119
  {willShowInput &&
120
120
  <div className="hidden sm:flex flex-1 items-center px-3 border rounded-full h-8 c-rex-search-bar">
121
- <SearchInput autocompleteType={autocompleteType} onSelectPath={onSelectPath} {...props} />
121
+ <SearchInput
122
+ autocompleteType={autocompleteType}
123
+ onSelectPath={onSelectPath}
124
+ {...props}
125
+ />
122
126
  </div>
123
127
  }
124
128