diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 60b385403..3471f4f88 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,13 @@ "ghcr.io/devcontainers/features/node:1": {} }, "onCreateCommand": "./.devcontainer/on-create.sh", - "forwardPorts": [3000, 54320, 9000, 2500, 1100], + "forwardPorts": [ + 3000, + 54320, + 9000, + 2500, + 1100 + ], "customizations": { "vscode": { "extensions": [ @@ -25,8 +31,8 @@ "GitHub.copilot", "GitHub.vscode-pull-request-github", "Prisma.prisma", - "VisualStudioExptTeam.vscodeintellicode", + "VisualStudioExptTeam.vscodeintellicode" ] } } -} +} \ No newline at end of file diff --git a/.github/actions/cache-build/action.yml b/.github/actions/cache-build/action.yml new file mode 100644 index 000000000..e1eb4da22 --- /dev/null +++ b/.github/actions/cache-build/action.yml @@ -0,0 +1,24 @@ +name: Cache production build binaries +description: 'Cache or restore if necessary' +inputs: + node_version: + required: false + default: v18.x +runs: + using: 'composite' + steps: + - name: Cache production build + uses: actions/cache@v3 + id: production-build-cache + with: + path: | + ${{ github.workspace }}/apps/web/.next + ${{ github.workspace }}/apps/marketing/.next + **/.turbo/** + **/dist/** + + key: prod-build-${{ github.run_id }} + restore-keys: prod-build- + + - run: npm run build + shell: bash diff --git a/.github/actions/node-install/action.yml b/.github/actions/node-install/action.yml new file mode 100644 index 000000000..77483a9a4 --- /dev/null +++ b/.github/actions/node-install/action.yml @@ -0,0 +1,39 @@ +name: 'Setup node and cache node_modules' +inputs: + node_version: + required: false + default: v18.x + +runs: + using: 'composite' + steps: + - name: Set up Node ${{ inputs.node_version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + + - name: Cache npm + uses: actions/cache@v3 + with: + path: ~/.npm + key: npm-${{ hashFiles('package-lock.json') }} + restore-keys: npm- + + - name: Cache node_modules + uses: actions/cache@v3 + id: cache-node-modules + with: + path: | + node_modules + packages/*/node_modules + apps/*/node_modules + key: modules-${{ hashFiles('package-lock.json') }} + + - name: Install dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + shell: bash + run: | + npm ci --no-audit + npm run prisma:generate + env: + HUSKY: '0' diff --git a/.github/actions/playwright-install/action.yml b/.github/actions/playwright-install/action.yml new file mode 100644 index 000000000..27d0e66b4 --- /dev/null +++ b/.github/actions/playwright-install/action.yml @@ -0,0 +1,19 @@ +name: Install playwright binaries +description: 'Install playwright, cache and restore if necessary' +runs: + using: 'composite' + steps: + - name: Cache playwright + id: cache-playwright + uses: actions/cache@v3 + with: + path: | + ~/.cache/ms-playwright + ${{ github.workspace }}/node_modules/playwright + key: playwright-${{ hashFiles('**/package-lock.json') }} + restore-keys: playwright- + + - name: Install playwright + if: steps.cache-playwright.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deda53ff0..bebca8e85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: 'Continuous Integration' on: + workflow_call: push: branches: ['main'] pull_request: @@ -10,9 +11,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true -env: - HUSKY: 0 - jobs: build_app: name: Build App @@ -23,20 +21,12 @@ jobs: with: fetch-depth: 2 - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm - - - name: Install dependencies - run: npm ci + - uses: ./.github/actions/node-install - name: Copy env run: cp .env.example .env - - name: Build - run: npm run build + - uses: ./.github/actions/cache-build build_docker: name: Build Docker Image @@ -47,5 +37,31 @@ jobs: with: fetch-depth: 2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Build Docker Image - run: ./docker/build.sh + uses: docker/build-push-action@v5 + with: + push: false + context: . + file: ./docker/Dockerfile + tags: documenso-${{ github.sha }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + + - # Temp fix + # https://github.com/docker/build-push-action/issues/252 + # https://github.com/moby/buildkit/issues/1896 + name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.github/workflows/clean-cache.yml b/.github/workflows/clean-cache.yml new file mode 100644 index 000000000..2cb13f661 --- /dev/null +++ b/.github/workflows/clean-cache.yml @@ -0,0 +1,29 @@ +name: cleanup caches by a branch +on: + pull_request: + types: + - closed + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 465041c0a..314dc7b7b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,19 +25,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm - - - name: Install Dependencies - run: npm ci - - name: Copy env run: cp .env.example .env - - name: Build Documenso - run: npm run build + - uses: ./.github/actions/node-install + + - uses: ./.github/actions/cache-build - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 7b05458d9..12a7d9521 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -6,29 +6,21 @@ on: branches: ['main'] jobs: e2e_tests: - name: "E2E Tests" + name: 'E2E Tests' timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm - - name: Install dependencies - run: npm ci - name: Copy env run: cp .env.example .env + - uses: ./.github/actions/node-install + - name: Start Services run: npm run dx:up - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - - name: Generate Prisma Client - run: npm run prisma:generate -w @documenso/prisma + - uses: ./.github/actions/playwright-install - name: Create the database run: npm run prisma:migrate-dev @@ -36,6 +28,8 @@ jobs: - name: Seed the database run: npm run prisma:seed + - uses: ./.github/actions/cache-build + - name: Run Playwright tests run: npm run ci @@ -43,7 +37,7 @@ jobs: if: always() with: name: test-results - path: "packages/app-tests/**/test-results/*" + path: 'packages/app-tests/**/test-results/*' retention-days: 30 env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..f69ddb57b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,133 @@ +name: Publish Docker + +on: + push: + branches: ['release'] + +jobs: + build_and_publish_platform_containers: + name: Build and publish platform containers + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - warp-ubuntu-latest-x64-4x + - warp-ubuntu-latest-arm64-4x + + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + + - name: Build the docker image + env: + BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }} + run: | + APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')" + GIT_SHA="$(git rev-parse HEAD)" + + docker build \ + -f ./docker/Dockerfile \ + --progress=plain \ + -t "documenso/documenso-$BUILD_PLATFORM:latest" \ + -t "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \ + -t "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \ + -t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest" \ + -t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \ + -t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \ + . + + - name: Push the docker image to DockerHub + run: docker push --all-tags "documenso/documenso-$BUILD_PLATFORM" + env: + BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }} + + - name: Push the docker image to GitHub Container Registry + run: docker push --all-tags "ghcr.io/documenso/documenso-$BUILD_PLATFORM" + env: + BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }} + + create_and_publish_manifest: + name: Create and publish manifest + runs-on: ubuntu-latest + needs: build_and_publish_platform_containers + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + + - name: Create and push DockerHub manifest + run: | + APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')" + GIT_SHA="$(git rev-parse HEAD)" + + docker manifest create \ + documenso/documenso:latest \ + --amend documenso/documenso-amd64:latest \ + --amend documenso/documenso-arm64:latest \ + + docker manifest create \ + documenso/documenso:$GIT_SHA \ + --amend documenso/documenso-amd64:$GIT_SHA \ + --amend documenso/documenso-arm64:$GIT_SHA \ + + docker manifest create \ + documenso/documenso:$APP_VERSION \ + --amend documenso/documenso-amd64:$APP_VERSION \ + --amend documenso/documenso-arm64:$APP_VERSION \ + + docker manifest push documenso/documenso:latest + docker manifest push documenso/documenso:$GIT_SHA + docker manifest push documenso/documenso:$APP_VERSION + + - name: Create and push Github Container Registry manifest + run: | + APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')" + GIT_SHA="$(git rev-parse HEAD)" + + docker manifest create \ + ghcr.io/documenso/documenso:latest \ + --amend ghcr.io/documenso/documenso-amd64:latest \ + --amend ghcr.io/documenso/documenso-arm64:latest \ + + docker manifest create \ + ghcr.io/documenso/documenso:$GIT_SHA \ + --amend ghcr.io/documenso/documenso-amd64:$GIT_SHA \ + --amend ghcr.io/documenso/documenso-arm64:$GIT_SHA \ + + docker manifest create \ + ghcr.io/documenso/documenso:$APP_VERSION \ + --amend ghcr.io/documenso/documenso-amd64:$APP_VERSION \ + --amend ghcr.io/documenso/documenso-arm64:$APP_VERSION \ + + docker manifest push ghcr.io/documenso/documenso:latest + docker manifest push ghcr.io/documenso/documenso:$GIT_SHA + docker manifest push ghcr.io/documenso/documenso:$APP_VERSION diff --git a/README.md b/README.md index 6d2fab334..93c6d9f95 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ ->We are nominated for a Product Hunt Gold Kitty ๐Ÿ˜บโœจ and appreciate any support: https://documen.so/kitty - Documenso Logo

@@ -29,7 +27,7 @@ open in devcontainer - Contributor Covenant + Contributor Covenant

@@ -107,7 +105,7 @@ Contact us if you are interested in our Enterprise plan for large organizations To run Documenso locally, you will need -- Node.js +- Node.js (v18 or above) - Postgres SQL Database - Docker (optional) @@ -209,7 +207,16 @@ If you're a visual learner and prefer to watch a video walkthrough of setting up ## Docker -๐Ÿšง Docker containers and images are current in progress. We are actively working on bringing a simple Docker build and publish pipeline for Documenso. +We provide a Docker container for Documenso, which is published on both DockerHub and GitHub Container Registry. + +- DockerHub: [https://hub.docker.com/r/documenso/documenso](https://hub.docker.com/r/documenso/documenso) +- GitHub Container Registry: [https://ghcr.io/documenso/documenso](https://ghcr.io/documenso/documenso) + +You can pull the Docker image from either of these registries and run it with your preferred container hosting provider. + +Please note that you will need to provide environment variables for connecting to the database, mailserver, and so forth. + +For detailed instructions on how to configure and run the Docker container, please refer to the [Docker README](./docker/README.md) in the `docker` directory. ## Self Hosting diff --git a/apps/marketing/content/blog/early-adopters.mdx b/apps/marketing/content/blog/early-adopters.mdx index 2ff7ae1f6..ddd779fbc 100644 --- a/apps/marketing/content/blog/early-adopters.mdx +++ b/apps/marketing/content/blog/early-adopters.mdx @@ -24,6 +24,8 @@ tags: +> ๐Ÿ”” UPDATE: We launched teams and the early adopters plan will be replaced by the new teams pricing as soon as all availible early adopters seats are filled. + ## Community-Driven Development As we ramp up hiring and development speed for Documenso, I want to discuss how we plan to build its core version. diff --git a/apps/marketing/content/blog/launch-week-2-day-1.mdx b/apps/marketing/content/blog/launch-week-2-day-1.mdx new file mode 100644 index 000000000..2799baafe --- /dev/null +++ b/apps/marketing/content/blog/launch-week-2-day-1.mdx @@ -0,0 +1,64 @@ +--- +title: Launch Week II - Day 1 - Teams +description: Teams for Documenso are here. And they come free for early adopters! +authorName: 'Timur Ercan' +authorImage: '/blog/blog-author-timur.jpeg' +authorRole: 'Co-Founder' +date: 2024-02-26 +tags: + - Launch Week + - Teams + - Early Adopter Perks +--- + + + +> TLDR; Docucmenso now supports teams that share documents, templates and a team mail address. Early Adopter get UNLIMITED1 Users. + +## Kicking off Launch Week II - "Connected" + +The day has come! Roughly 5 months after kicked off our first launch week with open sourcing our design and Malfunction Mania, Launch Week #2 is here ๐ŸŽ‰ This Launch Week's theme is "connected", since this is all about connecting humans, machines and documents. + +Working with documents and getting that signature is a team sport. This is why we are kicking it off today with a very long-awaited feature: Documenso now supports teams! + +## Introducing Teams for Documenso + +You can now create teams next to your personal account: Simply invite your colleagues, and you can include everyone you like in working with your documents. With teams, you can: + +- Send unlimited signature requests with unlimited recipients +- Create, view, edit and sign documents owned by the team +- Define a dedicated team email, to receive signing requests into a team inbox for the owner to sign +- Manage team roles: Member (Create+Edit), Managers (+Manage Team Members), Owner (+Transfer Team +Delete Team + Sign Documents sent to team email) + +## Pricing + +Together with Teams, we are announcing the new teams pricing: + +- $10 per seat per month +- 5 seats minimum +- You can add seats dynamically as needed + +This pricing will take effect, as soon as the early adopter seats run out. Want to check out teams: [https://documen.so/teams](https://documen.so/teams). + +## Early Adopter Perks + +There is one more point on pricing I have been looking forward to for a long time: + +All early adopter plans now include **UNLIMITED teams and users**1 . We appreciate your support so far very much, and I'm happy to announce this first of more early adopter perks to come. We have roughly 48 early adopter plans left, so if you plan to onboard your team, now is a great time to [grab your early adopter seat.](https://documen.so/claim-early-adopters-plan) + +We are eager to hear from all teams users how you like this addition and what we can add to make it even better. Connect with us on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord) if you have any questions or comments! We're always here, and we would love to hear from you :) + +> ๐Ÿšจ We need you help to help us to make this the biggest launch week yet: Support us on Twitter or anywhere to spread awareness for open signing! The best posts will receive merch codes ๐Ÿ‘€ + +Best from Hamburg\ +Timur + +\ +[1] Within reason. If you are unsure what that means, feel free to contact hi@documenso.com and ask for clarification if it's more than 100. diff --git a/apps/marketing/content/blog/launch-week-2-day-2.mdx b/apps/marketing/content/blog/launch-week-2-day-2.mdx new file mode 100644 index 000000000..3a67977ec --- /dev/null +++ b/apps/marketing/content/blog/launch-week-2-day-2.mdx @@ -0,0 +1,76 @@ +--- +title: Launch Week II - Day 2 - Templates +description: Templates help you prepare regular documents faster. And you can share them with your team! +authorName: 'Timur Ercan' +authorImage: '/blog/blog-author-timur.jpeg' +authorRole: 'Co-Founder' +date: 2024-02-27 +tags: + - Launch Week + - Templates +--- + + + +> TLDR; You can now reuse documents via templates. More field types coming soon as well. + +## Introducing Templates + +It's day 2 of Launch Week, everybody ๐Ÿ™Œ After introducing [Teams](https://documenso.com/blog/launch-week-2-day-1) yesterday, today we are looking at making Documenso faster for daily use: +We are launching templates for Documenso! Templates are an easy way to reuse documents you send out often with just a few clicks. With templates, you can: + +
+ + +
+ Quickly fill out recipients, when creating from a template +
+
+ +- Save often-uploaded documents for reuse +- Pre-define fields, so you just have to send the document +- Quickly fill out recipients and roles for new documents +- Share templates with your team to make working together even easier + +
+ + +
+ POV: You are a diligent german and create custom receipts with Documenso +
+
+ +## Pricing + +Templates are **included in all Documenso Plans!** That includes our free tier: The limit of 5 documents per month still applies, but you are free to reach it with less friction using templates. Sharing templates with other users is only possible with the teams plan. If you want to share templates with people not in your team, we might have something coming up later this week ๐Ÿ‘€ + +## What's Next for Templates + +We have a lot of great stuff coming up for templates as well: + +- More Field Types are in the pipeline +- Sharing Templates Externally ๐Ÿ‘€ + +Check out templates [here](https://documen.so/templates) and let us know what you think and what we can improve. Which field types are you missing? Connect with us on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord) if you have any questions or comments! We're always here and would love to hear from you :) + +> ๐Ÿšจ We need you help to help us to make this the biggest launch week yet: Support us on Twitter or anywhere to spread awareness for open signing! The best posts will receive merch codes ๐Ÿ‘€ + +Best from Hamburg\ +Timur diff --git a/apps/marketing/content/blog/launch-week-2-day-3.mdx b/apps/marketing/content/blog/launch-week-2-day-3.mdx new file mode 100644 index 000000000..6ea0db9b9 --- /dev/null +++ b/apps/marketing/content/blog/launch-week-2-day-3.mdx @@ -0,0 +1,53 @@ +--- +title: Launch Week II - Day 3 - API +description: Documenso's mission is to create a plattform developers all around the world can build upon. Today we are releasing the first version of our public API, included in all plans! +authorName: 'Timur Ercan' +authorImage: '/blog/blog-author-timur.jpeg' +authorRole: 'Co-Founder' +date: 2024-02-28 +tags: + - Launch Week + - API +--- + + + +> TLDR; The public API is now availible for all plans. + +## Introducing the public Documenso API + +Launch. Week. Day. 3 ๐ŸŽ‰ Documenso's mission is to create a platform that developers all around the world can build upon. Today we are releasing the first version of our public API, and we are pumped. Since this is the first version, we focused on the basics. With the new API you can: + +- Get Documents (Individual or all Accessible) +- Upload Documents +- Delete Documents +- Create Documents from Templates +- Trigger Sending Documents for Singing + +You can check out the detailed API documentation here: + +> API DOCUMENTATION: [https://app.documenso.com/api/v1/openapi](https://app.documenso.com/api/v1/openapi) + +## Pricing + +We are building Documenso to be an open and extendable platform; therefore the API is included in all current plans. The API is authenticated via auth tokens, which every user can create at no extra cost, as can teams. Existing limits still apply (i.e., the number of included documents for the free plan). While we don't have all the details yet, we don't intend to price the API usage in itself (rather the accounts using it) since we want you to build on Documenso without being smothered by API costs. + +> Try the API here for free: [https://documen.so/api](https://documen.so/api) + +## What's next for the API + +You tell us. This is by far the most requested feature, so we would like to hear from you. What should we add? How can we integrate even better? + +Connect with us on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord) if you have any questions or comments! We're always here and would love to hear from you :) + +> ๐Ÿšจ We need you help to help us to make this the biggest launch week yet: Support us on Twitter or anywhere to spread awareness for open signing! The best posts will receive merch codes ๐Ÿ‘€ + +Best from Hamburg\ +Timur diff --git a/apps/marketing/content/blog/launch-week-2-day-4.mdx b/apps/marketing/content/blog/launch-week-2-day-4.mdx new file mode 100644 index 000000000..b6f5691fd --- /dev/null +++ b/apps/marketing/content/blog/launch-week-2-day-4.mdx @@ -0,0 +1,63 @@ +--- +title: Launch Week II - Day 4 - Webhooks and Zapier +description: If you want to integrate Documenso without fiddling with the API, we got you as well. You can now integrate Documenso via Zapier, included in all plans! +authorName: 'Timur Ercan' +authorImage: '/blog/blog-author-timur.jpeg' +authorRole: 'Co-Founder' +date: 2024-02-29 +tags: + - Launch Week + - Zapier + - Webhooks +--- + + + +> TLDR; Zapier Integration is now available for all plans. + +## Introducing Zapier for Documenso + +Day 4 ๐Ÿฅณ Yesterday we introduced our [public API](https://documen.so/day3) for developers to build on Documenso. If you are not a developer or simple want a quicker integration this is for you: Documenso now support Zapier Integrations! Just connect your Documenso account via a simple login flow with Zapier and you will have access to Zapier's universe of integrations ๐Ÿ’ซ The integration currently supports: + +- Document Created ([https://documen.so/zapier-created](https://documen.so/zapier-created)) +- Document Sent ([Chttps://documen.so/zapier-sent](https://documen.so/zapier-sent)) +- Document Opened ([https://documen.so/zapier-opened](https://documen.so/zapier-opened)) +- Document Signed ([https://documen.so/zapier-signed](https://documen.so/zapier-signed)) +- Document Completed ([https://documen.so/zapier-completed](https://documen.so/zapier-completed)) + +> โšก๏ธ You can create your own Zaps here: https://zapier.com/apps/documenso/integrations + +Each event comes with extensive meta-data for you to use in Zapier. Missing something? Reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord). We're always here and would love to hear from you :) + +## Also Introducing for Documenso: Webhooks + +To build the Zapier Integration, we needed a good webhooks concept, so we added that as well. Together with your Zaps, you can also now create customer webhooks in Documenso. You can try webhooks here for free: [https://documen.so/webhooks](https://documen.so/webhooks) + +
+ + +
+ Create unlimited custom webhooks with each plan. +
+
+ +## Pricing + +Just like the API, we consider the Zapier integration and webhooks part of the open Documenso platform. Zapier is **available for all Documenso plans**, including free! [Login now](https://documen.so/login) to check it out. + +> ๐Ÿšจ We need you help to help us to make this the biggest launch week yet: Support us on Twitter or anywhere to spread awareness for open signing! The best posts will receive merch codes ๐Ÿ‘€ + +Best from Hamburg\ +Timur diff --git a/apps/marketing/content/blog/launch-week-2-day-5.mdx b/apps/marketing/content/blog/launch-week-2-day-5.mdx new file mode 100644 index 000000000..04d639206 --- /dev/null +++ b/apps/marketing/content/blog/launch-week-2-day-5.mdx @@ -0,0 +1,61 @@ +--- +title: Launch Week II - Day 5 - Documenso Profiles +description: Documenso profiles allow you to send signing links to people so they can sign anytime and see who you are. Documenso Profile Usernames can be claimed starting today. Long ones free, short ones paid. Profiles will launch as soon as they are shiny. +authorName: 'Timur Ercan' +authorImage: '/blog/blog-author-timur.jpeg' +authorRole: 'Co-Founder' +date: 2024-03-01 +tags: + - Launch Week + - Profiles +--- + + + +> TLDR; Documenso profiles allow you to send signing links to people so they can sign anytime and see who you are. Documenso Profile Usernames can be claimed starting today. Long ones free, short ones paid. Profiles launch as soon as they are shiny. + +## Introducing Documenso Profile Links + +Day 5 - The Finale ๐Ÿ”ฅ + +Signing documents has always been between humans, and signing something together should be as frictionless as possible. It should also be async, so you don't force your counterpart to jog to their device to send something when you are ready. Today we are announcing the new Documenso Profiles: + +
+ + +
+ Async > Sync: Add public templates to your Documenso Link and let people sign whenever they are ready. +
+
+ +Documenso profiles work with your existing templates. You can just add them to your public profile to let everyone with your link sign them. With profiles, we want to bring back the human aspect of signing. + +By making profiles public, you can always access what your counterparty offers and make them more visible in the process. Long-term, we plan to add more to profiles to help you ensure the person you are dealing with is who they claim to be. Documenso wants to be the trust layer of the internet, and we want to start at the very fundamental level: The individual transaction. + +Profiles are our first step towards bringing more trust into everything, simply by making the use of signing more frictionless. As there is more and more content of questionable origin out there, we want to support you in making it clear what you send out and what not. + +## Pricing and Claiming + +Documenso profile username can be claimed starting today. Documenso profiles will launch as soon as we are happy with the details โœจ + +- Long usernames (6 characters or more) come free with every account, e.g. **documenso.com/u/timurercan** +- Short usernames (5 characters or fewer) or less require any paid account ([Early Adopter](https://documen.so/claim-early-adopters-plan), [Teams](https://documen.so/teams) or Enterprise): **e.g., documenso.com/u/timur** + +You can claim your username here: [https://documen.so/claim](https://documen.so/claim) + +> ๐Ÿšจ We need you help to help us to make this the biggest launch week yet: Support us on Twitter or anywhere to spread awareness for open signing! The best posts will receive merch codes ๐Ÿ‘€ + +Best from Hamburg\ +Timur diff --git a/apps/marketing/content/blog/removing-server-actions.mdx b/apps/marketing/content/blog/removing-server-actions.mdx new file mode 100644 index 000000000..36dabdf9d --- /dev/null +++ b/apps/marketing/content/blog/removing-server-actions.mdx @@ -0,0 +1,56 @@ +--- +title: 'Embracing the Future and Moving Back Again: From Server Actions to tRPC' +description: 'This article talks about the need for the public API and the process of building it. It also talks about the requirements, constraints, challenges and lessons learnt from building it.' +authorName: 'Lucas Smith' +authorImage: '/blog/blog-author-lucas.png' +authorRole: 'Co-Founder' +date: 2024-03-07 +tags: + - Development +--- + +On October 26, 2022, during the Next.js Conf, Vercel unveiled Next.js 13, introducing a bunch of new features and a whole new paradigm shift for creating Next.js apps. + +React Server Components (RSCs) have been known for some time but haven't yet been implemented in a React meta-framework. With Next.js now adding them, the world of server-side rendering React applications was about to change. + +## The promise of React Server Components + +Described by Vercel as a tool for writing UIs rendered or optionally cached on the server, RSC promised direct integration with databases and server-specific capabilities, a departure from the typical React and SSR models. + +This feature initially appeared as a game-changer, promising simpler data fetching and routing, thanks to async server components and additional nested layout support. + +After experimenting with it for about six months on other projects, I came to the conclusion that while it was a little rough at the edges, it was indeed a game changer and something that should be adopted in Next.js projects moving forward. + +## A new new paradigm: Server Actions + +Vercel didn't stop at adding Server Components. A short while after the initial Next.js 13 release, they introduced "Server Actions." Server Actions allow for the calling of server-side functions without API routes, reducing the amount of ceremony required for adding a new mutation or event to your application. + +## Betting on new technology + +Following the release of both Server Actions and Server Components, we at Documenso embarked on a rewrite of the application. We had accumulated a bit of technical debt from the initial MVP, which was best shed as we also improved the design with help from our new designer. + +Having had much success with Server Components on other projects, I opted to go all in on both Server Components and Server Actions for the new version of the application. I was excited to see how much simpler and more efficient the application would be with these new technologies. + +Following our relaunch, we felt quite happy with our choices of server actions, which even let us cocktail certain clientโ€”and server-side logic into a single component without needing a bunch of effort on our end. + +## Navigating challenges with Server Actions + +While initially successful, we soon encountered issues with Server Actions, particularly when monitoring the application. Unfortunately, server actions make it much harder to monitor network requests and identify failed server actions since they use the route that they are currently on. + +Despite this issue, things were manageable; however, a critical issue arose when the usage of server actions caused bundle corruption, resulting in a top-level await insertion that would cause builds and tests to fail. + +This last issue was a deal breaker since it highlighted how much magic we were relying on with server actions. I like to avoid adding more magic where possible since, with bundlers and meta-frameworks, we are already handing off a lot of heavy lifting and getting a lot of magic in return. + +## Going all in on tRPC + +My quick and final solution to the issues we were facing was to fully embrace [tRPC](https://trpc.io/), which we already used in other app areas. The migration took less than a day and resolved all our issues while adding a lot more visibility to failing actions since they now had a dedicated API route. + +For those unfamiliar with tRPC, it is a framework for building end-to-end typesafe APIs with TypeScript and Next.js. It's an awesome library that lets you call your defined API safely within the client, causing build time errors when things inevitably drift. + +I've been a big fan of tRPC for some time now, so the decision to drop server actions and instead use more of tRPC was easy for me. + +## Lessons learned and the future + +While I believe server actions are a great idea, and I'm sure they will be improved in the future, I've relearned the lesson that it's best to avoid magic where possible. + +This doesn't mean we won't consider moving certain things back to server actions in the future. But for now, we will wait and watch how others use them and how they evolve. diff --git a/apps/marketing/next.config.js b/apps/marketing/next.config.js index 9c61e4ada..0a0032868 100644 --- a/apps/marketing/next.config.js +++ b/apps/marketing/next.config.js @@ -21,6 +21,7 @@ const FONT_CAVEAT_BYTES = fs.readFileSync( const config = { experimental: { outputFileTracingRoot: path.join(__dirname, '../../'), + serverComponentsExternalPackages: ['@node-rs/bcrypt'], serverActions: { bodySizeLimit: '50mb', }, diff --git a/apps/marketing/public/blog/hooks.png b/apps/marketing/public/blog/hooks.png new file mode 100644 index 000000000..9c324db0b Binary files /dev/null and b/apps/marketing/public/blog/hooks.png differ diff --git a/apps/marketing/public/blog/profile.png b/apps/marketing/public/blog/profile.png new file mode 100644 index 000000000..b216e9758 Binary files /dev/null and b/apps/marketing/public/blog/profile.png differ diff --git a/apps/marketing/public/blog/quickfill.png b/apps/marketing/public/blog/quickfill.png new file mode 100644 index 000000000..17765b643 Binary files /dev/null and b/apps/marketing/public/blog/quickfill.png differ diff --git a/apps/marketing/public/blog/template.png b/apps/marketing/public/blog/template.png new file mode 100644 index 000000000..74dd48de9 Binary files /dev/null and b/apps/marketing/public/blog/template.png differ diff --git a/apps/marketing/src/app/(marketing)/[content]/page.tsx b/apps/marketing/src/app/(marketing)/[content]/page.tsx index ba23e6b81..72941fbc5 100644 --- a/apps/marketing/src/app/(marketing)/[content]/page.tsx +++ b/apps/marketing/src/app/(marketing)/[content]/page.tsx @@ -5,11 +5,10 @@ import { allDocuments } from 'contentlayer/generated'; import type { MDXComponents } from 'mdx/types'; import { useMDXComponent } from 'next-contentlayer/hooks'; -export const generateStaticParams = () => - allDocuments.map((post) => ({ post: post._raw.flattenedPath })); +export const dynamic = 'force-dynamic'; export const generateMetadata = ({ params }: { params: { content: string } }) => { - const document = allDocuments.find((post) => post._raw.flattenedPath === params.content); + const document = allDocuments.find((doc) => doc._raw.flattenedPath === params.content); if (!document) { return { title: 'Not Found' }; diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx index 495b8946e..14b8b2d8f 100644 --- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx +++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx @@ -9,9 +9,6 @@ import { useMDXComponent } from 'next-contentlayer/hooks'; export const dynamic = 'force-dynamic'; -export const generateStaticParams = () => - allBlogPosts.map((post) => ({ post: post._raw.flattenedPath })); - export const generateMetadata = ({ params }: { params: { post: string } }) => { const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`); diff --git a/apps/marketing/src/app/(marketing)/blog/page.tsx b/apps/marketing/src/app/(marketing)/blog/page.tsx index 2eac963d1..4be1ab694 100644 --- a/apps/marketing/src/app/(marketing)/blog/page.tsx +++ b/apps/marketing/src/app/(marketing)/blog/page.tsx @@ -5,6 +5,7 @@ import { allBlogPosts } from 'contentlayer/generated'; export const metadata: Metadata = { title: 'Blog', }; + export default function BlogPage() { const blogPosts = allBlogPosts.sort((a, b) => { const dateA = new Date(a.date); diff --git a/apps/marketing/src/app/(marketing)/layout.tsx b/apps/marketing/src/app/(marketing)/layout.tsx index dd1a46418..a7c599b36 100644 --- a/apps/marketing/src/app/(marketing)/layout.tsx +++ b/apps/marketing/src/app/(marketing)/layout.tsx @@ -2,8 +2,12 @@ import React, { useEffect, useState } from 'react'; +import Image from 'next/image'; import { usePathname } from 'next/navigation'; +import launchWeekTwoImage from '@documenso/assets/images/background-lw-2.png'; +import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag'; +import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { cn } from '@documenso/ui/lib/utils'; import { Footer } from '~/components/(marketing)/footer'; @@ -17,6 +21,10 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) { const [scrollY, setScrollY] = useState(0); const pathname = usePathname(); + const { getFlag } = useFeatureFlags(); + + const showProfilesAnnouncementBar = getFlag('marketing_profiles_announcement_bar'); + useEffect(() => { const onScroll = () => { setScrollY(window.scrollY); @@ -38,6 +46,31 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) { 'bg-background/50 backdrop-blur-md': scrollY > 5, })} > + {showProfilesAnnouncementBar && ( +
+
+ Launch Week 2 +
+ +
+ Claim your documenso public profile username now!{' '} + documenso.com/u/yourname +
+ + Claim Now + +
+
+
+ )} +
diff --git a/apps/marketing/src/app/(marketing)/open/cap-table.tsx b/apps/marketing/src/app/(marketing)/open/cap-table.tsx index ca63bd7bf..ba6a12dc4 100644 --- a/apps/marketing/src/app/(marketing)/open/cap-table.tsx +++ b/apps/marketing/src/app/(marketing)/open/cap-table.tsx @@ -1,6 +1,7 @@ 'use client'; -import { HTMLAttributes, useEffect, useState } from 'react'; +import type { HTMLAttributes } from 'react'; +import { useEffect, useState } from 'react'; import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts'; diff --git a/apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx b/apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx index b96bbf50d..0df73e30c 100644 --- a/apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx +++ b/apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx @@ -14,7 +14,7 @@ export type MonthlyNewUsersChartProps = { export const MonthlyNewUsersChart = ({ className, data }: MonthlyNewUsersChartProps) => { const formattedData = [...data].reverse().map(({ month, count }) => { return { - month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLL'), + month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLLL'), count: Number(count), }; }); @@ -32,6 +32,9 @@ export const MonthlyNewUsersChart = ({ className, data }: MonthlyNewUsersChartPr [Number(value).toLocaleString('en-US'), 'New Users']} cursor={{ fill: 'hsl(var(--primary) / 10%)' }} /> diff --git a/apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx b/apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx index e31bb9def..96ce34556 100644 --- a/apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx +++ b/apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx @@ -14,7 +14,7 @@ export type MonthlyTotalUsersChartProps = { export const MonthlyTotalUsersChart = ({ className, data }: MonthlyTotalUsersChartProps) => { const formattedData = [...data].reverse().map(({ month, cume_count: count }) => { return { - month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLL'), + month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLLL'), count: Number(count), }; }); @@ -32,6 +32,9 @@ export const MonthlyTotalUsersChart = ({ className, data }: MonthlyTotalUsersCha [Number(value).toLocaleString('en-US'), 'Total Users']} cursor={{ fill: 'hsl(var(--primary) / 10%)' }} /> diff --git a/apps/marketing/src/app/(marketing)/open/page.tsx b/apps/marketing/src/app/(marketing)/open/page.tsx index 76de85fcf..8fef81134 100644 --- a/apps/marketing/src/app/(marketing)/open/page.tsx +++ b/apps/marketing/src/app/(marketing)/open/page.tsx @@ -15,6 +15,7 @@ import { MonthlyNewUsersChart } from './monthly-new-users-chart'; import { MonthlyTotalUsersChart } from './monthly-total-users-chart'; import { TeamMembers } from './team-members'; import { OpenPageTooltip } from './tooltip'; +import { Typefully } from './typefully'; export const metadata: Metadata = { title: 'Open Startup', @@ -237,6 +238,8 @@ export default async function OpenPage() { + +

Where's the rest?

diff --git a/apps/marketing/src/app/(marketing)/open/typefully.tsx b/apps/marketing/src/app/(marketing)/open/typefully.tsx new file mode 100644 index 000000000..a233904db --- /dev/null +++ b/apps/marketing/src/app/(marketing)/open/typefully.tsx @@ -0,0 +1,39 @@ +'use client'; + +import type { HTMLAttributes } from 'react'; + +import Link from 'next/link'; + +import { FaXTwitter } from 'react-icons/fa6'; + +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; + +export type TypefullyProps = HTMLAttributes; + +export const Typefully = ({ className, ...props }: TypefullyProps) => { + return ( +
+

Twitter Stats

+ +
+
+ + +

Documenso on X

+ + + +
+
+
+ ); +}; diff --git a/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx b/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx index fbf020c38..51fbaff36 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx @@ -27,7 +27,7 @@ export default async function SinglePlayerModeSuccessPage({ return notFound(); } - const signatures = await getRecipientSignatures({ recipientId: document.Recipient.id }); + const signatures = await getRecipientSignatures({ recipientId: document.Recipient[0].id }); return ; } diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index e0b55dbf5..a8e0a0c63 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -191,7 +191,7 @@ export const SinglePlayerClient = () => {

Create a{' '} @@ -203,7 +203,7 @@ export const SinglePlayerClient = () => { target="_blank" className="hover:text-foreground/80 font-semibold transition-colors" > - community plan + early adopter plan {' '} for exclusive features, including the ability to collaborate with multiple signers.

@@ -256,6 +256,7 @@ export const SinglePlayerClient = () => { fields={fields} onSubmit={onSignSubmit} requireName={Boolean(fields.find((field) => field.type === 'NAME'))} + requireCustomText={Boolean(fields.find((field) => field.type === 'TEXT'))} requireSignature={Boolean(fields.find((field) => field.type === 'SIGNATURE'))} /> diff --git a/apps/marketing/src/app/layout.tsx b/apps/marketing/src/app/layout.tsx index 57da42c3f..99a1a6483 100644 --- a/apps/marketing/src/app/layout.tsx +++ b/apps/marketing/src/app/layout.tsx @@ -2,6 +2,8 @@ import { Suspense } from 'react'; import { Caveat, Inter } from 'next/font/google'; +import { PublicEnvScript } from 'next-runtime-env'; + import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/feature-flag'; import { NEXT_PUBLIC_MARKETING_URL } from '@documenso/lib/constants/app'; import { getAllAnonymousFlags } from '@documenso/lib/universal/get-feature-flag'; @@ -62,6 +64,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo + diff --git a/apps/marketing/src/components/(marketing)/callout.tsx b/apps/marketing/src/components/(marketing)/callout.tsx index 72ae3907b..faa486c46 100644 --- a/apps/marketing/src/components/(marketing)/callout.tsx +++ b/apps/marketing/src/components/(marketing)/callout.tsx @@ -40,9 +40,9 @@ export const Callout = ({ starCount }: CalloutProps) => { className="rounded-full bg-transparent backdrop-blur-sm" onClick={onSignUpClick} > - Get the Early Adopters Plan - - $30/mo. forever! + Claim Early Adopter Plan + + $30/mo diff --git a/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx b/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx index ee123d7ad..b80b2fe8c 100644 --- a/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx +++ b/apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'react'; +import type { HTMLAttributes } from 'react'; import Image from 'next/image'; diff --git a/apps/marketing/src/components/(marketing)/header.tsx b/apps/marketing/src/components/(marketing)/header.tsx index e1813f7f6..915c13852 100644 --- a/apps/marketing/src/components/(marketing)/header.tsx +++ b/apps/marketing/src/components/(marketing)/header.tsx @@ -9,6 +9,7 @@ import Link from 'next/link'; import LogoImage from '@documenso/assets/logo.png'; import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag'; import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; import { HamburgerMenu } from './mobile-hamburger'; import { MobileNavigation } from './mobile-navigation'; @@ -68,12 +69,18 @@ export const Header = ({ className, ...props }: HeaderProps) => { Sign in + +
{ className="rounded-full bg-transparent backdrop-blur-sm" onClick={onSignUpClick} > - Get the Early Adopters Plan - - $30/mo. forever! + Claim Early Adopter Plan + + $30/mo @@ -224,7 +225,7 @@ export const Hero = ({ className, ...props }: HeroProps) => { (in a non-legally binding, but heartfelt way) {' '} - and lock in the early supporter plan for forever, including everything we build this + and lock in the early adopter plan for forever, including everything we build this year.

diff --git a/apps/marketing/src/components/(marketing)/mobile-navigation.tsx b/apps/marketing/src/components/(marketing)/mobile-navigation.tsx index 982e2967a..434b30053 100644 --- a/apps/marketing/src/components/(marketing)/mobile-navigation.tsx +++ b/apps/marketing/src/components/(marketing)/mobile-navigation.tsx @@ -47,9 +47,13 @@ export const MENU_NAVIGATION_LINKS = [ text: 'Privacy', }, { - href: 'https://app.documenso.com/signin', + href: 'https://app.documenso.com/signin?utm_source=marketing-header', text: 'Sign in', }, + { + href: 'https://app.documenso.com/signup?utm_source=marketing-header', + text: 'Sign up', + }, ]; export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigationProps) => { diff --git a/apps/marketing/src/components/(marketing)/pricing-table.tsx b/apps/marketing/src/components/(marketing)/pricing-table.tsx index 0d9956d86..2cd6eec00 100644 --- a/apps/marketing/src/components/(marketing)/pricing-table.tsx +++ b/apps/marketing/src/components/(marketing)/pricing-table.tsx @@ -83,7 +83,11 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {

@@ -98,7 +102,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {

Early Adopters

@@ -114,33 +118,31 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {

-

- {' '} - - The Early Adopter Deal: +

+ + Limited Time Offer: Read More

-

Join the movement

-

Simple signing solution

+

Unlimited Teams

+

Unlimited Users

+

Unlimited Documents per month

+

Includes all upcoming features

Email, Discord and Slack assistance

-

- - {' '} - - Includes all upcoming features - - -

-

Fixed, straightforward pricing

+
@@ -65,7 +65,7 @@ export const SinglePlayerModeSuccess = ({
@@ -86,7 +86,7 @@ export const SinglePlayerModeSuccess = ({

Create a{' '} diff --git a/apps/marketing/src/components/(marketing)/widget.tsx b/apps/marketing/src/components/(marketing)/widget.tsx index fe7502d27..8b6c3cd8e 100644 --- a/apps/marketing/src/components/(marketing)/widget.tsx +++ b/apps/marketing/src/components/(marketing)/widget.tsx @@ -199,7 +199,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => { className="bg-foreground/5 col-span-12 flex flex-col rounded-2xl p-6 lg:col-span-5" onSubmit={handleSubmit(onFormSubmit)} > -

Sign up for the early adopters plan

+

Sign up to Early Adopter Plan

with Timur Ercan & Lucas Smith from Documenso

@@ -208,7 +208,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => { -
); }; diff --git a/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx b/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx new file mode 100644 index 000000000..351e146ff --- /dev/null +++ b/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx @@ -0,0 +1,200 @@ +'use client'; + +import { useRouter } from 'next/navigation'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import type { z } from 'zod'; + +import type { TSiteSettingsBannerSchema } from '@documenso/lib/server-only/site-settings/schemas/banner'; +import { + SITE_SETTINGS_BANNER_ID, + ZSiteSettingsBannerSchema, +} from '@documenso/lib/server-only/site-settings/schemas/banner'; +import { TRPCClientError } from '@documenso/trpc/client'; +import { trpc as trpcReact } from '@documenso/trpc/react'; +import { Button } from '@documenso/ui/primitives/button'; +import { ColorPicker } from '@documenso/ui/primitives/color-picker'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; +import { Switch } from '@documenso/ui/primitives/switch'; +import { Textarea } from '@documenso/ui/primitives/textarea'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +const ZBannerFormSchema = ZSiteSettingsBannerSchema; + +type TBannerFormSchema = z.infer; + +export type BannerFormProps = { + banner?: TSiteSettingsBannerSchema; +}; + +export function BannerForm({ banner }: BannerFormProps) { + const router = useRouter(); + const { toast } = useToast(); + + const form = useForm({ + resolver: zodResolver(ZBannerFormSchema), + defaultValues: { + id: SITE_SETTINGS_BANNER_ID, + enabled: banner?.enabled ?? false, + data: { + content: banner?.data?.content ?? '', + bgColor: banner?.data?.bgColor ?? '#000000', + textColor: banner?.data?.textColor ?? '#FFFFFF', + }, + }, + }); + + const enabled = form.watch('enabled'); + + const { mutateAsync: updateSiteSetting, isLoading: isUpdateSiteSettingLoading } = + trpcReact.admin.updateSiteSetting.useMutation(); + + const onBannerUpdate = async ({ id, enabled, data }: TBannerFormSchema) => { + try { + await updateSiteSetting({ + id, + enabled, + data, + }); + + toast({ + title: 'Banner Updated', + description: 'Your banner has been updated successfully.', + duration: 5000, + }); + + router.refresh(); + } catch (err) { + if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { + toast({ + title: 'An error occurred', + description: err.message, + variant: 'destructive', + }); + } else { + toast({ + title: 'An unknown error occurred', + variant: 'destructive', + description: + 'We encountered an unknown error while attempting to update the banner. Please try again later.', + }); + } + } + }; + + return ( +
+

Site Banner

+

+ The site banner is a message that is shown at the top of the site. It can be used to display + important information to your users. +

+ +
+ +
+ ( + + Enabled + + +
+ +
+
+
+ )} + /> + +
+ ( + + Background Color + + +
+ +
+
+ + +
+ )} + /> + + ( + + Text Color + + +
+ +
+
+ + +
+ )} + /> +
+
+ +
+ ( + + Content + + +