diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..53fe52e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,134 @@ +name: CI — NoteLett + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + backend: + name: Backend — typecheck + test + build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Checkout common-plat (for @bytelyst/* packages) + uses: actions/checkout@v4 + with: + repository: saravanakumardb1/learning_ai_common_plat + path: learning_ai_common_plat + token: ${{ secrets.GH_PAT }} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Enable pnpm + run: corepack enable + + - name: Build @bytelyst/* packages + working-directory: learning_ai_common_plat + run: | + pnpm install --frozen-lockfile + pnpm build + + - name: Install workspace dependencies + run: pnpm install --frozen-lockfile + + - name: Backend lint + run: pnpm --filter @notelett/backend run lint + + - name: Backend typecheck + run: pnpm --filter @notelett/backend run typecheck + + - name: Backend tests + run: pnpm --filter @notelett/backend run test + env: + DB_PROVIDER: memory + JWT_SECRET: ci-test-secret-at-least-32-characters-long + + - name: Backend build + run: pnpm --filter @notelett/backend run build + + web: + name: Web — typecheck + test + build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Checkout common-plat (for @bytelyst/* packages) + uses: actions/checkout@v4 + with: + repository: saravanakumardb1/learning_ai_common_plat + path: learning_ai_common_plat + token: ${{ secrets.GH_PAT }} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Enable pnpm + run: corepack enable + + - name: Build @bytelyst/* packages + working-directory: learning_ai_common_plat + run: | + pnpm install --frozen-lockfile + pnpm build + + - name: Install workspace dependencies + run: pnpm install --frozen-lockfile + + - name: Web lint + run: pnpm --filter @notelett/web run lint + + - name: Web typecheck + run: pnpm --filter @notelett/web run typecheck + + - name: Web tests + run: pnpm --filter @notelett/web run test + + - name: Web build + run: pnpm --filter @notelett/web run build + + mobile: + name: Mobile — typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Checkout common-plat (for @bytelyst/* packages) + uses: actions/checkout@v4 + with: + repository: saravanakumardb1/learning_ai_common_plat + path: learning_ai_common_plat + token: ${{ secrets.GH_PAT }} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + + - name: Enable pnpm + run: corepack enable + + - name: Build @bytelyst/* packages + working-directory: learning_ai_common_plat + run: | + pnpm install --frozen-lockfile + pnpm build + + - name: Install workspace dependencies + run: pnpm install --frozen-lockfile + + - run: pnpm --filter @notelett/mobile run typecheck diff --git a/docker-compose.yml b/docker-compose.yml index a82c27d..9d9de28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: - NODE_ENV=production - PORT=4016 - HOST=0.0.0.0 + - PRODUCT_ID=notelett + - SERVICE_NAME=notelett-backend - JWT_SECRET=${JWT_SECRET:-dev-secret-change-me} - COSMOS_ENDPOINT=${COSMOS_ENDPOINT:-} - COSMOS_KEY=${COSMOS_KEY:-} @@ -17,6 +19,11 @@ services: - CORS_ORIGIN=${CORS_ORIGIN:-http://localhost:3000} - PLATFORM_SERVICE_URL=${PLATFORM_SERVICE_URL:-http://localhost:4003} - EXTRACTION_SERVICE_URL=${EXTRACTION_SERVICE_URL:-http://localhost:4005} + - MCP_SERVER_URL=${MCP_SERVER_URL:-http://localhost:4007} + - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} + - FEATURE_FLAGS_ENABLED=${FEATURE_FLAGS_ENABLED:-false} + - FIELD_ENCRYPT_ENABLED=${FIELD_ENCRYPT_ENABLED:-false} + - FIELD_ENCRYPT_KEY_PROVIDER=${FIELD_ENCRYPT_KEY_PROVIDER:-memory} restart: unless-stopped healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:4016/health"] @@ -32,8 +39,13 @@ services: - "3000:3000" environment: - NODE_ENV=production - - NEXT_PUBLIC_BACKEND_URL=http://backend:4016 - - NEXT_PUBLIC_PLATFORM_URL=${PLATFORM_SERVICE_URL:-http://localhost:4003} + - NEXT_PUBLIC_PRODUCT_NAME=NoteLett + - NEXT_PUBLIC_PRODUCT_ID=notelett + - NEXT_PUBLIC_NOTES_API_URL=http://backend:4016/api + - NEXT_PUBLIC_PLATFORM_SERVICE_URL=${PLATFORM_SERVICE_URL:-http://localhost:4003}/api + - NEXT_PUBLIC_EXTRACTION_SERVICE_URL=${EXTRACTION_SERVICE_URL:-http://localhost:4005} + - NEXT_PUBLIC_DIAGNOSTICS_URL=${DIAGNOSTICS_URL:-http://localhost:3000} + - NEXT_PUBLIC_TELEMETRY_TRANSPORT=fetch depends_on: backend: condition: service_healthy diff --git a/web/.env.example b/web/.env.example index e1644b9..50d5df6 100644 --- a/web/.env.example +++ b/web/.env.example @@ -1,6 +1,7 @@ -NEXT_PUBLIC_PRODUCT_NAME=ByteLyst Agentic Notes -NEXT_PUBLIC_PRODUCT_ID=agentic-notes +NEXT_PUBLIC_PRODUCT_NAME=NoteLett +NEXT_PUBLIC_PRODUCT_ID=notelett NEXT_PUBLIC_PLATFORM_SERVICE_URL=http://localhost:4003/api NEXT_PUBLIC_NOTES_API_URL=http://localhost:4016/api +NEXT_PUBLIC_EXTRACTION_SERVICE_URL=http://localhost:4005 NEXT_PUBLIC_DIAGNOSTICS_URL=http://localhost:3000 NEXT_PUBLIC_TELEMETRY_TRANSPORT=fetch diff --git a/web/next.config.ts b/web/next.config.ts index a680de3..4fac420 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -26,14 +26,18 @@ const nextConfig: NextConfig = { transpilePackages: [ "@bytelyst/api-client", "@bytelyst/blob-client", + "@bytelyst/broadcast-client", "@bytelyst/dashboard-components", "@bytelyst/design-tokens", "@bytelyst/diagnostics-client", "@bytelyst/extraction", "@bytelyst/feature-flag-client", + "@bytelyst/feedback-client", "@bytelyst/kill-switch-client", + "@bytelyst/offline-queue", "@bytelyst/platform-client", "@bytelyst/react-auth", + "@bytelyst/survey-client", "@bytelyst/telemetry-client", ], webpack: (config) => { diff --git a/web/package.json b/web/package.json index 2212e91..d44ceef 100644 --- a/web/package.json +++ b/web/package.json @@ -22,7 +22,11 @@ "@bytelyst/kill-switch-client": "^0.1.0", "@bytelyst/platform-client": "^0.1.0", "@bytelyst/dashboard-components": "^0.1.0", + "@bytelyst/broadcast-client": "^0.1.0", "@bytelyst/extraction": "^0.1.0", + "@bytelyst/feedback-client": "^0.1.0", + "@bytelyst/offline-queue": "^0.1.0", + "@bytelyst/survey-client": "^0.1.0", "@tiptap/extension-placeholder": "^2.11.0", "@tiptap/pm": "^2.11.0", "@tiptap/react": "^2.11.0", diff --git a/web/src/app/(app)/layout.tsx b/web/src/app/(app)/layout.tsx index 9106211..4b57bd8 100644 --- a/web/src/app/(app)/layout.tsx +++ b/web/src/app/(app)/layout.tsx @@ -1,11 +1,15 @@ import type { ReactNode } from "react"; import { AuthGuard } from "@/components/AuthGuard"; import { KeyboardShortcuts } from "@/components/KeyboardShortcuts"; +import { BroadcastBanner } from "@/components/BroadcastBanner"; +import { SurveyBanner } from "@/components/SurveyBanner"; export default function ProductLayout({ children }: { children: ReactNode }) { return ( + +
{children}
); diff --git a/web/src/app/(app)/settings/page.tsx b/web/src/app/(app)/settings/page.tsx index 5233bf2..6cf44ae 100644 --- a/web/src/app/(app)/settings/page.tsx +++ b/web/src/app/(app)/settings/page.tsx @@ -1,18 +1,82 @@ "use client"; +import { useState } from "react"; import { useTheme } from "@/lib/use-theme"; +import { useAuth } from "@/lib/auth"; import { AppShell } from "@/components/AppShell"; +import { getFeedbackClient } from "@/lib/feedback-client"; +import { toast } from "@/lib/toast"; export default function SettingsPage() { const { theme, toggle } = useTheme(); + const { user, logout, changePassword, deleteAccount, isLoading, error, success, clearMessages } = useAuth(); + + const [feedbackTitle, setFeedbackTitle] = useState(""); + const [feedbackBody, setFeedbackBody] = useState(""); + const [feedbackType, setFeedbackType] = useState<"bug" | "feature" | "praise" | "other">("bug"); + const [submittingFeedback, setSubmittingFeedback] = useState(false); + + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + + async function handleSubmitFeedback() { + if (!feedbackTitle.trim()) return; + setSubmittingFeedback(true); + try { + await getFeedbackClient().submitWithScreenshot({ + type: feedbackType, + title: feedbackTitle.trim(), + body: feedbackBody.trim() || undefined, + platform: "web", + }); + toast.success("Feedback submitted"); + setFeedbackTitle(""); + setFeedbackBody(""); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Failed to submit feedback"); + } finally { + setSubmittingFeedback(false); + } + } + + async function handleChangePassword(e: React.FormEvent) { + e.preventDefault(); + clearMessages(); + const ok = await changePassword(currentPassword, newPassword); + if (ok) { + setCurrentPassword(""); + setNewPassword(""); + toast.success("Password changed"); + } + } + + async function handleDeleteAccount() { + const pw = prompt("Enter your password to confirm account deletion:"); + if (!pw) return; + const ok = await deleteAccount(pw); + if (ok) toast.success("Account deleted"); + } return ( Auth fallback active} + description="Account, preferences, feedback, and session management." + actions={ + + } > -
+
+ {/* Profile */} +
+ Profile +
+ {user?.name ?? "—"} · {user?.email ?? "—"} · {user?.role ?? "—"} +
+
+ + {/* Appearance */}
Appearance @@ -21,32 +85,56 @@ export default function SettingsPage() { +
+ + {/* Change password */} +
+ Change password + {error &&
{error}
} + {success &&
{success}
} +
+ setCurrentPassword(e.target.value)} required /> + setNewPassword(e.target.value)} required /> + +
+
+ + {/* Danger zone */} +
+ Danger zone +
-
-
- Authentication -
- Initial shell uses a demo session fallback until platform auth contracts are finalized. -
-
-
- Telemetry & diagnostics -
- Both clients are initialized on app boot with best-effort defaults and no hard dependency on backend readiness. -
-
-
- Deferred configuration -
- Feature flags, blob upload policies, saved views, and extraction preferences remain follow-up work. -
-
+ {/* Feedback */} +
+ Send feedback +
+ + setFeedbackTitle(e.target.value)} /> +
+