diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json index 94fd739..9648a95 100644 --- a/src/i18n/en/translation.json +++ b/src/i18n/en/translation.json @@ -92,6 +92,12 @@ "emailCannotBeEmpty": "Please enter your email address.", "invalidEmail": "Please enter a valid email address." }, - "letsGetStarted": "Let’s get started" + "letsGetStarted": "Let’s get started", + "experimentalFeatures": "Experimental features", + "counters": "Counters", + "yourCounters": "Your Counters", + "createCounter": "Create Counter", + "counterCreated": "Counter created!", + "incrementCounter": "Increment counter" } } diff --git a/src/i18n/ru/translation.json b/src/i18n/ru/translation.json index b9e3120..0f38a97 100644 --- a/src/i18n/ru/translation.json +++ b/src/i18n/ru/translation.json @@ -92,6 +92,12 @@ "emailCannotBeEmpty": "Пожалуйста, введите адрес электронной почты.", "invalidEmail": "Пожалуйста, введите корректный адрес электронной почты." }, - "letsGetStarted": "Давайте начнем" + "letsGetStarted": "Давайте начнем", + "experimentalFeatures": "Экспериментальные функции", + "counters": "Счетчики", + "yourCounters": "Ваши счетчики", + "createCounter": "Создать счетчик", + "counterCreated": "Счетчик создан!", + "incrementCounter": "Увеличить счетчик" } } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c90dba1..a2bdbd9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -8,6 +8,10 @@ import { LoginPage } from './LoginPage' import ProtectedRoute from '~/feature/ProtectedRoute' import { SettingsPage } from '~/pages/SettingsPage.tsx' import { Routes } from '~/shared/constants' +import { Counters } from '~/widget/Counters' +import { AddNewCounter } from '~/widget/Counters/AddNewCounter.tsx' +import { Counter } from '~/widget/Counters/Counter.tsx' +import { ExperimentalFeatures } from '~/widget/ExperimentalFeatures' import { FastLoginSetting } from '~/widget/FastLoginSetting' import { AddNewDevice } from '~/widget/FastLoginSetting/AddNewDevice.tsx' import { LanguageSetting } from '~/widget/LanguageSetting' @@ -41,6 +45,22 @@ const router = createBrowserRouter([ path: Routes.SETTINGS_LANGUAGE, element: , }, + { + path: Routes.SETTINGS_EXPERIMENTAL_FEATURES, + element: , + }, + { + path: Routes.COUNTERS, + element: , + }, + { + path: Routes.COUNTERS_CREATE, + element: , + }, + { + path: Routes.COUNTER.path, + element: , + }, { path: Routes.SETTINGS_FAST_LOGIN, children: [ diff --git a/src/shared/api/counters.service.ts b/src/shared/api/counters.service.ts new file mode 100644 index 0000000..d3656e6 --- /dev/null +++ b/src/shared/api/counters.service.ts @@ -0,0 +1,42 @@ +import { api } from '~/shared/api/api.ts' +import { SuccessResponse } from '~/shared/api/types.ts' + +interface CreateCounterPayload { + name: string + counter: number +} + +interface CounterDTO { + id: string + name: string + counter: number +} + +interface CountersDTO { + data: Array +} + +interface SuccessCounterDTO { + counter: CounterDTO +} + +function createCounterService() { + return { + getCounters: () => { + return api.get>('/protected/counter') + }, + createCounter: (payload: CreateCounterPayload) => { + return api.post('/protected/counter/create', payload) + }, + incrementCounter: (counterId: string) => { + return api.post(`/protected/counter/increment/${counterId}`) + }, + getCounterById: (counterId: string) => { + return api.get>( + `/protected/counter/${counterId}`, + ) + }, + } +} + +export const counterService = createCounterService() diff --git a/src/shared/constants/routes.ts b/src/shared/constants/routes.ts index ebfb16c..ba00722 100644 --- a/src/shared/constants/routes.ts +++ b/src/shared/constants/routes.ts @@ -5,6 +5,14 @@ export const Routes = { SETTINGS_LANGUAGE: '/settings/language', SETTINGS_FAST_LOGIN: '/settings/fast-login', SETTINGS_FAST_LOGIN_CREATE: '/settings/fast-login/create', + SETTINGS_EXPERIMENTAL_FEATURES: '/settings/experimental-features', + COUNTERS: '/settings/experimental-features/counters', + COUNTER: { + path: '/settings/experimental-features/counters/:counterId', + navigateTo: (counterId: string) => + `/settings/experimental-features/counters/${counterId}`, + }, + COUNTERS_CREATE: '/settings/experimental-features/counters/create', CHALLENGE: { path: '/challenge/:challengeId', navigateTo: (id: string, type: string) => `/challenge/${id}?type=${type}`, diff --git a/src/widget/Counters/AddNewCounter.tsx b/src/widget/Counters/AddNewCounter.tsx new file mode 100644 index 0000000..f5a526e --- /dev/null +++ b/src/widget/Counters/AddNewCounter.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react' + +import { useMutation } from '@tanstack/react-query' +import { useNavigate } from 'react-router-dom' + +import { queryClient } from '~/app/App.tsx' +import { useCustomTranslation } from '~/feature/translation' +import { counterService } from '~/shared/api/counters.service.ts' +import { Routes } from '~/shared/constants' +import { useToast } from '~/shared/hooks' +import { Button, Typography } from '~/shared/ui' + +export const AddNewCounter = () => { + const { t } = useCustomTranslation() + const navigate = useNavigate() + const { showSuccessToast } = useToast() + + const [counterName, setCounterName] = useState('') + + const createCounterMut = useMutation({ + mutationFn: counterService.createCounter, + onSuccess: () => { + console.info('[CreateCounter:onSuccess]') + showSuccessToast(t('counterCreated')) + queryClient.invalidateQueries({ + queryKey: ['counter'], + }) + navigate(Routes.COUNTERS, { replace: true }) + }, + onError: (err) => { + console.info(`[CreateCounter:onError]: ${JSON.stringify(err)}`) + }, + }) + + const createCounter = async () => { + createCounterMut.mutate({ name: counterName, counter: 0 }) + } + + const isLoading = createCounterMut.isPending + + return ( +
+
+ setCounterName(e.target.value)} + /> + +
+ +
+
+
+ ) +} diff --git a/src/widget/Counters/Counter.tsx b/src/widget/Counters/Counter.tsx new file mode 100644 index 0000000..912760a --- /dev/null +++ b/src/widget/Counters/Counter.tsx @@ -0,0 +1,59 @@ +import { useMutation } from '@tanstack/react-query' +import { useParams } from 'react-router-dom' + +import { queryClient } from '~/app/App.tsx' +import { useCustomTranslation } from '~/feature/translation' +import { counterService } from '~/shared/api/counters.service.ts' +import { Button, PageLoader, Typography } from '~/shared/ui' +import { useCounterQuery } from '~/widget/Counters/lib/useCounterQuery.ts' + +export const Counter = () => { + const { counterId } = useParams() + const { t } = useCustomTranslation() + + const counterQuery = useCounterQuery(counterId ?? 'undefined') + + const counterMut = useMutation({ + mutationFn: () => counterService.incrementCounter(counterId ?? 'undefined'), + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['counter', counterId], + }) + }, + }) + + const incrementCounter = () => { + counterMut.mutate() + } + + const counterData = counterQuery.data?.data.details.counter + + if (counterQuery.isPending) { + return + } + + return ( +
+
+ + +
+ +
+
+
+ ) +} diff --git a/src/widget/Counters/CounterListItem.tsx b/src/widget/Counters/CounterListItem.tsx new file mode 100644 index 0000000..9a23eaf --- /dev/null +++ b/src/widget/Counters/CounterListItem.tsx @@ -0,0 +1,24 @@ +import { Typography } from '~/shared/ui' + +type Props = { + label: string + onClick: () => void +} + +export const CounterListItem = ({ label, onClick }: Props) => { + return ( +
+
+
+ +
+
+
+ ) +} diff --git a/src/widget/Counters/index.tsx b/src/widget/Counters/index.tsx new file mode 100644 index 0000000..95c9431 --- /dev/null +++ b/src/widget/Counters/index.tsx @@ -0,0 +1,72 @@ +import { useNavigate } from 'react-router-dom' + +import { useCustomTranslation } from '~/feature/translation' +import { Routes } from '~/shared/constants' +import { PlusIcon } from '~/shared/icon' +import { PageLoader, Typography } from '~/shared/ui' +import { CounterListItem } from '~/widget/Counters/CounterListItem.tsx' +import { useCountersQuery } from '~/widget/Counters/lib/useCountersQuery.ts' + +export const Counters = () => { + const { t } = useCustomTranslation() + const navigate = useNavigate() + + const counterQuery = useCountersQuery() + + const counters = counterQuery.data?.data.details.data ?? [] + + if (counterQuery.isPending) { + return + } + + return ( +
+
+ + +
+ + +
+ {counters.length === 0 && ( + + )} + +
+ {counters?.length > 0 && + counters.map((el) => { + return ( + { + return navigate(Routes.COUNTER.navigateTo(el.id)) + }} + /> + ) + })} +
+
+
+
+
+ ) +} diff --git a/src/widget/Counters/lib/useCounterQuery.ts b/src/widget/Counters/lib/useCounterQuery.ts new file mode 100644 index 0000000..dad0435 --- /dev/null +++ b/src/widget/Counters/lib/useCounterQuery.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query' + +import { counterService } from '~/shared/api/counters.service.ts' + +export const useCounterQuery = (counterId: string) => { + return useQuery({ + queryKey: ['counter', counterId], + queryFn: () => counterService.getCounterById(counterId), + }) +} diff --git a/src/widget/Counters/lib/useCountersQuery.ts b/src/widget/Counters/lib/useCountersQuery.ts new file mode 100644 index 0000000..c918c58 --- /dev/null +++ b/src/widget/Counters/lib/useCountersQuery.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query' + +import { counterService } from '~/shared/api/counters.service.ts' + +export const useCountersQuery = () => { + return useQuery({ + queryKey: ['counter'], + queryFn: counterService.getCounters, + }) +} diff --git a/src/widget/ExperimentalFeatures/index.tsx b/src/widget/ExperimentalFeatures/index.tsx new file mode 100644 index 0000000..51d2800 --- /dev/null +++ b/src/widget/ExperimentalFeatures/index.tsx @@ -0,0 +1,22 @@ +import { useNavigate } from 'react-router-dom' + +import { useCustomTranslation } from '~/feature/translation' +import { Routes } from '~/shared/constants' +import { SettingItem } from '~/widget/SettingList/SettingItem.tsx' + +export const ExperimentalFeatures = () => { + const { t } = useCustomTranslation() + const navigate = useNavigate() + + return ( +
+
+ navigate(Routes.COUNTERS)} + isDisabled={false} + /> +
+
+ ) +} diff --git a/src/widget/FastLoginSetting/index.tsx b/src/widget/FastLoginSetting/index.tsx index e545be5..34085e5 100644 --- a/src/widget/FastLoginSetting/index.tsx +++ b/src/widget/FastLoginSetting/index.tsx @@ -42,7 +42,10 @@ export const FastLoginSetting = () => { - +
@@ -78,7 +81,8 @@ export const FastLoginSetting = () => { devices.map((device) => { const name = device.deviceName ?? 'N/A' - const isLoading = passkeyRemoveMutation.variables === device.id + const isLoading = + passkeyRemoveMutation.variables === device.id return ( { onClick={() => navigate(Routes.SETTINGS_FAST_LOGIN)} isDisabled={isPending} /> + navigate(Routes.SETTINGS_EXPERIMENTAL_FEATURES)} + isDisabled={isPending} + />