From 9226c9a5bd34d12a2c550032b7e644f4b03d90a6 Mon Sep 17 00:00:00 2001 From: usounds Date: Fri, 15 Aug 2025 18:11:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix=20:=20=E3=82=BB=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E5=88=87=E3=82=8C=E5=BE=8C=E3=81=AB=E5=89=8D?= =?UTF-8?q?=E5=9B=9E=E3=83=AD=E3=82=B0=E3=82=A4=E3=83=B3=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=83=8F=E3=83=B3=E3=83=89=E3=83=AB=E3=81=8C=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.gitignore | 3 ++- frontend/src/components/login/Login.tsx | 8 ++++++-- frontend/src/locales/en.ts | 1 + frontend/src/locales/ja.ts | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/.gitignore b/frontend/.gitignore index 9ee0d26..6f9c7cf 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -48,4 +48,5 @@ next-env.d.ts create_app.json .open-next -.wrangler/ \ No newline at end of file +.wrangler/ +.dev.vars \ No newline at end of file diff --git a/frontend/src/components/login/Login.tsx b/frontend/src/components/login/Login.tsx index 45f1856..3fb64af 100644 --- a/frontend/src/components/login/Login.tsx +++ b/frontend/src/components/login/Login.tsx @@ -9,7 +9,7 @@ import { TextInput } from '@mantine/core'; import { notifications } from '@mantine/notifications'; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { HiX } from "react-icons/hi"; import LanguageSelect from "../LanguageSelect"; @@ -168,12 +168,16 @@ export function AuthenticationTitle() { }) } + useEffect(() => { + if(!handle) setHandle(localStorage.getItem('oauth.handle')||''); + }, []); + return ( Date: Fri, 15 Aug 2025 18:12:20 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix=20:=20R2=E3=82=92=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/open-next.config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/open-next.config.ts b/frontend/open-next.config.ts index 100253a..59b156c 100644 --- a/frontend/open-next.config.ts +++ b/frontend/open-next.config.ts @@ -1,6 +1,4 @@ import { defineCloudflareConfig } from '@opennextjs/cloudflare' -import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache"; export default defineCloudflareConfig({ - incrementalCache: r2IncrementalCache, }); \ No newline at end of file From 7b86b2992e10e5166c3de2b481eaff6cc24dc7c0 Mon Sep 17 00:00:00 2001 From: usounds Date: Fri, 15 Aug 2025 18:14:30 +0900 Subject: [PATCH 3/5] fix : eslint --- frontend/src/components/login/Login.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/login/Login.tsx b/frontend/src/components/login/Login.tsx index 3fb64af..6424b2f 100644 --- a/frontend/src/components/login/Login.tsx +++ b/frontend/src/components/login/Login.tsx @@ -168,10 +168,9 @@ export function AuthenticationTitle() { }) } - useEffect(() => { - if(!handle) setHandle(localStorage.getItem('oauth.handle')||''); - }, []); - + useEffect(() => { + if (!handle) setHandle(localStorage.getItem('oauth.handle') || ''); + }, [handle]); return ( From d62ce42205c6354049c204c4bd015d253b2b9aef Mon Sep 17 00:00:00 2001 From: usounds Date: Sat, 16 Aug 2025 13:09:32 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix=20:=20Locale=E3=82=92=E5=85=83=E3=81=AB?= =?UTF-8?q?=E6=88=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 65 ++++++++++--------- frontend/package.json | 2 +- .../src/components/AvatorDropdownMenu.tsx | 1 + frontend/src/components/DropdownMenu.tsx | 1 + frontend/src/components/LanguageToggle.tsx | 2 +- frontend/src/components/Reaction.tsx | 1 + frontend/src/components/ReplyList.tsx | 1 + frontend/src/components/URLCopyButton.tsx | 1 + frontend/src/state/Locale.ts | 45 ++++++------- 9 files changed, 60 insertions(+), 59 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ca78be6..147bc37 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -561,9 +561,9 @@ } }, "node_modules/@aws-sdk/client-dynamodb": { - "version": "3.864.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.864.0.tgz", - "integrity": "sha512-Z+8qCU8A8RKI/vaMZx3bUG3ZIvEBZzYRIEZA06Qx0QHttkDV/i2Q31Bs98Za/UV0aMXJYsgpHCvXeRgR7r8hYA==", + "version": "3.868.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.868.0.tgz", + "integrity": "sha512-a2uKLRplH3vTHgTHr+MaZ1YH0Sy0yVHK9rhM/drktxgXehMf4p7dZtt783c9SEupZvo46LxXryvwifoUdL4nRg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -7698,9 +7698,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", - "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -9364,9 +9364,9 @@ } }, "node_modules/@noble/curves": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.6.tgz", - "integrity": "sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" @@ -11150,9 +11150,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.10.tgz", - "integrity": "sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==", + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -12386,9 +12386,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001734", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz", - "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "funding": [ { "type": "opencollective", @@ -12558,9 +12558,9 @@ } }, "node_modules/cloudflare/node_modules/@types/node": { - "version": "18.19.122", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.122.tgz", - "integrity": "sha512-yzegtT82dwTNEe/9y+CM8cgb42WrUfMMCg2QqSddzO1J6uPmBD7qKCZ7dOHZP2Yrpm/kb0eqdNMn2MUyEiqBmA==", + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -13022,9 +13022,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.200", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz", - "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==", + "version": "1.5.203", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", + "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==", "dev": true, "license": "ISC" }, @@ -14031,10 +14031,13 @@ } }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -15607,9 +15610,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250813.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250813.0.tgz", - "integrity": "sha512-PsAGaNpdKXZvnaOvw2dpWWszhHtOX5ZwHLf7fEtW/g6QBSzdS707vFFbBBaew63hcpgo33CbuXZc0Z0P/5jNWQ==", + "version": "4.20250813.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250813.1.tgz", + "integrity": "sha512-6PyXwR4pZmH9ukO0jR5LmhlFVMktsVVGVcUjD9Lpev5QwnqjTRPEv73cnXCe0+oTbIm5TYnvXsAklaWxQuxstA==", "license": "MIT", "peer": true, "dependencies": { @@ -19127,9 +19130,9 @@ } }, "node_modules/wrangler": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.29.1.tgz", - "integrity": "sha512-PAGFQ6SS3fbpu0wrc4zO9wHYKWqIo7KmoAe66LGS3QdP3318O+dF1jL4d/kwNaj9Gh7HYQeGnTjeihqnhp9YWQ==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.30.0.tgz", + "integrity": "sha512-NXJUObuXxgG8/ChQ4yXkWLmDQ5ZcO98gyq1yFKYVntJ884C0IpDQrVnAv2RA0ZEz5eB8zal+4OKnr26P3N7ItA==", "license": "MIT OR Apache-2.0", "peer": true, "dependencies": { @@ -19137,7 +19140,7 @@ "@cloudflare/unenv-preset": "2.6.1", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250813.0", + "miniflare": "4.20250813.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.19", "workerd": "1.20250813.0" diff --git a/frontend/package.json b/frontend/package.json index cdb9d85..881e2e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,7 @@ "gen-lex": "lex-cli generate-main ../lexicon/uk/skyblur/*/*.json -o ./src/lexicon/UkSkyblur.ts", "build": "next build", "start": "next start", - "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview", + "preview": "opennextjs-cloudflare build && wrangler dev --ip 192.168.1.130 --port 4500", "deploy:preview": "opennextjs-cloudflare build && opennextjs-cloudflare deploy --env preview", "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy --env production", "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts", diff --git a/frontend/src/components/AvatorDropdownMenu.tsx b/frontend/src/components/AvatorDropdownMenu.tsx index e014a4d..ebffe82 100644 --- a/frontend/src/components/AvatorDropdownMenu.tsx +++ b/frontend/src/components/AvatorDropdownMenu.tsx @@ -1,3 +1,4 @@ +"use client" import { useXrpcAgentStore } from '@/state/XrpcAgent'; import { OAuthUserAgent, deleteStoredSession, getSession } from '@atcute/oauth-browser-client'; import { Avatar, Menu } from '@mantine/core'; diff --git a/frontend/src/components/DropdownMenu.tsx b/frontend/src/components/DropdownMenu.tsx index 0315d1b..9368e89 100644 --- a/frontend/src/components/DropdownMenu.tsx +++ b/frontend/src/components/DropdownMenu.tsx @@ -1,3 +1,4 @@ +"use client" import { useLocaleStore } from "@/state/Locale"; import { PostListItem, SKYBLUR_POST_COLLECTION, VISIBILITY_PASSWORD, VISIBILITY_PUBLIC } from "@/types/types"; import { Client } from '@atcute/client'; diff --git a/frontend/src/components/LanguageToggle.tsx b/frontend/src/components/LanguageToggle.tsx index 85c78ea..2ba57c9 100644 --- a/frontend/src/components/LanguageToggle.tsx +++ b/frontend/src/components/LanguageToggle.tsx @@ -1,4 +1,4 @@ - +"use client" import { Locales, useLocaleStore } from "@/state/Locale"; import { ActionIcon } from '@mantine/core'; import React from "react"; diff --git a/frontend/src/components/Reaction.tsx b/frontend/src/components/Reaction.tsx index 1c55bc0..ed0a1d6 100644 --- a/frontend/src/components/Reaction.tsx +++ b/frontend/src/components/Reaction.tsx @@ -1,3 +1,4 @@ +"use client" import { transformUrl } from "@/logic/HandleBluesky"; import { useLocaleStore } from "@/state/Locale"; import { useViewerStore } from "@/state/Viewer"; diff --git a/frontend/src/components/ReplyList.tsx b/frontend/src/components/ReplyList.tsx index eaf4a39..9e69e6f 100644 --- a/frontend/src/components/ReplyList.tsx +++ b/frontend/src/components/ReplyList.tsx @@ -1,3 +1,4 @@ +"use client" import { formatDateToLocale } from "@/logic/LocaledDatetime"; import { useXrpcAgentStore } from "@/state/XrpcAgent"; import { useLocaleStore } from "@/state/Locale"; diff --git a/frontend/src/components/URLCopyButton.tsx b/frontend/src/components/URLCopyButton.tsx index 92c4c64..17c5591 100644 --- a/frontend/src/components/URLCopyButton.tsx +++ b/frontend/src/components/URLCopyButton.tsx @@ -1,3 +1,4 @@ +"use client" import { useLocaleStore } from "@/state/Locale"; import Link from 'next/link'; import { Button } from '@mantine/core'; diff --git a/frontend/src/state/Locale.ts b/frontend/src/state/Locale.ts index 1137b5a..85df5d9 100644 --- a/frontend/src/state/Locale.ts +++ b/frontend/src/state/Locale.ts @@ -1,6 +1,5 @@ import en from "@/locales/en"; import ja from "@/locales/ja"; -//import kr from "@/locales/kr"; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; @@ -9,17 +8,16 @@ type LocaleInfo = { label: string; }; -export type Locales = 'en' | 'ja' +export type Locales = 'en' | 'ja'; type LocaleData = typeof en; export const localeDataMap: Record = { ja: { data: ja, label: 'Japanese' }, en: { data: en, label: 'English' }, -// kr: { data: kr, label: 'Korean' }, }; type State = { - locale: Locales|null; + locale: Locales; localeData: LocaleData; }; @@ -27,32 +25,27 @@ type Action = { setLocale: (locale: Locales) => void; }; -const getUserLocale = (): Locales => { - if (typeof window !== "undefined" && typeof navigator !== "undefined") { - const userLanguages = navigator.language; - if (userLanguages.startsWith('ja')) return 'ja'; - // if (userLanguages.startsWith('kr')) return 'kr'; - return 'en'; - } - return 'en'; -}; - export const useLocaleStore = create()( persist( - (set) => { - const initialLocale = getUserLocale(); - return { - locale: null, - localeData: localeDataMap[initialLocale].data, - - setLocale: (locale) => { - set({ locale, localeData: localeDataMap[locale].data }); - }, - }; - }, + (set) => ({ + // locale は初期化時に undefined(localStorage に任せる) + locale: 'ja', // ここはダミー値。rehydrate で localStorage の値に置き換わる + localeData: localeDataMap['ja'].data, + + setLocale: (locale) => set({ locale, localeData: localeDataMap[locale].data }), + }), { name: 'zustand.preference.locale', - partialize: (state) => ({ locale: state.locale }) + partialize: (state) => ({ locale: state.locale }), + + // rehydrate 直後に localeData を更新 + onRehydrateStorage: () => (state) => { + if (state) { + useLocaleStore.setState({ + localeData: localeDataMap[state.locale].data, + }); + } + }, } ) ); From 1d166747a2c48e036916d83e49ea02116212b8b3 Mon Sep 17 00:00:00 2001 From: usounds Date: Sat, 16 Aug 2025 14:54:19 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat=20:=20=E8=A8=80=E8=AA=9E=E5=88=87?= =?UTF-8?q?=E3=82=8A=E6=9B=BF=E3=81=88=E6=A9=9F=E8=83=BD=E3=81=AE=E5=86=8D?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 2 +- frontend/src/app/not-found.tsx | 1 + frontend/src/app/settings/main.tsx | 429 ++++++++++++++++++ frontend/src/app/settings/page.tsx | 425 +---------------- .../src/components/AvatorDropdownMenu.tsx | 5 +- frontend/src/components/DynamicHeader.tsx | 33 +- frontend/src/components/Header.tsx | 10 +- frontend/src/components/LanguageSelect.tsx | 25 +- frontend/src/components/LanguageToggle.tsx | 11 + frontend/src/state/Locale.ts | 25 +- 10 files changed, 521 insertions(+), 445 deletions(-) create mode 100644 frontend/src/app/settings/main.tsx diff --git a/frontend/package.json b/frontend/package.json index 881e2e4..35a489e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,7 @@ "gen-lex": "lex-cli generate-main ../lexicon/uk/skyblur/*/*.json -o ./src/lexicon/UkSkyblur.ts", "build": "next build", "start": "next start", - "preview": "opennextjs-cloudflare build && wrangler dev --ip 192.168.1.130 --port 4500", + "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview --ip 192.168.1.130 --port 4500", "deploy:preview": "opennextjs-cloudflare build && opennextjs-cloudflare deploy --env preview", "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy --env production", "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts", diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx index ec5e4e7..8a18042 100644 --- a/frontend/src/app/not-found.tsx +++ b/frontend/src/app/not-found.tsx @@ -1,3 +1,4 @@ +'use client'; export default function NotFound() { return ( <> diff --git a/frontend/src/app/settings/main.tsx b/frontend/src/app/settings/main.tsx new file mode 100644 index 0000000..03f87a6 --- /dev/null +++ b/frontend/src/app/settings/main.tsx @@ -0,0 +1,429 @@ +"use client" +import Loading from "@/components/Loading"; +import URLCopyButton from "@/components/URLCopyButton"; +import { getPreference } from "@/logic/HandleBluesky"; +import { useLocaleStore } from "@/state/Locale"; +import { useXrpcAgentStore } from "@/state/XrpcAgent"; +import { ActorIdentifier, ResourceUri } from '@atcute/lexicons/syntax'; +import { Button, LoadingOverlay, Switch, Textarea, TextInput } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import Image from "next/image"; +import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useEffect, useState } from "react"; +import { HiCheck } from "react-icons/hi"; +import { MdArrowBack } from "react-icons/md"; + +export default function Settings() { + const agent = useXrpcAgentStore((state) => state.agent); + const userProf = useXrpcAgentStore((state) => state.userProf); + const did = useXrpcAgentStore((state) => state.did); + const [isLoading, setIsLoading] = useState(true) + const [isUseMyPage, setIsUseMyPage] = useState(false) + const [preferenceMode, setPreferenceMode] = useState<"create" | "update">('create') + const [myPageDescription, setMyPageDescription] = useState('') + const [isCustomFeed, setIsCustomFeed] = useState(false) + const [isSave, setIsSave] = useState(false) + const locale = useLocaleStore((state) => state.localeData); + const [feedName, setFeedName] = useState(locale.Pref_CustomFeedDefaltName?.replace('{1}', did || '') || '') + const [feedDescription, setFeedDescription] = useState('') + const [feedUpdateMessage, setFeedUpdateMessage] = useState('') + const [feedAvatarImg, setFeedAvatarImg] = useState('') + const [feedAvatar, setFeedAvatar] = useState() + const router = useRouter(); + const [isMounted, setIsMounted] = useState(false); + const searchParams = useSearchParams(); + const lang = searchParams.get('lang') || 'ja'; + + useEffect(() => { + if (!agent || !did) { + router.push('/') + return + } + setIsMounted(true); + setIsLoading(true) + const fetchData = async () => { + try { + + const value = await getPreference(agent, did) + if (value === null) { + setPreferenceMode('create') + setIsLoading(false) + return + } + setPreferenceMode('update') + if (value.myPage.isUseMyPage) setIsUseMyPage(true) + setMyPageDescription(value.myPage.description || '') + + } catch (e) { + console.error(e) + } + + const feedATUri = 'at://' + did + '/app.bsky.feed.generator/skyblurCustomFeed' + const result = await agent.get('app.bsky.feed.getFeedGenerator', { + params: { + feed: feedATUri as ResourceUri, + }, + }); + + if (!result.ok) { + setFeedName(locale.Pref_CustomFeedDefaltName?.replace('{1}', userProf?.displayName || '').slice(0, 24) || '') + setFeedDescription('') + } else { + setFeedName(result.data.view.displayName) + setFeedDescription(result.data.view.description || '') + setFeedAvatarImg(result.data.view.avatar || '') + setIsCustomFeed(true) + + } + setIsLoading(false) + + }; + + fetchData(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [did]); + + + const changeFeedAvatar = (e: React.ChangeEvent) => { + if (!e.target.files) return; + + const imgObject = e.target.files[0]; + setFeedAvatar(imgObject) + if (imgObject) { + setFeedAvatarImg(window.URL.createObjectURL(imgObject)) + } else { + setFeedAvatarImg('') + + } + + }; + + const handleIsUseMyPage = async (param: boolean) => { + if (!agent || !did) return + setIsSave(true) + + try { + const writes = [ + { + $type: `com.atproto.repo.applyWrites#${preferenceMode}` as const, + collection: 'uk.skyblur.preference' as `${string}.${string}.${string}`, + rkey: 'self', + value: { + myPage: { + isUseMyPage: param, + description: myPageDescription, + }, + }, + }, + ]; + + await agent.post('com.atproto.repo.applyWrites', { + input: { + repo: did as ActorIdentifier, + writes: writes, + }, + }); + setIsUseMyPage(param) + + + } catch (e) { + console.error(e) + } + setIsSave(false) + + } + + + const submitFeedRecord = async () => { + if (!agent || !did) return + let avatarRef: Blob | null = null; + + let encoding: string = '' + + if (feedAvatar?.name.endsWith('png')) { + encoding = 'image/png' + } else if (feedAvatar?.name.endsWith('jpg') || feedAvatar?.name.endsWith('jpeg')) { + encoding = 'image/jpeg' + } else if (feedAvatar !== undefined) { + setFeedUpdateMessage(locale.Pref_FileType) + setIsLoading(false) + return + } + + try { + + if (feedAvatar) { + const fileUint = new Uint8Array(await feedAvatar.arrayBuffer()); + + const blobRes = await agent.post('com.atproto.repo.uploadBlob', { + input: fileUint, + encoding, + headers: { 'Content-Type': 'application/octet-stream' }, + }); + + if (!blobRes.ok) { + setFeedUpdateMessage('error'); + return + } + + avatarRef = blobRes.data.blob as unknown as Blob; + + } else if (feedAvatarImg) { + // feedAvatarImgからCIDと拡張子を取得 + let parts = feedAvatarImg.split('/'); + parts = parts[7].split('@'); + + const didValue = did as `did:${string}:${string}`; + + // BlobをGET + const ret = await agent.get('com.atproto.sync.getBlob', { + params: { + did: didValue, + cid: parts[0], + }, + as: 'blob', + }); + + if (!ret.ok) { + throw new Error('Failed to get blob'); + } + + const fileUint = new Uint8Array(await ret.data.arrayBuffer()); + + const blobRes = await agent.post('com.atproto.repo.uploadBlob', { + input: fileUint, + encoding, + headers: { 'Content-Type': 'application/octet-stream' }, + }); + + if (!blobRes.ok) { + setFeedUpdateMessage('error'); + return + } + + avatarRef = blobRes.data.blob as unknown as Blob; + } + + const record = { + did: 'did:web:feed.skyblur.uk', + displayName: feedName, + description: feedDescription, + createdAt: new Date().toISOString(), + ...(avatarRef ? { avatar: avatarRef } : {}), + }; + + await agent.post('com.atproto.repo.putRecord', { + input: { + repo: did as ActorIdentifier, + collection: 'app.bsky.feed.generator' as `${string}.${string}.${string}`, + rkey: 'skyblurCustomFeed', + record, + }, + }); + } catch (e) { + setFeedUpdateMessage('Error:' + e) + setIsSave(false) + return + } + + } + + const handleCustomFeed = async (param: boolean) => { + if (!agent || !did) return + + try { + if (param) { + await submitFeedRecord(); + } else { + const writes: { + $type: 'com.atproto.repo.applyWrites#delete'; + collection: 'app.bsky.feed.generator'; // 削除対象のコレクション名に変更 + rkey: string; + }[] = []; + + writes.push({ + $type: 'com.atproto.repo.applyWrites#delete', + collection: 'app.bsky.feed.generator', // 元の collection に合わせる + rkey: 'skyblurCustomFeed', // 削除したい rkey を指定 + }); + + await agent.post('com.atproto.repo.applyWrites', { + input: { + repo: did as ActorIdentifier, + writes, + }, + }); + + + } + + } catch (e) { + console.error(e) + } + + } + + + const handleSave = async () => { + if (!agent || !did) return + setFeedUpdateMessage("") + setIsSave(true) + + notifications.show({ + title: locale.Menu_Settings, + loading: true, + autoClose: false, + message: locale.Pref_SaveIsInProgress, + }); + try { + await handleCustomFeed(isCustomFeed) + await handleIsUseMyPage(isUseMyPage) + notifications.clean() + notifications.show({ + title: 'Success', + message: locale.Pref_SaveCompleted, + color: 'teal', + icon: + }); + router.push('/') + } catch (e) { + notifications.clean() + setFeedUpdateMessage('Error:' + e) + } + setIsSave(false) + + } + + + if (!isMounted) { + return ( + + ); + } + + return ( + < > +
+ <> +
+ {locale.Pref_Title} +
+
+ <> + <> + + {locale.Pref_MyPage} +
{locale.Pref_MyPagePublishDescription}
+
+ setIsUseMyPage(event.currentTarget.checked)} + + label={locale.Pref_MyPagePublish} + /> +
+ {isUseMyPage && ( + <> +
{locale.Pref_MyPageDesc}
+