Compare commits

...
Author SHA1 Message Date
Hanzo Dev 5981dca439 ci(docker): reduce to sync-notice — native pipeline is .hanzo/workflows/deploy.yml
Image build+push+deploy now runs in-cluster (act_runner + BuildKit → operator).
GitHub is a mirror; this workflow is retained only as a manual sync notice.
2026-07-24 15:14:30 -07:00
Hanzo Dev af73ce21fe ci(deploy): add native Hanzo deploy pipeline (BuildKit → ghcr → operator patch)
Native flow: Hanzo Git push → act_runner → BuildKit builds Dockerfile →
ghcr.io/hanzoai/dataroom:<sha> → kubectl patch app dataroom → operator reconcile.
GitHub Actions reduced to sync-only.
2026-07-24 15:14:20 -07:00
1b4bb4830b feat(goja): self-contained goja bundle — fold dataroom into hanzoai/cloud (#101) (#6)
* feat(goja): self-contained goja bundle — fold dataroom into hanzoai/cloud (#101)

Extract the dataroom business logic into a self-contained, ESM-free goja bundle
(goja/bundle.js) exposing globalThis.handle(req), authored to run verbatim inside
the unified hanzoai/cloud binary (HIP-0106, task #101 / epic #96) — the same
in-process pattern @hanzo/plans and @hanzo/pricing use.

The complete flow — documents (metadata; bytes on the cloud object-storage seam),
data rooms + membership, shareable links (email/allow-list gate + bcrypt password
+ expiry + download toggle), viewers, per-page view analytics — is re-expressed as
a route table over injected host functions (globalThis.db.query/exec bound
per-tenant to Base/SQLite, globalThis.crypto bcrypt helpers). The Prisma models
become CREATE TABLE statements in the bundle's migrate route. No Postgres, no
Next.js; the standalone pod is retired.

cloud/clients/dataroom go:embeds a byte-identical vendored copy (task mandates
go:embed here since this repo is a TS app, not a Go module).

Claude-Session: https://claude.ai/code/session_016yg7GPhYdWCh9vpp4HEwLZ

* goja: run on the shared clients/gojabase RW-Base binding (#101)

Rework the bundle onto the reusable clients/gojabase host contract (the RW-Base
goja binding captable pilots, esign reuses) instead of a bespoke DB binding — one
binding, not two. The bundle now calls __db.query/__db.exec, __newId, __now and
__bcrypt.hash/verify (the leaf's bcrypt HostFn), and handle({route,params,query,
orgId,body}); the per-tenant schema (DDL) moves to the Go leaf where gojabase runs
it on first open, and each dispatch is one per-tenant transaction (commits iff
status<400). No behaviour change to the dataroom flow; README updated.

Claude-Session: https://claude.ai/code/session_016yg7GPhYdWCh9vpp4HEwLZ

---------

Co-authored-by: hanzo-dev <dev@hanzo.ai>
2026-07-10 03:06:43 -07:00
0e31093a74 kv: harden in-process backend — atomic getdel, active expiry, multi-replica doc (#5)
Three RED-blessed non-blocking follow-ups on the KV-optional work.

1. Drop the non-atomic getdel() shim in lib/redis.ts. It shadowed BOTH backends'
   native atomic GETDEL with a get->del pipeline (other commands interleave
   between GET and DEL), reintroducing the one-time login-code double-use race
   that fetchAndDeleteLoginCodeData relies on GETDEL to prevent. ioredis maps
   getdel to the single-round-trip Redis GETDEL command; MemoryKV.getdel now
   reads+deletes in one un-yielded step (was `await this.get()` then delete — the
   await yielded the event loop, letting two racers both observe the value).

2. Bound MemoryKV growth. Lazy on-read eviction never fires for write-once-
   never-read keys (rl:<ip> rate-limit counters, one-shot tokens), so the map
   grew unbounded until the pod OOMKilled. Add sweepExpired() (one O(n) active-
   expire pass) run on an unref'd 60s timer, started by lib/redis.ts for the
   server singleton only (edge-gated; no test imports that module, so unit runs
   stay timer-free). MemoryKV stays pure (zero imports).

3. Document in .env.example that re-enabling multi-replica REQUIRES KV_URL
   (single-replica-by-construction otherwise: SQLite-on-RWO + in-process KV).
   The one-time non-silent in-process warning already exists in lib/kv/select.ts;
   no metrics surface exists, so log + doc is sufficient.

Tests: +3 cases (getdel atomicity under concurrent race; 100k-key sweep reclaims
to 0; sweep retains live/no-TTL keys). 28 pass, 3 skip (select.ts skips without
ioredis in a bare checkout), 0 fail.

Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-07-07 16:08:16 -07:00
4ed98334f0 KV-optional: run WITH and WITHOUT external Hanzo KV (#4)
* KV-optional: in-process backend when KV_URL unset

Make dataroom run WITH and WITHOUT external Hanzo KV, uniform with commerce
infra/kv.go. KV_URL unset -> in-process MemoryKV (single-replica correct: cache,
rate-limit, tus upload locks, export/download job stores, digest queues all work
with no external datastore). KV_URL set -> external Hanzo KV over ioredis
(multi-replica HA). Malformed KV_URL fails CLOSED (throws) -- never a silent
in-process fallback.

- lib/kv/url.ts: resolveKvUrl -- fail-closed parse, kv:// brand scheme -> redis://
  wire scheme, password redaction in errors.
- lib/kv/memory-kv.ts: in-process ioredis-compatible store (strings+TTL, zset,
  set, hash, list, pipeline) -- the exact surface dataroom uses.
- lib/kv/select.ts: the one backend selector (mirrors NewKVClient/FromURL).
- lib/redis.ts: use createKvClient() for redis + lockerRedisClient; drop the
  redis://localhost:6379 silent default (the WITHOUT-KV break) and vestigial
  REDIS_URL; Upstash-compat shims unchanged.
- Tests (node --test, dep-free): 25 pass proving selection + in-process
  correctness incl NX lock mutual-exclusion, TTL eviction, zset ordering,
  pipeline shape, fail-closed URL, cross-instance isolation (multi-replica
  split-brain contract).

* kv: warn once when falling back to in-process (non-silent split-brain)

Emit a one-time startup warning when KV_URL is unset so the WITHOUT-KV mode is
never silent — a multi-replica deploy with KV_URL unset splits sessions,
rate-limit windows and tus locks across replicas (single-replica only).

---------

Co-authored-by: Hanzo AI <ai@hanzo.ai>
2026-07-07 15:47:14 -07:00
zeekayandClaude Opus 4.8 9921a9a896 fix(db): re-inject Prisma enum objects for the SQLite client
SQLite has no Prisma enums, so removing the enum blocks also dropped the enum
*objects* the app imports from @prisma/client (LinkType.DOCUMENT_LINK, etc.),
crashing Next.js prerender ("Cannot read properties of undefined"). A
build-time step (after `prisma generate`) re-injects the 18 enum objects into
the generated client, so no app files change. Verified: local `next build`
now prerenders all 90 pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 00:59:54 -07:00
zeekayandClaude Opus 4.8 0aaaa1fa7e feat(db): migrate Prisma datasource postgres -> sqlite (native, file-backed)
Drops the shared sql-0 postgres dependency in favor of an embedded SQLite
file (DATABASE_URL=file:/data/<db>), WAL-replicated to SeaweedFS by the
operator persistence sidecar — same pattern as esign (Documenso).

Schema changes for the sqlite connector:
- datasource provider postgresql -> sqlite; drop directUrl/shadowDatabaseUrl
- 18 enums -> String (values preserved as string defaults)
- scalar-list fields (String[]/Int[]) -> Json @default("[]") (arrays
  round-trip through Prisma Json at runtime; next.config ignoreBuildErrors
  keeps the type-only churn out of the build)
- strip pg-native @db.* attributes (@db.Text/@db.Timestamp)
- startup: prisma migrate deploy -> prisma db push (pg migration history is
  postgres-specific; db push reconciles the sqlite schema idempotently)

CI: amd64-only (DOKS has no arm64); deploy:false — rollout is operator-managed
via the universe Service CR, never `kubectl set image`.

Verified: `prisma validate` + `prisma db push` materialize all 62 tables on a
fresh sqlite file. Source DB is empty (0 app rows), so cutover is data-safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 00:33:13 -07:00
2952083ef0 build: base on ghcr.io/hanzoai/nodejs:v24.18.0 (Node 24 + sqlite3)
Adopt the canonical Hanzo Node base image. Node 24 uniform across the
fleet; sqlite3 bundled (node:sqlite builtin + native better-sqlite3
toolchain). One base, one way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 12:49:21 -07:00
z af886c00a3 docs(brand): add hero banner 2026-06-28 20:09:09 -07:00
z 99203b41dd chore(brand): dynamic hero banner 2026-06-28 20:09:08 -07:00
Hanzo AI af5811b206 auth: use canonical /v1/iam/oauth/* IAM endpoints (bare /oauth/* hit SPA HTML) 2026-06-25 18:56:05 -07:00
7ec15f6af9 ci: run on self-hosted ARC pool (hanzo-build-linux-amd64/deploy), not GitHub-hosted (#3)
Co-authored-by: zeekay <z@hanzo.ai>
2026-06-19 20:33:48 -07:00
Antje WorringandClaude Opus 4.8 843791d1ee docs: tidy LLM.md indexes; CLAUDE.md -> LLM.md symlink convention
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:12:19 -07:00
zeekay c404568baf ci: route to canonical native arcd labels [self-hosted, linux, <arch>]
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
2026-06-10 20:11:17 -07:00
Hanzo AI 4c1325cca9 ci: retrigger after orphan package deletion (auto-link on first push) 2026-06-08 12:42:42 -07:00
Hanzo AI 5f784733cf ci: retrigger after package linkage (image.source label set) 2026-06-08 09:36:07 -07:00
Hanzo AI 109e9eead2 ci: re-trigger after package claim + spark-hanzo label 2026-06-08 09:23:10 -07:00
Hanzo AI 2705155c2e ci: runner-arm64 → spark-hanzo (the label that's actually online)
The reusable workflow defaults runner-arm64 to spark-hanzoai-arm64,
but that label has zero online registrations on the hanzoai org
runner pool. Spark.local is registered as spark-hanzo (and
spark-hanzobot-arm64) — that's the canonical org-level pool label.
Builds queue forever otherwise.
2026-06-07 16:14:46 -07:00
Hanzo AI 31abfcc2da brand: real Hanzo mark + white-label env hooks
Replace the placeholder H badge with the canonical Hanzo mark (the
abstract H-shape from ~/work/hanzo/logo). Mark renders via
currentColor so it inherits whatever color the parent text sets —
no per-theme variants needed.

White-label is now fully driven by env:

  NEXT_PUBLIC_APP_NAME           full app name ('Hanzo Dataroom')
  NEXT_PUBLIC_APP_NAME_PRIMARY   first wordmark token (default: first
                                 word of APP_NAME)
  NEXT_PUBLIC_APP_NAME_SUFFIX    remainder (default: rest of APP_NAME)
  NEXT_PUBLIC_APP_TAGLINE        sub-line under the welcome headline
  NEXT_PUBLIC_IAM_PROVIDER_NAME  IAM brand for 'Sign in with X' button
  NEXT_PUBLIC_MARKETING_URL      base URL for Terms / Privacy links

Tenants drop in their own NEXT_PUBLIC_APP_NAME='Acme Rooms' and the
whole page reads correctly with no code changes — wordmark + welcome
copy + terms attribution all pick up automatically. The mark itself
stays Hanzo (this IS a Hanzo app); tenants that need their own glyph
swap the HanzoMark import for a custom <img src=BRAND_LOGO_URL />.

Favicons regenerated from the canonical SVG too.
2026-06-07 16:01:01 -07:00
Hanzo AI 617480df3a ui: dark + monochrome + clean centered IAM signin
Login was a split-pane marketing layout with a salmon-pink sign-in
button, light-gray panel, testimonial photo + quote, and 'Trusted by
teams at' sponsor wall. None of that is wanted on a Hanzo property.

Now:
  * Solid black full-screen background, no testimonial panel, no
    sponsor logos.
  * Centered logo + 'Welcome to Hanzo Dataroom' headline + tagline.
  * Monochrome white-on-black 'Sign in with Hanzo' button (bg-white,
    text-black). No red.
  * Terms/Privacy links in zinc-300 underline, body in zinc-400/500.
  * <html class='dark'> on the root layout — dark is the default theme.
  * Favicon swapped to a Hanzo-H mark across all favicon sizes.

The IAM redirect itself is unchanged — signIn('hanzo-iam') still
hands off to NEXT_PUBLIC_IAM_PROVIDER_NAME via NextAuth, so 'Sign in
with Hanzo' is now the only UI surface and IAM owns everything past
the redirect.
2026-06-07 13:20:36 -07:00
Hanzo AI 2cf7f9503e polish: text-based Hanzo Dataroom logo + fix duplicated 'Data Rooms' suffix
Visual cleanups verified via Playwright after first dev run:

  * Replaced the three logo SVGs (light, dark, mark) with text-based
    placeholders that read 'Hanzo Dataroom' / 'H' instead of the
    original Papermark vector wordmarks. Local dev now reads correctly
    in the auth flow; real artwork can drop in by overwriting the
    same files.

  * Removed 'Data Rooms' duplication from the rebrand-sweep
    artifacts. The original copy reads '<brand> Data Rooms' which
    became 'Hanzo Dataroom Data Rooms' after the naive
    Papermark→Hanzo Dataroom substitution. Fixed across:
      - app/(auth)/login/page-client.tsx testimonial
      - app/(auth)/auth/email/[[...params]]/page-client.tsx
      - components/emails/data-rooms-information.tsx
      - components/emails/dataroom-trial-24h.tsx
      - components/welcome/dataroom-trial.tsx
2026-06-07 12:32:31 -07:00
Hanzo AI b827b3a7bd rebrand: Papermark → Hanzo Dataroom across user-facing surfaces
108 files: README, marketing copy, page titles, meta tags, footer
text, email templates, .env.example, UI strings. URLs swapped
from papermark.com/papermark.io to dataroom.hanzo.ai.

Internal identifiers left intact per repo policy: localStorage
keys (papermark.email/papermark.name/last_papermark_login —
changing them logs users out), type field names
(papermarkUserId), internal helper symbols (PAPERMARK_HEADERS,
PapermarkSparkle component, isPapermarkUrl helper),
internal-only filenames. LLM.md retains 'Upstream: Papermark'
attribution line. Per-locale .po translations regenerate from
source on the next lingui-extract CI run.

DB migrations untouched (append-only history; rewriting them
breaks installed schemas). LICENSE stays AGPL-3.0.
2026-06-07 12:23:40 -07:00
Hanzo AI d4f4071ee4 rip: enterprise dirs + paywalls — full AGPL, no commercial-license code
Drop the dual-licensed `ee/` tree entirely. All features previously
locked behind the Hanzo Dataroom Commercial License are now part of the
AGPL-3.0 core and freely available — custom domains, branded viewing,
advanced analytics, watermarks, access controls, Q&A, AI vector stores,
workflows, SAML SSO, dataroom invitations, conversations, templates.

Layout changes:
* `ee/features/{ai,conversations,workflows,permissions,storage,templates,
  dataroom-invitations,access-notifications,security,conversions}/` →
  `features/<name>/` (AGPL).
* `ee/limits/` → `lib/billing/limits/` with every plan resolving to an
  unlimited profile (no users/links/documents/domains/datarooms cap,
  conversations and watermarks always on).
* `ee/stripe/` → `lib/billing/legacy/` (already a compat shim over
  `@hanzoai/commerce`; renewal-reminder webhook is now a no-op).
* `ee/features/billing/cancellation/` deleted. Pause/unpause/cancel/
  retention routes now compile against `lib/billing/cancellation.ts`
  stubs that return 410 Gone; `isTeamPaused`/`isTeamPausedById` in
  `lib/billing/paused.ts` always report active. `CancellationModal` is
  a no-op shim in `components/billing/`.
* `app/(ee)/api/*` route group folded into `app/api/*` (SAML auth,
  SCIM, AI chat, workflows, FAQ, link-upload).

Build: `npm run build` green.
2026-06-07 11:46:48 -07:00
Hanzo AI caf8fa1053 ci: build amd64 + arm64 natively on hanzo runner pools
Reverts the amd64-only narrowing and restores the canonical
multi-arch build per Hanzo's runner topology (RUNNERS.md):
- amd64 leg targets hanzo-build-linux-amd64 (DOKS ARC pool on
  hanzo-k8s, default in the shared docker-build.yml — no caller
  override needed).
- arm64 leg targets spark-hanzoai-arm64 (Ampere bare metal,
  also default).

Both are native — no QEMU emulation. arm64 jobs queue while
spark arcd is offline; once spark is online (or an equivalent
arm64 ARC pool is installed somewhere reachable) the queue
drains automatically.
2026-06-07 11:46:03 -07:00
Hanzo AI 7aafdf2b65 ci: amd64-only build (matches hanzo runner pool topology)
hanzo-build-linux-amd64 is the only ARC runner pool installed on
hanzo-k8s today (arm64 pool is paused until DigitalOcean ships
arm64 droplets, per ~/work/hanzo/.github/RUNNERS.md). The arm64
job was waiting on spark-hanzoai-arm64 — not provisioned in this
cluster — so every build sat in queue forever and the amd64 leg
got cancelled.

Set platforms: linux/amd64 on the reusable build call to skip
arm64 entirely. Re-add arm64 if/when DO ships arm64 droplets and
the spark runner pool comes back online.
2026-06-07 11:38:49 -07:00
Hanzo AI d568e189a0 vendor: handsontable 6.2.2 full.min.css for excel-viewer build
components/view/viewer/excel-viewer.tsx imports
'@/public/vendor/handsontable/handsontable.full.min.css' at
build time, but the file was never committed — every Docker build
failed at the webpack module-resolution step.

The companion JS loads via CDN at runtime
(cdnjs.cloudflare.com/ajax/libs/handsontable/6.2.2/handsontable.full.min.js).
Pin the CSS to the same version, vendor it in, and the build
clears.
2026-06-07 11:19:56 -07:00
Hanzo AI d21548746c Merge remote-tracking branch 'origin/main' 2026-06-01 16:18:03 -07:00
Hanzo AI d5a58f710c merge: ci/canonical-docker-build-1776995235 2026-06-01 16:18:02 -07:00
Hanzo AI 77799ddce2 chore: rip stripe → @hanzoai/commerce + @hanzoai/pay (no stripe period)
Per CTO directive: zero Stripe across all our repos. Migration mirrors
hanzoai/gui@8d05cf6fb3.

- lib/commerce.ts: new hand-rolled Hanzo Commerce REST client + minimal
  CommerceTypes namespace covering only what the dataroom touches
  (Subscription, Invoice, Customer, CheckoutSession, PortalSession,
  Coupon, Event, Checkout.Session). HMAC-SHA256 webhook signature
  verification matches the gui pattern.
- ee/stripe/index.ts: now a thin shim — `stripeInstance(...)` returns
  the Commerce client; `cancelSubscription(...)` delegates to it.
- ee/stripe/client.ts: Stripe.js loadStripe + redirectToCheckout
  replaced with a redirect helper that bounces the browser to
  pay.hanzo.ai (@hanzoai/pay). No more card UI in the dataroom.
- ee/stripe/utils.ts: drop `import Stripe from "stripe"`; use
  CommerceTypes.Subscription. Stamped TODO(stripe-rip) on the PLANS
  const — the Stripe price IDs embedded there still need re-keying
  to Commerce plan IDs (5 plans × monthly+yearly × test+prod × new+old
  account; coordinate with Commerce admin).
- ee/stripe/webhooks/{checkout-session-completed,customer-subscription-
  {updated,deleted},invoice-upcoming}.ts: drop stripe SDK imports;
  rebind types to CommerceTypes.
- ee/features/security/lib/fraud-prevention.ts: addEmailToStripeRadar
  becomes a no-op with TODO(stripe-rip). Commerce does fraud at the
  gateway and does not yet expose a Radar-equivalent endpoint; the
  Edge Config blocklist remains the effective block surface.
- ee/features/billing/cancellation/api/*.ts: error message
  "No Stripe customer ID" -> "No billing customer ID"; the local
  `stripe` variable is now a Commerce instance via stripeInstance().
- pages/api/stripe/webhook.ts + webhook-old.ts: DELETED. Commerce
  owns webhook termination; the new dispatcher is at
  pages/api/commerce/webhook.ts and reuses the existing per-event
  business-logic handlers in ee/stripe/webhooks/.
- pages/api/commerce/webhook.ts: NEW. Accepts hanzo-commerce-signature
  (falls back to stripe-signature header so a side-by-side rollout
  works), verifies HMAC with HANZO_COMMERCE_WEBHOOK_SECRET, dispatches
  to the same handlers as before.
- package.json: removed `stripe` (^16.12.0), `@stripe/stripe-js`
  (^4.10.0), and the `stripe:webhook` pkgx script. No new deps — the
  Commerce REST client is hand-rolled per spec.

TODO(stripe-rip) markers flag the remaining deep work:
- PLANS const price IDs need re-keying to Commerce plan IDs
- ee/stripe/ directory should be renamed to ee/commerce/
- Local `stripeInstance` identifier should be renamed `commerce`
- Subscription.pause_collection / proration_behavior contract needs
  to land in the Commerce OpenAPI spec
- Commerce fraud-blocklist API needs to ship to replace the Radar
  call we just stubbed out

`tsc --noEmit` shows zero new errors in ee/, lib/commerce.ts,
pages/api/commerce/, components/billing/. Pre-existing TS errors in
redis-job-store, lib/auth, app routes are unchanged.
2026-05-28 02:03:38 -07:00
Hanzo AI dc592aa79d docs(llm): explicit Upstream attribution line
LICENSE-attested or repo-attested upstream is now called out at the
top of LLM.md alongside the project description, so downstream
audits (universe/docs/PRODUCTS.md) can rely on a single canonical
location for the OSS lineage statement.
2026-05-18 21:35:47 -07:00
Hanzo AI f99ecc8e90 ci: add id-token: write to caller permissions
Required for hanzoai/.github/.github/workflows/docker-build.yml@main —
without it the workflow_call dies as startup_failure with no jobs
dispatched. Caller permissions are a CEILING.
2026-05-07 09:09:52 -07:00
Hanzo DevandGitHub 30cdcf7339 ci: migrate to canonical hanzoai/.github/docker-build.yml reusable (#2) 2026-04-23 18:53:36 -07:00
Hanzo AI c1e7842cca ci: migrate to canonical hanzoai/.github/docker-build.yml reusable 2026-04-23 18:48:09 -07:00
Hanzo DevandGitHub 978f6c5e88 ci: migrate to canonical docker-build reusable workflow (#1)
Replace bespoke build-and-push + universe-reusable-deploy-service pair
with the canonical caller that does both in a single workflow:
hanzoai/.github/.github/workflows/docker-build.yml@main.

Native amd64+arm64 ARC runners, semver + branch tags, multi-arch manifest,
kubectl rollout restart on main after push.

Co-authored-by: Hanzo AI <dev@hanzo.ai>
2026-04-23 18:12:23 -07:00
459 changed files with 3293 additions and 4502 deletions
+19 -2
View File
@@ -73,8 +73,25 @@ NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_CONTENTS=
# Encryption key for document passwords.
NEXT_PRIVATE_DOCUMENT_PASSWORD_KEY=my-superstrong-document-secret
# [[REDIS LOCKER CONFIGURATION]]
# For bulk upload using tus.io, we use a Redis-based locker to prevent corruption of the data.
# [[HANZO KV]] — OPTIONAL. Leave UNSET to run on the in-process backend
# (single-replica correct: cache, rate-limit, tus upload locks, export/download
# job stores and digest queues all work with no external datastore). SET it to
# an external Hanzo KV instance for multi-replica HA (shared state across pods).
# Accepts the Hanzo KV brand scheme kv:// (kvs:// for TLS) or a redis:// DSN.
# A malformed value fails CLOSED (the app throws rather than silently degrade).
#
# Re-enabling multi-replica (replicas > 1) REQUIRES KV_URL. dataroom is
# single-replica-by-construction otherwise: SQLite on a ReadWriteOnce volume plus
# the in-process KV. Without KV_URL each pod owns its OWN map, so sessions,
# rate-limit windows and tus upload locks split-brain across replicas — scaling
# out needs a shared DB AND KV_URL set.
# KV_URL=kv://:password@hanzo-kv:6379
KV_URL=
# [[TUS UPLOAD LOCKER]] — for bulk upload via tus.io, an exclusive locker
# prevents data corruption. It uses the Hanzo KV client above (KV_URL); with
# KV_URL unset the lock is in-process (correct for a single replica). The legacy
# Upstash REST locker below is unused and kept only for reference.
UPSTASH_REDIS_REST_LOCKER_URL=
UPSTASH_REDIS_REST_LOCKER_TOKEN=
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="dataroom">
<rect width="1280" height="640" fill="#0A0A0A"/>
<svg x="96" y="215" width="210" height="210" viewBox="0 0 67 67"><path d="M22.21 67V44.6369H0V67H22.21Z" fill="#fff"/><path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#fff"/><path d="M22.21 0H0V22.3184H22.21V0Z" fill="#fff"/><path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#fff"/><path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#fff"/></svg>
<text x="378" y="276" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">dataroom</text>
<text x="378" y="322" font-family="Inter,system-ui,sans-serif" font-size="30" fill="#ffffff" opacity=".66">Papermark is the open-source DocSend alternative with built-in…</text>
<rect x="378" y="338" width="806" height="3" rx="1.5" fill="#ffffff" opacity=".9"/>
<text x="378" y="390" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">github.com/hanzoai</text>
<text x="1184" y="390" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">hanzo.ai</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1 -1
View File
@@ -13,7 +13,7 @@ permissions:
jobs:
CLAAssistant:
runs-on: ubuntu-latest
runs-on: hanzo-build-linux-amd64
steps:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
+6 -43
View File
@@ -1,48 +1,11 @@
name: Build & Deploy
name: Docker
# Native deploy pipeline is .hanzo/workflows/deploy.yml (Hanzo Git → act_runner →
# BuildKit → ghcr.io/hanzoai/dataroom:<sha> → operator reconcile → hanzocd).
# GitHub is a mirror; this workflow is retained only as a manual sync notice.
on:
push:
branches: [main]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: hanzoai/dataroom
jobs:
build-and-push:
notice:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
uses: hanzoai/universe/.github/workflows/reusable-deploy-service.yml@main
with:
service: dataroom
image: ghcr.io/hanzoai/dataroom:${{ github.sha }}
cluster: hanzo-k8s
namespace: hanzo
secrets:
HANZO_API_KEY: ${{ secrets.HANZO_API_KEY }}
- run: echo "native pipeline is .hanzo/workflows/deploy.yml; GitHub is a mirror"
+7
View File
@@ -0,0 +1,7 @@
name: Workflow Sanity
on:
pull_request:
paths: ['.github/workflows/**']
jobs:
sanity:
uses: hanzoai/.github/.github/workflows/workflow-sanity.yml@main
+24
View File
@@ -0,0 +1,24 @@
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: hanzo-linux-amd64
steps:
- uses: actions/checkout@v4
- name: Build + push image
run: |
SHA="${GITHUB_SHA::8}"
buildctl-daemonless.sh build --frontend=dockerfile.v0 \
--opt context="${{ github.server_url }}/${{ github.repository }}.git#${GITHUB_SHA}" \
--opt filename=Dockerfile --opt platform=linux/amd64 \
--secret id=GIT_AUTH_TOKEN,env=GIT_AUTH_TOKEN \
--output "type=image,name=ghcr.io/hanzoai/dataroom:${SHA},push=true" --progress=plain
env:
GIT_AUTH_TOKEN: ${{ secrets.GIT_CLONE_TOKEN }}
- name: Deploy — declare tag to operator
run: |
for app in dataroom; do
kubectl -n hanzo patch app "$app" --type=merge -p "{\"spec\":{\"image\":{\"repository\":\"ghcr.io/hanzoai/dataroom\",\"tag\":\"${GITHUB_SHA::8}\"}}}"
done
+9 -3
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine AS base
FROM ghcr.io/hanzoai/nodejs:v24.18.0 AS base
RUN apk add --no-cache libc6-compat openssl python3 make g++
FROM base AS deps
@@ -20,10 +20,13 @@ ENV OPENAI_API_KEY=build-placeholder
ENV HANKO_API_KEY=build-placeholder
ENV NEXT_PUBLIC_HANKO_TENANT_ID=build-placeholder
RUN npx prisma generate
# SQLite has no Prisma enums; re-inject the enum objects the app imports from
# @prisma/client (e.g. LinkType.DOCUMENT_LINK) so runtime/prerender resolves them.
RUN node prisma/inject-sqlite-enums.cjs
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm run build
FROM node:22-alpine AS runner
FROM ghcr.io/hanzoai/nodejs:v24.18.0 AS runner
RUN apk add --no-cache openssl
WORKDIR /app
ENV NODE_ENV=production
@@ -44,4 +47,7 @@ COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.bin ./node_modules/
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["sh", "-c", "node_modules/.bin/prisma migrate deploy --schema prisma/schema/schema.prisma && node server.js"]
# SQLite: reconcile schema on startup via `db push` (Prisma has no migrate-deploy
# path for the sqlite provider here; the pg migration history under prisma/migrations
# is postgres-specific and unused). Schema folder = prismaSchemaFolder preview.
CMD ["sh", "-c", "node_modules/.bin/prisma db push --schema prisma/schema --skip-generate --accept-data-loss && node server.js"]
+14 -3
View File
@@ -1,7 +1,18 @@
# LLM.md - Hanzo Dataroom
# Hanzo Dataroom
## Overview
Hanzo dataroom service
Hanzo dataroom service.
**Upstream**: [Papermark](https://github.com/mfts/papermark) (AGPL-3.0). LICENSE retains "Copyright (c) 2023-present Papermark, Inc." Open-source DocSend alternative. This fork is Hanzo Dataroom — single-license AGPL, no `ee/` commercial directory.
## In-process fold (HIP-0106, task #101)
The production runtime is the **goja bundle** in [`goja/`](goja/): the ESM-free
port of the API handlers, run in-process inside the unified `hanzoai/cloud` binary
over Hanzo Base/SQLite (per-tenant) + the cloud object-storage seam. The standalone
Next.js app + Postgres pod is **retired**; cloud serves `/v1/dataroom/*` itself.
See [`goja/README.md`](goja/README.md) for the host contract. The Next.js/TS
sources below remain the reference for the domain model that `goja/bundle.js`
implements.
## Tech Stack
- **Language**: TypeScript/JavaScript
@@ -27,7 +38,7 @@ dataroom/
components/
components.json
context/
ee/
features/
lib/
middleware.ts
```
+2
View File
@@ -1,3 +1,5 @@
<p align="center"><img src=".github/hero.svg" alt="dataroom" width="880"></p>
<div align="center">
<h1 align="center">Hanzo Dataroom</h1>
<h3>The open-source DocSend alternative.</h3>
@@ -117,8 +117,8 @@ export default function EmailVerificationClient() {
<div className="items-left flex flex-col space-y-3 px-4 py-6 pt-8 sm:px-12">
<Link href="https://dataroom.hanzo.ai" target="_blank">
<img
src="/_static/papermark-logo.svg"
alt="Papermark Logo"
src="/_static/hanzo-dataroom-logo.svg"
alt="Hanzo Dataroom Logo"
className="-mt-8 mb-36 h-7 w-auto self-start sm:mb-32 md:mb-48"
/>
</Link>
@@ -155,8 +155,8 @@ export default function EmailVerificationClient() {
<div className="items-left flex flex-col space-y-3 px-4 py-6 pt-8 sm:px-12">
<Link href="https://dataroom.hanzo.ai" target="_blank">
<img
src="/_static/papermark-logo.svg"
alt="Papermark Logo"
src="/_static/hanzo-dataroom-logo.svg"
alt="Hanzo Dataroom Logo"
className="-mt-8 mb-36 h-7 w-auto self-start sm:mb-32 md:mb-48"
/>
</Link>
@@ -312,7 +312,7 @@ function TestimonialSection() {
<div className="max-w-xl text-center">
<blockquote className="text-balance font-normal leading-8 text-white sm:text-xl sm:leading-9">
<p>
&quot;We raised our 30M Fund with Hanzo Dataroom Data Rooms. Love
&quot;We raised our 30M Fund with Hanzo Dataroom. Love
the customization, security and ease of use.&quot;
</p>
</blockquote>
+72 -116
View File
@@ -6,131 +6,87 @@ import { useParams } from "next/navigation";
import { signIn } from "next-auth/react";
import { Button } from "@/components/ui/button";
import { LogoCloud } from "@/components/shared/logo-cloud";
import { HanzoMark } from "@/components/shared/icons/hanzo-mark";
const APP_NAME =
process.env.NEXT_PUBLIC_APP_NAME || "Hanzo Dataroom";
// White-label hooks: every visible brand token comes from env so tenants
// drop in their own name/words/IAM provider without touching this file.
//
// NEXT_PUBLIC_APP_NAME e.g. "Hanzo Dataroom" | "Acme Rooms"
// NEXT_PUBLIC_APP_NAME_PRIMARY first word of wordmark; defaults to first
// word of NEXT_PUBLIC_APP_NAME ("Hanzo")
// NEXT_PUBLIC_APP_NAME_SUFFIX second token of wordmark; defaults to
// remainder of NEXT_PUBLIC_APP_NAME ("Dataroom")
// NEXT_PUBLIC_APP_TAGLINE one-line under the welcome headline
// NEXT_PUBLIC_IAM_PROVIDER_NAME IAM brand for the "Sign in with X" button
// NEXT_PUBLIC_MARKETING_URL base URL the Terms / Privacy links resolve against
const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || "Hanzo Dataroom";
const [defaultPrimary, ...defaultSuffixParts] = APP_NAME.split(" ");
const APP_NAME_PRIMARY =
process.env.NEXT_PUBLIC_APP_NAME_PRIMARY || defaultPrimary || APP_NAME;
const APP_NAME_SUFFIX =
process.env.NEXT_PUBLIC_APP_NAME_SUFFIX || defaultSuffixParts.join(" ");
const APP_TAGLINE =
process.env.NEXT_PUBLIC_APP_TAGLINE || "Share documents. Not attachments.";
const IAM_PROVIDER_NAME =
process.env.NEXT_PUBLIC_IAM_PROVIDER_NAME || "Hanzo";
const MARKETING_URL =
process.env.NEXT_PUBLIC_MARKETING_URL || "";
const MARKETING_URL = process.env.NEXT_PUBLIC_MARKETING_URL || "";
export default function Login() {
const { next } = useParams as { next?: string };
return (
<div className="flex h-screen w-full flex-wrap">
{/* Left part */}
<div className="flex w-full justify-center bg-gray-50 md:w-1/2 lg:w-1/2">
<div
className="absolute inset-x-0 top-10 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl"
aria-hidden="true"
></div>
<div className="z-10 mx-5 mt-[calc(1vh)] h-fit w-full max-w-md overflow-hidden rounded-lg sm:mx-0 sm:mt-[calc(2vh)] md:mt-[calc(3vh)]">
<div className="items-left flex flex-col space-y-3 px-4 py-6 pt-8 sm:px-12">
<Link href="/" target="_blank">
<img
src="/_static/papermark-logo.svg"
alt={`${APP_NAME} Logo`}
className="md:mb-48s -mt-8 mb-36 h-7 w-auto self-start sm:mb-32"
/>
</Link>
<Link href="/">
<span className="text-balance text-3xl font-semibold text-gray-900">
Welcome to {APP_NAME}
</span>
</Link>
<h3 className="text-balance text-sm text-gray-800">
Share documents. Not attachments.
</h3>
</div>
<div className="flex flex-col gap-4 px-4 pt-8 sm:px-12">
<Button
onClick={() => {
signIn("hanzo-iam", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
});
}}
className="flex w-full items-center justify-center space-x-2 border border-red-500 bg-red-500 font-normal text-white hover:bg-red-600"
>
<span>
Sign in with{" "}
<span className="font-bold">{IAM_PROVIDER_NAME}</span>
</span>
</Button>
</div>
<p className="mt-10 w-full max-w-md px-4 text-xs text-muted-foreground sm:px-12">
By clicking continue, you acknowledge that you have read and agree
to {APP_NAME}&apos;s{" "}
<a
href={`${MARKETING_URL}/terms`}
target="_blank"
className="underline"
>
Terms of Service
</a>{" "}
and{" "}
<a
href={`${MARKETING_URL}/privacy`}
target="_blank"
className="underline"
>
Privacy Policy
</a>
.
</p>
</div>
</div>
<div className="relative hidden w-full justify-center overflow-hidden bg-black md:flex md:w-1/2 lg:w-1/2">
<div className="relative m-0 flex h-full min-h-[700px] w-full p-0">
<div
className="relative flex h-full w-full flex-col justify-between"
id="features"
<main className="flex min-h-screen w-full items-center justify-center bg-black px-6 text-white">
<div className="w-full max-w-sm">
<Link href="/" className="mb-12 flex items-center gap-3">
<HanzoMark size={28} className="text-white" />
<span className="text-base font-medium tracking-tight">
{APP_NAME_PRIMARY}
{APP_NAME_SUFFIX && (
<span className="text-zinc-400"> {APP_NAME_SUFFIX}</span>
)}
</span>
</Link>
<h1 className="text-balance text-3xl font-semibold text-white">
Welcome to {APP_NAME}
</h1>
<p className="mt-2 text-balance text-sm text-zinc-400">{APP_TAGLINE}</p>
<Button
onClick={() =>
signIn("hanzo-iam", {
...(next && next.length > 0 ? { callbackUrl: next } : {}),
})
}
className="mt-8 flex w-full items-center justify-center bg-white font-normal text-black hover:bg-zinc-200"
>
<span>
Sign in with <span className="font-bold">{IAM_PROVIDER_NAME}</span>
</span>
</Button>
<p className="mt-8 text-xs text-zinc-500">
By clicking continue, you acknowledge that you have read and agree to{" "}
{APP_NAME}&apos;s{" "}
<a
href={`${MARKETING_URL}/terms`}
target="_blank"
className="text-zinc-300 underline hover:text-white"
>
{/* Testimonial top 2/3 */}
<div
className="flex w-full flex-col items-center justify-center"
style={{ height: "66.6666%" }}
>
{/* Image container */}
<div className="mb-4 h-64 w-80">
<img
className="h-full w-full rounded-2xl object-cover shadow-2xl"
src="/_static/testimonials/backtrace.jpeg"
alt="Backtrace Capital"
/>
</div>
{/* Text content */}
<div className="max-w-xl text-center">
<blockquote className="text-balance font-normal leading-8 text-white sm:text-xl sm:leading-9">
<p>
&quot;We raised our &euro;30M Fund with {APP_NAME} Data Rooms.
Love the customization, security and ease of use.&quot;
</p>
</blockquote>
<figcaption className="mt-4">
<div className="text-balance font-normal text-white">
Michael M&uuml;nnix
</div>
<div className="text-balance font-light text-gray-400">
Partner, Backtrace Capital
</div>
</figcaption>
</div>
</div>
{/* White block with logos bottom 1/3, full width/height */}
<div
className="absolute bottom-0 left-0 flex w-full flex-col items-center justify-center bg-white"
style={{ height: "33.3333%" }}
>
<div className="mb-4 max-w-xl text-balance text-center font-semibold text-gray-900">
Trusted by teams at
</div>
<LogoCloud />
</div>
</div>
</div>
Terms of Service
</a>{" "}
and{" "}
<a
href={`${MARKETING_URL}/privacy`}
target="_blank"
className="text-zinc-300 underline hover:text-white"
>
Privacy Policy
</a>
.
</p>
</div>
</div>
</main>
);
}
+2 -2
View File
@@ -6,7 +6,7 @@ import { useParams } from "next/navigation";
import { useState } from "react";
import HanzoLogo from "@/public/_static/papermark-logo.svg";
import HanzoLogo from "@/public/_static/hanzo-dataroom-logo.svg";
import { signIn } from "next-auth/react";
import { toast } from "sonner";
@@ -40,7 +40,7 @@ export default function Register() {
src={HanzoLogo}
width={119}
height={32}
alt="Papermark Logo"
alt="Hanzo Dataroom Logo"
/>
</Link>
<h3 className="text-2xl font-medium text-foreground">
@@ -37,7 +37,7 @@ export default function InvitationStatusContent({
</div>
<div className="w-full space-y-4">
<h4 className="text-center text-sm font-medium text-gray-800">
Create your own Papermark account
Create your own Hanzo Dataroom account
</h4>
<div className="space-y-3">
<Link href="/login" className="block w-full">
+1 -1
View File
@@ -191,7 +191,7 @@ export default async function VerifyInvitationPage({
<blockquote className="text-l text-balance leading-8 text-gray-100 sm:text-xl sm:leading-9">
<p>
True builders listen to their users and build what they
need. Thanks Papermark team for solving a big pain point.
need. Thanks Hanzo Dataroom team for solving a big pain point.
DocSend monopoly will end soon!
</p>
</blockquote>
-1
View File
@@ -1 +0,0 @@
../ee/LICENSE.md
-1
View File
@@ -1 +0,0 @@
../ee/README.md
@@ -1,10 +1,10 @@
import { NextRequest } from "next/server";
import { generateChatTitle } from "@/ee/features/ai/lib/chat/generate-chat-title";
import { getFilteredDataroomDocumentIds } from "@/ee/features/ai/lib/chat/get-filtered-dataroom-document-ids";
import { sendMessage } from "@/ee/features/ai/lib/chat/send-message";
import { validateChatAccess } from "@/ee/features/ai/lib/permissions/validate-chat-access";
import { sendMessageSchema } from "@/ee/features/ai/schemas/chat";
import { generateChatTitle } from "@/features/ai/lib/chat/generate-chat-title";
import { getFilteredDataroomDocumentIds } from "@/features/ai/lib/chat/get-filtered-dataroom-document-ids";
import { sendMessage } from "@/features/ai/lib/chat/send-message";
import { validateChatAccess } from "@/features/ai/lib/permissions/validate-chat-access";
import { sendMessageSchema } from "@/features/ai/schemas/chat";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { validateChatAccess } from "@/ee/features/ai/lib/permissions/validate-chat-access";
import { validateChatAccess } from "@/features/ai/lib/permissions/validate-chat-access";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { createChat } from "@/ee/features/ai/lib/chat/create-chat";
import { createChatSchema } from "@/ee/features/ai/schemas/chat";
import { createChat } from "@/features/ai/lib/chat/create-chat";
import { createChatSchema } from "@/features/ai/schemas/chat";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
import { z } from "zod";
@@ -4,8 +4,8 @@ import {
SUPPORTED_AI_CONTENT_TYPES,
addFileToVectorStoreTask,
processDocumentForAITask,
} from "@/ee/features/ai/lib/trigger";
import { createDataroomVectorStore } from "@/ee/features/ai/lib/vector-stores/create-dataroom-vector-store";
} from "@/features/ai/lib/trigger";
import { createDataroomVectorStore } from "@/features/ai/lib/vector-stores/create-dataroom-vector-store";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
@@ -4,9 +4,9 @@ import {
addFileToVectorStoreTask,
processDocumentForAITask,
SUPPORTED_AI_CONTENT_TYPES,
} from "@/ee/features/ai/lib/trigger";
import { createTeamVectorStore } from "@/ee/features/ai/lib/vector-stores/create-team-vector-store";
import { removeFileFromVectorStore } from "@/ee/features/ai/lib/vector-stores/remove-file-from-vector-store";
} from "@/features/ai/lib/trigger";
import { createTeamVectorStore } from "@/features/ai/lib/vector-stores/create-team-vector-store";
import { removeFileFromVectorStore } from "@/features/ai/lib/vector-stores/remove-file-from-vector-store";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { getVectorStoreInfo } from "@/ee/features/ai/lib/vector-stores/get-vector-store-info";
import { getVectorStoreInfo } from "@/features/ai/lib/vector-stores/get-vector-store-info";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
+1 -1
View File
@@ -18,7 +18,7 @@ export async function GET(request: NextRequest) {
return new ImageResponse(
(
<div tw="flex flex-col items-center justify-between w-full h-full bg-white text-black p-12">
<div tw="text-[32px] flex items-center tracking-tighter">Papermark</div>
<div tw="text-[32px] flex items-center tracking-tighter">Hanzo Dataroom</div>
<div tw="text-[42px] text-center">{title}</div>
<div tw="text-[32px] flex items-center">
Open-Source Document Sharing
+2 -2
View File
@@ -29,7 +29,7 @@ export async function GET(req: NextRequest) {
{/* Left Side Text */}
<div tw="flex flex-col text-white" style={{ marginLeft: "48px" }}>
<div tw="flex text-7xl font-bold mb-4 tracking-tighter">
Papermark
Hanzo Dataroom
</div>
<div tw="flex text-5xl mb-4">Year in Review</div>
<div tw="flex text-7xl font-bold">{year}</div>
@@ -49,7 +49,7 @@ export async function GET(req: NextRequest) {
{/* Header Section */}
<div tw="flex items-start p-8 items-center">
<div tw="flex text-2xl font-bold text-white tracking-tighter">
Papermark
Hanzo Dataroom
</div>
</div>
+2 -2
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { reportDeniedAccessAttempt } from "@/ee/features/access-notifications";
import { getTeamStorageConfigById } from "@/ee/features/storage/config";
import { reportDeniedAccessAttempt } from "@/features/access-notifications";
import { getTeamStorageConfigById } from "@/features/storage/config";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { ItemType, LinkAudienceType } from "@prisma/client";
import { ipAddress, waitUntil } from "@vercel/functions";
+2 -2
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { reportDeniedAccessAttempt } from "@/ee/features/access-notifications";
import { getTeamStorageConfigById } from "@/ee/features/storage/config";
import { reportDeniedAccessAttempt } from "@/features/access-notifications";
import { getTeamStorageConfigById } from "@/features/storage/config";
// Import authOptions directly from the source
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { ipAddress, waitUntil } from "@vercel/functions";
@@ -1,11 +1,11 @@
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import { WorkflowEngine } from "@/ee/features/workflows/lib/engine";
import { WorkflowEngine } from "@/features/workflows/lib/engine";
import {
AccessRequestSchema,
VerifyEmailRequestSchema,
} from "@/ee/features/workflows/lib/types";
} from "@/features/workflows/lib/types";
import { ipAddress, waitUntil } from "@vercel/functions";
import { z } from "zod";
@@ -1,8 +1,8 @@
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import { WorkflowEngine } from "@/ee/features/workflows/lib/engine";
import { AccessRequestSchema } from "@/ee/features/workflows/lib/types";
import { WorkflowEngine } from "@/features/workflows/lib/engine";
import { AccessRequestSchema } from "@/features/workflows/lib/types";
import { ipAddress } from "@vercel/functions";
import { z } from "zod";
@@ -6,7 +6,7 @@ import { ratelimit } from "@/lib/redis";
import { sendOtpVerificationEmail } from "@/lib/emails/send-email-otp-verification";
import { generateOTP } from "@/lib/utils/generate-otp";
import { validateEmail } from "@/lib/utils/validate-email";
import { VerifyEmailRequestSchema } from "@/ee/features/workflows/lib/types";
import { VerifyEmailRequestSchema } from "@/features/workflows/lib/types";
// POST /app/(ee)/api/workflow-entry/[entryLinkId]/verify - Send OTP
export async function POST(
@@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import {
UpdateWorkflowRequestSchema,
formatZodError,
} from "@/ee/features/workflows/lib/validation";
} from "@/features/workflows/lib/validation";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { customAlphabet } from "nanoid";
import { getServerSession } from "next-auth";
@@ -5,7 +5,7 @@ import {
formatZodError,
validateActions,
validateConditions,
} from "@/ee/features/workflows/lib/validation";
} from "@/features/workflows/lib/validation";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
import { z } from "zod";
@@ -10,8 +10,8 @@ import {
formatZodError,
validateConditions,
validateActions,
} from "@/ee/features/workflows/lib/validation";
import { ReorderStepsRequest } from "@/ee/features/workflows/lib/types";
} from "@/features/workflows/lib/validation";
import { ReorderStepsRequest } from "@/features/workflows/lib/types";
// GET /app/(ee)/api/workflows/[workflowId]/steps?teamId=xxx - List all steps
export async function GET(
@@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import {
CreateWorkflowRequestSchema,
formatZodError,
} from "@/ee/features/workflows/lib/validation";
} from "@/features/workflows/lib/validation";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
import { z } from "zod";
+1 -1
View File
@@ -47,7 +47,7 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="en">
<html lang="en" className="dark">
<body className={inter.className}>{children}</body>
</html>
);
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
ColumnDef,
SortingState,
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
ColumnDef,
SortingState,
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { differenceInDays, format, startOfDay, subDays } from "date-fns";
import { CalendarIcon, ChevronDown, CrownIcon } from "lucide-react";
import { DateRange } from "react-day-picker";
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
ColumnDef,
SortingState,
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
ColumnDef,
SortingState,
+2 -2
View File
@@ -4,8 +4,8 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useTeam } from "@/context/team-context";
import { getPriceIdFromPlan } from "@/ee/stripe/functions/get-price-id-from-plan";
import { getQuantityFromPriceId } from "@/ee/stripe/functions/get-quantity-from-plan";
import { getPriceIdFromPlan } from "@/lib/billing/legacy/functions/get-price-id-from-plan";
import { getQuantityFromPriceId } from "@/lib/billing/legacy/functions/get-quantity-from-plan";
import { toast } from "sonner";
import { useAnalytics } from "@/lib/analytics";
+16
View File
@@ -0,0 +1,16 @@
/**
* Stub `CancellationModal` the commercial cancellation funnel was removed
* with the AGPL paywall rip. We keep the component as a no-op so existing
* call sites compile.
*/
export type CancellationModalProps = {
open?: boolean;
onOpenChange?: (open: boolean) => void;
};
export function CancellationModal(_props: CancellationModalProps) {
return null;
}
export default CancellationModal;
+2 -2
View File
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { Dispatch, SetStateAction, useState } from "react";
import { useTeam } from "@/context/team-context";
import { getPriceIdFromPlan } from "@/ee/stripe/functions/get-price-id-from-plan";
import { getPriceIdFromPlan } from "@/lib/billing/legacy/functions/get-price-id-from-plan";
import Cookies from "js-cookie";
import { toast } from "sonner";
@@ -41,7 +41,7 @@ export default function ProAnnualBanner({
<span className="sr-only">Close</span>
</button>
<div className="flex space-x-2">
<span className="text-sm font-bold">Papermark Pro Annual </span>
<span className="text-sm font-bold">Hanzo Dataroom Pro Annual </span>
</div>
<p className="my-4 text-sm">
Lock in a better price and get 2 months free.
+2 -2
View File
@@ -1,6 +1,6 @@
import { Dispatch, SetStateAction } from "react";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import Cookies from "js-cookie";
import X from "@/components/shared/icons/x";
@@ -31,7 +31,7 @@ export default function ProBanner({
<span className="sr-only">Close</span>
</button>
<div className="flex space-x-2">
<span className="text-sm font-bold"> Papermark Business </span>
<span className="text-sm font-bold"> Hanzo Dataroom Business </span>
</div>
<p className="my-4 text-sm">
Upgrade to unlock custom branding, team members, domains and data rooms.
@@ -3,8 +3,8 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { CancellationModal } from "@/ee/features/billing/cancellation/components";
import { PlanEnum } from "@/ee/stripe/constants";
import { CancellationModal } from "@/components/billing/cancellation-modal";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
BanIcon,
CirclePauseIcon,
@@ -4,8 +4,8 @@ import { useEffect, useMemo, useState } from "react";
import React from "react";
import { useTeam } from "@/context/team-context";
import { getStripe } from "@/ee/stripe/client";
import { PLANS } from "@/ee/stripe/utils";
import { getStripe } from "@/lib/billing/legacy/client";
import { PLANS } from "@/lib/billing/legacy/utils";
import { CheckIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -53,7 +53,7 @@ export function UpgradePlanModal({
"Custom branding",
"Folder organization",
"Require email verification",
"Papermark branding removed",
"Hanzo Dataroom branding removed",
"1-year analytics retention",
];
}
@@ -287,11 +287,11 @@ export function UpgradePlanModal({
</DataroomTrialModal>
) : (
<a
href="https://cal.com/marcseitz/papermark"
href="https://dataroom.hanzo.ai/contact"
target="_blank"
className="underline-offset-4 transition-all hover:text-gray-800 hover:underline hover:dark:text-muted-foreground/80"
>
Looking for Papermark Enterprise?
Looking for Hanzo Dataroom Enterprise?
</a>
)}
</div>
@@ -5,10 +5,10 @@ import { useEffect, useMemo, useState } from "react";
import React from "react";
import { useTeam } from "@/context/team-context";
import { getStripe } from "@/ee/stripe/client";
import { Feature, PlanEnum, getPlanFeatures } from "@/ee/stripe/constants";
import { getPriceIdFromPlan } from "@/ee/stripe/functions/get-price-id-from-plan";
import { PLANS } from "@/ee/stripe/utils";
import { getStripe } from "@/lib/billing/legacy/client";
import { Feature, PlanEnum, getPlanFeatures } from "@/lib/billing/legacy/constants";
import { getPriceIdFromPlan } from "@/lib/billing/legacy/functions/get-price-id-from-plan";
import { PLANS } from "@/lib/billing/legacy/utils";
import {
CheckIcon,
CircleHelpIcon,
+4 -4
View File
@@ -5,10 +5,10 @@ import { useEffect, useMemo, useState } from "react";
import React from "react";
import { useTeam } from "@/context/team-context";
import { getStripe } from "@/ee/stripe/client";
import { Feature, PlanEnum, getPlanFeatures } from "@/ee/stripe/constants";
import { getPriceIdFromPlan } from "@/ee/stripe/functions/get-price-id-from-plan";
import { PLANS } from "@/ee/stripe/utils";
import { getStripe } from "@/lib/billing/legacy/client";
import { Feature, PlanEnum, getPlanFeatures } from "@/lib/billing/legacy/constants";
import { getPriceIdFromPlan } from "@/lib/billing/legacy/functions/get-price-id-from-plan";
import { PLANS } from "@/lib/billing/legacy/utils";
import { CheckIcon, CircleHelpIcon, Users2Icon, XIcon } from "lucide-react";
import { useAnalytics } from "@/lib/analytics";
+3 -3
View File
@@ -5,9 +5,9 @@ import { useRouter } from "next/router";
import { Dispatch, SetStateAction, useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum, getPlanFeatures } from "@/ee/stripe/constants";
import { getPriceIdFromPlan } from "@/ee/stripe/functions/get-price-id-from-plan";
import { PLANS } from "@/ee/stripe/utils";
import { PlanEnum, getPlanFeatures } from "@/lib/billing/legacy/constants";
import { getPriceIdFromPlan } from "@/lib/billing/legacy/functions/get-price-id-from-plan";
import { PLANS } from "@/lib/billing/legacy/utils";
import Cookies from "js-cookie";
import { CheckIcon, Sparkles, X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
+2 -2
View File
@@ -1,7 +1,7 @@
"use client";
import { ConversationListItem as ConversationListItemEE } from "@/ee/features/conversations/components/dashboard/conversation-list-item";
import { ConversationMessage as ConversationMessageEE } from "@/ee/features/conversations/components/shared/conversation-message";
import { ConversationListItem as ConversationListItemEE } from "@/features/conversations/components/dashboard/conversation-list-item";
import { ConversationMessage as ConversationMessageEE } from "@/features/conversations/components/shared/conversation-message";
export function ConversationListItem(props: any) {
return <ConversationListItemEE {...props} />;
@@ -1,6 +1,6 @@
import { useState } from "react";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
FileJson,
FileSlidersIcon,
@@ -1,6 +1,6 @@
import { useState } from "react";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { ListOrderedIcon } from "lucide-react";
import { toast } from "sonner";
+1 -1
View File
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
Brain,
BriefcaseIcon,
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { E164Number } from "libphonenumber-js";
import { toast } from "sonner";
import { mutate } from "swr";
@@ -263,7 +263,7 @@ export function DataroomTrialModal({
<div className="text-xs text-muted-foreground">
After the trial, upgrade to{" "}
<UpgradePlanModal clickedPlan={PlanEnum.Business}>
<button className="underline">Papermark Business</button>
<button className="underline">Hanzo Dataroom Business</button>
</UpgradePlanModal>{" "}
to continue using data rooms.
</div>
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { useUninvitedMembers } from "@/ee/features/dataroom-invitations/lib/swr/use-dataroom-invitations";
import { useUninvitedMembers } from "@/features/dataroom-invitations/lib/swr/use-dataroom-invitations";
import {
MailCheckIcon,
MoreHorizontalIcon,
@@ -40,7 +40,7 @@ import {
import { TimestampTooltip } from "@/components/ui/timestamp-tooltip";
import { VisitorAvatar } from "@/components/visitors/visitor-avatar";
import { InviteViewersModal } from "../../../ee/features/dataroom-invitations/components/invite-viewers-modal";
import { InviteViewersModal } from "@/features/dataroom-invitations/components/invite-viewers-modal";
import { AddGroupMemberModal } from "./add-member-modal";
export default function GroupMemberTable({
@@ -2,7 +2,7 @@ import Link from "next/link";
import { Dispatch, SetStateAction, useMemo, useState } from "react";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { CircleHelpIcon, Tag } from "lucide-react";
import { toast } from "sonner";
import { mutate } from "swr";
@@ -2,8 +2,8 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useLimits } from "@/ee/limits/swr-handler";
import { PlanEnum } from "@/ee/stripe/constants";
import { useLimits } from "@/lib/billing/limits/swr-handler";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { toast } from "sonner";
import { mutate } from "swr";
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { FormEvent, useEffect, useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { DefaultPermissionStrategy } from "@prisma/client";
import { parsePageId } from "notion-utils";
import { toast } from "sonner";
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import { TeamContextType } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import {
BetweenHorizontalStartIcon,
ChevronRight,
+2 -2
View File
@@ -4,8 +4,8 @@ import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import { useTeam } from "@/context/team-context";
import { DocumentAIDialog } from "@/ee/features/ai/components/document-ai-dialog";
import { PlanEnum } from "@/ee/stripe/constants";
import { DocumentAIDialog } from "@/features/ai/components/document-ai-dialog";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { Document, DocumentVersion } from "@prisma/client";
import {
ArrowRightIcon,
+6 -6
View File
@@ -1,7 +1,7 @@
import { type ElementType, useEffect, useRef, useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { PlanEnum } from "@/lib/billing/legacy/constants";
import { LinkType } from "@prisma/client";
import { AlertTriangleIcon, CircleCheckIcon, InfoIcon } from "lucide-react";
import { toast } from "sonner";
@@ -65,7 +65,7 @@ const STATUS_CONFIG: Record<
},
"has site": {
suffix:
"is currently pointing to an existing website. Only proceed if you're sure you want to use this domain for Papermark links.",
"is currently pointing to an existing website. Only proceed if you're sure you want to use this domain for Hanzo Dataroom links.",
icon: InfoIcon,
className: "bg-blue-100 text-blue-800",
},
@@ -139,9 +139,9 @@ export function AddDomainModal({
return;
}
if (debouncedDomain.includes("papermark")) {
if (debouncedDomain.includes("hanzo")) {
setDomainStatus("invalid");
setStatusMessageOverride("Domain cannot contain 'papermark'.");
setStatusMessageOverride("Domain cannot contain 'hanzo'.");
return;
}
@@ -201,8 +201,8 @@ export function AddDomainModal({
return toast.error("Please enter a valid domain (e.g., example.com).");
}
if (normalizedDomain.includes("papermark")) {
return toast.error("Domain cannot contain 'papermark'.");
if (normalizedDomain.includes("hanzo")) {
return toast.error("Domain cannot contain 'hanzo'.");
}
if (saveDisabled) {
+3 -3
View File
@@ -45,12 +45,12 @@ export default function CustomDomainSetup({
return (
<Html>
<Head />
<Preview>Your Papermark custom domain set up</Preview>
<Preview>Your Hanzo Dataroom custom domain set up</Preview>
<Tailwind>
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
{title}
@@ -84,7 +84,7 @@ export default function CustomDomainSetup({
<ol className="list-inside list-decimal text-sm">
<li>Choose your subdomain (e.g. docs.yourcompany.com)</li>
<li>Add a CNAME record pointing to dataroom.hanzo.ai</li>
<li>Configure the domain in your Papermark settings</li>
<li>Configure the domain in your Hanzo Dataroom settings</li>
<li>Start sharing with your branded domain!</li>
</ol>
+4 -4
View File
@@ -25,14 +25,14 @@ const DataRoomsInformationEmail = () => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
Virtual Data Rooms
</Text>
<Text className="text-sm">Unlimited branded data rooms!</Text>
<Text className="text-sm">
With Papermark Data Rooms plan you can:
With Hanzo Dataroom plan you can:
</Text>
<ul className="list-inside list-disc text-sm">
<li>Share data rooms with one link</li>
@@ -45,7 +45,7 @@ const DataRoomsInformationEmail = () => {
<li>Use advanced link settings</li>
</ul>
<Text className="text-sm">
All about Papermark{" "}
All about Hanzo Dataroom{" "}
<a
href="https://dataroom.hanzo.ai/data-room"
className="text-blue-500 underline"
@@ -66,7 +66,7 @@ const DataRoomsInformationEmail = () => {
<Text className="text-sm">
If you require a fully customizable experience,{" "}
<a
href="https://cal.com/marcseitz/papermark"
href="https://dataroom.hanzo.ai/contact"
className="text-blue-500 underline"
>
book a call
@@ -49,7 +49,7 @@ export default function DataroomDigestNotification({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mb-8 mt-4 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-semibold mb-8 mt-4 text-center text-xl">
{`${count} new document${count !== 1 ? "s" : ""} in ${dataroomName}`}
@@ -82,7 +82,7 @@ export default function DataroomDigestNotification({
or copy and paste this URL into your browser: <br />
{url}
</Text>
<Text className="text-sm text-gray-400">Papermark</Text>
<Text className="text-sm text-gray-400">Hanzo Dataroom</Text>
<Hr />
<Section className="text-gray-400">
@@ -95,7 +95,7 @@ export default function DataroomDigestNotification({
<span className="font-semibold">{senderEmail}</span> because you
viewed the dataroom{" "}
<span className="font-semibold">{dataroomName}</span> on
Papermark. If you have any feedback or questions about this
Hanzo Dataroom. If you have any feedback or questions about this
email, simply reply to it.{" "}
<a
href={preferencesUrl}
+3 -3
View File
@@ -34,7 +34,7 @@ export default function DataroomNotification({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mb-8 mt-4 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-seminbold mb-8 mt-4 text-center text-xl">
{`New document available for ${dataroomName}`}
@@ -58,7 +58,7 @@ export default function DataroomNotification({
or copy and paste this URL into your browser: <br />
{`${url}`}
</Text>
<Text className="text-sm text-gray-400">Papermark</Text>
<Text className="text-sm text-gray-400">Hanzo Dataroom</Text>
<Hr />
<Section className="text-gray-400">
@@ -71,7 +71,7 @@ export default function DataroomNotification({
<span className="font-semibold">{senderEmail}</span> because you
viewed the dataroom{" "}
<span className="font-semibold">{dataroomName}</span> on
Papermark. If you have any feedback or questions about this
Hanzo Dataroom. If you have any feedback or questions about this
email, simply reply to it.{" "}
<a
href={unsubscribeUrl}
+3 -3
View File
@@ -24,12 +24,12 @@ const DataroomTrial24hReminderEmail = ({ name }: TrialEndReminderEmail) => {
return (
<Html>
<Head />
<Preview>Upgrade to Hanzo Dataroom Data Rooms Plan</Preview>
<Preview>Upgrade to Hanzo Dataroom Plan</Preview>
<Tailwind>
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-seminbold mx-0 mb-8 mt-4 p-0 text-center text-xl">
Your Data Room plan trial expires in 24 hours
@@ -38,7 +38,7 @@ const DataroomTrial24hReminderEmail = ({ name }: TrialEndReminderEmail) => {
Hey{name && ` ${name}`}!
</Text>
<Text className="text-sm leading-6 text-black">
Your Papermark Data Room plan trial expires in 24 hours.
Your Hanzo Dataroom Data Room plan trial expires in 24 hours.
Don&apos;t lose access to these features -{" "}
<Link href={`https://dataroom.hanzo.ai/settings/billing`}>
upgrade today
+2 -2
View File
@@ -29,7 +29,7 @@ const DataroomTrialEnd = ({ name }: DataroomTrialEnd) => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mb-8 mt-4 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-seminbold mb-8 mt-4 text-center text-xl">
Your Data Room plan trial has expired
@@ -38,7 +38,7 @@ const DataroomTrialEnd = ({ name }: DataroomTrialEnd) => {
Hey{name && ` ${name}`}!
</Text>
<Text className="text-sm leading-6 text-black">
Your Papermark Data Room trial has expired.
Your Hanzo Dataroom Data Room trial has expired.
<br />
<Link
href={`https://dataroom.hanzo.ai/settings/billing`}
+3 -3
View File
@@ -14,10 +14,10 @@ const DataroomTrialWelcomeEmail = ({ name }: WelcomeEmailProps) => {
<Body className="font-sans text-sm">
<Text>Hi{name && ` ${name}`},</Text>
<Text>
I am Marc, founder of Papermark. Thanks for creating a trial. Do you
need any help with Data Rooms setup?
Thanks for starting a Hanzo Dataroom trial. Do you need any help with
Data Rooms setup?
</Text>
<Text>Marc</Text>
<Text>The Hanzo Dataroom Team</Text>
</Body>
</Tailwind>
</Html>
@@ -40,7 +40,7 @@ export default function DataroomUploadNotification({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
New File Upload
+2 -2
View File
@@ -28,14 +28,14 @@ export default function DomainDeleted({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-seminbold mx-0 mb-8 mt-4 p-0 text-center text-xl">
Domain Deleted
</Text>
<Text className="text-sm leading-6 text-black">
Your domain <code className="text-purple-600">{domain}</code> for
your Papermark account has been invalid for 30 days. As a result,
your Hanzo Dataroom account has been invalid for 30 days. As a result,
it has been deleted from Hanzo Dataroom.
</Text>
<Text className="text-sm leading-6 text-black">
+2 -2
View File
@@ -52,7 +52,7 @@ export default function DownloadReady({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="text-sm leading-6 text-black">
Your download of <strong>{dataroomName}</strong> is ready!
@@ -60,7 +60,7 @@ export default function DownloadReady({
<Text className="text-sm leading-6 text-black">
{isViewer
? "Click the button below to open your downloads page and get your files."
: "Click the button below to download your files. You'll need to be logged in to your Papermark account to access the download."}
: "Click the button below to download your files. You'll need to be logged in to your Hanzo Dataroom account to access the download."}
</Text>
<Section className="my-8 text-center">
+2 -2
View File
@@ -29,13 +29,13 @@ export function EmailUpdated({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
Your email address has been changed
</Text>
<Text className="text-sm leading-6 text-black">
The email address for your Papermark account has been changed from{" "}
The email address for your Hanzo Dataroom account has been changed from{" "}
<strong>{oldEmail}</strong> to <strong>{newEmail}</strong>.
</Text>
<Text className="text-sm leading-6 text-black">
+2 -2
View File
@@ -30,10 +30,10 @@ export default function ExportReady({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="text-sm leading-6 text-black">
The export you requested is ready to download for your Papermark
The export you requested is ready to download for your Hanzo Dataroom
account. Make sure you&apos;re signed into this account, and click
below to download. The file will be available for the next three
days.
+2 -13
View File
@@ -21,24 +21,13 @@ const HundredViewsCongratsEmail = ({
<Body className="font-sans text-sm">
<Text>Hi{name && ` ${name}`},</Text>
<Text>
I&apos;m Marc, founder of Papermark. Congrats on 100 views on your
From the Hanzo Dataroom team congrats on 100 views on your
documents.
</Text>
<Text>Would you help others discover us too?</Text>
<Text>
<Link
href="https://www.g2.com/products/papermark/reviews"
target="_blank"
className="text-blue-500 underline"
>
Leave a review on G2
</Link>
</Text>
<Text>Small gift from us inside.</Text>
<Text>
Thanks so much,
<br />
Marc
The Hanzo Dataroom Team
</Text>
</Body>
</Tailwind>
@@ -42,7 +42,7 @@ export default function SlackIntegrationNotification({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
An integration has been added to your team
+2 -2
View File
@@ -31,7 +31,7 @@ export default function InvalidDomain({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
{invalidDays >= 14
@@ -40,7 +40,7 @@ export default function InvalidDomain({
</Text>
<Text className="text-sm leading-6 text-black">
Your domain <code className="text-purple-600">{domain}</code> for
your Papermark account{" "}
your Hanzo Dataroom account{" "}
{invalidDays >= 14
? `has been invalid for ${invalidDays} days.`
: `is still unconfigured.`}
+2 -2
View File
@@ -24,7 +24,7 @@ const Onboarding1Email = () => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
Turn your documents into links
@@ -33,7 +33,7 @@ const Onboarding1Email = () => {
It all starts from sharing your first document!
</Text>
<Text className="text-sm">
With Papermark you can upload different kind of documents and turn
With Hanzo Dataroom you can upload different kind of documents and turn
them into shareable links:
</Text>
<ul className="list-inside list-disc text-sm">
+2 -2
View File
@@ -22,7 +22,7 @@ const Onboarding2Email = () => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
Set link permissions
@@ -31,7 +31,7 @@ const Onboarding2Email = () => {
There are many ways how you can protect your documents!
</Text>
<Text className="text-sm">
With Papermark you can use different link settings for shared
With Hanzo Dataroom you can use different link settings for shared
documents and data rooms:
</Text>
<ul className="list-inside list-disc text-sm">
+2 -2
View File
@@ -22,7 +22,7 @@ const Onboarding3Email = () => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
Watch the views come in real-time
@@ -31,7 +31,7 @@ const Onboarding3Email = () => {
You need to know who viewed your documents!
</Text>
<Text className="text-sm">
With Papermark you can track progress on each page of your
With Hanzo Dataroom you can track progress on each page of your
document and other analytics:
</Text>
<ul className="list-inside list-disc text-sm">
+3 -3
View File
@@ -24,7 +24,7 @@ const Onboarding4Email = () => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
Custom domains and branding
@@ -32,7 +32,7 @@ const Onboarding4Email = () => {
<Text className="text-sm">
Look professional with custom branding!
</Text>
<Text className="text-sm">With Papermark you can:</Text>
<Text className="text-sm">With Hanzo Dataroom you can:</Text>
<ul className="list-inside list-disc text-sm">
<li>
Share documnets with your <strong>custom domain💫</strong>{" "}
@@ -60,7 +60,7 @@ const Onboarding4Email = () => {
<Text className="text-sm">
If you are looking for full white-labelling just{" "}
<a
href="https://cal.com/marcseitz/papermark"
href="https://dataroom.hanzo.ai/contact"
className="text-blue-500 underline"
>
book a call
+1 -1
View File
@@ -43,7 +43,7 @@ export default function OtpEmailVerification({
/>
) : (
<Text className="text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
)}
</div>
+1 -1
View File
@@ -28,7 +28,7 @@ const SlackIntegrationEmail = ({ name }: SlackIntegrationEmailProps) => {
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
Connect Slack in 2 clicks
+2 -2
View File
@@ -34,7 +34,7 @@ export default function TeamInvitation({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-seminbold mx-0 mb-8 mt-4 p-0 text-center text-xl">
{`Join ${teamName} on Hanzo Dataroom`}
@@ -44,7 +44,7 @@ export default function TeamInvitation({
<span className="font-semibold">{senderName}</span> ({senderEmail}
) has invited you to the{" "}
<span className="font-semibold">{teamName}</span> team on{" "}
<span className="font-semibold">Papermark</span>.
<span className="font-semibold">Hanzo Dataroom</span>.
</Text>
<Section className="my-8 text-center">
<Button
+2 -12
View File
@@ -25,7 +25,7 @@ const ThousandViewsCongratsEmail = ({
<Body className="font-sans text-sm">
<Text>Hi{name && ` ${name}`},</Text>
<Text>
I&apos;m Marc, founder of Papermark. Congrats on 1000 views on your
From the Hanzo Dataroom team congrats on 1000 views on your
documents.
</Text>
<Text>How is your experience so far?</Text>
@@ -33,17 +33,7 @@ const ThousandViewsCongratsEmail = ({
<Text>
Thanks so much,
<br />
Marc
</Text>
<Text>
<Link
href="https://www.g2.com/products/papermark/reviews"
target="_blank"
className="text-blue-500 underline"
rel="noopener noreferrer"
>
Leave us a G2 review
</Link>
The Hanzo Dataroom Team
</Text>
</Body>
</Tailwind>
@@ -26,13 +26,10 @@ const UpgradePersonalEmail = ({
<Body className="font-sans text-sm">
<Text>Hi{name && ` ${name}`},</Text>
<Text>
I&apos;m Iuliia, co-founder of Papermark. Thanks for upgrading!
I&apos;m thrilled to have you on our {planName} plan.
Thanks for upgrading to {planName}! You now have access to advanced
features. Any questions so far?
</Text>
<Text>
You now have access to advanced features. Any questions so far??
</Text>
<Text>Iuliia</Text>
<Text>The Hanzo Dataroom Team</Text>
</Body>
</Tailwind>
</Html>
+1 -1
View File
@@ -79,7 +79,7 @@ const UpgradePlanEmail = ({
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Text className="font-seminbold mx-0 mb-8 mt-4 p-0 text-center text-xl">
Thanks for upgrading to Hanzo Dataroom {planTypeText}!
@@ -25,10 +25,10 @@ const SixMonthMilestoneEmail = ({
<Tailwind>
<Body className="font-sans text-sm">
<Text>Hi {name},</Text>
<Text>What&apos;s been your biggest win using Papermark?</Text>
<Text>What&apos;s been your biggest win using Hanzo Dataroom?</Text>
<Text>
Marc here. It&apos;s been 6 months since you using advanced
Papermark features! Excited to hear your story and feedback for us.
Hanzo Dataroom features! Excited to hear your story and feedback for us.
</Text>
<Text>Marc</Text>
@@ -33,10 +33,10 @@ export function ConfirmEmailChange({
<Container className="mx-auto my-10 max-w-[500px] rounded border border-solid border-gray-200 px-10 py-5">
<Section>
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
<span className="font-bold tracking-tighter">Hanzo Dataroom</span>
</Text>
<Heading className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
Your Papermark Email Change Confirmation Link
Your Hanzo Dataroom Email Change Confirmation Link
</Heading>
</Section>
<Heading className="text-sm leading-6 text-black">
+3 -3
View File
@@ -23,20 +23,20 @@ const VerificationCodeEmail = ({
return (
<Html>
<Head />
<Preview>Your login code for Papermark: {code}</Preview>
<Preview>Your login code for Hanzo Dataroom: {code}</Preview>
<Tailwind>
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 max-w-[600px] rounded border border-solid border-neutral-200 px-10 py-5">
<Section className="mt-8">
<Text className="text-2xl font-bold tracking-tighter">
Papermark
Hanzo Dataroom
</Text>
</Section>
<Heading className="mx-0 my-7 p-0 text-xl font-semibold text-black">
Your login code
</Heading>
<Text className="text-sm leading-6 text-neutral-600">
Enter this code to sign in to your Papermark account:
Enter this code to sign in to your Hanzo Dataroom account:
</Text>
<Section className="my-6">
<Text

Some files were not shown because too many files have changed in this diff Show More