diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf586111..e62a6fb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,26 +10,25 @@ on: jobs: build: + timeout-minutes: 15 runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20] + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + TURBO_REMOTE_ONLY: true steps: - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v4 with: - version: 9.5.0 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + version: 9.6.0 + - uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 20 cache: "pnpm" - name: Install dependencies - run: pnpm install + run: pnpm install --prefer-offline - name: Run GraphQL diagnostics run: pnpm gql:check diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..e77dc86c --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,97 @@ +name: Playwright Tests + +on: + push: + branches: + - "**" + pull_request: + branches: + - main + +jobs: + e2e: + if: false # Disable this job temporarily + timeout-minutes: 60 + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + TURBO_REMOTE_ONLY: true + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9.6.0 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --prefer-offline + + - name: Install Playwright Browsers + run: pnpm dlx playwright install --with-deps + + - name: Build project + env: + TURSO_CONNECTION_URL: ${{ secrets.TURSO_CONNECTION_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: pnpm build + + - name: Run Playwright tests + env: + TURSO_CONNECTION_URL: ${{ secrets.TURSO_CONNECTION_URL }} + TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }} + AES_KEY: ${{ secrets.AES_KEY }} + run: pnpm test -- -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: blob-report + retention-days: 1 + + merge-reports: + # if: ${{ !cancelled() }} + if: false # Disable this job temporarily + needs: [e2e] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9.5.0 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --prefer-offline + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 diff --git a/README.md b/README.md index 62acf3bb..3013b770 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ If you like this project, please consider giving it a star! ✨ If you wish to s | `@umamin/db` | Database schema & migrations using Drizzle ORM | | `@umamin/gql` | GraphQL schema models and resolvers using Pothos | | `@umamin/aes` | Encryption algorithm using AES in Galois/Counter Mode (AES-GCM) | +| `@umamin/e2e` | End-to-end testing suite using Playwright | ### Prerequisites - [`Turso CLI`](https://docs.turso.tech/cli/installation) (for local libSQL server) diff --git a/apps/www/package.json b/apps/www/package.json index 19cb4053..5b5d1fb4 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -15,35 +15,35 @@ }, "dependencies": { "@fingerprintjs/botd": "^1.9.1", - "@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", + "@graphql-yoga/plugin-csrf-prevention": "^3.6.2", + "@graphql-yoga/plugin-disable-introspection": "^2.6.2", + "@graphql-yoga/plugin-persisted-operations": "^3.6.2", + "@graphql-yoga/plugin-response-cache": "^3.8.2", + "@hookform/resolvers": "^3.9.0", "@lucia-auth/adapter-drizzle": "^1.0.7", "@mdx-js/loader": "^3.0.1", "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.2.5", "@node-rs/argon2": "^1.8.3", - "@sentry/nextjs": "^8", + "@sentry/nextjs": "^8.21.0", "@types/mdx": "^2.0.13", "@umamin/aes": "workspace:*", "@umamin/db": "workspace:*", "@umamin/gql": "workspace:*", "@umamin/ui": "workspace:*", - "@urql/core": "^5.0.4", + "@urql/core": "^5.0.5", "@urql/exchange-graphcache": "^7.1.1", "@urql/exchange-persisted": "^4.3.0", "@urql/next": "^1.1.1", - "arctic": "^1.9.1", + "arctic": "^1.9.2", "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", + "clsx": "^2.1.1", "date-fns": "^3.6.0", - "firebase": "^10.12.1", - "geist": "^1.3.0", - "gql.tada": "^1.8.2", + "firebase": "^10.12.4", + "geist": "^1.3.1", + "gql.tada": "^1.8.5", "graphql": "^16.9.0", - "graphql-yoga": "^5.6.0", + "graphql-yoga": "^5.6.2", "lucia": "^3.2.0", "lucide-react": "^0.407.0", "modern-screenshot": "^4.4.39", @@ -54,18 +54,19 @@ "oslo": "^1.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.51.2", + "react-hook-form": "^7.52.1", "react-intersection-observer": "^9.10.2", "remark-gfm": "^4.0.0", "sonner": "^1.5.0", - "tailwind-merge": "^2.2.2", + "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", "urql": "^4.1.0", "zod": "^3.22.4", - "zustand": "^4.5.4" + "zustand": "^4.5.4", + "@whatwg-node/server": "^0.9.46" }, "devDependencies": { - "@0no-co/graphqlsp": "^1.12.11", + "@0no-co/graphqlsp": "^1.12.12", "@types/node": "^20", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", @@ -74,8 +75,8 @@ "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.2.5", - "postcss": "^8", - "tailwindcss": "^3.3.0", - "typescript": "^5.4.5" + "postcss": "^8.4.40", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.4" } } diff --git a/apps/www/src/app/(profile)/inbox/components/received/card.tsx b/apps/www/src/app/(profile)/inbox/components/received/card.tsx index 4ce80443..587c0e56 100644 --- a/apps/www/src/app/(profile)/inbox/components/received/card.tsx +++ b/apps/www/src/app/(profile)/inbox/components/received/card.tsx @@ -58,7 +58,10 @@ export function ReceivedMessageCard({

-
+
{msg.content}
diff --git a/apps/www/src/app/(profile)/inbox/components/received/list.tsx b/apps/www/src/app/(profile)/inbox/components/received/list.tsx index c9774a28..67739f31 100644 --- a/apps/www/src/app/(profile)/inbox/components/received/list.tsx +++ b/apps/www/src/app/(profile)/inbox/components/received/list.tsx @@ -1,6 +1,7 @@ "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"; @@ -12,6 +13,10 @@ 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 ReceivedMessagesFromCursor($input: MessagesFromCursorInput!) { @@ -96,9 +101,14 @@ export function ReceivedMessagesList({ return ( <> - {messages?.map((msg) => ( + {messages?.map((msg, i) => (
+ + {/* v2-received-list */} + {(i + 1) % 5 === 0 && ( + + )}
))} diff --git a/apps/www/src/app/(profile)/inbox/components/received/reply.tsx b/apps/www/src/app/(profile)/inbox/components/received/reply.tsx index e5e4cc6e..d366d31d 100644 --- a/apps/www/src/app/(profile)/inbox/components/received/reply.tsx +++ b/apps/www/src/app/(profile)/inbox/components/received/reply.tsx @@ -115,7 +115,11 @@ export function ReplyDialog(props: Props) { className="focus-visible:ring-transparent flex-1 text-base resize-none min-h-10 max-h-20" autoComplete="off" /> - diff --git a/apps/www/src/app/(user)/to/[username]/components/form.tsx b/apps/www/src/app/(user)/to/[username]/components/form.tsx index 2bb339ea..2373b274 100644 --- a/apps/www/src/app/(user)/to/[username]/components/form.tsx +++ b/apps/www/src/app/(user)/to/[username]/components/form.tsx @@ -16,7 +16,6 @@ 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"; -import { ProgressDialog } from "@/app/components/progress-dialog"; const CREATE_MESSAGE_MUTATION = graphql(` mutation CreateMessage($input: CreateMessageInput!) { @@ -41,7 +40,6 @@ export default function ChatForm({ currentUserId, user }: Props) { const [content, setContent] = useState(""); const [message, setMessage] = useState(""); const [isFetching, setIsFetching] = useState(false); - const [dialogOpen, setDialogOpen] = useState(false); const inputRef = useDynamicTextarea(content); @@ -76,7 +74,9 @@ export default function ChatForm({ currentUserId, user }: Props) { return; } - setDialogOpen(true); + setContent(""); + toast.success("Message sent anonymously"); + setMessage(content.replace(/(\r\n|\n|\r){2,}/g, "\n\n")); setIsFetching(false); logEvent(analytics, "send_message"); @@ -87,65 +87,58 @@ export default function ChatForm({ currentUserId, user }: Props) { } return ( - <> - { - setMessage(content.replace(/(\r\n|\n|\r){2,}/g, "\n\n")); - setContent(""); - }} - /> -
-
- +
+ +
+ + {user?.quietMode ? ( + + User has enabled quiet mode + + ) : ( +
+