diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 06fe2e77..b174be51 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,45 +1,34 @@
name: CI
on:
- - push
- - pull_request
+ - push:
+ branches:
+ - "**"
+ - pull_request:
+ branches:
+ - main
jobs:
- cache-and-build:
- runs-on: ubuntu-latest
+ build:
+ uses: ./.github/workflows/setup.yml
+ with:
+ node-version: 20
+ pnpm-version: 9.5.0
steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - uses: pnpm/action-setup@v3
- name: Install pnpm
- with:
- version: 9.4.0
- run_install: false
-
- - name: Get pnpm store directory
- shell: bash
- run: |
- echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
-
- - uses: actions/cache@v4
- name: Setup pnpm cache
- with:
- path: ${{ env.STORE_PATH }}
- key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-pnpm-store-
-
- - name: Install dependencies
- run: pnpm install
-
- name: Build project
env:
TURSO_CONNECTION_URL: ${{ secrets.TURSO_CONNECTION_URL }}
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: pnpm build
+
+ graphql-diagnostics:
+ need: build
+ uses: ./.github/workflows/setup.yml
+ with:
+ node-version: 20
+ pnpm-version: 9.5.0
+
+ steps:
+ - name: Run GraphQL diagnostics
+ run: pnpm gql:check
diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml
new file mode 100644
index 00000000..752760d4
--- /dev/null
+++ b/.github/workflows/setup.yml
@@ -0,0 +1,44 @@
+name: Setup
+
+on:
+ workflow_call:
+ inputs:
+ node-version:
+ required: true
+ type: string
+ pnpm-version:
+ required: true
+ type: string
+
+jobs:
+ setup:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ inputs.node-version }}
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: ${{ inputs.pnpm-version }}
+ run_install: false
+
+ - name: Get pnpm store directory
+ run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Cache pnpm store
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+
+ - name: Install dependencies
+ run: pnpm install
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 00000000..f0eb61e0
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": false
+}
diff --git a/apps/www/.gitignore b/apps/www/.gitignore
index fd3dbb57..1dd45b20 100644
--- a/apps/www/.gitignore
+++ b/apps/www/.gitignore
@@ -34,3 +34,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# Sentry Config File
+.env.sentry-build-plugin
diff --git a/apps/www/next.config.mjs b/apps/www/next.config.mjs
index 05656a82..d0e234ad 100644
--- a/apps/www/next.config.mjs
+++ b/apps/www/next.config.mjs
@@ -1,3 +1,4 @@
+import { withSentryConfig } from "@sentry/nextjs";
import remarkGfm from "remark-gfm";
import createMDX from "@next/mdx";
@@ -5,7 +6,7 @@ import createMDX from "@next/mdx";
const nextConfig = {
pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
experimental: {
- serverComponentsExternalPackages: ["@node-rs/argon2"],
+ serverComponentsExternalPackages: ["@node-rs/argon2", "@sentry/nextjs"],
},
compiler: {
removeConsole: process.env.NODE_ENV === "production",
@@ -28,4 +29,38 @@ const withMDX = createMDX({
},
});
-export default withMDX(nextConfig);
+export default withSentryConfig(withMDX(nextConfig), {
+ // For all available options, see:
+ // https://github.com/getsentry/sentry-webpack-plugin#options
+
+ org: "omsimos",
+ project: "umamin",
+
+ // Only print logs for uploading source maps in CI
+ silent: !process.env.CI,
+
+ // For all available options, see:
+ // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
+
+ // Upload a larger set of source maps for prettier stack traces (increases build time)
+ widenClientFileUpload: true,
+
+ // Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
+ // This can increase your server load as well as your hosting bill.
+ // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
+ // side errors will fail.
+ // tunnelRoute: "/monitoring",
+
+ // Hides source maps from generated client bundles
+ hideSourceMaps: true,
+
+ // Automatically tree-shake Sentry logger statements to reduce bundle size
+ disableLogger: true,
+
+ // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
+ // See the following for more information:
+ // https://docs.sentry.io/product/crons/
+ // https://vercel.com/docs/cron-jobs
+ automaticVercelMonitors: true,
+});
+
diff --git a/apps/www/package.json b/apps/www/package.json
index a78e02ff..19cb4053 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -8,20 +8,24 @@
"start": "next start",
"clean": "rm -rf ./node_modules .turbo .next",
"check-types": "tsc --noEmit && gql.tada check",
- "lint": "next lint"
+ "lint": "next lint",
+ "gql:check": "gql.tada check",
+ "gql:generate-persisted": "gql.tada generate-persisted",
+ "gql:generate-schema": "gql.tada generate-schema http://localhost:3000/api/graphql"
},
"dependencies": {
"@fingerprintjs/botd": "^1.9.1",
- "@graphql-yoga/plugin-apq": "^3.4.0",
- "@graphql-yoga/plugin-csrf-prevention": "^3.4.0",
- "@graphql-yoga/plugin-disable-introspection": "^2.4.0",
+ "@graphql-yoga/plugin-csrf-prevention": "^3.6.0",
+ "@graphql-yoga/plugin-disable-introspection": "^2.6.0",
+ "@graphql-yoga/plugin-persisted-operations": "^3.6.0",
"@graphql-yoga/plugin-response-cache": "^3.8.0",
"@hookform/resolvers": "^3.3.4",
"@lucia-auth/adapter-drizzle": "^1.0.7",
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
- "@next/mdx": "^14.2.4",
+ "@next/mdx": "^14.2.5",
"@node-rs/argon2": "^1.8.3",
+ "@sentry/nextjs": "^8",
"@types/mdx": "^2.0.13",
"@umamin/aes": "workspace:*",
"@umamin/db": "workspace:*",
@@ -29,6 +33,7 @@
"@umamin/ui": "workspace:*",
"@urql/core": "^5.0.4",
"@urql/exchange-graphcache": "^7.1.1",
+ "@urql/exchange-persisted": "^4.3.0",
"@urql/next": "^1.1.1",
"arctic": "^1.9.1",
"class-variance-authority": "^0.7.0",
@@ -36,14 +41,14 @@
"date-fns": "^3.6.0",
"firebase": "^10.12.1",
"geist": "^1.3.0",
- "gql.tada": "^1.8.0",
- "graphql": "^16.8.1",
- "graphql-yoga": "^5.4.0",
+ "gql.tada": "^1.8.2",
+ "graphql": "^16.9.0",
+ "graphql-yoga": "^5.6.0",
"lucia": "^3.2.0",
- "lucide-react": "^0.358.0",
+ "lucide-react": "^0.407.0",
"modern-screenshot": "^4.4.39",
"nanoid": "^5.0.7",
- "next": "14.2.4",
+ "next": "14.2.5",
"next-themes": "^0.3.0",
"nextjs-toploader": "^1.6.12",
"oslo": "^1.2.1",
@@ -60,7 +65,7 @@
"zustand": "^4.5.4"
},
"devDependencies": {
- "@0no-co/graphqlsp": "^1.12.9",
+ "@0no-co/graphqlsp": "^1.12.11",
"@types/node": "^20",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
@@ -68,7 +73,7 @@
"@umamin/tsconfig": "workspace:*",
"autoprefixer": "^10.0.1",
"eslint": "^8",
- "eslint-config-next": "14.1.3",
+ "eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5.4.5"
diff --git a/apps/www/sentry.client.config.ts b/apps/www/sentry.client.config.ts
new file mode 100644
index 00000000..ace8a931
--- /dev/null
+++ b/apps/www/sentry.client.config.ts
@@ -0,0 +1,31 @@
+// This file configures the initialization of Sentry on the client.
+// The config you add here will be used whenever a users loads a page in their browser.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import * as Sentry from "@sentry/nextjs";
+
+Sentry.init({
+ enabled: process.env.NODE_ENV === "production",
+ dsn: "https://91072fe34a5a53274d5c2372c9a85120@o4507604730183680.ingest.us.sentry.io/4507604736802816",
+
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1,
+
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
+
+ replaysOnErrorSampleRate: 1.0,
+
+ // This sets the sample rate to be 10%. You may want this to be 100% while
+ // in development and sample at a lower rate in production
+ replaysSessionSampleRate: 0.1,
+
+ // You can remove this option if you're not planning to use the Sentry Session Replay feature:
+ integrations: [
+ Sentry.replayIntegration({
+ // Additional Replay configuration goes in here, for example:
+ maskAllText: true,
+ blockAllMedia: true,
+ }),
+ ],
+});
diff --git a/apps/www/sentry.edge.config.ts b/apps/www/sentry.edge.config.ts
new file mode 100644
index 00000000..d005a05f
--- /dev/null
+++ b/apps/www/sentry.edge.config.ts
@@ -0,0 +1,17 @@
+// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
+// The config you add here will be used whenever one of the edge features is loaded.
+// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import * as Sentry from "@sentry/nextjs";
+
+Sentry.init({
+ enabled: process.env.NODE_ENV === "production",
+ dsn: "https://91072fe34a5a53274d5c2372c9a85120@o4507604730183680.ingest.us.sentry.io/4507604736802816",
+
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1,
+
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
+});
diff --git a/apps/www/sentry.server.config.ts b/apps/www/sentry.server.config.ts
new file mode 100644
index 00000000..56916c95
--- /dev/null
+++ b/apps/www/sentry.server.config.ts
@@ -0,0 +1,19 @@
+// This file configures the initialization of Sentry on the server.
+// The config you add here will be used whenever the server handles a request.
+// https://docs.sentry.io/platforms/javascript/guides/nextjs/
+
+import * as Sentry from "@sentry/nextjs";
+
+Sentry.init({
+ enabled: process.env.NODE_ENV === "production",
+ dsn: "https://91072fe34a5a53274d5c2372c9a85120@o4507604730183680.ingest.us.sentry.io/4507604736802816",
+
+ // Adjust this value in production, or use tracesSampler for greater control
+ tracesSampleRate: 1,
+
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
+ debug: false,
+
+ // Uncomment the line below to enable Spotlight (https://spotlightjs.com)
+ // spotlight: process.env.NODE_ENV === 'development',
+});
diff --git a/apps/www/src/app/login/components/form.tsx b/apps/www/src/app/(authentication)/login/components/form.tsx
similarity index 100%
rename from apps/www/src/app/login/components/form.tsx
rename to apps/www/src/app/(authentication)/login/components/form.tsx
diff --git a/apps/www/src/app/login/components/login-button.tsx b/apps/www/src/app/(authentication)/login/components/login-button.tsx
similarity index 100%
rename from apps/www/src/app/login/components/login-button.tsx
rename to apps/www/src/app/(authentication)/login/components/login-button.tsx
diff --git a/apps/www/src/app/login/google/callback/route.ts b/apps/www/src/app/(authentication)/login/google/callback/route.ts
similarity index 100%
rename from apps/www/src/app/login/google/callback/route.ts
rename to apps/www/src/app/(authentication)/login/google/callback/route.ts
diff --git a/apps/www/src/app/login/google/route.ts b/apps/www/src/app/(authentication)/login/google/route.ts
similarity index 100%
rename from apps/www/src/app/login/google/route.ts
rename to apps/www/src/app/(authentication)/login/google/route.ts
diff --git a/apps/www/src/app/login/loading.tsx b/apps/www/src/app/(authentication)/login/loading.tsx
similarity index 100%
rename from apps/www/src/app/login/loading.tsx
rename to apps/www/src/app/(authentication)/login/loading.tsx
diff --git a/apps/www/src/app/login/page.tsx b/apps/www/src/app/(authentication)/login/page.tsx
similarity index 90%
rename from apps/www/src/app/login/page.tsx
rename to apps/www/src/app/(authentication)/login/page.tsx
index 0704af59..6e90ec90 100644
--- a/apps/www/src/app/login/page.tsx
+++ b/apps/www/src/app/(authentication)/login/page.tsx
@@ -1,9 +1,13 @@
import Link from "next/link";
+import dynamic from "next/dynamic";
import { getSession } from "@/lib/auth";
import { redirect } from "next/navigation";
import { LoginForm } from "./components/form";
-import { V1Link } from "../components/v1-link";
-import { BrowserWarning } from "@umamin/ui/components/browser-warning";
+import { V1Link } from "@/app/components/v1-link";
+
+const BrowserWarning = dynamic(
+ () => import("@umamin/ui/components/browser-warning"),
+);
export const metadata = {
title: "Umamin — Login",
diff --git a/apps/www/src/app/register/components/button.tsx b/apps/www/src/app/(authentication)/register/components/button.tsx
similarity index 100%
rename from apps/www/src/app/register/components/button.tsx
rename to apps/www/src/app/(authentication)/register/components/button.tsx
diff --git a/apps/www/src/app/register/components/form.tsx b/apps/www/src/app/(authentication)/register/components/form.tsx
similarity index 100%
rename from apps/www/src/app/register/components/form.tsx
rename to apps/www/src/app/(authentication)/register/components/form.tsx
diff --git a/apps/www/src/app/inbox/loading.tsx b/apps/www/src/app/(authentication)/register/loading.tsx
similarity index 100%
rename from apps/www/src/app/inbox/loading.tsx
rename to apps/www/src/app/(authentication)/register/loading.tsx
diff --git a/apps/www/src/app/register/page.tsx b/apps/www/src/app/(authentication)/register/page.tsx
similarity index 92%
rename from apps/www/src/app/register/page.tsx
rename to apps/www/src/app/(authentication)/register/page.tsx
index 6b413565..bcd2e543 100644
--- a/apps/www/src/app/register/page.tsx
+++ b/apps/www/src/app/(authentication)/register/page.tsx
@@ -1,9 +1,13 @@
import Link from "next/link";
+import dynamic from "next/dynamic";
import { getSession } from "@/lib/auth";
import { redirect } from "next/navigation";
-import { V1Link } from "../components/v1-link";
import { RegisterForm } from "./components/form";
-import { BrowserWarning } from "@umamin/ui/components/browser-warning";
+import { V1Link } from "@/app/components/v1-link";
+
+const BrowserWarning = dynamic(
+ () => import("@umamin/ui/components/browser-warning"),
+);
export const metadata = {
title: "Umamin — Register",
diff --git a/apps/www/src/app/privacy/layout.tsx b/apps/www/src/app/(markdown)/privacy/layout.tsx
similarity index 100%
rename from apps/www/src/app/privacy/layout.tsx
rename to apps/www/src/app/(markdown)/privacy/layout.tsx
diff --git a/apps/www/src/app/privacy/page.mdx b/apps/www/src/app/(markdown)/privacy/page.mdx
similarity index 100%
rename from apps/www/src/app/privacy/page.mdx
rename to apps/www/src/app/(markdown)/privacy/page.mdx
diff --git a/apps/www/src/app/terms/layout.tsx b/apps/www/src/app/(markdown)/terms/layout.tsx
similarity index 100%
rename from apps/www/src/app/terms/layout.tsx
rename to apps/www/src/app/(markdown)/terms/layout.tsx
diff --git a/apps/www/src/app/terms/page.mdx b/apps/www/src/app/(markdown)/terms/page.mdx
similarity index 100%
rename from apps/www/src/app/terms/page.mdx
rename to apps/www/src/app/(markdown)/terms/page.mdx
diff --git a/apps/www/src/app/inbox/components/received/card.tsx b/apps/www/src/app/(profile)/inbox/components/received/card.tsx
similarity index 100%
rename from apps/www/src/app/inbox/components/received/card.tsx
rename to apps/www/src/app/(profile)/inbox/components/received/card.tsx
diff --git a/apps/www/src/app/inbox/components/received/list.tsx b/apps/www/src/app/(profile)/inbox/components/received/list.tsx
similarity index 70%
rename from apps/www/src/app/inbox/components/received/list.tsx
rename to apps/www/src/app/(profile)/inbox/components/received/list.tsx
index 704053bf..c9774a28 100644
--- a/apps/www/src/app/inbox/components/received/list.tsx
+++ b/apps/www/src/app/(profile)/inbox/components/received/list.tsx
@@ -1,22 +1,20 @@
"use client";
import { toast } from "sonner";
-import dynamic from "next/dynamic";
import { graphql } from "gql.tada";
import { useInView } from "react-intersection-observer";
import { useCallback, useEffect, useState } from "react";
import client from "@/lib/gql/client";
-import { ReceivedMessagesResult } from "../../queries";
+import { formatError } from "@/lib/utils";
+import type { InboxProps } from "../../queries";
import { Skeleton } from "@umamin/ui/components/skeleton";
import { useMessageStore } from "@/store/useMessageStore";
import { ReceivedMessageCard, receivedMessageFragment } from "./card";
-const AdContainer = dynamic(() => import("@umamin/ui/ad"), { ssr: false });
-
const MESSAGES_FROM_CURSOR_QUERY = graphql(
`
- query MessagesFromCursor($input: MessagesFromCursorInput!) {
+ query ReceivedMessagesFromCursor($input: MessagesFromCursorInput!) {
messagesFromCursor(input: $input) {
__typename
data {
@@ -34,20 +32,20 @@ const MESSAGES_FROM_CURSOR_QUERY = graphql(
}
}
`,
- [receivedMessageFragment],
+ [receivedMessageFragment]
+);
+
+const messagesFromCursorPersisted = graphql.persisted(
+ "10ae521c718fee919520bf95d2cdc74ee1bd0d862d468ca4948ad705bb1e2909",
+ MESSAGES_FROM_CURSOR_QUERY
);
export function ReceivedMessagesList({
messages,
-}: {
- messages: ReceivedMessagesResult;
-}) {
+ initialCursor,
+}: InboxProps<"received">) {
const { ref, inView } = useInView();
-
- const [cursor, setCursor] = useState({
- id: messages[messages.length - 1]?.id ?? null,
- createdAt: messages[messages.length - 1]?.createdAt ?? null,
- });
+ const [cursor, setCursor] = useState(initialCursor);
const msgList = useMessageStore((state) => state.receivedList);
const updateMsgList = useMessageStore((state) => state.updateReceivedList);
@@ -59,7 +57,7 @@ export function ReceivedMessagesList({
if (hasMore) {
setIsFetching(true);
- const res = await client.query(MESSAGES_FROM_CURSOR_QUERY, {
+ const res = await client.query(messagesFromCursorPersisted, {
input: {
type: "received",
cursor,
@@ -67,7 +65,7 @@ export function ReceivedMessagesList({
});
if (res.error) {
- toast.error(res.error.message);
+ toast.error(formatError(res.error.message));
return;
}
@@ -98,25 +96,15 @@ export function ReceivedMessagesList({
return (
<>
- {messages?.map((msg, i) => (
+ {messages?.map((msg) => (
-
- {/* v2-received-list */}
- {(i + 1) % 5 === 0 && (
-
- )}
))}
- {msgList?.map((msg, i) => (
+ {msgList?.map((msg) => (
-
- {/* v2-received-list */}
- {(i + 1) % 5 === 0 && (
-
- )}
))}
diff --git a/apps/www/src/app/inbox/components/received/menu.tsx b/apps/www/src/app/(profile)/inbox/components/received/menu.tsx
similarity index 92%
rename from apps/www/src/app/inbox/components/received/menu.tsx
rename to apps/www/src/app/(profile)/inbox/components/received/menu.tsx
index bc04190e..53ef0f33 100644
--- a/apps/www/src/app/inbox/components/received/menu.tsx
+++ b/apps/www/src/app/(profile)/inbox/components/received/menu.tsx
@@ -29,6 +29,11 @@ const DELETE_MESSAGE_MUTATION = graphql(`
}
`);
+const deleteMessagePersisted = graphql.persisted(
+ "402dc5134e5ce477b966ad52be60a7ce75ca058a46a6f024bf34074a878a5a7d",
+ DELETE_MESSAGE_MUTATION
+);
+
export type ReceivedMenuProps = {
id: string;
question: string;
@@ -44,7 +49,7 @@ export function ReceivedMessageMenu(props: ReceivedMenuProps) {
const [open, setOpen] = useState(false);
const onDelete = async () => {
- const res = await client.mutation(DELETE_MESSAGE_MUTATION, { id });
+ const res = await client.mutation(deleteMessagePersisted, { id });
if (res.error) {
toast.error(formatError(res.error.message));
diff --git a/apps/www/src/app/(profile)/inbox/components/received/messages.tsx b/apps/www/src/app/(profile)/inbox/components/received/messages.tsx
new file mode 100644
index 00000000..32fa7d69
--- /dev/null
+++ b/apps/www/src/app/(profile)/inbox/components/received/messages.tsx
@@ -0,0 +1,24 @@
+import { ReceivedMessagesList } from "./list";
+import { getReceivedMessages } from "../../queries";
+
+export async function ReceivedMessages({ sessionId }: { sessionId?: string }) {
+ const messages = await getReceivedMessages(sessionId);
+
+ return (
+
+ {!messages?.length ? (
+
+ No messages to show
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/apps/www/src/app/inbox/components/received/reply.tsx b/apps/www/src/app/(profile)/inbox/components/received/reply.tsx
similarity index 95%
rename from apps/www/src/app/inbox/components/received/reply.tsx
rename to apps/www/src/app/(profile)/inbox/components/received/reply.tsx
index c4132ebd..e5e4cc6e 100644
--- a/apps/www/src/app/inbox/components/received/reply.tsx
+++ b/apps/www/src/app/(profile)/inbox/components/received/reply.tsx
@@ -31,6 +31,11 @@ const CREATE_REPLY_MUTATION = graphql(`
}
`);
+const createReplyPeresisted = graphql.persisted(
+ "a9f43ea2b50cd25c32cadd6fd3db25e06ed0919f12705455856d59a48dd572cd",
+ CREATE_REPLY_MUTATION
+);
+
export function ReplyDialog(props: Props) {
const router = useRouter();
const [content, setContent] = useState("");
@@ -43,7 +48,7 @@ export function ReplyDialog(props: Props) {
e.preventDefault();
setLoading(true);
- const res = await client.mutation(CREATE_REPLY_MUTATION, {
+ const res = await client.mutation(createReplyPeresisted, {
messageId: props.data.id,
content,
});
diff --git a/apps/www/src/app/inbox/components/sent/card.tsx b/apps/www/src/app/(profile)/inbox/components/sent/card.tsx
similarity index 100%
rename from apps/www/src/app/inbox/components/sent/card.tsx
rename to apps/www/src/app/(profile)/inbox/components/sent/card.tsx
diff --git a/apps/www/src/app/inbox/components/sent/list.tsx b/apps/www/src/app/(profile)/inbox/components/sent/list.tsx
similarity index 70%
rename from apps/www/src/app/inbox/components/sent/list.tsx
rename to apps/www/src/app/(profile)/inbox/components/sent/list.tsx
index 9e3ad613..47a8f5e0 100644
--- a/apps/www/src/app/inbox/components/sent/list.tsx
+++ b/apps/www/src/app/(profile)/inbox/components/sent/list.tsx
@@ -1,22 +1,20 @@
"use client";
import { toast } from "sonner";
-import dynamic from "next/dynamic";
import { graphql } from "gql.tada";
import { useInView } from "react-intersection-observer";
import { useCallback, useEffect, useState } from "react";
import client from "@/lib/gql/client";
-import { SentMessageResult } from "../../queries";
+import { formatError } from "@/lib/utils";
+import type { InboxProps } from "../../queries";
import { Skeleton } from "@umamin/ui/components/skeleton";
import { useMessageStore } from "@/store/useMessageStore";
import { sentMessageFragment, SentMessageCard } from "./card";
-const AdContainer = dynamic(() => import("@umamin/ui/ad"), { ssr: false });
-
const MESSAGES_FROM_CURSOR_QUERY = graphql(
`
- query MessagesFromCursor($input: MessagesFromCursorInput!) {
+ query SentMessagesFromCursor($input: MessagesFromCursorInput!) {
messagesFromCursor(input: $input) {
__typename
data {
@@ -34,20 +32,20 @@ const MESSAGES_FROM_CURSOR_QUERY = graphql(
}
}
`,
- [sentMessageFragment],
+ [sentMessageFragment]
+);
+
+const messagesFromCursorPersisted = graphql.persisted(
+ "9ceb104c0e9991769bf9544b2f7d605f0c66bfcfc488c0ae3dfaa7a975001b30",
+ MESSAGES_FROM_CURSOR_QUERY
);
export function SentMessagesList({
messages,
-}: {
- messages: SentMessageResult;
-}) {
+ initialCursor,
+}: InboxProps<"sent">) {
const { ref, inView } = useInView();
-
- const [cursor, setCursor] = useState({
- id: messages[messages.length - 1]?.id ?? null,
- createdAt: messages[messages.length - 1]?.createdAt ?? null,
- });
+ const [cursor, setCursor] = useState(initialCursor);
const msgList = useMessageStore((state) => state.sentList);
const updateMsgList = useMessageStore((state) => state.updateSentList);
@@ -59,7 +57,7 @@ export function SentMessagesList({
if (hasMore) {
setIsFetching(true);
- const res = await client.query(MESSAGES_FROM_CURSOR_QUERY, {
+ const res = await client.query(messagesFromCursorPersisted, {
input: {
type: "sent",
cursor,
@@ -67,7 +65,7 @@ export function SentMessagesList({
});
if (res.error) {
- toast.error(res.error.message);
+ toast.error(formatError(res.error.message));
return;
}
@@ -98,25 +96,15 @@ export function SentMessagesList({
return (
<>
- {messages?.map((msg, i) => (
+ {messages?.map((msg) => (
-
- {/* v2-sent-list */}
- {(i + 1) % 5 === 0 && (
-
- )}
))}
- {msgList?.map((msg, i) => (
+ {msgList?.map((msg) => (
-
- {/* v2-sent-list */}
- {(i + 1) % 5 === 0 && (
-
- )}
))}
diff --git a/apps/www/src/app/(profile)/inbox/components/sent/messages.tsx b/apps/www/src/app/(profile)/inbox/components/sent/messages.tsx
new file mode 100644
index 00000000..5a3ab90f
--- /dev/null
+++ b/apps/www/src/app/(profile)/inbox/components/sent/messages.tsx
@@ -0,0 +1,24 @@
+import { SentMessagesList } from "./list";
+import { getSentMessages } from "../../queries";
+
+export async function SentMessages({ sessionId }: { sessionId: string }) {
+ const messages = await getSentMessages(sessionId);
+
+ return (
+
+ {!messages?.length ? (
+
+ No messages to show
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/apps/www/src/app/register/loading.tsx b/apps/www/src/app/(profile)/inbox/loading.tsx
similarity index 100%
rename from apps/www/src/app/register/loading.tsx
rename to apps/www/src/app/(profile)/inbox/loading.tsx
diff --git a/apps/www/src/app/inbox/page.tsx b/apps/www/src/app/(profile)/inbox/page.tsx
similarity index 98%
rename from apps/www/src/app/inbox/page.tsx
rename to apps/www/src/app/(profile)/inbox/page.tsx
index d6e72868..4d4b362c 100644
--- a/apps/www/src/app/inbox/page.tsx
+++ b/apps/www/src/app/(profile)/inbox/page.tsx
@@ -9,7 +9,7 @@ import {
TabsList,
TabsTrigger,
} from "@umamin/ui/components/tabs";
-import { UserCard } from "../components/user-card";
+import { UserCard } from "@/app/components/user-card";
import { SentMessages } from "./components/sent/messages";
import { Skeleton } from "@umamin/ui/components/skeleton";
import { ReceivedMessages } from "./components/received/messages";
diff --git a/apps/www/src/app/(profile)/inbox/queries.ts b/apps/www/src/app/(profile)/inbox/queries.ts
new file mode 100644
index 00000000..534052f6
--- /dev/null
+++ b/apps/www/src/app/(profile)/inbox/queries.ts
@@ -0,0 +1,78 @@
+import { cache } from "react";
+import getClient from "@/lib/gql/rsc";
+import { ResultOf, graphql } from "gql.tada";
+
+import { sentMessageFragment } from "./components/sent/card";
+import { receivedMessageFragment } from "./components/received/card";
+
+const RECEIVED_MESSAGES_QUERY = graphql(
+ `
+ query ReceivedMessages($type: String!) {
+ messages(type: $type) {
+ __typename
+ id
+ createdAt
+ ...MessageFragment
+ }
+ }
+ `,
+ [receivedMessageFragment]
+);
+
+const SENT_MESSAGES_QUERY = graphql(
+ `
+ query SentMessages($type: String!) {
+ messages(type: $type) {
+ __typename
+ id
+ createdAt
+ ...SentMessageFragment
+ }
+ }
+ `,
+ [sentMessageFragment]
+);
+
+const receivedMessagesPersisted = graphql.persisted(
+ "f07a17f7e44b839d7a1449115b9810d55447696a558d7416f16dc0b9c978217f",
+ RECEIVED_MESSAGES_QUERY
+);
+
+const sentMessagesPersisted = graphql.persisted(
+ "05e86ea80c5038a466e952fe9fceeb57d537e1afbe6575df5f27b44944a1531f",
+ SENT_MESSAGES_QUERY
+);
+
+export const getReceivedMessages = cache(async (sessionId?: string) => {
+ const result = await getClient(sessionId).query(receivedMessagesPersisted, {
+ type: "received",
+ });
+
+ return result?.data?.messages;
+});
+
+export const getSentMessages = cache(async (sessionId?: string) => {
+ const result = await getClient(sessionId).query(sentMessagesPersisted, {
+ type: "sent",
+ });
+
+ return result?.data?.messages;
+});
+
+export type ReceivedMessagesResult = ResultOf<
+ typeof RECEIVED_MESSAGES_QUERY
+>["messages"];
+
+export type SentMessageResult = ResultOf<
+ typeof SENT_MESSAGES_QUERY
+>["messages"];
+
+type Cursor = {
+ id: string | null;
+ createdAt: number | null;
+};
+
+export type InboxProps = {
+ messages?: T extends "received" ? ReceivedMessagesResult : SentMessageResult;
+ initialCursor: Cursor;
+};
diff --git a/apps/www/src/app/settings/components/account-form.tsx b/apps/www/src/app/(profile)/settings/components/account-form.tsx
similarity index 100%
rename from apps/www/src/app/settings/components/account-form.tsx
rename to apps/www/src/app/(profile)/settings/components/account-form.tsx
diff --git a/apps/www/src/app/settings/components/account.tsx b/apps/www/src/app/(profile)/settings/components/account.tsx
similarity index 100%
rename from apps/www/src/app/settings/components/account.tsx
rename to apps/www/src/app/(profile)/settings/components/account.tsx
diff --git a/apps/www/src/app/settings/components/danger.tsx b/apps/www/src/app/(profile)/settings/components/danger.tsx
similarity index 100%
rename from apps/www/src/app/settings/components/danger.tsx
rename to apps/www/src/app/(profile)/settings/components/danger.tsx
diff --git a/apps/www/src/app/settings/components/delete-button.tsx b/apps/www/src/app/(profile)/settings/components/delete-button.tsx
similarity index 100%
rename from apps/www/src/app/settings/components/delete-button.tsx
rename to apps/www/src/app/(profile)/settings/components/delete-button.tsx
diff --git a/apps/www/src/app/settings/components/general.tsx b/apps/www/src/app/(profile)/settings/components/general.tsx
similarity index 95%
rename from apps/www/src/app/settings/components/general.tsx
rename to apps/www/src/app/(profile)/settings/components/general.tsx
index dee13482..f3eaaece 100644
--- a/apps/www/src/app/settings/components/general.tsx
+++ b/apps/www/src/app/(profile)/settings/components/general.tsx
@@ -34,6 +34,11 @@ const UPDATE_USER_MUTATION = graphql(`
}
`);
+const updateUserPersisted = graphql.persisted(
+ "0eb5223468cd7923b5d9a12fc2104425d047f9d34a871160b2e8d37bfc9224fc",
+ UPDATE_USER_MUTATION
+);
+
const FormSchema = z.object({
question: z
.string()
@@ -90,7 +95,7 @@ export function GeneralSettings({ user }: { user: CurrentUserResult }) {
setSaving(true);
- const res = await client.mutation(UPDATE_USER_MUTATION, {
+ const res = await client.mutation(updateUserPersisted, {
input: {
...data,
username: data.username.toLowerCase(),
@@ -147,7 +152,7 @@ export function GeneralSettings({ user }: { user: CurrentUserResult }) {
{account ? (
- Previous usernames will be available to others
+ Your previous username will be available to other users.
) : (
diff --git a/apps/www/src/app/settings/components/privacy.tsx b/apps/www/src/app/(profile)/settings/components/privacy.tsx
similarity index 88%
rename from apps/www/src/app/settings/components/privacy.tsx
rename to apps/www/src/app/(profile)/settings/components/privacy.tsx
index 4abc21ce..b662e9db 100644
--- a/apps/www/src/app/settings/components/privacy.tsx
+++ b/apps/www/src/app/(profile)/settings/components/privacy.tsx
@@ -27,6 +27,16 @@ const UPDATE_QUIET_MODE_MUTATION = graphql(`
}
`);
+const updatePicturePersisted = graphql.persisted(
+ "84fbec200028bf15058bc1addbef6e960ac4013dfb8c03205440e89e9f6cb19d",
+ UPDATE_PICTURE_MUTATION,
+);
+
+const updateQuietModePersisted = graphql.persisted(
+ "8c072442a1cbead14dc07404113b1dc2e3473fbd060fb5658f2c0acf6189a6c7",
+ UPDATE_QUIET_MODE_MUTATION,
+);
+
export function PrivacySettings({ user }: { user: CurrentUserResult }) {
const router = useRouter();
const [loading, setLoading] = useState(false);
@@ -42,7 +52,7 @@ export function PrivacySettings({ user }: { user: CurrentUserResult }) {
return;
}
- const res = await client.mutation(UPDATE_PICTURE_MUTATION, {
+ const res = await client.mutation(updatePicturePersisted, {
imageUrl: picture ? null : user?.accounts[0]?.picture,
});
@@ -63,7 +73,7 @@ export function PrivacySettings({ user }: { user: CurrentUserResult }) {
const toggleQuietMode = async () => {
setLoading(true);
- const res = await client.mutation(UPDATE_QUIET_MODE_MUTATION, {
+ const res = await client.mutation(updateQuietModePersisted, {
quietMode: !quietMode,
});
diff --git a/apps/www/src/app/settings/components/sign-out-button.tsx b/apps/www/src/app/(profile)/settings/components/sign-out-button.tsx
similarity index 100%
rename from apps/www/src/app/settings/components/sign-out-button.tsx
rename to apps/www/src/app/(profile)/settings/components/sign-out-button.tsx
diff --git a/apps/www/src/app/(profile)/settings/loading.tsx b/apps/www/src/app/(profile)/settings/loading.tsx
new file mode 100644
index 00000000..e1cdb72b
--- /dev/null
+++ b/apps/www/src/app/(profile)/settings/loading.tsx
@@ -0,0 +1,44 @@
+import { Skeleton } from "@umamin/ui/components/skeleton";
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/src/app/settings/page.tsx b/apps/www/src/app/(profile)/settings/page.tsx
similarity index 89%
rename from apps/www/src/app/settings/page.tsx
rename to apps/www/src/app/(profile)/settings/page.tsx
index a0d20afa..ecf50f0a 100644
--- a/apps/www/src/app/settings/page.tsx
+++ b/apps/www/src/app/(profile)/settings/page.tsx
@@ -1,10 +1,8 @@
-import { redirect } from "next/navigation";
-
import { logout } from "@/actions";
-import getClient from "@/lib/gql/rsc";
import { getSession } from "@/lib/auth";
+import { redirect } from "next/navigation";
-import { CURRENT_USER_QUERY } from "./queries";
+import { getCurrentUser } from "./queries";
import { GeneralSettings } from "./components/general";
import { AccountSettings } from "./components/account";
import { PrivacySettings } from "./components/privacy";
@@ -16,7 +14,6 @@ import {
TabsList,
TabsTrigger,
} from "@umamin/ui/components/tabs";
-import { cache } from "react";
export const metadata = {
title: "Umamin — Settings",
@@ -41,12 +38,6 @@ export const metadata = {
},
};
-const getUser = cache(async (sessionId?: string) => {
- const result = await getClient(sessionId).query(CURRENT_USER_QUERY, {});
-
- return result?.data?.user;
-});
-
export default async function Settings() {
const { user, session } = await getSession();
@@ -54,7 +45,7 @@ export default async function Settings() {
redirect("/login");
}
- const userData = await getUser(session?.id);
+ const userData = await getCurrentUser(session?.id);
const tabsData = [
{
diff --git a/apps/www/src/app/settings/queries.ts b/apps/www/src/app/(profile)/settings/queries.ts
similarity index 53%
rename from apps/www/src/app/settings/queries.ts
rename to apps/www/src/app/(profile)/settings/queries.ts
index cd919ac7..385027f5 100644
--- a/apps/www/src/app/settings/queries.ts
+++ b/apps/www/src/app/(profile)/settings/queries.ts
@@ -1,3 +1,5 @@
+import { cache } from "react";
+import getClient from "@/lib/gql/rsc";
import { graphql, ResultOf } from "gql.tada";
export const CURRENT_USER_QUERY = graphql(`
@@ -23,4 +25,15 @@ export const CURRENT_USER_QUERY = graphql(`
}
`);
+const currentUserPersisted = graphql.persisted(
+ "3f2320bbe96bd7895f618b6cdedfdee5d2f40e3e0c1d75095ea0844a0ff107b4",
+ CURRENT_USER_QUERY,
+);
+
+export const getCurrentUser = cache(async (sessionId?: string) => {
+ const result = await getClient(sessionId).query(currentUserPersisted, {});
+
+ return result?.data?.user;
+});
+
export type CurrentUserResult = ResultOf["user"];
diff --git a/apps/www/src/app/(user)/queries.ts b/apps/www/src/app/(user)/queries.ts
new file mode 100644
index 00000000..ce853087
--- /dev/null
+++ b/apps/www/src/app/(user)/queries.ts
@@ -0,0 +1,37 @@
+import { cache } from "react";
+import getClient from "@/lib/gql/rsc";
+import { ResultOf, graphql } from "gql.tada";
+
+export const USER_BY_USERNAME_QUERY = graphql(`
+ query UserByUsername($username: String!) {
+ userByUsername(username: $username) {
+ __typename
+ id
+ bio
+ question
+ username
+ displayName
+ question
+ quietMode
+ imageUrl
+ createdAt
+ }
+ }
+`);
+
+const userByUsernamePersisted = graphql.persisted(
+ "e56708c4cacdba6c698de1f4bc45a999ca535fb519bd806caf9a62a6582159e4",
+ USER_BY_USERNAME_QUERY,
+);
+
+export const getUserByUsername = cache(async (username: string) => {
+ const result = await getClient().query(userByUsernamePersisted, {
+ username,
+ });
+
+ return result.data?.userByUsername;
+});
+
+export type UserByUsernameQueryResult = ResultOf<
+ typeof USER_BY_USERNAME_QUERY
+>["userByUsername"];
diff --git a/apps/www/src/app/to/[username]/components/form.tsx b/apps/www/src/app/(user)/to/[username]/components/form.tsx
similarity index 92%
rename from apps/www/src/app/to/[username]/components/form.tsx
rename to apps/www/src/app/(user)/to/[username]/components/form.tsx
index f46381f0..c4583143 100644
--- a/apps/www/src/app/to/[username]/components/form.tsx
+++ b/apps/www/src/app/(user)/to/[username]/components/form.tsx
@@ -12,10 +12,10 @@ import { cn } from "@umamin/ui/lib/utils";
import { formatError } from "@/lib/utils";
import { Button } from "@umamin/ui/components/button";
import { ChatList } from "@/app/components/chat-list";
-import { UserByUsernameQueryResult } from "../queries";
import useBotDetection from "@/hooks/use-bot-detection";
import { Textarea } from "@umamin/ui/components/textarea";
import { useDynamicTextarea } from "@/hooks/use-dynamic-textarea";
+import type { UserByUsernameQueryResult } from "../../../queries";
const CREATE_MESSAGE_MUTATION = graphql(`
mutation CreateMessage($input: CreateMessageInput!) {
@@ -25,6 +25,11 @@ const CREATE_MESSAGE_MUTATION = graphql(`
}
`);
+const createMessagePersisted = graphql.persisted(
+ "3550bab6df63cc9b4f891263677b487dbf67eba1b5cc9af9fec5fc037d2e49f0",
+ CREATE_MESSAGE_MUTATION,
+);
+
type Props = {
currentUserId?: string;
user: UserByUsernameQueryResult;
@@ -54,7 +59,7 @@ export default function ChatForm({ currentUserId, user }: Props) {
setIsFetching(true);
try {
- const res = await client.mutation(CREATE_MESSAGE_MUTATION, {
+ const res = await client.mutation(createMessagePersisted, {
input: {
senderId: currentUserId,
receiverId: user?.id,
diff --git a/apps/www/src/app/to/[username]/components/unauthenticated.tsx b/apps/www/src/app/(user)/to/[username]/components/unauthenticated.tsx
similarity index 100%
rename from apps/www/src/app/to/[username]/components/unauthenticated.tsx
rename to apps/www/src/app/(user)/to/[username]/components/unauthenticated.tsx
diff --git a/apps/www/src/app/to/[username]/loading.tsx b/apps/www/src/app/(user)/to/[username]/loading.tsx
similarity index 94%
rename from apps/www/src/app/to/[username]/loading.tsx
rename to apps/www/src/app/(user)/to/[username]/loading.tsx
index d1e3032e..60b97e44 100644
--- a/apps/www/src/app/to/[username]/loading.tsx
+++ b/apps/www/src/app/(user)/to/[username]/loading.tsx
@@ -4,7 +4,7 @@ export default function Loading() {
return (
-
+
diff --git a/apps/www/src/app/to/[username]/page.tsx b/apps/www/src/app/(user)/to/[username]/page.tsx
similarity index 76%
rename from apps/www/src/app/to/[username]/page.tsx
rename to apps/www/src/app/(user)/to/[username]/page.tsx
index 18d2ae49..dced0d18 100644
--- a/apps/www/src/app/to/[username]/page.tsx
+++ b/apps/www/src/app/(user)/to/[username]/page.tsx
@@ -1,14 +1,13 @@
-import { cache } from "react";
import dynamic from "next/dynamic";
import { redirect } from "next/navigation";
import { BadgeCheck, Lock, MessageCircleOff } from "lucide-react";
-import getClient from "@/lib/gql/rsc";
import { getSession } from "@/lib/auth";
-import { USER_BY_USERNAME_QUERY } from "./queries";
+import { getUserByUsername } from "../../queries";
import { ShareButton } from "@/app/components/share-button";
import { Card, CardHeader } from "@umamin/ui/components/card";
+const AdContainer = dynamic(() => import("@umamin/ui/ad"));
const ChatForm = dynamic(() => import("./components/form"));
const UnauthenticatedDialog = dynamic(
() => import("./components/unauthenticated"),
@@ -52,20 +51,12 @@ export async function generateMetadata({
};
}
-const getUser = cache(async (username: string) => {
- const result = await getClient().query(USER_BY_USERNAME_QUERY, {
- username,
- });
-
- return result.data?.userByUsername;
-});
-
export default async function SendMessage({
params,
}: {
params: { username: string };
}) {
- const user = await getUser(params.username);
+ const user = await getUserByUsername(params.username);
if (!user) {
redirect("/404");
@@ -74,7 +65,10 @@ export default async function SendMessage({
const { session } = await getSession();
return (
-
+
+ {/* v2-send-to */}
+
+
@@ -93,16 +87,6 @@ export default async function SendMessage({
- {/*
- @{user.username}
- */}
-
umamin
@@ -112,16 +96,12 @@ export default async function SendMessage({
-
+
- Messages are automatically encrypted
+ Advanced Encryption Standard
- {/* v2-send-to
-
- */}
-
);
diff --git a/apps/www/src/app/user/[username]/loading.tsx b/apps/www/src/app/(user)/user/[username]/loading.tsx
similarity index 100%
rename from apps/www/src/app/user/[username]/loading.tsx
rename to apps/www/src/app/(user)/user/[username]/loading.tsx
diff --git a/apps/www/src/app/user/[username]/page.tsx b/apps/www/src/app/(user)/user/[username]/page.tsx
similarity index 77%
rename from apps/www/src/app/user/[username]/page.tsx
rename to apps/www/src/app/(user)/user/[username]/page.tsx
index 25a17351..b2ff2988 100644
--- a/apps/www/src/app/user/[username]/page.tsx
+++ b/apps/www/src/app/(user)/user/[username]/page.tsx
@@ -1,32 +1,15 @@
import Link from "next/link";
-import { cache } from "react";
-import { graphql } from "gql.tada";
import dynamic from "next/dynamic";
import { redirect } from "next/navigation";
import { MessageSquareMore, UserPlus } from "lucide-react";
-import getClient from "@/lib/gql/rsc";
import { cn } from "@umamin/ui/lib/utils";
+import { getUserByUsername } from "../../queries";
import { UserCard } from "@/app/components/user-card";
import { Button, buttonVariants } from "@umamin/ui/components/button";
const AdContainer = dynamic(() => import("@umamin/ui/ad"), { ssr: false });
-const USER_BY_USERNAME_QUERY = graphql(`
- query UserByUsername($username: String!) {
- userByUsername(username: $username) {
- __typename
- id
- bio
- username
- displayName
- imageUrl
- createdAt
- quietMode
- }
- }
-`);
-
export async function generateMetadata({
params,
}: {
@@ -68,20 +51,12 @@ export async function generateMetadata({
};
}
-const getUser = cache(async (username: string) => {
- const result = await getClient().query(USER_BY_USERNAME_QUERY, {
- username,
- });
-
- return result.data?.userByUsername;
-});
-
export default async function Page({
params,
}: {
params: { username: string };
}) {
- const user = await getUser(params.username);
+ const user = await getUserByUsername(params.username);
if (!user) {
redirect("/404");
diff --git a/apps/www/src/app/api/graphql/route.ts b/apps/www/src/app/api/graphql/route.ts
index 8ffa8377..6efa7f35 100644
--- a/apps/www/src/app/api/graphql/route.ts
+++ b/apps/www/src/app/api/graphql/route.ts
@@ -1,10 +1,11 @@
import { cookies } from "next/headers";
import { createYoga } from "graphql-yoga";
import { getSession, lucia } from "@/lib/auth";
-import { useAPQ } from "@graphql-yoga/plugin-apq";
import { gqlSchema, initContextCache } from "@umamin/gql";
+import persistedOperations from "@/persisted-operations.json";
import { useResponseCache } from "@graphql-yoga/plugin-response-cache";
import { useCSRFPrevention } from "@graphql-yoga/plugin-csrf-prevention";
+import { usePersistedOperations } from "@graphql-yoga/plugin-persisted-operations";
import { useDisableIntrospection } from "@graphql-yoga/plugin-disable-introspection";
const { handleRequest } = createYoga({
@@ -44,15 +45,48 @@ const { handleRequest } = createYoga({
},
ttl: 30_000,
ttlPerSchemaCoordinate: {
- "Query.notes": 180_000,
- "Query.notesFromCursor": 180_000,
- "Query.userByUsername": 300_000,
+ "Query.notes": 120_000,
+ "Query.notesFromCursor": 120_000,
+ "Query.userByUsername": 120_000,
},
}),
useDisableIntrospection({
isDisabled: () => process.env.NODE_ENV === "production",
}),
- useAPQ(),
+ usePersistedOperations({
+ allowArbitraryOperations: process.env.NODE_ENV === "development",
+ customErrors: {
+ notFound: {
+ message: "Operation is not found",
+ extensions: {
+ http: {
+ status: 404,
+ },
+ },
+ },
+ keyNotFound: {
+ message: "Key is not found",
+ extensions: {
+ http: {
+ status: 404,
+ },
+ },
+ },
+ persistedQueryOnly: {
+ message: "Operation is not allowed",
+ extensions: {
+ http: {
+ status: 403,
+ },
+ },
+ },
+ },
+ skipDocumentValidation: true,
+ async getPersistedOperation(key: string) {
+ // @ts-ignore
+ return persistedOperations[key];
+ },
+ }),
],
});
diff --git a/apps/www/src/app/components/chat-list.tsx b/apps/www/src/app/components/chat-list.tsx
index b0e78eab..8df8a8cb 100644
--- a/apps/www/src/app/components/chat-list.tsx
+++ b/apps/www/src/app/components/chat-list.tsx
@@ -34,7 +34,7 @@ export const ChatList = ({ imageUrl, question, reply, response }: Props) => {
)}
{response && (
-
+
diff --git a/apps/www/src/app/components/navbar.tsx b/apps/www/src/app/components/navbar.tsx
index 668b9afd..bd1482ee 100644
--- a/apps/www/src/app/components/navbar.tsx
+++ b/apps/www/src/app/components/navbar.tsx
@@ -24,7 +24,7 @@ export async function Navbar() {
: "v2.0.0";
return (
-