@nexttylabs/echo 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/lib/auth/config.ts +0 -7
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -116
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- package/vercel.json +0 -4
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
# Project Settings Tabs Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Restructure project settings into three top-level tabs (通用设置 / Widget / Portal) and embed the Portal overview flow within the Portal tab.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Convert the project settings UI into a tabbed layout using the existing shadcn Tabs component. Extract the Portal overview into a reusable component so it can be rendered both inside the Portal tab and the dedicated Portal route pages.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Next.js App Router, React 19, TypeScript, shadcn/ui (Tabs, Card, Button, Badge, Switch), Tailwind CSS.
|
|
10
|
-
|
|
11
|
-
### Task 1: Add a reusable Portal overview component
|
|
12
|
-
|
|
13
|
-
**Files:**
|
|
14
|
-
- Create: `components/portal/portal-overview.tsx`
|
|
15
|
-
- Modify: `app/(dashboard)/settings/projects/[projectSlug]/portal/page.tsx`
|
|
16
|
-
- Test: `tests/components/portal-overview.test.tsx`
|
|
17
|
-
|
|
18
|
-
**Step 1: Write the failing test**
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
import { describe, expect, it } from "bun:test";
|
|
22
|
-
import { PortalOverview } from "@/components/portal/portal-overview";
|
|
23
|
-
|
|
24
|
-
describe("PortalOverview", () => {
|
|
25
|
-
it("exports a component", () => {
|
|
26
|
-
expect(typeof PortalOverview).toBe("function");
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Step 2: Run test to verify it fails**
|
|
32
|
-
|
|
33
|
-
Run: `bun test tests/components/portal-overview.test.tsx`
|
|
34
|
-
Expected: FAIL with "Cannot find module '@/components/portal/portal-overview'"
|
|
35
|
-
|
|
36
|
-
**Step 3: Write minimal implementation**
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
40
|
-
import { Badge } from "@/components/ui/badge";
|
|
41
|
-
import { Button } from "@/components/ui/button";
|
|
42
|
-
import { PortalModulesPanel } from "@/components/portal/portal-modules-panel";
|
|
43
|
-
import { getDefaultPortalModules } from "@/lib/portal/modules";
|
|
44
|
-
import type { PortalConfig } from "@/lib/db/schema/projects";
|
|
45
|
-
|
|
46
|
-
export function PortalOverview({
|
|
47
|
-
projectSlug,
|
|
48
|
-
portalLink,
|
|
49
|
-
portalConfig,
|
|
50
|
-
projectId,
|
|
51
|
-
}: {
|
|
52
|
-
projectSlug: string;
|
|
53
|
-
portalLink: string;
|
|
54
|
-
portalConfig: PortalConfig | null;
|
|
55
|
-
projectId: string;
|
|
56
|
-
}) {
|
|
57
|
-
const portalEnabled = portalConfig?.sharing?.enabled ?? true;
|
|
58
|
-
const themeSummary = portalConfig?.theme?.primaryColor
|
|
59
|
-
? `主色 ${portalConfig.theme.primaryColor}`
|
|
60
|
-
: "使用默认主题";
|
|
61
|
-
const languageSummary = portalConfig?.defaultLanguage ?? "zh-CN";
|
|
62
|
-
const seoSummary = portalConfig?.seo?.metaTitle
|
|
63
|
-
? portalConfig.seo.metaTitle
|
|
64
|
-
: "未设置 SEO 标题";
|
|
65
|
-
|
|
66
|
-
const modules = portalConfig?.modules ?? getDefaultPortalModules();
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<div className="space-y-8">
|
|
70
|
-
<Card className="border-slate-200/80 bg-white/80 shadow-sm">
|
|
71
|
-
<CardHeader className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
72
|
-
<div>
|
|
73
|
-
<CardTitle className="text-xl">Portal 状态</CardTitle>
|
|
74
|
-
<CardDescription>当前项目的门户入口与可见性</CardDescription>
|
|
75
|
-
</div>
|
|
76
|
-
<div className="flex flex-wrap items-center gap-3">
|
|
77
|
-
<Badge variant={portalEnabled ? "default" : "outline"}>
|
|
78
|
-
{portalEnabled ? "公开中" : "已关闭"}
|
|
79
|
-
</Badge>
|
|
80
|
-
<Button asChild variant="outline" size="sm">
|
|
81
|
-
<a href={portalLink}>预览 Portal</a>
|
|
82
|
-
</Button>
|
|
83
|
-
<Button asChild size="sm">
|
|
84
|
-
<a href={portalLink}>打开 Portal</a>
|
|
85
|
-
</Button>
|
|
86
|
-
</div>
|
|
87
|
-
</CardHeader>
|
|
88
|
-
<CardContent>
|
|
89
|
-
<div className="text-sm text-slate-600">
|
|
90
|
-
门户链接:<span className="font-medium text-slate-900">{portalLink}</span>
|
|
91
|
-
</div>
|
|
92
|
-
</CardContent>
|
|
93
|
-
</Card>
|
|
94
|
-
|
|
95
|
-
<div className="grid gap-6 lg:grid-cols-3">
|
|
96
|
-
<PortalGroupCard
|
|
97
|
-
title="体验"
|
|
98
|
-
description="主题、文案与语言"
|
|
99
|
-
items={[
|
|
100
|
-
themeSummary,
|
|
101
|
-
portalConfig?.copy?.title ? `标题:${portalConfig.copy.title}` : "未设置门户标题",
|
|
102
|
-
`默认语言:${languageSummary}`,
|
|
103
|
-
]}
|
|
104
|
-
href={`/settings/projects/${projectSlug}/portal/experience`}
|
|
105
|
-
/>
|
|
106
|
-
<PortalGroupCard
|
|
107
|
-
title="传播"
|
|
108
|
-
description="分享与 SEO 配置"
|
|
109
|
-
items={[
|
|
110
|
-
seoSummary,
|
|
111
|
-
portalConfig?.sharing?.socialSharing ? "社交分享已配置" : "未配置社交分享",
|
|
112
|
-
portalConfig?.seo?.ogImage ? "已设置 OG 图片" : "未设置 OG 图片",
|
|
113
|
-
]}
|
|
114
|
-
href={`/settings/projects/${projectSlug}/portal/growth`}
|
|
115
|
-
/>
|
|
116
|
-
<PortalGroupCard
|
|
117
|
-
title="可见性"
|
|
118
|
-
description="公开权限与索引"
|
|
119
|
-
items={[
|
|
120
|
-
portalEnabled ? "门户公开" : "门户关闭",
|
|
121
|
-
portalConfig?.sharing?.allowPublicVoting ? "允许公开投票" : "关闭公开投票",
|
|
122
|
-
portalConfig?.seo?.noIndex ? "禁止索引" : "允许索引",
|
|
123
|
-
]}
|
|
124
|
-
href={`/settings/projects/${projectSlug}/portal/access`}
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<details className="group">
|
|
129
|
-
<summary className="cursor-pointer list-none text-sm font-semibold text-slate-700">
|
|
130
|
-
<span className="inline-flex items-center gap-2">
|
|
131
|
-
<span className="rounded-full bg-slate-200 px-2 py-0.5 text-xs font-medium text-slate-600">
|
|
132
|
-
模块开关
|
|
133
|
-
</span>
|
|
134
|
-
管理 Portal 模块显示
|
|
135
|
-
</span>
|
|
136
|
-
</summary>
|
|
137
|
-
<div className="mt-4">
|
|
138
|
-
<PortalModulesPanel projectId={projectId} initialModules={modules} />
|
|
139
|
-
</div>
|
|
140
|
-
</details>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function PortalGroupCard({
|
|
146
|
-
title,
|
|
147
|
-
description,
|
|
148
|
-
items,
|
|
149
|
-
href,
|
|
150
|
-
}: {
|
|
151
|
-
title: string;
|
|
152
|
-
description: string;
|
|
153
|
-
items: string[];
|
|
154
|
-
href: string;
|
|
155
|
-
}) {
|
|
156
|
-
return (
|
|
157
|
-
<Card className="border-slate-200/80 bg-white/80 shadow-sm">
|
|
158
|
-
<CardHeader>
|
|
159
|
-
<CardTitle className="text-lg">{title}</CardTitle>
|
|
160
|
-
<CardDescription>{description}</CardDescription>
|
|
161
|
-
</CardHeader>
|
|
162
|
-
<CardContent className="space-y-3">
|
|
163
|
-
<ul className="space-y-1 text-sm text-slate-600">
|
|
164
|
-
{items.map((item) => (
|
|
165
|
-
<li key={item}>• {item}</li>
|
|
166
|
-
))}
|
|
167
|
-
</ul>
|
|
168
|
-
<Button asChild variant="outline" size="sm">
|
|
169
|
-
<a href={href}>进入设置</a>
|
|
170
|
-
</Button>
|
|
171
|
-
</CardContent>
|
|
172
|
-
</Card>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Step 4: Run test to verify it passes**
|
|
178
|
-
|
|
179
|
-
Run: `bun test tests/components/portal-overview.test.tsx`
|
|
180
|
-
Expected: PASS
|
|
181
|
-
|
|
182
|
-
**Step 5: Commit**
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
git add components/portal/portal-overview.tsx app/(dashboard)/settings/projects/[projectSlug]/portal/page.tsx tests/components/portal-overview.test.tsx
|
|
186
|
-
git commit -m "feat: extract portal overview component"
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Task 2: Add tabs to project settings (通用设置 / Widget / Portal)
|
|
190
|
-
|
|
191
|
-
**Files:**
|
|
192
|
-
- Modify: `components/project/project-settings.tsx`
|
|
193
|
-
- Modify: `app/(dashboard)/settings/projects/[projectSlug]/page.tsx`
|
|
194
|
-
- Test: `tests/components/project-settings-tabs.test.tsx`
|
|
195
|
-
|
|
196
|
-
**Step 1: Write the failing test**
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
import { describe, expect, it } from "bun:test";
|
|
200
|
-
import { ProjectSettings } from "@/components/project/project-settings";
|
|
201
|
-
|
|
202
|
-
describe("ProjectSettings tabs", () => {
|
|
203
|
-
it("exports a component", () => {
|
|
204
|
-
expect(typeof ProjectSettings).toBe("function");
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**Step 2: Run test to verify it fails**
|
|
210
|
-
|
|
211
|
-
Run: `bun test tests/components/project-settings-tabs.test.tsx`
|
|
212
|
-
Expected: PASS (component exists) — update test to check for tabs labels after implementation
|
|
213
|
-
|
|
214
|
-
**Step 3: Write minimal implementation**
|
|
215
|
-
|
|
216
|
-
- Use `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` from `components/ui/tabs.tsx`
|
|
217
|
-
- Create three tabs: 通用设置 / Widget / Portal
|
|
218
|
-
- Move existing Widget form + preview into Widget tab
|
|
219
|
-
- Move Portal entry into Portal tab and render `PortalOverview` component
|
|
220
|
-
- Keep “危险区域” inside 通用设置 tab
|
|
221
|
-
|
|
222
|
-
**Step 4: Run test to verify it passes**
|
|
223
|
-
|
|
224
|
-
Run: `bun test tests/components/project-settings-tabs.test.tsx`
|
|
225
|
-
Expected: PASS
|
|
226
|
-
|
|
227
|
-
**Step 5: Commit**
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
git add components/project/project-settings.tsx app/(dashboard)/settings/projects/[projectSlug]/page.tsx tests/components/project-settings-tabs.test.tsx
|
|
231
|
-
git commit -m "feat: add project settings tabs"
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Task 3: Wire Portal tab to new overview and subpages
|
|
235
|
-
|
|
236
|
-
**Files:**
|
|
237
|
-
- Modify: `components/project/project-settings.tsx`
|
|
238
|
-
- Modify: `app/(dashboard)/settings/projects/[projectSlug]/portal/page.tsx`
|
|
239
|
-
- Test: `tests/app/project-portal-overview.test.tsx`
|
|
240
|
-
|
|
241
|
-
**Step 1: Write the failing test**
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import { describe, expect, it } from "bun:test";
|
|
245
|
-
import { PortalOverview } from "@/components/portal/portal-overview";
|
|
246
|
-
|
|
247
|
-
describe("Portal overview routing", () => {
|
|
248
|
-
it("exports a component", () => {
|
|
249
|
-
expect(typeof PortalOverview).toBe("function");
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**Step 2: Run test to verify it fails**
|
|
255
|
-
|
|
256
|
-
Run: `bun test tests/app/project-portal-overview.test.tsx`
|
|
257
|
-
Expected: PASS
|
|
258
|
-
|
|
259
|
-
**Step 3: Write minimal implementation**
|
|
260
|
-
|
|
261
|
-
- Replace inline overview markup in portal overview page with `PortalOverview`
|
|
262
|
-
- Use same component in Portal tab content
|
|
263
|
-
|
|
264
|
-
**Step 4: Run test to verify it passes**
|
|
265
|
-
|
|
266
|
-
Run: `bun test tests/app/project-portal-overview.test.tsx`
|
|
267
|
-
Expected: PASS
|
|
268
|
-
|
|
269
|
-
**Step 5: Commit**
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
git add components/project/project-settings.tsx app/(dashboard)/settings/projects/[projectSlug]/portal/page.tsx tests/app/project-portal-overview.test.tsx
|
|
273
|
-
git commit -m "feat: embed portal overview in project settings"
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Task 4: Verification
|
|
277
|
-
|
|
278
|
-
**Files:**
|
|
279
|
-
- None
|
|
280
|
-
|
|
281
|
-
**Step 1: Run lint**
|
|
282
|
-
|
|
283
|
-
Run: `bun run lint`
|
|
284
|
-
Expected: PASS with existing warnings only
|
|
285
|
-
|
|
286
|
-
**Step 2: Manual check**
|
|
287
|
-
|
|
288
|
-
- Visit `/settings/projects/[projectSlug]` and verify tabs and content
|
|
289
|
-
- Visit `/settings/projects/[projectSlug]/portal` and verify overview + module toggle
|
|
290
|
-
- Verify Portal subpages still accessible
|
|
291
|
-
|
|
292
|
-
**Step 3: Commit (if needed)**
|
|
293
|
-
|
|
294
|
-
```bash
|
|
295
|
-
git status -sb
|
|
296
|
-
```
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# Playwright Feedback Submission E2E Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add a Playwright E2E smoke test that registers a user, opens the organization portal, submits feedback, and confirms it appears in the list.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Use Playwright for browser automation and `page.request` to call `/api/auth/register` in the same browser context to seed a user+organization, then drive the portal UI to create a feedback post. Store test config in `playwright.config.ts` with a local dev server and standard artifacts.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Playwright, Bun, Next.js App Router, better-auth, Drizzle/Postgres.
|
|
10
|
-
|
|
11
|
-
### Task 1: Add the failing E2E test (red)
|
|
12
|
-
|
|
13
|
-
**Files:**
|
|
14
|
-
- Create: `tests/e2e/feedback-submit.spec.ts`
|
|
15
|
-
|
|
16
|
-
**Step 1: Write the failing test**
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import { test, expect } from "@playwright/test";
|
|
20
|
-
|
|
21
|
-
function uniqueEmail() {
|
|
22
|
-
return `e2e+${Date.now()}@example.com`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function uniqueTitle() {
|
|
26
|
-
return `E2E feedback ${Date.now()}`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test("feedback submission from portal creates a new post", async ({ page }) => {
|
|
30
|
-
const email = uniqueEmail();
|
|
31
|
-
const password = "StrongPass123!";
|
|
32
|
-
const name = "E2E User";
|
|
33
|
-
|
|
34
|
-
const register = await page.request.post("/api/auth/register", {
|
|
35
|
-
data: { name, email, password },
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
expect(register.ok()).toBeTruthy();
|
|
39
|
-
const json = await register.json();
|
|
40
|
-
const slug = json?.data?.organization?.slug;
|
|
41
|
-
expect(slug).toBeTruthy();
|
|
42
|
-
|
|
43
|
-
const title = uniqueTitle();
|
|
44
|
-
|
|
45
|
-
await page.goto(`/${slug}`);
|
|
46
|
-
await page.getByRole("button", { name: "Submit Feedback" }).click();
|
|
47
|
-
|
|
48
|
-
await page.getByLabel("Title").fill(title);
|
|
49
|
-
await page.getByLabel("Description").fill("This is an E2E submission.");
|
|
50
|
-
await page.getByRole("button", { name: "Create Post" }).click();
|
|
51
|
-
|
|
52
|
-
await expect(page.getByRole("heading", { name: title })).toBeVisible();
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Step 2: Run test to verify it fails**
|
|
57
|
-
|
|
58
|
-
Run: `bunx playwright test tests/e2e/feedback-submit.spec.ts`
|
|
59
|
-
Expected: FAIL because Playwright is not installed/configured.
|
|
60
|
-
|
|
61
|
-
### Task 2: Add Playwright tooling/configuration (green for tooling)
|
|
62
|
-
|
|
63
|
-
**Files:**
|
|
64
|
-
- Create: `playwright.config.ts`
|
|
65
|
-
- Modify: `package.json`
|
|
66
|
-
- Modify: `.gitignore`
|
|
67
|
-
|
|
68
|
-
**Step 1: Write minimal Playwright config**
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
import { defineConfig, devices } from "@playwright/test";
|
|
72
|
-
|
|
73
|
-
export default defineConfig({
|
|
74
|
-
testDir: "./tests/e2e",
|
|
75
|
-
timeout: 60_000,
|
|
76
|
-
expect: { timeout: 10_000 },
|
|
77
|
-
retries: 1,
|
|
78
|
-
use: {
|
|
79
|
-
baseURL: process.env.E2E_BASE_URL ?? "http://localhost:3000",
|
|
80
|
-
trace: "retain-on-failure",
|
|
81
|
-
screenshot: "only-on-failure",
|
|
82
|
-
video: "retain-on-failure",
|
|
83
|
-
},
|
|
84
|
-
webServer: {
|
|
85
|
-
command: "bun dev",
|
|
86
|
-
url: "http://localhost:3000",
|
|
87
|
-
reuseExistingServer: !process.env.CI,
|
|
88
|
-
timeout: 120_000,
|
|
89
|
-
},
|
|
90
|
-
projects: [
|
|
91
|
-
{
|
|
92
|
-
name: "chromium",
|
|
93
|
-
use: { ...devices["Desktop Chrome"] },
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**Step 2: Add scripts/deps**
|
|
100
|
-
|
|
101
|
-
- Add devDependency: `@playwright/test`
|
|
102
|
-
- Add script: `"test:e2e": "bunx playwright test"`
|
|
103
|
-
- Add script: `"test:e2e:ui": "bunx playwright test --ui"`
|
|
104
|
-
|
|
105
|
-
**Step 3: Ignore Playwright artifacts**
|
|
106
|
-
|
|
107
|
-
Add to `.gitignore`:
|
|
108
|
-
```
|
|
109
|
-
playwright-report/
|
|
110
|
-
test-results/
|
|
111
|
-
.playwright/
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**Step 4: Run the failing test again**
|
|
115
|
-
|
|
116
|
-
Run: `bunx playwright test tests/e2e/feedback-submit.spec.ts`
|
|
117
|
-
Expected: FAIL due to missing/incorrect behavior in the app (if any), or PASS if flow works end-to-end.
|
|
118
|
-
|
|
119
|
-
### Task 3: Make the test pass (minimal changes)
|
|
120
|
-
|
|
121
|
-
**Files:**
|
|
122
|
-
- Modify (if needed): `components/portal/create-post-dialog.tsx`
|
|
123
|
-
- Modify (if needed): `components/portal/feedback-board.tsx`
|
|
124
|
-
- Modify (if needed): `app/[organizationSlug]/page.tsx`
|
|
125
|
-
- Modify (if needed): `tests/e2e/feedback-submit.spec.ts`
|
|
126
|
-
|
|
127
|
-
**Step 1: Observe failure and identify the minimal fix**
|
|
128
|
-
|
|
129
|
-
Run: `bunx playwright test tests/e2e/feedback-submit.spec.ts`
|
|
130
|
-
Expected: FAIL with a clear reason (e.g., button labels differ, missing fields, redirect to login).
|
|
131
|
-
|
|
132
|
-
**Step 2: Apply minimal code change**
|
|
133
|
-
|
|
134
|
-
Examples (only if needed):
|
|
135
|
-
- If button label mismatches, prefer role/label selectors or update labels.
|
|
136
|
-
- If dialog doesn’t open due to auth, ensure register call sets cookies (use `page.request` only, not a separate request context).
|
|
137
|
-
|
|
138
|
-
**Step 3: Re-run the test**
|
|
139
|
-
|
|
140
|
-
Run: `bunx playwright test tests/e2e/feedback-submit.spec.ts`
|
|
141
|
-
Expected: PASS.
|
|
142
|
-
|
|
143
|
-
**Step 4: Commit**
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
git add playwright.config.ts package.json .gitignore tests/e2e/feedback-submit.spec.ts
|
|
147
|
-
# plus any modified app files if needed
|
|
148
|
-
git commit -m "test: add Playwright E2E feedback submission"
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Task 4: Document local E2E prerequisites
|
|
152
|
-
|
|
153
|
-
**Files:**
|
|
154
|
-
- Modify: `README.md`
|
|
155
|
-
|
|
156
|
-
**Step 1: Add E2E run instructions**
|
|
157
|
-
|
|
158
|
-
Add a short section:
|
|
159
|
-
- Set `DATABASE_URL` and `BETTER_AUTH_SECRET`
|
|
160
|
-
- Run migrations: `bun run db:migrate`
|
|
161
|
-
- Run: `bun run test:e2e`
|
|
162
|
-
|
|
163
|
-
**Step 2: Verify docs**
|
|
164
|
-
|
|
165
|
-
Run: `rg -n "E2E" README.md`
|
|
166
|
-
Expected: section present.
|
|
167
|
-
|
|
168
|
-
**Step 3: Commit**
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
git add README.md
|
|
172
|
-
git commit -m "docs: add Playwright E2E setup"
|
|
173
|
-
```
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# Feedback Management Organization Context - Design
|
|
2
|
-
|
|
3
|
-
Date: 2026-01-15
|
|
4
|
-
|
|
5
|
-
## Summary
|
|
6
|
-
Refactor Feedback Management to be organization-aware by default. Introduce a unified organization context resolver that reads organization from URL, headers, and cookies (in that priority) and enforces organization membership on server-side. Add an organization switcher only on `/dashboard` that writes the selected org to cookie and URL. All feedback-related pages and APIs must use the resolver to prevent cross-organization data access.
|
|
7
|
-
|
|
8
|
-
## Goals
|
|
9
|
-
- Enforce organization isolation across all feedback pages and APIs.
|
|
10
|
-
- Centralize organization resolution and authorization on the server.
|
|
11
|
-
- Support multi-organization users with a dashboard switcher.
|
|
12
|
-
- Persist organization selection with cookie and allow URL override.
|
|
13
|
-
|
|
14
|
-
## Non-Goals
|
|
15
|
-
- No global organization picker outside `/dashboard`.
|
|
16
|
-
- No super-admin global org visibility.
|
|
17
|
-
- No UI redesign beyond adding the switcher on `/dashboard`.
|
|
18
|
-
|
|
19
|
-
## Current State
|
|
20
|
-
- Some endpoints accept `organizationId` query/header but not consistently enforced.
|
|
21
|
-
- `/dashboard` uses a single organization and some pages still hardcode IDs.
|
|
22
|
-
- Multiple feedback APIs and pages query feedback by ID without consistent org checks.
|
|
23
|
-
|
|
24
|
-
## Proposed Architecture
|
|
25
|
-
### Organization Context Resolver
|
|
26
|
-
Add a new module (e.g. `lib/auth/org-context.ts`) that provides:
|
|
27
|
-
- `getOrgContext(request, options)`
|
|
28
|
-
- Resolves `organizationId` from:
|
|
29
|
-
1) URL query (`organizationId`)
|
|
30
|
-
2) Header (`x-organization-id`)
|
|
31
|
-
3) Cookie (`orgId`)
|
|
32
|
-
- Optionally reads `organizationSlug` for portal routes when provided.
|
|
33
|
-
- Returns `{ organizationId, memberRole, source }`.
|
|
34
|
-
- If user is logged in and `organizationId` is present, verifies membership.
|
|
35
|
-
- If user is anonymous, allows read-only contexts for public routes.
|
|
36
|
-
- `requireOrgContext(request, options)`
|
|
37
|
-
- Same as above, but throws/returns 403 when membership is missing or invalid.
|
|
38
|
-
|
|
39
|
-
### Enforcement
|
|
40
|
-
- All feedback list, detail, stats, comments, votes, duplicates, and similarity APIs must call `requireOrgContext`.
|
|
41
|
-
- All feedback pages (dashboard, admin, feedback detail) must use resolver-derived org.
|
|
42
|
-
- Portal pages use slug -> org ID and bypass membership checks for read-only views.
|
|
43
|
-
|
|
44
|
-
## Data Flow
|
|
45
|
-
1. User lands on `/dashboard`.
|
|
46
|
-
2. Dashboard requests user organizations and renders a switcher.
|
|
47
|
-
3. Switcher selection writes `orgId` cookie and updates URL query (`?organizationId=...`).
|
|
48
|
-
4. Server components and API routes resolve org via URL > header > cookie.
|
|
49
|
-
5. Feedback queries always include `organizationId` constraint and org membership checks.
|
|
50
|
-
|
|
51
|
-
## Error Handling
|
|
52
|
-
- Missing org: return 400 `MISSING_ORG_ID`.
|
|
53
|
-
- Not a member: return 403 `FORBIDDEN`.
|
|
54
|
-
- Feedback not in org: return 404.
|
|
55
|
-
- Anonymous access to protected endpoints: return 401/403.
|
|
56
|
-
|
|
57
|
-
## Compatibility
|
|
58
|
-
- Existing callers that pass `organizationId` continue to work.
|
|
59
|
-
- New behavior is more strict; missing org will now fail with 400.
|
|
60
|
-
- Portal routes remain public read-only with explicit org resolution.
|
|
61
|
-
|
|
62
|
-
## Implementation Scope
|
|
63
|
-
- Add org context resolver and membership utilities.
|
|
64
|
-
- Update all feedback APIs to use resolver.
|
|
65
|
-
- Update feedback pages to consume resolver-derived org ID.
|
|
66
|
-
- Add dashboard organization switcher (only on `/dashboard`).
|
|
67
|
-
- Cookie handling (set on dashboard, read everywhere).
|
|
68
|
-
|
|
69
|
-
## Risks & Mitigations
|
|
70
|
-
- **Default org on first load**: if cookie is missing, select the first org and persist it.
|
|
71
|
-
- **Portal contamination**: portal routes should not read org cookie; rely on slug-derived org ID.
|
|
72
|
-
- **Incomplete enforcement**: audit all feedback routes and service-layer queries.
|
|
73
|
-
|
|
74
|
-
## Testing
|
|
75
|
-
- Unit tests for resolver precedence (URL > header > cookie) and membership check.
|
|
76
|
-
- API tests for org isolation on list/detail endpoints.
|
|
77
|
-
- Dashboard switcher test to verify cookie + URL update.
|
|
78
|
-
|
|
79
|
-
## Rollout
|
|
80
|
-
1. Ship resolver + API enforcement.
|
|
81
|
-
2. Ship dashboard switcher and default org behavior.
|
|
82
|
-
3. Add logging for missing org or forbidden access to validate adoption.
|