@knotpad/app 0.1.0 → 0.1.2
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/bin/brief.js +165 -78
- package/package.json +6 -86
- package/app/(app)/calendar/page.tsx +0 -57
- package/app/(app)/error.tsx +0 -35
- package/app/(app)/graph/page.tsx +0 -32
- package/app/(app)/guide/page.tsx +0 -21
- package/app/(app)/kanban/loading.tsx +0 -24
- package/app/(app)/kanban/page.tsx +0 -59
- package/app/(app)/layout.tsx +0 -122
- package/app/(app)/list/loading.tsx +0 -21
- package/app/(app)/list/page.tsx +0 -137
- package/app/(app)/loading.tsx +0 -18
- package/app/(app)/notes/[noteId]/page.tsx +0 -84
- package/app/(app)/notes/layout.tsx +0 -30
- package/app/(app)/notes/page.tsx +0 -39
- package/app/(app)/page.tsx +0 -5
- package/app/(app)/settings/agent-token/page.tsx +0 -59
- package/app/(app)/settings/backup/page.tsx +0 -49
- package/app/(app)/settings/billing/page.tsx +0 -53
- package/app/(app)/settings/calendar/page.tsx +0 -41
- package/app/(app)/settings/layout.test.tsx +0 -39
- package/app/(app)/settings/layout.tsx +0 -71
- package/app/(app)/settings/page.tsx +0 -4
- package/app/(app)/settings/security/page.tsx +0 -43
- package/app/(app)/settings/team/page.tsx +0 -74
- package/app/(app)/settings/workspace/page.tsx +0 -27
- package/app/(app)/tasks/[taskId]/page.tsx +0 -79
- package/app/(auth)/forgot-password/page.tsx +0 -106
- package/app/(auth)/guest/page.tsx +0 -56
- package/app/(auth)/layout.tsx +0 -13
- package/app/(auth)/login/page.tsx +0 -14
- package/app/(auth)/register/page.tsx +0 -193
- package/app/(auth)/reset-password/page.tsx +0 -138
- package/app/api/account/claim/route.tsx +0 -135
- package/app/api/admin/backfill-encryption/route.tsx +0 -43
- package/app/api/admin/license/route.tsx +0 -42
- package/app/api/auth/2fa/route.tsx +0 -148
- package/app/api/auth/[...nextauth]/route.tsx +0 -3
- package/app/api/auth/change-password/route.tsx +0 -61
- package/app/api/auth/check-2fa/route.tsx +0 -19
- package/app/api/auth/forgot-password/route.tsx +0 -65
- package/app/api/auth/reset-password/route.tsx +0 -52
- package/app/api/auth/verify-2fa/route.tsx +0 -88
- package/app/api/backup/download/db/route.ts +0 -29
- package/app/api/backup/download/notes/route.ts +0 -25
- package/app/api/backup/settings/route.ts +0 -92
- package/app/api/billing/checkout/route.tsx +0 -81
- package/app/api/billing/migrate/route.tsx +0 -163
- package/app/api/billing/portal/route.tsx +0 -24
- package/app/api/billing/setup-intent/route.tsx +0 -55
- package/app/api/billing/status/route.tsx +0 -36
- package/app/api/billing/subscribe/route.tsx +0 -85
- package/app/api/billing/webhook/route.tsx +0 -199
- package/app/api/calendar-feeds/[feedId]/route.tsx +0 -67
- package/app/api/calendar-feeds/[feedId]/sync/route.tsx +0 -37
- package/app/api/calendar-feeds/events/route.tsx +0 -82
- package/app/api/calendar-feeds/route.tsx +0 -52
- package/app/api/calendar-feeds/sync-all/route.tsx +0 -34
- package/app/api/cron/calendar-feeds/route.tsx +0 -31
- package/app/api/cron/stale-tasks/route.tsx +0 -51
- package/app/api/cron/sync/route.tsx +0 -34
- package/app/api/devices/[deviceId]/route.tsx +0 -25
- package/app/api/devices/route.tsx +0 -41
- package/app/api/export/route.tsx +0 -40
- package/app/api/feedback/route.tsx +0 -54
- package/app/api/folders/[folderId]/route.tsx +0 -51
- package/app/api/folders/route.tsx +0 -37
- package/app/api/graph/route.tsx +0 -242
- package/app/api/guest/route.tsx +0 -58
- package/app/api/health/route.tsx +0 -10
- package/app/api/holidays/countries/route.tsx +0 -14
- package/app/api/holidays/route.tsx +0 -49
- package/app/api/holidays/states/route.tsx +0 -21
- package/app/api/invites/[token]/route.tsx +0 -131
- package/app/api/invites/route.tsx +0 -74
- package/app/api/mcp/generate-token/route.tsx +0 -55
- package/app/api/mcp/revoke-token/[tokenId]/route.tsx +0 -30
- package/app/api/mcp/update-alias/[tokenId]/route.tsx +0 -22
- package/app/api/notes/[noteId]/export/route.tsx +0 -45
- package/app/api/notes/[noteId]/route.tsx +0 -360
- package/app/api/notes/route.tsx +0 -112
- package/app/api/notifications/route.tsx +0 -44
- package/app/api/register/route.tsx +0 -67
- package/app/api/restore/route.tsx +0 -148
- package/app/api/sync/conflicts/[conflictId]/route.tsx +0 -134
- package/app/api/sync/conflicts/route.tsx +0 -48
- package/app/api/sync/status/route.tsx +0 -49
- package/app/api/sync/trigger/route.tsx +0 -15
- package/app/api/tasks/[taskId]/detail/route.tsx +0 -68
- package/app/api/tasks/[taskId]/route.tsx +0 -259
- package/app/api/tasks/bulk/route.tsx +0 -133
- package/app/api/tasks/route.tsx +0 -36
- package/app/api/workspace/active/route.tsx +0 -39
- package/app/api/workspace/create-team/route.tsx +0 -42
- package/app/api/workspace/kanban-statuses/route.tsx +0 -71
- package/app/api/workspace/members/[memberId]/route.tsx +0 -69
- package/app/api/workspace/route.tsx +0 -24
- package/app/download/page.tsx +0 -170
- package/app/favicon.ico +0 -0
- package/app/generated/prisma/client.d.ts +0 -1
- package/app/generated/prisma/client.js +0 -5
- package/app/generated/prisma/default.d.ts +0 -1
- package/app/generated/prisma/default.js +0 -5
- package/app/generated/prisma/edge.d.ts +0 -1
- package/app/generated/prisma/edge.js +0 -497
- package/app/generated/prisma/index-browser.js +0 -523
- package/app/generated/prisma/index.d.ts +0 -46376
- package/app/generated/prisma/index.js +0 -497
- package/app/generated/prisma/package.json +0 -144
- package/app/generated/prisma/query_compiler_fast_bg.js +0 -2
- package/app/generated/prisma/query_compiler_fast_bg.wasm +0 -0
- package/app/generated/prisma/query_compiler_fast_bg.wasm-base64.js +0 -2
- package/app/generated/prisma/runtime/client.d.ts +0 -3386
- package/app/generated/prisma/runtime/client.js +0 -86
- package/app/generated/prisma/runtime/index-browser.d.ts +0 -90
- package/app/generated/prisma/runtime/index-browser.js +0 -6
- package/app/generated/prisma/runtime/wasm-compiler-edge.js +0 -76
- package/app/generated/prisma/schema.prisma +0 -456
- package/app/generated/prisma/wasm-edge-light-loader.mjs +0 -5
- package/app/generated/prisma/wasm-worker-loader.mjs +0 -5
- package/app/globals.css +0 -54
- package/app/invite/[token]/page.tsx +0 -52
- package/app/layout.tsx +0 -90
- package/app/mcp/route.tsx +0 -430
- package/app/opengraph-image.tsx +0 -120
- package/app/page.tsx +0 -398
- package/app/privacy/page.tsx +0 -69
- package/app/robots.tsx +0 -25
- package/app/sitemap.tsx +0 -36
- package/app/terms/page.tsx +0 -69
- package/app/upgrade/page.tsx +0 -75
- package/auth.config.ts +0 -33
- package/auth.ts +0 -79
- package/components/auth/login-form.tsx +0 -302
- package/components/auth/password-checklist.tsx +0 -31
- package/components/auth/password-input.tsx +0 -36
- package/components/auth/switch-account-button.test.tsx +0 -22
- package/components/auth/switch-account-button.tsx +0 -19
- package/components/auth/two-factor-input.tsx +0 -116
- package/components/billing/billing-dashboard.tsx +0 -265
- package/components/billing/card-form.tsx +0 -210
- package/components/billing/claim-account-form.tsx +0 -99
- package/components/branding/app-logo.test.tsx +0 -20
- package/components/branding/app-logo.tsx +0 -25
- package/components/calendar/calendar-agenda.tsx +0 -150
- package/components/calendar/calendar-drag.test.tsx +0 -177
- package/components/calendar/calendar-grid.tsx +0 -357
- package/components/calendar/calendar-hooks.test.tsx +0 -27
- package/components/calendar/calendar-hooks.ts +0 -351
- package/components/calendar/calendar-toolbar.test.tsx +0 -68
- package/components/calendar/calendar-toolbar.tsx +0 -291
- package/components/calendar/calendar-types.ts +0 -148
- package/components/calendar/calendar-view.test.tsx +0 -295
- package/components/calendar/calendar-view.tsx +0 -307
- package/components/calendar/day-detail-popover.tsx +0 -174
- package/components/calendar/task-chip.tsx +0 -86
- package/components/command/command-palette.test.tsx +0 -33
- package/components/command/command-palette.tsx +0 -310
- package/components/download-cta.tsx +0 -87
- package/components/feedback/feedback-popup.tsx +0 -207
- package/components/graph/graph-draw.ts +0 -337
- package/components/graph/graph-overlays.tsx +0 -160
- package/components/graph/graph-page.test.tsx +0 -131
- package/components/graph/graph-page.tsx +0 -263
- package/components/graph/graph-types.ts +0 -47
- package/components/graph/graph-view.tsx +0 -322
- package/components/guide/guide-view.tsx +0 -522
- package/components/kanban/kanban-board.test.tsx +0 -128
- package/components/kanban/kanban-board.tsx +0 -361
- package/components/kanban/kanban-card-menu.tsx +0 -102
- package/components/kanban/kanban-card.tsx +0 -227
- package/components/kanban/kanban-column.tsx +0 -49
- package/components/kanban/kanban-status-context.tsx +0 -28
- package/components/landing/calendar-sandbox.test.tsx +0 -15
- package/components/landing/calendar-sandbox.tsx +0 -107
- package/components/landing/graph-sandbox.test.tsx +0 -27
- package/components/landing/graph-sandbox.tsx +0 -80
- package/components/landing/kanban-sandbox.test.tsx +0 -24
- package/components/landing/kanban-sandbox.tsx +0 -101
- package/components/landing/landing-showcase.test.tsx +0 -21
- package/components/landing/landing-showcase.tsx +0 -54
- package/components/landing/list-sandbox.tsx +0 -86
- package/components/landing/mock-workspace.ts +0 -168
- package/components/landing/notes-sandbox.test.tsx +0 -14
- package/components/landing/notes-sandbox.tsx +0 -88
- package/components/layout/app-shell.tsx +0 -83
- package/components/layout/backup-scheduler.tsx +0 -122
- package/components/layout/bottom-nav.tsx +0 -43
- package/components/layout/icon-bar.test.tsx +0 -29
- package/components/layout/icon-bar.tsx +0 -118
- package/components/layout/mobile-top-bar.tsx +0 -68
- package/components/layout/notes-panel-folder.tsx +0 -127
- package/components/layout/notes-panel-note-item.tsx +0 -140
- package/components/layout/notes-panel-task-tab.tsx +0 -63
- package/components/layout/notes-panel-types.ts +0 -44
- package/components/layout/notes-panel.tsx +0 -476
- package/components/layout/notification-bell.tsx +0 -251
- package/components/layout/paywall-screen.tsx +0 -41
- package/components/layout/pro-banner.tsx +0 -76
- package/components/layout/sw-register.tsx +0 -27
- package/components/layout/workspace-switcher.tsx +0 -90
- package/components/notes/mobile-bottom-sheet.tsx +0 -99
- package/components/notes/note-editor-context-menu.tsx +0 -47
- package/components/notes/note-editor-dom.ts +0 -33
- package/components/notes/note-editor-dropdowns.tsx +0 -484
- package/components/notes/note-editor-hooks.ts +0 -692
- package/components/notes/note-editor-keyboard.ts +0 -305
- package/components/notes/note-editor-overlay.tsx +0 -90
- package/components/notes/note-editor.test.tsx +0 -372
- package/components/notes/note-editor.tsx +0 -662
- package/components/notes/note-preview-pane.tsx +0 -156
- package/components/notes/note-tabs.tsx +0 -120
- package/components/notes/note-types.tsx +0 -157
- package/components/settings/accept-invite.tsx +0 -108
- package/components/settings/agent-token-settings.tsx +0 -369
- package/components/settings/backup-restore-settings.test.tsx +0 -25
- package/components/settings/backup-restore-settings.tsx +0 -327
- package/components/settings/calendar-feeds-settings.tsx +0 -489
- package/components/settings/calendar-general-settings.tsx +0 -174
- package/components/settings/confirm-danger-action.test.tsx +0 -215
- package/components/settings/confirm-danger-action.tsx +0 -65
- package/components/settings/security-settings.tsx +0 -252
- package/components/settings/settings-guidance.test.tsx +0 -98
- package/components/settings/team-settings.tsx +0 -319
- package/components/settings/two-factor-auth.tsx +0 -296
- package/components/settings/workspace-settings-client.tsx +0 -363
- package/components/settings/workspace-settings-form.tsx +0 -73
- package/components/sync/conflict-viewer.tsx +0 -247
- package/components/sync/sync-indicator.tsx +0 -171
- package/components/tasks/snippet-thread.tsx +0 -119
- package/components/tasks/status-dot.tsx +0 -47
- package/components/tasks/task-badge.tsx +0 -43
- package/components/tasks/task-detail.test.tsx +0 -187
- package/components/tasks/task-detail.tsx +0 -458
- package/components/tasks/task-list-filters.test.tsx +0 -75
- package/components/tasks/task-list-filters.tsx +0 -163
- package/components/tasks/task-list-types.ts +0 -20
- package/components/tasks/task-list.test.tsx +0 -175
- package/components/tasks/task-list.tsx +0 -481
- package/components/tasks/task-row.tsx +0 -85
- package/components/tasks/task-table-row.tsx +0 -259
- package/components/ui/skeleton.tsx +0 -3
- package/components/ui/toast.test.tsx +0 -42
- package/components/ui/toast.tsx +0 -70
- package/electron/main.ts +0 -251
- package/electron/preload.ts +0 -56
- package/instrumentation.tsx +0 -23
- package/lib/api-error.ts +0 -50
- package/lib/backup/backup-runner.test.ts +0 -32
- package/lib/backup/backup-runner.ts +0 -19
- package/lib/backup/backup-schedule.test.ts +0 -23
- package/lib/backup/backup-schedule.ts +0 -55
- package/lib/backup/backup-settings.test.ts +0 -30
- package/lib/backup/backup-settings.ts +0 -27
- package/lib/backup/export-notes-zip.test.ts +0 -26
- package/lib/backup/export-notes-zip.ts +0 -82
- package/lib/backup/export-workspace-backup.test.ts +0 -17
- package/lib/backup/export-workspace-backup.ts +0 -77
- package/lib/backup/restore-workspace-from-export.test.ts +0 -18
- package/lib/backup/restore-workspace-from-export.ts +0 -183
- package/lib/backup/types.ts +0 -14
- package/lib/brand-icons.ts +0 -1
- package/lib/calendar-feed-crypto.ts +0 -38
- package/lib/calendar-feed.ts +0 -239
- package/lib/client/online-status.ts +0 -47
- package/lib/conflict-resolver.test.ts +0 -57
- package/lib/conflict-resolver.ts +0 -240
- package/lib/db-init.ts +0 -79
- package/lib/email.ts +0 -159
- package/lib/encryption.test.ts +0 -41
- package/lib/encryption.ts +0 -98
- package/lib/extract-snippet.test.ts +0 -123
- package/lib/extract-snippet.ts +0 -69
- package/lib/kanban-status.ts +0 -55
- package/lib/license.ts +0 -21
- package/lib/limits.ts +0 -31
- package/lib/mcp-auth.test.ts +0 -58
- package/lib/mcp-auth.ts +0 -65
- package/lib/mcp-contract.test.ts +0 -25
- package/lib/mcp-contract.ts +0 -210
- package/lib/mcp-handler.ts +0 -31
- package/lib/mcp-url.test.ts +0 -12
- package/lib/mcp-url.ts +0 -7
- package/lib/mentions.test.ts +0 -45
- package/lib/mentions.ts +0 -73
- package/lib/note-crypto.ts +0 -108
- package/lib/note-sync.ts +0 -201
- package/lib/note-title.ts +0 -93
- package/lib/prisma.ts +0 -193
- package/lib/pro-flush.ts +0 -292
- package/lib/rate-limit.ts +0 -57
- package/lib/stripe.ts +0 -38
- package/lib/sync-worker.ts +0 -388
- package/lib/task-parser.test.ts +0 -91
- package/lib/task-parser.ts +0 -81
- package/lib/task-utils.ts +0 -52
- package/lib/use-is-electron.ts +0 -19
- package/lib/use-is-mobile.ts +0 -22
- package/lib/validation/calendar-feed.ts +0 -31
- package/lib/validation/note.ts +0 -27
- package/lib/validation/task.ts +0 -26
- package/lib/view-preferences.test.ts +0 -54
- package/lib/view-preferences.ts +0 -28
- package/lib/workspace.ts +0 -66
- package/next.config.ts +0 -21
- package/postcss.config.mjs +0 -7
- package/prisma/migrations/20260519021916_init/migration.sql +0 -388
- package/prisma/migrations/20260519061113_drop_sync_password/migration.sql +0 -8
- package/prisma/migrations/20260520065016_add_task_start_date/migration.sql +0 -2
- package/prisma/migrations/20260529010600_remove_encryption_fields/migration.sql +0 -12
- package/prisma/migrations/20260529020000_restore_encryption_salt/migration.sql +0 -3
- package/prisma/migrations/20260529030000_add_folders/migration.sql +0 -17
- package/prisma/migrations/20260605000000_deferred_fixes/migration.sql +0 -31
- package/prisma/migrations/20260605020806_add_pending_sync_to_note_and_task/migration.sql +0 -5
- package/prisma/migrations/20260605063634_add_stripe_webhook_event_sync_lock/migration.sql +0 -14
- package/prisma/migrations/20260605100000_add_prod_indexes/migration.sql +0 -26
- package/prisma/migrations/20260608081404_add_kanban_statuses/migration.sql +0 -23
- package/prisma/migrations/20260611032723_add_calendar_feeds/migration.sql +0 -43
- package/prisma/migrations/20260611040000_add_calendar_feed_color/migration.sql +0 -2
- package/prisma/migrations/20260611050000_add_task_priority/migration.sql +0 -14
- package/prisma/migrations/20260612060000_add_critical_priority/migration.sql +0 -2
- package/prisma/migrations/20260613090000_add_backup_settings/migration.sql +0 -25
- package/prisma/migrations/20260614160000_add_feedback/migration.sql +0 -20
- package/prisma/migrations/20260614210000_add_2fa/migration.sql +0 -4
- package/prisma/migrations/migration_lock.toml +0 -3
- package/prisma/schema.prisma +0 -457
- package/public/Logo_icon.svg +0 -1
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon.svg +0 -4
- package/public/icon_dark.svg +0 -1
- package/public/knotpad_icon.svg +0 -1
- package/public/knotpad_logo_full.svg +0 -1
- package/public/manifest.json +0 -14
- package/public/next.svg +0 -1
- package/public/sw.js +0 -137
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- package/tsconfig.json +0 -35
package/app/(app)/layout.tsx
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { IconBar } from "@/components/layout/icon-bar";
|
|
5
|
-
import { NotesPanel } from "@/components/layout/notes-panel";
|
|
6
|
-
import { ProBanner } from "@/components/layout/pro-banner";
|
|
7
|
-
import { AppShell } from "@/components/layout/app-shell";
|
|
8
|
-
import { PaywallScreen } from "@/components/layout/paywall-screen";
|
|
9
|
-
import { SessionProvider } from "next-auth/react";
|
|
10
|
-
import { getActiveWorkspaceId, listUserWorkspaces } from "@/lib/workspace";
|
|
11
|
-
import { getKanbanStatuses } from "@/lib/kanban-status";
|
|
12
|
-
import { isWorkspacePro } from "@/lib/license";
|
|
13
|
-
import { KanbanStatusProvider } from "@/components/kanban/kanban-status-context";
|
|
14
|
-
import { CommandPaletteProvider } from "@/components/command/command-palette";
|
|
15
|
-
import { BackupScheduler } from "@/components/layout/backup-scheduler";
|
|
16
|
-
|
|
17
|
-
// Cap the shell's eager note/task fetch so navigation stays fast at scale.
|
|
18
|
-
const LAYOUT_LIST_CAP = 500;
|
|
19
|
-
|
|
20
|
-
const IS_CLOUD = process.env.IS_CLOUD === "true";
|
|
21
|
-
|
|
22
|
-
export default async function AppLayout({ children }: { children: React.ReactNode }) {
|
|
23
|
-
const session = await auth();
|
|
24
|
-
if (!session) redirect("/login");
|
|
25
|
-
|
|
26
|
-
const [workspaceId, allWorkspaces] = await Promise.all([
|
|
27
|
-
getActiveWorkspaceId(session.user.id),
|
|
28
|
-
listUserWorkspaces(session.user.id),
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
if (!workspaceId) {
|
|
32
|
-
// On cloud, a user with no Pro-accessible workspace isn't missing
|
|
33
|
-
// membership — they're FREE-only and must upgrade or be invited to a team.
|
|
34
|
-
if (IS_CLOUD) return <PaywallScreen />;
|
|
35
|
-
redirect("/login");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const workspaces = IS_CLOUD ? allWorkspaces.filter((w) => w.isPro) : allWorkspaces;
|
|
39
|
-
|
|
40
|
-
const member = await prisma.workspaceMember.findFirst({
|
|
41
|
-
where: { userId: session.user.id, workspaceId },
|
|
42
|
-
include: {
|
|
43
|
-
workspace: {
|
|
44
|
-
include: {
|
|
45
|
-
notes: {
|
|
46
|
-
orderBy: { updatedAt: "desc" },
|
|
47
|
-
// Bounded so the shell (and command-palette index) stay fast on
|
|
48
|
-
// large workspaces. Matches the cap on GET /api/notes.
|
|
49
|
-
take: LAYOUT_LIST_CAP,
|
|
50
|
-
include: {
|
|
51
|
-
folder: { select: { id: true, name: true } },
|
|
52
|
-
_count: { select: { tasks: true } },
|
|
53
|
-
tasks: {
|
|
54
|
-
where: { status: { in: ["CLAIMED", "IN_PROGRESS", "REVIEW"] } },
|
|
55
|
-
select: { id: true },
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
folders: { orderBy: { name: "asc" }, select: { id: true, name: true } },
|
|
60
|
-
tasks: {
|
|
61
|
-
orderBy: { updatedAt: "desc" },
|
|
62
|
-
take: LAYOUT_LIST_CAP,
|
|
63
|
-
select: {
|
|
64
|
-
id: true,
|
|
65
|
-
title: true,
|
|
66
|
-
status: true,
|
|
67
|
-
note: { select: { id: true, title: true } },
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
if (!member) redirect("/login");
|
|
76
|
-
|
|
77
|
-
const notes = member.workspace.notes.map((n) => ({
|
|
78
|
-
id: n.id,
|
|
79
|
-
title: n.title,
|
|
80
|
-
taskCount: n._count.tasks,
|
|
81
|
-
activeTaskCount: n.tasks.length,
|
|
82
|
-
isLocked: n.isLocked,
|
|
83
|
-
folderId: n.folderId,
|
|
84
|
-
}));
|
|
85
|
-
const folders = member.workspace.folders;
|
|
86
|
-
const tasks = member.workspace.tasks.map((t) => ({
|
|
87
|
-
id: t.id,
|
|
88
|
-
title: t.title,
|
|
89
|
-
status: t.status,
|
|
90
|
-
note: t.note,
|
|
91
|
-
}));
|
|
92
|
-
const latestNoteId = notes[0]?.id;
|
|
93
|
-
const notesHref = latestNoteId ? `/notes/${latestNoteId}` : "/notes";
|
|
94
|
-
|
|
95
|
-
const commandData = {
|
|
96
|
-
notes: notes.map((n) => ({ id: n.id, title: n.title })),
|
|
97
|
-
tasks: tasks.map((t) => ({ id: t.id, title: t.title, note: t.note })),
|
|
98
|
-
folders,
|
|
99
|
-
notesHref,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const kanbanStatuses = await getKanbanStatuses(workspaceId);
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<SessionProvider>
|
|
106
|
-
<BackupScheduler />
|
|
107
|
-
<KanbanStatusProvider statuses={kanbanStatuses}>
|
|
108
|
-
<CommandPaletteProvider data={commandData}>
|
|
109
|
-
<div className="flex h-full flex-col overflow-hidden">
|
|
110
|
-
<ProBanner initialIsPro={isWorkspacePro(member.workspace)} />
|
|
111
|
-
<div className="flex flex-1 min-h-0 overflow-hidden">
|
|
112
|
-
<IconBar latestNoteId={latestNoteId} />
|
|
113
|
-
<AppShell panel={<NotesPanel notes={notes} folders={folders} tasks={tasks} workspaces={workspaces} activeWorkspaceId={workspaceId} />}>
|
|
114
|
-
{children}
|
|
115
|
-
</AppShell>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</CommandPaletteProvider>
|
|
119
|
-
</KanbanStatusProvider>
|
|
120
|
-
</SessionProvider>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Skeleton } from "@/components/ui/skeleton";
|
|
2
|
-
|
|
3
|
-
export default function ListLoading() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="flex flex-1 flex-col overflow-hidden">
|
|
6
|
-
<div className="border-b border-zinc-800 px-3 py-3 md:px-6">
|
|
7
|
-
<Skeleton className="h-4 w-24" />
|
|
8
|
-
</div>
|
|
9
|
-
<div className="flex flex-col gap-4 px-3 py-4 md:px-6">
|
|
10
|
-
{[0, 1, 2].map((g) => (
|
|
11
|
-
<div key={g} className="space-y-2">
|
|
12
|
-
<Skeleton className="h-3 w-20" />
|
|
13
|
-
{[0, 1, 2].map((r) => (
|
|
14
|
-
<Skeleton key={r} className="h-10 w-full" />
|
|
15
|
-
))}
|
|
16
|
-
</div>
|
|
17
|
-
))}
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
);
|
|
21
|
-
}
|
package/app/(app)/list/page.tsx
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { getActiveWorkspaceId } from "@/lib/workspace";
|
|
5
|
-
import { TaskList } from "@/components/tasks/task-list";
|
|
6
|
-
import { TaskListFilters } from "@/components/tasks/task-list-filters";
|
|
7
|
-
import { TaskStatus } from "@/app/generated/prisma/client";
|
|
8
|
-
|
|
9
|
-
export default async function ListPage({
|
|
10
|
-
searchParams,
|
|
11
|
-
}: {
|
|
12
|
-
searchParams: Promise<{
|
|
13
|
-
note?: string;
|
|
14
|
-
sort?: string;
|
|
15
|
-
folder?: string;
|
|
16
|
-
status?: string;
|
|
17
|
-
assigneeType?: string;
|
|
18
|
-
overdue?: string;
|
|
19
|
-
hasDueDate?: string;
|
|
20
|
-
}>;
|
|
21
|
-
}) {
|
|
22
|
-
const session = await auth();
|
|
23
|
-
if (!session) redirect("/login");
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
note: filterNoteId,
|
|
27
|
-
sort = "status",
|
|
28
|
-
folder: filterFolderId,
|
|
29
|
-
status: statusFilter,
|
|
30
|
-
assigneeType: assigneeFilter,
|
|
31
|
-
overdue: overdueOnly,
|
|
32
|
-
hasDueDate: hasDueDateFilter,
|
|
33
|
-
} = await searchParams;
|
|
34
|
-
|
|
35
|
-
const workspaceId = await getActiveWorkspaceId(session.user.id);
|
|
36
|
-
if (!workspaceId) redirect("/login");
|
|
37
|
-
|
|
38
|
-
// Build status filter condition
|
|
39
|
-
const statusFilterCondition = statusFilter
|
|
40
|
-
? { status: { in: statusFilter.split(",") as TaskStatus[] } }
|
|
41
|
-
: {};
|
|
42
|
-
|
|
43
|
-
// Build assignee filter condition
|
|
44
|
-
const assigneeFilterCondition = assigneeFilter
|
|
45
|
-
? { assigneeType: assigneeFilter as "HUMAN" | "AGENT" }
|
|
46
|
-
: {};
|
|
47
|
-
|
|
48
|
-
// Build overdue filter condition
|
|
49
|
-
const now = new Date();
|
|
50
|
-
const overdueFilterCondition = overdueOnly === "true"
|
|
51
|
-
? { dueDate: { lt: now }, NOT: { status: "DONE" as TaskStatus } }
|
|
52
|
-
: {};
|
|
53
|
-
|
|
54
|
-
// Build hasDueDate filter condition
|
|
55
|
-
const hasDueDateCondition = hasDueDateFilter === "true"
|
|
56
|
-
? { dueDate: { not: null } }
|
|
57
|
-
: {};
|
|
58
|
-
|
|
59
|
-
// Get folders for filter dropdown
|
|
60
|
-
const foldersPromise = prisma.folder.findMany({
|
|
61
|
-
where: { workspaceId },
|
|
62
|
-
select: { id: true, name: true },
|
|
63
|
-
orderBy: { name: "asc" },
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const [tasks, filterNote, folders] = await Promise.all([
|
|
67
|
-
prisma.task.findMany({
|
|
68
|
-
where: {
|
|
69
|
-
workspaceId,
|
|
70
|
-
...(filterNoteId && { noteId: filterNoteId }),
|
|
71
|
-
...(filterFolderId && { note: { folderId: filterFolderId } }),
|
|
72
|
-
...statusFilterCondition,
|
|
73
|
-
...assigneeFilterCondition,
|
|
74
|
-
...overdueFilterCondition,
|
|
75
|
-
...hasDueDateCondition,
|
|
76
|
-
},
|
|
77
|
-
orderBy: { createdAt: "desc" },
|
|
78
|
-
include: {
|
|
79
|
-
note: { select: { id: true, title: true, folder: { select: { id: true, name: true } } } },
|
|
80
|
-
assignee: { select: { id: true, name: true, email: true, image: true } },
|
|
81
|
-
},
|
|
82
|
-
}),
|
|
83
|
-
filterNoteId
|
|
84
|
-
? prisma.note.findFirst({ where: { id: filterNoteId, workspaceId }, select: { title: true } })
|
|
85
|
-
: Promise.resolve(null),
|
|
86
|
-
foldersPromise,
|
|
87
|
-
]);
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<div className="flex flex-1 flex-col overflow-hidden">
|
|
91
|
-
<div className="flex items-center justify-between border-b border-zinc-800 px-3 md:px-6 py-3">
|
|
92
|
-
<div className="flex items-center gap-2">
|
|
93
|
-
<h1 className="text-sm font-semibold text-zinc-300">
|
|
94
|
-
{filterNoteId || filterFolderId ? "Filtered tasks" : "All Tasks"}
|
|
95
|
-
</h1>
|
|
96
|
-
{filterNote && (
|
|
97
|
-
<span className="rounded-full bg-zinc-800 px-2 py-0.5 text-xs text-zinc-400">
|
|
98
|
-
{filterNote.title}
|
|
99
|
-
</span>
|
|
100
|
-
)}
|
|
101
|
-
</div>
|
|
102
|
-
<div className="flex items-center gap-2 text-xs text-zinc-500">
|
|
103
|
-
<span>Sort:</span>
|
|
104
|
-
{(() => {
|
|
105
|
-
const params = new URLSearchParams();
|
|
106
|
-
if (filterNoteId) params.set("note", filterNoteId);
|
|
107
|
-
if (filterFolderId) params.set("folder", filterFolderId);
|
|
108
|
-
const baseQuery = params.toString();
|
|
109
|
-
return (
|
|
110
|
-
<>
|
|
111
|
-
<a href={`?${baseQuery ? baseQuery + "&" : ""}sort=status`}
|
|
112
|
-
className={sort === "status" ? "text-zinc-300" : "hover:text-zinc-300"}>Status</a>
|
|
113
|
-
<span>/</span>
|
|
114
|
-
<a href={`?${baseQuery ? baseQuery + "&" : ""}sort=note`}
|
|
115
|
-
className={sort === "note" ? "text-zinc-300" : "hover:text-zinc-300"}>Note</a>
|
|
116
|
-
</>
|
|
117
|
-
);
|
|
118
|
-
})()}
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
{/* Filter Bar - Tasks 2.4.1-2.4.5 */}
|
|
123
|
-
<TaskListFilters
|
|
124
|
-
folders={folders}
|
|
125
|
-
filterFolderId={filterFolderId}
|
|
126
|
-
statusFilter={statusFilter}
|
|
127
|
-
assigneeFilter={assigneeFilter}
|
|
128
|
-
overdueOnly={overdueOnly}
|
|
129
|
-
hasDueDateFilter={hasDueDateFilter}
|
|
130
|
-
/>
|
|
131
|
-
|
|
132
|
-
<div className="flex-1 overflow-y-auto">
|
|
133
|
-
<TaskList tasks={tasks} groupBy={sort === "note" ? "note" : "status"} />
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
}
|
package/app/(app)/loading.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import Image from "next/image";
|
|
2
|
-
|
|
3
|
-
export default function Loading() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="flex flex-1 flex-col items-center justify-center gap-4 px-6">
|
|
6
|
-
<Image
|
|
7
|
-
src="/knotpad_logo_full.svg"
|
|
8
|
-
alt="Knotpad"
|
|
9
|
-
width={200}
|
|
10
|
-
height={60}
|
|
11
|
-
className="h-16 w-auto"
|
|
12
|
-
priority
|
|
13
|
-
/>
|
|
14
|
-
<p className="text-sm text-zinc-500">Welcome to Knotpad</p>
|
|
15
|
-
<div className="h-6 w-6 animate-spin rounded-full border-2 border-zinc-700 border-t-zinc-400" />
|
|
16
|
-
</div>
|
|
17
|
-
);
|
|
18
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect, notFound } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { decryptContent } from "@/lib/note-crypto";
|
|
5
|
-
import { getActiveWorkspaceId } from "@/lib/workspace";
|
|
6
|
-
import { NoteEditor } from "@/components/notes/note-editor";
|
|
7
|
-
|
|
8
|
-
export default async function NotePage({
|
|
9
|
-
params,
|
|
10
|
-
searchParams,
|
|
11
|
-
}: {
|
|
12
|
-
params: Promise<{ noteId: string }>;
|
|
13
|
-
searchParams: Promise<{ q?: string }>;
|
|
14
|
-
}) {
|
|
15
|
-
const session = await auth();
|
|
16
|
-
if (!session) redirect("/login");
|
|
17
|
-
|
|
18
|
-
const { noteId } = await params;
|
|
19
|
-
const { q: highlight } = await searchParams;
|
|
20
|
-
|
|
21
|
-
const activeWs = await getActiveWorkspaceId(session.user.id);
|
|
22
|
-
const member = await prisma.workspaceMember.findFirst({
|
|
23
|
-
where: { userId: session.user.id, workspaceId: activeWs ?? undefined },
|
|
24
|
-
include: {
|
|
25
|
-
workspace: {
|
|
26
|
-
include: {
|
|
27
|
-
members: {
|
|
28
|
-
include: { user: { select: { id: true, name: true, email: true } } },
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
if (!member) redirect("/login");
|
|
35
|
-
|
|
36
|
-
const [note, allNotes, allTasks] = await Promise.all([
|
|
37
|
-
prisma.note.findFirst({
|
|
38
|
-
where: { id: noteId, workspaceId: member.workspaceId },
|
|
39
|
-
include: {
|
|
40
|
-
folder: { select: { id: true, name: true } },
|
|
41
|
-
tasks: {
|
|
42
|
-
orderBy: { createdAt: "asc" },
|
|
43
|
-
include: { assignee: { select: { id: true, name: true, email: true } } },
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
}),
|
|
47
|
-
prisma.note.findMany({
|
|
48
|
-
where: { workspaceId: member.workspaceId },
|
|
49
|
-
orderBy: { updatedAt: "desc" },
|
|
50
|
-
select: { id: true, title: true },
|
|
51
|
-
}),
|
|
52
|
-
prisma.task.findMany({
|
|
53
|
-
where: { workspaceId: member.workspaceId, status: { not: "DONE" } },
|
|
54
|
-
orderBy: { createdAt: "desc" },
|
|
55
|
-
take: 200,
|
|
56
|
-
select: { id: true, title: true, note: { select: { title: true } } },
|
|
57
|
-
}),
|
|
58
|
-
]);
|
|
59
|
-
|
|
60
|
-
if (!note) notFound();
|
|
61
|
-
|
|
62
|
-
const decryptedContent = await decryptContent(note.content, member.workspaceId);
|
|
63
|
-
|
|
64
|
-
const members = member.workspace.members
|
|
65
|
-
.map((m) => ({
|
|
66
|
-
handle: `@${(m.user.name ?? m.user.email ?? "user").toLowerCase().replace(/\s+/g, "")}`,
|
|
67
|
-
label: (m.user.name ?? m.user.email ?? "Unknown") + (m.userId === session.user.id ? " (you)" : ""),
|
|
68
|
-
isAgent: false,
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
const notesList = allNotes
|
|
72
|
-
.filter((n) => n.id !== noteId)
|
|
73
|
-
.map((n) => ({ id: n.id, title: n.title }));
|
|
74
|
-
|
|
75
|
-
const tasksList = allTasks.map((t) => ({
|
|
76
|
-
id: t.id,
|
|
77
|
-
title: t.title,
|
|
78
|
-
noteTitle: t.note.title,
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
// key by note id so the editor fully remounts (fresh state + flush of the
|
|
82
|
-
// previous note's pending save) when navigating between notes via tabs/links.
|
|
83
|
-
return <NoteEditor key={note.id} note={{ ...note, content: decryptedContent }} members={members} notesList={notesList} tasksList={tasksList} highlight={highlight} />;
|
|
84
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { getActiveWorkspaceId } from "@/lib/workspace";
|
|
5
|
-
import { NoteTabs } from "@/components/notes/note-tabs";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Wraps the notes routes with a browser-style tab strip. Only mounts on
|
|
9
|
-
* /notes/* so the board/list/calendar views stay tab-free.
|
|
10
|
-
*/
|
|
11
|
-
export default async function NotesLayout({ children }: { children: React.ReactNode }) {
|
|
12
|
-
const session = await auth();
|
|
13
|
-
if (!session) redirect("/login");
|
|
14
|
-
|
|
15
|
-
const workspaceId = await getActiveWorkspaceId(session.user.id);
|
|
16
|
-
const notes = workspaceId
|
|
17
|
-
? await prisma.note.findMany({
|
|
18
|
-
where: { workspaceId },
|
|
19
|
-
orderBy: { updatedAt: "desc" },
|
|
20
|
-
select: { id: true, title: true },
|
|
21
|
-
})
|
|
22
|
-
: [];
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div className="flex flex-1 flex-col overflow-hidden">
|
|
26
|
-
<NoteTabs notes={notes} />
|
|
27
|
-
<div className="flex flex-1 flex-col overflow-hidden">{children}</div>
|
|
28
|
-
</div>
|
|
29
|
-
);
|
|
30
|
-
}
|
package/app/(app)/notes/page.tsx
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { getActiveWorkspaceId } from "@/lib/workspace";
|
|
5
|
-
|
|
6
|
-
export default async function NotesHome() {
|
|
7
|
-
const session = await auth();
|
|
8
|
-
if (!session) redirect("/login");
|
|
9
|
-
|
|
10
|
-
const workspaceId = await getActiveWorkspaceId(session.user.id);
|
|
11
|
-
const latestNote = workspaceId
|
|
12
|
-
? await prisma.note.findFirst({
|
|
13
|
-
where: { workspaceId },
|
|
14
|
-
orderBy: { updatedAt: "desc" },
|
|
15
|
-
select: { id: true },
|
|
16
|
-
})
|
|
17
|
-
: null;
|
|
18
|
-
if (latestNote) redirect(`/notes/${latestNote.id}`);
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className="flex flex-1 flex-col items-center justify-center gap-4 text-center px-6">
|
|
22
|
-
<div className="max-w-xs space-y-2">
|
|
23
|
-
<h2 className="text-lg font-semibold text-zinc-300">No notes yet</h2>
|
|
24
|
-
<p className="text-sm text-zinc-500">
|
|
25
|
-
Click the{" "}
|
|
26
|
-
<span className="font-mono text-xs bg-zinc-800 px-1.5 py-0.5 rounded text-zinc-400">
|
|
27
|
-
+
|
|
28
|
-
</span>{" "}
|
|
29
|
-
button in the panel to create your first note.
|
|
30
|
-
</p>
|
|
31
|
-
<p className="text-xs text-zinc-600 mt-3">
|
|
32
|
-
Write{" "}
|
|
33
|
-
<code className="bg-zinc-800 px-1 rounded text-zinc-400">- [ ] task @you</code>{" "}
|
|
34
|
-
inside a note to create tasks automatically.
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
package/app/(app)/page.tsx
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { AgentTokenSettings } from "@/components/settings/agent-token-settings";
|
|
5
|
-
import { getMcpUrl } from "@/lib/mcp-url";
|
|
6
|
-
|
|
7
|
-
export default async function AgentTokenPage() {
|
|
8
|
-
const session = await auth();
|
|
9
|
-
if (!session) redirect("/login");
|
|
10
|
-
|
|
11
|
-
const memberships = await prisma.workspaceMember.findMany({
|
|
12
|
-
where: { userId: session.user.id, revokedAt: null },
|
|
13
|
-
include: { workspace: true },
|
|
14
|
-
orderBy: { joinedAt: "asc" },
|
|
15
|
-
});
|
|
16
|
-
if (!memberships.length) redirect("/login");
|
|
17
|
-
|
|
18
|
-
const tokens = await prisma.mcpToken.findMany({
|
|
19
|
-
where: {
|
|
20
|
-
userId: session.user.id,
|
|
21
|
-
workspaceId: { in: memberships.map((m) => m.workspaceId) },
|
|
22
|
-
revokedAt: null,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const tokenByWorkspace = Object.fromEntries(tokens.map((t) => [t.workspaceId, t]));
|
|
27
|
-
|
|
28
|
-
const workspaces = memberships.map((m) => {
|
|
29
|
-
const t = tokenByWorkspace[m.workspaceId] ?? null;
|
|
30
|
-
return {
|
|
31
|
-
id: m.workspaceId,
|
|
32
|
-
name: m.workspace.name,
|
|
33
|
-
slug: m.workspace.slug,
|
|
34
|
-
existingToken: t
|
|
35
|
-
? { id: t.id, alias: t.alias, lastUsed: t.lastUsed?.toISOString() ?? null }
|
|
36
|
-
: null,
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
|
|
41
|
-
const mcpUrl = getMcpUrl(appUrl, process.env.NEXT_PUBLIC_MCP_URL);
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<div className="max-w-2xl space-y-6">
|
|
45
|
-
<div>
|
|
46
|
-
<h2 className="text-base font-semibold text-zinc-200">Agent Token</h2>
|
|
47
|
-
<p className="text-sm text-zinc-500 mt-1">
|
|
48
|
-
Generate a personal MCP token, then reuse the setup templates below whenever you reconnect an AI agent.
|
|
49
|
-
</p>
|
|
50
|
-
</div>
|
|
51
|
-
<AgentTokenSettings
|
|
52
|
-
workspaces={workspaces}
|
|
53
|
-
mcpUrl={mcpUrl}
|
|
54
|
-
appUrl={appUrl}
|
|
55
|
-
userId={session.user.id}
|
|
56
|
-
/>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { BackupRestoreSettings } from "@/components/settings/backup-restore-settings";
|
|
5
|
-
|
|
6
|
-
export default async function BackupPage() {
|
|
7
|
-
const session = await auth();
|
|
8
|
-
if (!session) redirect("/login");
|
|
9
|
-
|
|
10
|
-
const member = await prisma.workspaceMember.findFirst({
|
|
11
|
-
where: { userId: session.user.id, revokedAt: null },
|
|
12
|
-
include: {
|
|
13
|
-
workspace: {
|
|
14
|
-
include: {
|
|
15
|
-
backupSettings: true,
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
if (!member) redirect("/login");
|
|
21
|
-
|
|
22
|
-
const initialSettings = member.workspace.backupSettings
|
|
23
|
-
? {
|
|
24
|
-
scheduleEnabled: member.workspace.backupSettings.scheduleEnabled,
|
|
25
|
-
scheduleCadence: member.workspace.backupSettings.scheduleCadence,
|
|
26
|
-
destinationPath: member.workspace.backupSettings.destinationPath,
|
|
27
|
-
includeMarkdownZip: member.workspace.backupSettings.includeMarkdownZip,
|
|
28
|
-
lastBackupAt: member.workspace.backupSettings.lastBackupAt?.toISOString() ?? null,
|
|
29
|
-
lastBackupStatus: member.workspace.backupSettings.lastBackupStatus,
|
|
30
|
-
lastBackupError: member.workspace.backupSettings.lastBackupError,
|
|
31
|
-
}
|
|
32
|
-
: null;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className="max-w-2xl space-y-6">
|
|
36
|
-
<div>
|
|
37
|
-
<h2 className="text-base font-semibold text-zinc-200">Backup & Restore</h2>
|
|
38
|
-
<p className="mt-1 text-sm text-zinc-500">
|
|
39
|
-
Configure local backups, download exports, and restore from a backup file.
|
|
40
|
-
</p>
|
|
41
|
-
</div>
|
|
42
|
-
<BackupRestoreSettings
|
|
43
|
-
isCloudWorkspace={member.workspace.isCloud}
|
|
44
|
-
isOwner={member.role !== "MEMBER"}
|
|
45
|
-
initialSettings={initialSettings}
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { BillingDashboard } from "@/components/billing/billing-dashboard";
|
|
5
|
-
|
|
6
|
-
export default async function BillingPage() {
|
|
7
|
-
const session = await auth();
|
|
8
|
-
if (!session) redirect("/login");
|
|
9
|
-
|
|
10
|
-
const member = await prisma.workspaceMember.findFirst({
|
|
11
|
-
where: { userId: session.user.id, revokedAt: null },
|
|
12
|
-
include: {
|
|
13
|
-
workspace: {
|
|
14
|
-
include: { _count: { select: { members: { where: { revokedAt: null } } } } },
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
orderBy: { joinedAt: "asc" },
|
|
18
|
-
});
|
|
19
|
-
if (!member) redirect("/login");
|
|
20
|
-
|
|
21
|
-
const { workspace } = member;
|
|
22
|
-
|
|
23
|
-
// A guest (local, no-password account) must create a real account before they
|
|
24
|
-
// can pay — surfaced as a claim step in the dashboard.
|
|
25
|
-
const me = await prisma.user.findUnique({
|
|
26
|
-
where: { id: session.user.id },
|
|
27
|
-
select: { email: true, passwordHash: true },
|
|
28
|
-
});
|
|
29
|
-
const isGuest = !!me?.email?.endsWith("@local.brief") && !me?.passwordHash;
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="max-w-lg space-y-6">
|
|
33
|
-
<div>
|
|
34
|
-
<h2 className="text-base font-semibold text-zinc-200">Billing</h2>
|
|
35
|
-
<p className="text-sm text-zinc-500 mt-1">
|
|
36
|
-
Manage your subscription and seats.
|
|
37
|
-
</p>
|
|
38
|
-
</div>
|
|
39
|
-
<BillingDashboard
|
|
40
|
-
isPro={workspace.isPro}
|
|
41
|
-
isOwner={member.role === "OWNER"}
|
|
42
|
-
memberCount={workspace._count.members}
|
|
43
|
-
seatCount={workspace.seatCount}
|
|
44
|
-
stripeId={workspace.stripeId}
|
|
45
|
-
planType={workspace.planType}
|
|
46
|
-
licenseType={workspace.licenseType}
|
|
47
|
-
workspaceType={workspace.type}
|
|
48
|
-
workspaceId={workspace.id}
|
|
49
|
-
isGuest={isGuest}
|
|
50
|
-
/>
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { auth } from "@/auth";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
import { prisma } from "@/lib/prisma";
|
|
4
|
-
import { CalendarFeedsSettings } from "@/components/settings/calendar-feeds-settings";
|
|
5
|
-
import { CalendarGeneralSettings } from "@/components/settings/calendar-general-settings";
|
|
6
|
-
|
|
7
|
-
export default async function CalendarSettingsPage() {
|
|
8
|
-
const session = await auth();
|
|
9
|
-
if (!session) redirect("/login");
|
|
10
|
-
|
|
11
|
-
const feeds = await prisma.calendarFeed.findMany({
|
|
12
|
-
where: { userId: session.user.id },
|
|
13
|
-
orderBy: { createdAt: "asc" },
|
|
14
|
-
select: { id: true, label: true, color: true, enabled: true, lastFetchedAt: true, lastError: true },
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<div className="max-w-lg space-y-8">
|
|
19
|
-
<div>
|
|
20
|
-
<h2 className="text-base font-semibold text-zinc-200">Calendars</h2>
|
|
21
|
-
<p className="text-sm text-zinc-500 mt-1">
|
|
22
|
-
Connect read-only calendar subscriptions (Google, Apple, etc.) to see your busy times
|
|
23
|
-
on the Knotpad calendar. This is import-only — Knotpad never edits these calendars.
|
|
24
|
-
</p>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<CalendarGeneralSettings />
|
|
28
|
-
|
|
29
|
-
<CalendarFeedsSettings
|
|
30
|
-
feeds={feeds.map((f) => ({
|
|
31
|
-
id: f.id,
|
|
32
|
-
label: f.label,
|
|
33
|
-
color: f.color,
|
|
34
|
-
enabled: f.enabled,
|
|
35
|
-
lastFetchedAt: f.lastFetchedAt?.toISOString() ?? null,
|
|
36
|
-
lastError: f.lastError,
|
|
37
|
-
}))}
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|