diff --git a/.idea/game-node-web.iml b/.idea/game-node-web.iml index ef216d4..aa07d97 100644 --- a/.idea/game-node-web.iml +++ b/.idea/game-node-web.iml @@ -1,5 +1,6 @@ + diff --git a/docker-compose.yml b/docker-compose.yml index fd248ee..b5312d1 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,11 +13,11 @@ services: NEXT_PUBLIC_SEARCH_URL: ${NEXT_PUBLIC_SEARCH_URL} networks: - - game_node_app + - game_node_app_public networks: - game_node_app: + game_node_app_public: external: true volumes: diff --git a/package.json b/package.json index 9f87894..a88b80b 100755 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mantine/notifications": "^7.1.3", "@mantine/nprogress": "^7.1.3", "@mantine/tiptap": "^7.2.1", + "@socialgouv/matomo-next": "^1.8.1", "@tabler/icons-react": "^2.40.0", "@tanstack/react-query": "^5.8.7", "@tiptap/extension-link": "^2.1.12", diff --git a/src/components/auth/SuperTokensProvider.tsx b/src/components/auth/SuperTokensProvider.tsx index 5244e8f..422c15f 100755 --- a/src/components/auth/SuperTokensProvider.tsx +++ b/src/components/auth/SuperTokensProvider.tsx @@ -23,6 +23,17 @@ export const frontendConfig = () => { ThirdPartyPasswordlessReact.Discord.init(), ], }, + onHandleEvent: (context) => { + // Sends user to /wizard/init on sign-up + if (context.action === "SUCCESS") { + if ( + context.isNewRecipeUser && + context.user.loginMethods.length === 1 + ) { + Router.push("/wizard/init"); + } + } + }, }), SessionReact.init(), ], diff --git a/src/components/general/MatomoTracker.tsx b/src/components/general/MatomoTracker.tsx new file mode 100644 index 0000000..821d93e --- /dev/null +++ b/src/components/general/MatomoTracker.tsx @@ -0,0 +1,70 @@ +import React, { useCallback, useEffect } from "react"; +import { init } from "@socialgouv/matomo-next"; +import { useLocalStorage, useSessionStorage } from "@mantine/hooks"; +import { notifications } from "@mantine/notifications"; +import { Button, Flex, Group, Stack, Text } from "@mantine/core"; +import Link from "next/link"; + +const MATOMO_URL = process.env.NEXT_PUBLIC_MATOMO_URL; +const MATOMO_SITE_ID = process.env.NEXT_PUBLIC_MATOMO_SITE_ID; + +const MatomoTracker = () => { + const [hasAcceptedCookies, setHasAcceptedCookies] = + useLocalStorage({ + key: "cookie-consent", + defaultValue: false, + getInitialValueInEffect: false, + }); + + console.log(hasAcceptedCookies); + + const showCookieConsentNotification = useCallback(() => { + if (hasAcceptedCookies != undefined && hasAcceptedCookies) return; + notifications.show({ + id: "cookie-notice", + title: "Notice", + withCloseButton: false, + autoClose: false, + withBorder: true, + message: ( + + + We use cookies and similar storage methods to keep track + of your session and how you are using our website. We do + not use third-party cookies. + + + + + + + + + + + ), + }); + }, [hasAcceptedCookies, setHasAcceptedCookies]); + useEffect(() => { + if (!hasAcceptedCookies) { + showCookieConsentNotification(); + } else if (MATOMO_URL && MATOMO_SITE_ID) { + init({ + url: MATOMO_URL, + siteId: MATOMO_SITE_ID, + }); + } + }, [hasAcceptedCookies, showCookieConsentNotification]); + + useEffect(() => {}, []); + return <>; +}; + +export default MatomoTracker; diff --git a/src/components/general/NotificationsManager.tsx b/src/components/general/NotificationsManager.tsx index 114f4c2..7d4785e 100644 --- a/src/components/general/NotificationsManager.tsx +++ b/src/components/general/NotificationsManager.tsx @@ -58,35 +58,36 @@ const handleNotifications = async (notificationsEntities: Notification[]) => { const NotificationsManager = () => { const userId = useUserId(); const infiniteNotificationsQuery = useInfiniteAggregatedNotifications(); - useEffect(() => { - let eventSource: ReconnectingEventSource; - if (userId) { - const eventSource = new ReconnectingEventSource(targetSSEUrl, { - withCredentials: true, - max_retry_time: 5000, - }); - eventSource.onopen = (conn) => { - console.log("Connected to notifications SSE: ", conn); - }; - eventSource.onmessage = (message) => { - const notifications: Notification[] = JSON.parse(message.data); - handleNotifications(notifications) - .then(() => { - // Prevents unnecessary calls to notifications endpoint - if (notifications.length > 0) { - infiniteNotificationsQuery.invalidate(); - } - }) - .catch(console.error); - }; - } - - return () => { - if (eventSource) { - eventSource.close(); - } - }; - }, [infiniteNotificationsQuery, userId]); + // TODO: Check if this is causing trouble + // useEffect(() => { + // let eventSource: ReconnectingEventSource; + // if (userId) { + // const eventSource = new ReconnectingEventSource(targetSSEUrl, { + // withCredentials: true, + // max_retry_time: 5000, + // }); + // eventSource.onopen = (conn) => { + // console.log("Connected to notifications SSE: ", conn); + // }; + // eventSource.onmessage = (message) => { + // const notifications: Notification[] = JSON.parse(message.data); + // handleNotifications(notifications) + // .then(() => { + // // Prevents unnecessary calls to notifications endpoint + // if (notifications.length > 0) { + // infiniteNotificationsQuery.invalidate(); + // } + // }) + // .catch(console.error); + // }; + // } + // + // return () => { + // if (eventSource) { + // eventSource.close(); + // } + // }; + // }, [infiniteNotificationsQuery, userId]); return ; }; diff --git a/src/components/general/input/SearchBar/SearchBarWithSelect.tsx b/src/components/general/input/SearchBar/SearchBarWithSelect.tsx index a6d8fb2..468e53f 100644 --- a/src/components/general/input/SearchBar/SearchBarWithSelect.tsx +++ b/src/components/general/input/SearchBar/SearchBarWithSelect.tsx @@ -128,7 +128,6 @@ const SearchBarWithSelect = ({ )} } - mr={"0.5rem"} /> diff --git a/src/components/general/shell/GlobalShellHeader/GlobalShellHeaderNotifications.tsx b/src/components/general/shell/GlobalShellHeader/GlobalShellHeaderNotifications.tsx index 0ed4184..59305b6 100644 --- a/src/components/general/shell/GlobalShellHeader/GlobalShellHeaderNotifications.tsx +++ b/src/components/general/shell/GlobalShellHeader/GlobalShellHeaderNotifications.tsx @@ -4,11 +4,13 @@ import { Box, Button, Center, + Group, Popover, ScrollArea, Skeleton, Stack, Text, + Title, } from "@mantine/core"; import { IconBell, IconBellFilled } from "@tabler/icons-react"; import useOnMobile from "@/components/general/hooks/useOnMobile"; @@ -17,6 +19,7 @@ import { useDisclosure, useIntersection } from "@mantine/hooks"; import { useMutation } from "@tanstack/react-query"; import { Notification, NotificationsService } from "@/wrapper/server"; import { useInfiniteAggregatedNotifications } from "@/components/notifications/hooks/useInfiniteAggregatedNotifications"; +import CenteredLoading from "@/components/general/CenteredLoading"; const GlobalShellHeaderNotifications = () => { const [isPopoverOpened, popoverUtils] = useDisclosure(); @@ -69,12 +72,6 @@ const GlobalShellHeaderNotifications = () => { const hasNextPage = lastElement != undefined && lastElement.pagination.hasNextPage; - const buildNotificationsSkeletons = useCallback(() => { - return new Array(5).fill(0).map((v, i) => { - return ; - }); - }, []); - const isEmpty = !isLoading && (aggregations == undefined || aggregations.length === 0); @@ -111,40 +108,107 @@ const GlobalShellHeaderNotifications = () => { - + + + + {hasUnreadNotifications ? ( + + ) : ( + + )} + Notifications + + { + const unreadNotifications = aggregations + ?.filter((aggregation) => { + return aggregation.notifications.filter( + (notification) => + !notification.isViewed, + ); + }) + .flatMap( + (aggregate) => + aggregate.notifications, + ); + if ( + unreadNotifications && + unreadNotifications.length > 0 + ) { + console.log( + `Marking ${unreadNotifications.length} notifications as read`, + ); + notificationViewMutation.mutate( + unreadNotifications, + ); + } + }} + > + + + + + + {aggregations?.map((aggregatedNotification, index) => { - const key = `${aggregatedNotification.sourceType}-${aggregatedNotification.sourceId}-${aggregatedNotification.sourceType}`; - const hasUnreadAggregationNotifications = - aggregatedNotification.notifications.some( - (notification) => !notification.isViewed, - ); + if ( + aggregatedNotification.notifications.length === + 0 + ) { + return null; + } + + const key = aggregatedNotification.notifications + .map((notif) => notif.id) + .join(","); + return ( - { popoverUtils.close(); if ( notificationViewMutation.isPending || - isFetching || - !hasUnreadAggregationNotifications - ) + isFetching + ) { return; + } notificationViewMutation.mutate( aggregatedNotification.notifications, ); }} - > - - + /> ); })} - {isFetching && buildNotificationsSkeletons()} {isEmpty && No notifications.} + {isFetching && } + {hasNextPage && (
- + + + + } + type={"unordered"} + > + Must be unique + Must have at least 5 characters + + You can only change it after 30 days + + + + + + {withSkipButton && ( + + )} + + + + ); }; diff --git a/src/components/review/hooks/useReviewForUserIdAndGameId.ts b/src/components/review/hooks/useReviewForUserIdAndGameId.ts index 92ff8da..e09d46a 100644 --- a/src/components/review/hooks/useReviewForUserIdAndGameId.ts +++ b/src/components/review/hooks/useReviewForUserIdAndGameId.ts @@ -22,7 +22,10 @@ const UseReviewForUserIdAndGameId = ( gameId, ); // No idea why this happens. - if (typeof review === "string") { + if ( + typeof review === "undefined" || + typeof review === "string" + ) { return null; } return review; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d4f8889..9ea35a6 100755 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -28,6 +28,7 @@ import "@/components/globals.css"; import { theme } from "@/util/theme"; import NotificationsManager from "@/components/general/NotificationsManager"; +import MatomoTracker from "@/components/general/MatomoTracker"; /** * Basic configuration for wrapper services @@ -71,6 +72,7 @@ export default function App({ + diff --git a/src/pages/wizard/init/avatar.tsx b/src/pages/wizard/init/avatar.tsx new file mode 100644 index 0000000..b894c4f --- /dev/null +++ b/src/pages/wizard/init/avatar.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { Box, Button, Container, Flex, Stack, Title } from "@mantine/core"; +import PreferencesAvatarUploader from "@/components/preferences/handlers/PreferencesAvatarUploader"; +import { useRouter } from "next/router"; + +const Avatar = () => { + const router = useRouter(); + const onFinish = () => { + router.push("/search"); + }; + return ( + + + Upload a nice avatar so everyone can recognize you! + + + + + + + + + + + ); +}; + +export default Avatar; diff --git a/src/pages/wizard/init/index.tsx b/src/pages/wizard/init/index.tsx new file mode 100644 index 0000000..76b7849 --- /dev/null +++ b/src/pages/wizard/init/index.tsx @@ -0,0 +1,20 @@ +import React, { useEffect } from "react"; +import CenteredLoading from "@/components/general/CenteredLoading"; +import { useRouter } from "next/router"; +import { Container } from "@mantine/core"; + +const Index = () => { + const router = useRouter(); + useEffect(() => { + if (router.isReady) { + router.push("/wizard/init/username"); + } + }, [router]); + return ( + + + + ); +}; + +export default Index; diff --git a/src/pages/wizard/init/username.tsx b/src/pages/wizard/init/username.tsx new file mode 100644 index 0000000..0262d94 --- /dev/null +++ b/src/pages/wizard/init/username.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Container, Title } from "@mantine/core"; +import PreferencesUsernameChanger from "@/components/preferences/handlers/PreferencesUsernameChanger"; +import { useRouter } from "next/router"; + +const Username = () => { + const router = useRouter(); + return ( + + + How should we call you? + + { + router.push("/wizard/init/avatar"); + }} + onSkip={() => { + router.push("/wizard/init/avatar"); + }} + /> + + ); +}; + +export default Username; diff --git a/src/wrapper/input/search_swagger.json b/src/wrapper/input/search_swagger.json index 68baf93..3e06c47 100644 --- a/src/wrapper/input/search_swagger.json +++ b/src/wrapper/input/search_swagger.json @@ -16,7 +16,7 @@ "version": "1.0" }, "paths": { - "/search": { + "/search/games": { "post": { "description": "Returns a parsed search response from the Manticore engine", "consumes": [ @@ -28,7 +28,7 @@ "tags": [ "search" ], - "summary": "Searches using Manticore engine", + "summary": "Searches for games using Manticore engine", "parameters": [ { "description": "Account ID", @@ -49,6 +49,40 @@ } } } + }, + "/search/users": { + "post": { + "description": "Returns a parsed search response from the Manticore engine", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "summary": "Searches for users using Manticore engine", + "parameters": [ + { + "description": "Account ID", + "name": "query", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserSearchRequestDto" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/schema.UserSearchResponseDto" + } + } + } + } } }, "definitions": { @@ -212,6 +246,60 @@ "type": "string" } } + }, + "schema.UserDto": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "schema.UserSearchRequestDto": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "query": { + "type": "string" + } + } + }, + "schema.UserSearchResponseData": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.UserDto" + } + }, + "profile": { + "type": "object", + "additionalProperties": true + }, + "took": { + "type": "integer" + } + } + }, + "schema.UserSearchResponseDto": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.UserSearchResponseData" + }, + "pagination": { + "$ref": "#/definitions/schema.PaginationInfo" + } + } } } } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cd06ced..0af5995 100644 --- a/yarn.lock +++ b/yarn.lock @@ -464,6 +464,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz#2d4260033e199b3032a08b41348ac10de21c47e9" integrity sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA== +"@socialgouv/matomo-next@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@socialgouv/matomo-next/-/matomo-next-1.8.1.tgz#be686d2fdf5d451bd25f21a0e2effe1173a1196a" + integrity sha512-H6wm5zroqtBcsu1lWG78UQC6X7qj66jQD0o36GRiMSLrABCKP/QwbyLykc7Q0xlxsseJW1IWwSwbBa+9jr6f5Q== + "@swc/helpers@0.5.2": version "0.5.2" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"