diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 73f837f..902bb08 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -19,7 +19,9 @@
"@radix-ui/react-radio-group": "^1.1.2",
"@stitches/react": "^1.2.8",
"@tabler/icons-webfont": "^2.17.0",
+ "@tanstack/react-query": "^4.29.5",
"@trpc/client": "10.20.0",
+ "@trpc/react-query": "^10.23.0",
"@trpc/server": "10.20.0",
"dayjs": "^1.11.7",
"deepmerge": "^4.3.1",
diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx
index ca500e6..a5bf509 100644
--- a/packages/frontend/src/App.tsx
+++ b/packages/frontend/src/App.tsx
@@ -1,10 +1,8 @@
import React, { lazy } from 'react';
import { Route, Routes } from 'react-router-dom';
-
-import { SuspenseView } from './components/primitives/SuspenseView';
-
import { HeaderBar } from '@/components/HeaderBar.js';
+import { SuspenseView } from '@/components/primitives/SuspenseView';
import { useToolsGlobalEffects } from '@/global-effects';
import { token } from '@/misc/token';
@@ -13,6 +11,8 @@ const IndexWelcome = lazy(() => import('@/pages/index.welcome'));
const Settings = lazy(() => import('@/pages/settings'));
const Appearance = lazy(() => import('@/pages/settings/appearance'));
const Account = lazy(() => import('@/pages/settings/account'));
+const AnnouncementsPage = lazy(() => import('@/pages/announcements'));
+const NotFound = lazy(() => import('@/pages/not-found'));
export const App : React.FC = () => {
useToolsGlobalEffects();
@@ -28,6 +28,7 @@ export const App : React.FC = () => {
}/>
Not Found
}/>
+ }/>
}/>
diff --git a/packages/frontend/src/init.tsx b/packages/frontend/src/init.tsx
index 7265740..109da62 100644
--- a/packages/frontend/src/init.tsx
+++ b/packages/frontend/src/init.tsx
@@ -5,6 +5,7 @@ import { BrowserRouter } from 'react-router-dom';
import { App } from '@/App';
import { BackendError } from '@/components/domains/backend-error/BackendError.js';
+import { TRPCProvider } from '@/libs/trpc';
import 'vite/modulepreload-polyfill';
import 'ress';
@@ -20,8 +21,10 @@ const error = (window as any).__misshaialert?.error;
if (el != null) {
createRoot(el).render(error ? () : (
-
-
-
+
+
+
+
+
));
}
diff --git a/packages/frontend/src/libs/trpc.tsx b/packages/frontend/src/libs/trpc.tsx
new file mode 100644
index 0000000..558a334
--- /dev/null
+++ b/packages/frontend/src/libs/trpc.tsx
@@ -0,0 +1,45 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { httpLink } from '@trpc/client';
+import { createTRPCReact } from '@trpc/react-query';
+import { createTRPCJotai } from 'jotai-trpc';
+import React, { PropsWithChildren } from 'react';
+
+import type { AppRouter } from 'tools-backend';
+
+import { LOCALSTORAGE_KEY_TOKEN } from '@/const';
+
+const link = httpLink({
+ url: `${location.origin}/api`,
+ headers() {
+ const token = localStorage[LOCALSTORAGE_KEY_TOKEN];
+ if (!token) return {};
+ return {
+ Authorization: `Bearer ${token}`,
+ };
+ },
+});
+
+const init = {
+ links: [ link ],
+};
+
+export const trpcJotai = createTRPCJotai({
+ links: [ link ],
+});
+
+export const trpc = createTRPCReact();
+
+const providerInit = {
+ client: trpc.createClient(init),
+ queryClient: new QueryClient(),
+};
+
+export const TRPCProvider: React.FC = ({ children }) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/packages/frontend/src/pages/announcements.tsx b/packages/frontend/src/pages/announcements.tsx
new file mode 100644
index 0000000..cff11f5
--- /dev/null
+++ b/packages/frontend/src/pages/announcements.tsx
@@ -0,0 +1,55 @@
+import dayjs from 'dayjs';
+import { useAtomValue } from 'jotai';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import ReactMarkdown from 'react-markdown';
+import { useParams } from 'react-router-dom';
+
+import { PageRoot } from '@/components/PageRoot';
+import { Text } from '@/components/primitives/Text';
+import { styled } from '@/libs/stitches';
+import { trpc } from '@/libs/trpc';
+import { languageAtom } from '@/store/client-settings';
+
+const AnnouncementsPage: React.FC = () => {
+ const { id } = useParams<{id: string}>();
+
+ const [announcement] = trpc.announcements.get.useSuspenseQuery(Number(id));
+
+ const language = useAtomValue(languageAtom);
+
+ const { t } = useTranslation();
+
+ const Section = styled('section', {
+ padding: '$m',
+ 'h1, h2, h3': {
+ marginTop: '$l',
+ marginBottom: '$xs',
+ },
+ 'p, ul': {
+ marginBottom: '$m',
+ },
+ li: {
+ marginLeft: '$l',
+ },
+ });
+
+ return (
+
+
+
+ {announcement.title}{' '}
+
+
+
+ {dayjs(announcement.createdAt).locale(language.split('_')[0]).fromNow()}
+
+
+
+
+ );
+};
+
+export default AnnouncementsPage;
diff --git a/packages/frontend/src/pages/settings/index.tsx b/packages/frontend/src/pages/settings/index.tsx
index deab86c..eade418 100644
--- a/packages/frontend/src/pages/settings/index.tsx
+++ b/packages/frontend/src/pages/settings/index.tsx
@@ -58,22 +58,13 @@ const SettingsPage: React.FC = () => {
href: '/settings/appearance',
iconClassName: 'ti ti-palette',
label: t('appearance') ?? '',
- }, {
- type: 'link',
- href: '/settings/misshai',
- iconClassName: 'ti ti-antenna',
- label: t('missHaiAlert') ?? '',
}, {
type: 'separator',
}, {
type: 'button',
iconClassName: 'ti ti-logout',
label: t('logout') ?? '',
- } , {
- type: 'button',
- iconClassName: 'ti ti-trash',
- label: t('deleteAccount') ?? '',
- } ] as MenuItemWithoutNesting[], [t]);
+ }] as MenuItemWithoutNesting[], [t]);
const location = useLocation();
const navigate = useNavigate();
diff --git a/packages/frontend/src/store/api/account.ts b/packages/frontend/src/store/api/account.ts
index df192d6..45d8538 100644
--- a/packages/frontend/src/store/api/account.ts
+++ b/packages/frontend/src/store/api/account.ts
@@ -1,4 +1,4 @@
-import { trpcJotai } from '@/store/api';
+import { trpcJotai } from '@/libs/trpc';
export const accountAtom = trpcJotai.account.getMyself.atomWithQuery();
diff --git a/packages/frontend/src/store/api/announcements.ts b/packages/frontend/src/store/api/announcements.ts
deleted file mode 100644
index a0edf1a..0000000
--- a/packages/frontend/src/store/api/announcements.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { atom } from 'jotai';
-
-import { trpcJotai } from '@/store/api';
-
-export const announcementListAtom = trpcJotai.announcements.getAll.atomWithQuery();
-
-export const currentAnnouncementIdAtom = atom(0);
-
-export const announcementAtom = trpcJotai.announcements.get.atomWithQuery((get) => get(currentAnnouncementIdAtom));
-
-export const likeAtom = trpcJotai.announcements.like.atomWithMutation();
diff --git a/packages/frontend/src/store/api/index.ts b/packages/frontend/src/store/api/index.ts
deleted file mode 100644
index c0a3c28..0000000
--- a/packages/frontend/src/store/api/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { httpLink } from '@trpc/client';
-import { createTRPCJotai } from 'jotai-trpc';
-
-import type { AppRouter } from 'tools-backend';
-
-import { LOCALSTORAGE_KEY_TOKEN } from '@/const';
-
-const link = httpLink({
- url: `${location.origin}/api`,
- headers() {
- const token = localStorage[LOCALSTORAGE_KEY_TOKEN];
- if (!token) return {};
- return {
- Authorization: `Bearer ${token}`,
- };
- },
-});
-
-export const trpcJotai = createTRPCJotai({
- links: [ link ],
-});
diff --git a/packages/frontend/src/store/api/meta.ts b/packages/frontend/src/store/api/meta.ts
index f171e8a..5a7847e 100644
--- a/packages/frontend/src/store/api/meta.ts
+++ b/packages/frontend/src/store/api/meta.ts
@@ -1,3 +1,3 @@
-import { trpcJotai } from '.';
+import { trpcJotai } from '@/libs/trpc';
export const metaAtom = trpcJotai.meta.get.atomWithQuery();
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a430996..3a72ee7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -176,9 +176,15 @@ importers:
'@tabler/icons-webfont':
specifier: ^2.17.0
version: 2.17.0
+ '@tanstack/react-query':
+ specifier: ^4.29.5
+ version: 4.29.5(react-dom@18.2.0)(react@18.2.0)
'@trpc/client':
specifier: 10.20.0
version: 10.20.0(@trpc/server@10.20.0)
+ '@trpc/react-query':
+ specifier: ^10.23.0
+ version: 10.23.0(@tanstack/react-query@4.29.5)(@trpc/client@10.20.0)(@trpc/server@10.20.0)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server':
specifier: 10.20.0
version: 10.20.0
@@ -3706,6 +3712,28 @@ packages:
resolution: {integrity: sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==}
dev: false
+ /@tanstack/query-core@4.29.5:
+ resolution: {integrity: sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ==}
+ dev: false
+
+ /@tanstack/react-query@4.29.5(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+ dependencies:
+ '@tanstack/query-core': 4.29.5
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ use-sync-external-store: 1.2.0(react@18.2.0)
+ dev: false
+
/@testing-library/dom@8.20.0:
resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==}
engines: {node: '>=12'}
@@ -3742,6 +3770,22 @@ packages:
'@trpc/server': 10.20.0
dev: false
+ /@trpc/react-query@10.23.0(@tanstack/react-query@4.29.5)(@trpc/client@10.20.0)(@trpc/server@10.20.0)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-50vfR3HtOEcjCQMuHSGTFOG6W9WunvcI8msTp84UNAqZ9dz4YdjgBVNn2wxqB5LAQ6AGlcKuzJWjEj3yO56tvw==}
+ peerDependencies:
+ '@tanstack/react-query': ^4.18.0
+ '@trpc/client': 10.23.0
+ '@trpc/server': 10.23.0
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@tanstack/react-query': 4.29.5(react-dom@18.2.0)(react@18.2.0)
+ '@trpc/client': 10.20.0(@trpc/server@10.20.0)
+ '@trpc/server': 10.20.0
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@trpc/server@10.20.0:
resolution: {integrity: sha512-pbrdgw2mO8lWygyZjGLbOq/EoWFWEJvLaxp8JmGsivMIh/IZR5mksPJdywAGQ4TBrbcqssKhuoJ/8OE9mhaveg==}
dev: false
@@ -12617,6 +12661,14 @@ packages:
tslib: 2.5.0
dev: false
+ /use-sync-external-store@1.2.0(react@18.2.0):
+ resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/use@3.1.1:
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
engines: {node: '>=0.10.0'}