diff --git a/.env.example b/.env.example index 8b6154c..7e23620 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,8 @@ # frontend VITE_API_URL= +VITE_CLIENT_URL= VITE_REDIRECT_URL= +VITE_IDENTEAPOT_SALT= # backend POSTGRES_USER= diff --git a/.github/workflows/deploy-frontend.yaml b/.github/workflows/deploy-frontend.yaml index 293066d..1fc432b 100644 --- a/.github/workflows/deploy-frontend.yaml +++ b/.github/workflows/deploy-frontend.yaml @@ -26,6 +26,7 @@ jobs: VITE_API_URL: ${{ secrets.VITE_API_URL }} VITE_CLIENT_URL: ${{ secrets.VITE_CLIENT_URL }} VITE_REDIRECT_URL: ${{ secrets.VITE_REDIRECT_URL }} + VITE_IDENTEAPOT_SALT: ${{ secrets.VITE_IDENTEAPOT_SALT }} run: nix develop --command bash -c "pnpm i && pnpm run build" # Deploy Frontend to Cloudflare Workers - name: Deploy Frontend to Cloudflare Workers diff --git a/.github/workflows/preview-frontend.yaml b/.github/workflows/preview-frontend.yaml index 84c2c80..35a80c9 100644 --- a/.github/workflows/preview-frontend.yaml +++ b/.github/workflows/preview-frontend.yaml @@ -28,6 +28,7 @@ jobs: VITE_API_URL: ${{ secrets.VITE_API_URL }} VITE_CLIENT_URL: ${{ secrets.VITE_CLIENT_URL }} VITE_REDIRECT_URL: ${{ secrets.VITE_REDIRECT_URL }} + VITE_IDENTEAPOT_SALT: ${{ secrets.VITE_IDENTEAPOT_SALT }} run: nix develop --command bash -c "pnpm i && pnpm run frontend:build" # Deploy Preview to Cloudflare Workers # Ref: https://zenn.dev/lalalatotoro/articles/b8fa632b53d332 diff --git a/AI.md b/AI.md index f97cbd2..9433ef2 100644 --- a/AI.md +++ b/AI.md @@ -93,39 +93,3 @@ groupのメンバー 貸し借りの登録ボタン {groupId}の履歴一覧 ※各履歴には、削除ボタンを付ける (削除すると、削除したメンバーがその項目に表示されると共に、その項目の文字列に訂正線が引かれる。「削除されました」の文字も追加する) - - - -- メモ - -```ts -import { useEffect, type FC } from 'react'; -// @tanstack/react-query -import { $api } from '../../api/fetchClient'; -// react-router -import { useNavigate } from 'react-router'; - -const SessionCheck: FC = () => { - // sessionのチェック - const navigate = useNavigate(); - const sessionCheckMutation = $api.useMutation('get', '/api/session', { - onError: () => { - navigate('/login', { replace: true }); - }, - }); - useEffect(() => { - sessionCheckMutation.mutate({ credentials: 'include' }); - }, []); - - return ( - <> -

Pay Crew2

- {sessionCheckMutation.isPending &&

セッションの確認中...

} - {sessionCheckMutation.isError &&

セッションが無効です。ログインページへリダイレクトします...。

} - {sessionCheckMutation.isSuccess &&

各コンテンツ

;}; - - ); -} - -export default SessionCheck; -``` \ No newline at end of file diff --git a/package.json b/package.json index 3d35c5e..e26fede 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@hookform/resolvers": "catalog:frontend", "@scalar/hono-api-reference": "catalog:backend", "@tanstack/react-query": "^5.90.5", + "@teapotlabs/identeapots": "catalog:frontend", "@types/pg": "catalog:backend", "@types/react": "catalog:frontend", "@types/react-dom": "catalog:frontend", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 233ac92..df801cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,6 +81,9 @@ catalogs: '@tanstack/react-query': specifier: ^5.90.5 version: 5.90.5 + '@teapotlabs/identeapots': + specifier: ^1.1.1 + version: 1.1.1 '@types/react': specifier: ^19.2.2 version: 19.2.2 @@ -191,6 +194,9 @@ importers: '@tanstack/react-query': specifier: ^5.90.5 version: 5.90.5(react@19.2.0) + '@teapotlabs/identeapots': + specifier: catalog:frontend + version: 1.1.1 '@types/pg': specifier: catalog:backend version: 8.15.6 @@ -397,6 +403,9 @@ importers: '@hookform/resolvers': specifier: catalog:frontend version: 5.2.2(react-hook-form@7.65.0(react@19.2.0)) + '@teapotlabs/identeapots': + specifier: catalog:frontend + version: 1.1.1 better-auth: specifier: 'catalog:' version: 1.4.10(drizzle-kit@0.31.6)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20251014.0)(@types/pg@8.15.6)(kysely@0.28.9)(pg@8.16.3))(pg@8.16.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vitest@4.0.5) @@ -1834,6 +1843,9 @@ packages: peerDependencies: react: ^18 || ^19 + '@teapotlabs/identeapots@1.1.1': + resolution: {integrity: sha512-QJTUPvlECFiPppPOQJkVPEw2l4t5HuQtGnp2ykhn1IfTBrdY0n3bF1W6ZyjgaARPf4l1N1j/G/NrkE9aYjqflg==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -5384,6 +5396,8 @@ snapshots: '@tanstack/query-core': 5.90.5 react: 19.2.0 + '@teapotlabs/identeapots@1.1.1': {} + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 36c6983..0ef25fd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -33,6 +33,7 @@ catalogs: '@hookform/error-message': ^2.0.1 '@hookform/resolvers': ^5.2.2 '@tanstack/react-query': ^5.90.5 + "@teapotlabs/identeapots": ^1.1.1 '@types/react': ^19.2.2 '@types/react-dom': ^19.2.2 '@vitejs/plugin-react-swc': ^4.2.0 diff --git a/products/frontend/.env.example b/products/frontend/.env.example index 0419fb2..ed64439 100644 --- a/products/frontend/.env.example +++ b/products/frontend/.env.example @@ -1,3 +1,4 @@ VITE_API_URL= VITE_CLIENT_URL= VITE_REDIRECT_URL= +VITE_IDENTEAPOT_SALT= diff --git a/products/frontend/package.json b/products/frontend/package.json index 467c3ec..0453dfa 100644 --- a/products/frontend/package.json +++ b/products/frontend/package.json @@ -15,6 +15,7 @@ "ui": "vitest run --ui --typecheck" }, "dependencies": { + "@teapotlabs/identeapots": "catalog:frontend", "@hookform/error-message": "catalog:frontend", "@hookform/resolvers": "catalog:frontend", "better-auth": "catalog:", diff --git a/products/frontend/src/api/openapi.d.ts b/products/frontend/src/api/openapi.d.ts index f4a95ef..9d9c030 100644 --- a/products/frontend/src/api/openapi.d.ts +++ b/products/frontend/src/api/openapi.d.ts @@ -30,6 +30,8 @@ export interface paths { "application/json": { user_id: string; user_name: string; + /** Format: uri */ + user_avatar_url: string | null; }; }; }; diff --git a/products/frontend/src/routes/Root/components/Borrow/index.module.css b/products/frontend/src/routes/Root/components/Borrow/index.module.css index 48435ff..e6a7441 100644 --- a/products/frontend/src/routes/Root/components/Borrow/index.module.css +++ b/products/frontend/src/routes/Root/components/Borrow/index.module.css @@ -5,6 +5,7 @@ .ul { list-style: none; margin: 0 auto; + width: fit-content; max-width: 900px; } diff --git a/products/frontend/src/routes/Root/components/Lent/index.module.css b/products/frontend/src/routes/Root/components/Lent/index.module.css index d8b15f1..86b9ae4 100644 --- a/products/frontend/src/routes/Root/components/Lent/index.module.css +++ b/products/frontend/src/routes/Root/components/Lent/index.module.css @@ -5,6 +5,7 @@ .ul { list-style: none; margin: 0 auto; + width: fit-content; max-width: 900px; } diff --git a/products/frontend/src/routes/Share/components/Header/index.module.css b/products/frontend/src/routes/Share/components/Header/index.module.css index fb7fdb6..a7eaa71 100644 --- a/products/frontend/src/routes/Share/components/Header/index.module.css +++ b/products/frontend/src/routes/Share/components/Header/index.module.css @@ -15,11 +15,18 @@ .nav { display: flex; + align-items: center; gap: var(--space-base); } -@media screen and (max-width: 499px) { - .nav { +.identicon { + width: 40px; + height: 40px; + border-radius: 50%; +} + +@media screen and (max-width: 599px) { + .navLink { display: none; } } diff --git a/products/frontend/src/routes/Share/components/Header/index.tsx b/products/frontend/src/routes/Share/components/Header/index.tsx index 150cf77..dd83152 100644 --- a/products/frontend/src/routes/Share/components/Header/index.tsx +++ b/products/frontend/src/routes/Share/components/Header/index.tsx @@ -1,19 +1,34 @@ -import { useEffect, type FC } from 'react'; +import { useEffect, useState, type FC } from 'react'; // @tanstack/react-query import { $api } from '../../../../api/fetchClient'; // react-router import { Link, NavLink, useNavigate } from 'react-router'; +// icons +import { generateIdenteapot } from '@teapotlabs/identeapots'; +// toast +import { toast } from 'react-hot-toast'; // css import styles from './index.module.css'; const Header: FC = () => { + const [identicon, setIdenticon] = useState(''); // sessionのチェック const navigate = useNavigate(); const sessionCheckMutation = $api.useMutation('get', '/api/session', { + onSuccess: async (data) => { + try { + const salt = import.meta.env.VITE_IDENTEAPOT_SALT satisfies string; + const icon = await generateIdenteapot(data.user_id, salt); + setIdenticon(icon); + } catch { + toast.error('アイコンの生成に失敗しました。'); + } + }, onError: () => { navigate('/login', { replace: true }); }, }); + useEffect(() => { sessionCheckMutation.mutate({ credentials: 'include' }); }, []); @@ -24,8 +39,17 @@ const Header: FC = () => { Pay Crew2 ); diff --git a/setup/src/functions.ts b/setup/src/functions.ts index e2fad37..706aec5 100644 --- a/setup/src/functions.ts +++ b/setup/src/functions.ts @@ -11,6 +11,7 @@ export const dotenvLoader = (): EnvConfig => { const viteApiUrl = caster.castString(process.env.VITE_API_URL); const viteClientUrl = caster.castString(process.env.VITE_CLIENT_URL); const viteRedirectUrl = caster.castString(process.env.VITE_REDIRECT_URL); + const viteIdenteapotSalt = caster.castString(process.env.VITE_IDENTEAPOT_SALT); const postgresUser = caster.castString(process.env.POSTGRES_USER); const postgresPassword = caster.castString(process.env.POSTGRES_PASSWORD); const postgresDb = caster.castString(process.env.POSTGRES_DB); @@ -33,6 +34,7 @@ export const dotenvLoader = (): EnvConfig => { VITE_API_URL: viteApiUrl, VITE_CLIENT_URL: viteClientUrl, VITE_REDIRECT_URL: viteRedirectUrl, + VITE_IDENTEAPOT_SALT: viteIdenteapotSalt ? '*****' : '', POSTGRES_USER: postgresUser, POSTGRES_PASSWORD: postgresPassword ? '*****' : '', POSTGRES_DB: postgresDb, @@ -50,6 +52,7 @@ export const dotenvLoader = (): EnvConfig => { viteApiUrl, viteClientUrl, viteRedirectUrl, + viteIdenteapotSalt, }; const backendConfig: BackendConfig = { postgresUser, diff --git a/setup/src/index.ts b/setup/src/index.ts index aa18a09..5191163 100644 --- a/setup/src/index.ts +++ b/setup/src/index.ts @@ -19,6 +19,7 @@ const main = () => { const frontendDotenvData = `VITE_API_URL=${envConfig.frontendConfig.viteApiUrl} VITE_CLIENT_URL=${envConfig.frontendConfig.viteClientUrl} VITE_REDIRECT_URL=${envConfig.frontendConfig.viteRedirectUrl} +VITE_IDENTEAPOT_SALT=${envConfig.frontendConfig.viteIdenteapotSalt} `; fileWriter('./products/frontend/.env', frontendDotenvData); } diff --git a/setup/src/types.ts b/setup/src/types.ts index 909f320..36e97e3 100644 --- a/setup/src/types.ts +++ b/setup/src/types.ts @@ -7,6 +7,7 @@ export type FrontendConfig = { viteApiUrl: string; viteClientUrl: string; viteRedirectUrl: string; + viteIdenteapotSalt: string; }; export type BackendConfig = {