Compare commits

..

675 Commits

Author SHA1 Message Date
Mythie 1cd60e1abb feat: runtime env
Support runtime environment variables using server components.

This will mean docker images can change env vars for runtime as required.
2023-11-12 13:10:30 +11:00
Lucas Smith aec0d2ae97 fix: show initial data preview for duplication (#642) 2023-11-09 15:46:58 +11:00
Lucas Smith 86160136aa chore: improve command bar (#641) 2023-11-09 14:38:26 +11:00
Nafees Nazik 3490e2a3a8 feat: add command menu and keyboard shortcuts (#337) 2023-11-09 13:33:56 +11:00
sean-brydon 574539d6dd feat: duplicate document (#633) 2023-11-08 20:27:33 +11:00
Lucas Smith f5cba75355 chore: remove product hunt launch from readme (#634)
chore: remove product hunt launch from readme
2023-11-08 12:23:35 +11:00
Lucas Smith 2a4ada8294 fix: restore logo 2023-11-08 12:22:48 +11:00
Lucas Smith 7b8fc65836 chore: remove product hunt launch from readme 2023-11-08 12:21:02 +11:00
Lucas Smith 623257ff13 feat: the big merge
landing our massive rewrite onto main so we can be normal again
2023-11-08 12:19:28 +11:00
Timur Ercan 6646533965 Single Player Mode on Product Hunt
🚨 We are live on Product Hunt with Single Player Mode and the new free tier: [https://www.producthunt.com/products/documenso](https://www.producthunt.com/posts/documenso-singleplayer-mode)
2023-11-08 11:57:45 +11:00
Mythie d1fc7ea217 chore: update ci 2023-11-06 15:10:46 +11:00
Mythie b0e3abffd6 chore: restore dangling changes from rebase 2023-11-06 14:47:46 +11:00
Thomas Kaul ee3614f10e fix: typo in README.md (#630) 2023-11-06 13:02:21 +11:00
Mythie 2e9180acf5 chore: include total and new user charts 2023-11-06 13:02:21 +11:00
Mythie d27880b56b fix: add white background for og images 2023-11-06 13:02:21 +11:00
Mythie 2af100ef6d fix: add white background for og images 2023-11-06 13:02:21 +11:00
Mythie 9edf88692c feat: show monthly new users 2023-11-06 13:02:21 +11:00
David Nguyen f9a0ec99dc fix: correctly sign SPM documents (#627)
- Sign and email correct SPM document
- Optimise signing SPM documents
2023-11-06 13:02:21 +11:00
Mythie 3ebb30090c fix: dont use custom documentData for single player mode 2023-11-06 13:02:21 +11:00
Mythie 4af3d05a24 fix: updates from error logs 2023-11-06 13:02:21 +11:00
Adithya Krishna 803ab7a7da fix: hiding of action buttons (#460)
* chore: fix hiding of action buttons
2023-11-06 13:02:21 +11:00
Mythie 2065a0debc feat: add completed at timestamp 2023-11-06 13:02:21 +11:00
Anik Dhabal Babu f841683d82 fix: small typo error (#584) 2023-11-06 13:02:21 +11:00
Abhinav 8ae860432d fix: days filter working (#623) 2023-11-06 13:02:21 +11:00
David Nguyen ba7d92d255 chore: update bug report template (#580) 2023-11-06 13:02:21 +11:00
Timur Ercan 31eece8926 Update README.md 2023-11-06 13:02:21 +11:00
Anupam 746c97b64f fix: added the share btn in the UI and prewarm fetch (#615) 2023-11-06 13:02:20 +11:00
Catalin Pit d6e3b2b5dc fix: seal document for single player mode (#617) 2023-11-06 13:02:20 +11:00
18feb06 223a5cc9d0 feat: improving incorrect default signature behaviour #594 (#600) 2023-11-06 13:02:20 +11:00
Anik Dhabal Babu c2b078335e fix: improve the early adopters plan input section (#609) 2023-11-06 13:02:20 +11:00
Ephraim Atta-Duncan bbb7c6e0c7 docs: re-add launch week 5 blogpost (#614) 2023-11-06 13:02:20 +11:00
vimode 1e5adb59cd fix: typos and grammatical errors in readme (#616) 2023-11-06 13:02:20 +11:00
Sachin M Mane 02cbf3dfa0 chore:  Improved the issue templates with issue forms (supported viayml ) (#612)
GitHub supports the issue forms [1].
[1] https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms

This feature allows users to create issues using a structured and visually appealing form. This functionality is facilitated through a  file for the issue template.

Added the issue template for bug report, feature request, and improvement by adding respective  file. Also, removed previous  file which is no longer needed
2023-11-06 13:02:20 +11:00
18feb06 d177dd7c56 fix: page-not-found at share page for document signed in single-player-mode #605 (#606) 2023-11-06 13:02:20 +11:00
Mythie 7fd5f8da67 fix: invalid url with cloudfront 2023-11-06 13:02:20 +11:00
Mythie df4cda8a1b feat: support cloudfront presign 2023-11-06 13:02:20 +11:00
Mythie 5de5d8b0fb feat: add database indexes 2023-11-06 13:02:20 +11:00
Mythie a65db4f668 chore: remove malfunction mania and feat/refresh from readme 2023-11-06 13:02:20 +11:00
Mythie ec1f61faa0 feat: add user schema timestamps 2023-11-06 13:02:20 +11:00
Shivam Bhatnagar 9b5af329a8 fix(web): fix typo + refactor empty state messages (#583)
* fix(web): fix typo + refactor empty state messages

* fix(web): refactor default empty-state message
2023-11-06 13:02:20 +11:00
Olivier Lambert b9daf6219c feat: update the README for the self-hosting users
Signed-off-by: Olivier Lambert <olivier.lambert@vates.fr>
2023-11-06 13:02:20 +11:00
Lucas Smith 4aa43bcfa2 fix: add back slack support 2023-11-06 13:02:20 +11:00
Adithya Krishna c95171bbc9 chore: updated slack to discord links
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:02:20 +11:00
Mythie 7fa61c6bb6 chore: upgrade to next 14.0.0 2023-11-06 13:02:20 +11:00
Mythie 4f99b58795 fix: missing content updates 2023-11-06 13:02:20 +11:00
Mythie 67fcb154f5 fix: change sign in links 2023-11-06 13:02:20 +11:00
Mythie 9e5bfa543e fix: update early adopter error log 2023-11-06 13:02:20 +11:00
Mythie 165432c37c fix: update stripe metadata for early adopters 2023-11-06 13:02:20 +11:00
Mythie fcd2024132 fix: update limits handler 2023-11-06 13:02:20 +11:00
Mythie 9d9b1a6681 fix: attach document to completed email 2023-11-06 13:02:20 +11:00
Mythie b0bf69450a fix: limit recipients 2023-11-06 13:02:20 +11:00
Mythie 717ca8cdb2 fix: improve claim plan flow 2023-11-06 13:02:20 +11:00
Mythie a975509923 fix: return response for failed invoices 2023-11-06 13:02:20 +11:00
Mythie f68b65fe5f fix: update customer handling for checkouts 2023-11-06 13:02:20 +11:00
Mythie 687ce66e41 fix: update singleplayer add signature to card 2023-11-06 13:02:20 +11:00
Mythie 1d604fff2c fix: limits no longer cache during session changes 2023-11-06 13:02:20 +11:00
Mythie 2f2079e020 fix: optimise pdf viewer rerendering 2023-11-06 13:02:20 +11:00
Mythie 457e73342b fix: unbreak pdf viewer 2023-11-06 13:02:20 +11:00
Mythie 284a2870c2 fix: move getFile to client side 2023-11-06 13:02:20 +11:00
Mythie e8f08f4083 fix: update webhook handler 2023-11-06 13:02:20 +11:00
Mythie 603cc1cb0d fix: update webhook handler 2023-11-06 13:02:20 +11:00
Adithya Krishna c162cb3b2c chore: update zod to 3.22.4 (#563)
* chore: updated zod 

Signed-off-by: Adithya Krishna <aadithya794@gmail.com>

---------

Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:02:20 +11:00
Udit Takkar 6de14ab0ac fix: UI fixes and improvements (#559)
* chore: add cursor pointer

* fix: tooltip color

* chore: add tooltip

* fix: admin pages in mobile
2023-11-06 13:02:20 +11:00
Catalin Pit dec587e39e chore: add prisma studio command (#576)
Co-authored-by: pit <pit@pits-MacBook-Pro.local>
2023-11-06 13:02:20 +11:00
Mythie 89102bd221 fix: add mode to checkout session 2023-11-06 13:02:20 +11:00
Aditya Deshlahre b7950cf042 fix(bug): name field can be updated with spaces #555 (#558) 2023-11-06 13:02:19 +11:00
Mythie 19f1ddb8fe fix: downgrade react-pdf 2023-11-06 13:02:19 +11:00
Mythie 2ac0d79051 fix: support multi env 2023-11-06 13:02:19 +11:00
Tameem Asim 5445b85aae fix: invalid url on main page in self host 2023-11-06 13:02:19 +11:00
Nafees Nazik 04fc9eb187 chore: upgrade to latest next.js version (#553)
* chore: upgrade next.js
* fix: canvas not found error
* chore: upgrade package for marketing
* feat: add isServer conditional
* fix: inverse isServer condition
* fix: normalize packages
* fix: upgrade ee package
* fix: depdency nightmares
* fix: failing seed script
2023-11-06 13:02:19 +11:00
Catalin Pit f1732fab29 fix: truncate long file name in admin dashboard (#572)
Co-authored-by: pit <pit@192-168-0-136.rdsnet.ro>
2023-11-06 13:02:19 +11:00
18feb06 5bc18d4c73 fix: email requesting signature shows "completed document" in preview… (#514) 2023-11-06 13:02:19 +11:00
Abhinav-Developer-23 56914bf604 fix: support mailto link fix (#571) 2023-11-06 13:02:19 +11:00
Mythie 3d44821bff fix: build errors 2023-11-06 13:02:19 +11:00
Mythie 05fd01c72d fix: exports on next page 2023-11-06 13:02:19 +11:00
Mythie c343e8a221 feat: plan limits 2023-11-06 13:02:19 +11:00
Mythie f75f191a9a fix: add redirects for v0.9 requests 2023-11-06 13:02:19 +11:00
David Nguyen 1d291e8e03 feat: add safari clipboard copy support (#486) 2023-11-06 13:02:19 +11:00
Catalin Pit cff547d0d8 fix: user avatar on admin documents table (#570)
Co-authored-by: pit <pit@pits-MacBook-Pro.local>
2023-11-06 13:02:19 +11:00
Abhinav-Developer-23 4b2fec305f fix: fix for Accepting signatures or text fields with white space only #551 (#557) 2023-11-06 13:02:19 +11:00
Udit Takkar 352e4f81fd fix: enable dragging fields (#565) 2023-11-06 13:02:19 +11:00
Mythie 5f15721535 fix: filter out inactive products 2023-11-06 13:02:19 +11:00
Mythie 95a60331f8 fix: named exports 2023-11-06 13:02:19 +11:00
Mythie 17b43caa5c fix: create custom pricing table 2023-11-06 13:02:19 +11:00
David Nguyen 6d7fc32075 fix: stripe customer fetch logic 2023-11-06 13:02:19 +11:00
David Nguyen 81eea4213c fix: merge issues 2023-11-06 13:02:19 +11:00
David Nguyen f45cd81b5f feat: wip 2023-11-06 13:02:19 +11:00
David Nguyen 2856cd9c15 feat: add free tier Stripe subscription 2023-11-06 13:02:19 +11:00
Lucas Smith 5a79535080 fix: style updates 2023-11-06 13:02:19 +11:00
pit 9fa7505061 chore: revert back env file name 2023-11-06 13:02:19 +11:00
pit 39c2bf77c2 chore: implement feedback 2023-11-06 13:02:19 +11:00
pit 9932d805b7 chore: implement feedback 2023-11-06 13:02:19 +11:00
pit afd7dcd992 chore: remove this branch 2023-11-06 13:02:19 +11:00
pit c7f69bafd0 chore: update e2e tests 2023-11-06 13:02:19 +11:00
pit dc1b8c3cb8 chore: update import 2023-11-06 13:02:19 +11:00
pit af73aee113 chore: changes 2023-11-06 13:02:19 +11:00
pit 9151055936 chore: add schema location 2023-11-06 13:02:19 +11:00
pit ca51a926fb chore: change from npm to npx 2023-11-06 13:02:19 +11:00
pit 1bb47f98a1 chore: install prisma before prisma client 2023-11-06 13:02:19 +11:00
pit c00f06a0c5 chore: add remote caching 2023-11-06 13:02:19 +11:00
pit 3cbd4a2680 chore: use env vars for tests 2023-11-06 13:02:19 +11:00
pit c30695f2b8 ci: trigger ci 2023-11-06 13:02:19 +11:00
pit 434c4a6957 ci: trigger ci 2023-11-06 13:02:19 +11:00
pit 7942057c50 chore: add env step in gh action 2023-11-06 13:02:19 +11:00
pit 9afe8731b1 chore: added delete function 2023-11-06 13:02:19 +11:00
pit 7d21f607df chore: removed lint step 2023-11-06 13:02:19 +11:00
pit 00546c1290 feat: add playwright 2023-11-06 13:02:19 +11:00
David Nguyen 702df60076 fix: add cascade delete for share links 2023-11-06 13:02:19 +11:00
David Nguyen 38960c459b feat: add document share button to marketing (#422) 2023-11-06 13:02:19 +11:00
David Nguyen 6d0e8f6dd8 feat: single-player-mode-polish (#435) 2023-11-06 13:02:19 +11:00
Udit Takkar ec11686709 fix: background color of signature page (#487) 2023-11-06 13:02:19 +11:00
Abhinav-Developer-23 2340528d2e fix: bypass signature fix (#536) (#547) 2023-11-06 13:02:19 +11:00
Anik Dhabal Babu e8c1e70714 fix : add gittpod configuration 2023-11-06 13:02:19 +11:00
Anik Dhabal Babu 3778fc8d00 fix: Add gitpod configuration 2023-11-06 13:02:19 +11:00
Anik Dhabal Babu 2faca32427 fix: add gitpod configuration 2023-11-06 13:02:19 +11:00
Anik Dhabal Babu 51f8554ce8 fix: Add gitpod configuration 2023-11-06 13:02:19 +11:00
Lucas Smith 4c8a62a8ab fix: quick tweaks 2023-11-06 13:02:19 +11:00
pit f621e60c16 chore: implemented feedback 2023-11-06 13:02:19 +11:00
pit 4ed467693f chore: implemented feedback 2023-11-06 13:02:19 +11:00
pit 0e77faee3f chore: polished code 2023-11-06 13:02:18 +11:00
pit c78632870e chore: implemented feedback 2023-11-06 13:02:18 +11:00
pit 35087f551c chore: implement pr feedback 2023-11-06 13:02:18 +11:00
pit f569361e57 chore: feedback fix 2023-11-06 13:02:18 +11:00
pit d2263a6d72 chore: fix eslint issues 2023-11-06 13:02:18 +11:00
Nafees Nazik c9a71beb81 feat: delete draft document (#491) 2023-11-06 13:02:18 +11:00
Mythie 0d6efba3cf chore: disable dependabot for now 2023-11-06 13:02:18 +11:00
Anik Dhabal Babu 9d9faffc2f fix: mismatch the version of dotenv-cli 2023-11-06 13:02:18 +11:00
hallidayo f9a3a53c5b fix: frequency focus ring (#533) 2023-11-06 13:02:18 +11:00
Mythie fc876a41d9 fix: update script, docs and devcontainer 2023-11-06 13:02:18 +11:00
Aditya @ArchLinux 6fd72321b3 fix(script): minor change on scipt 2023-11-06 13:02:18 +11:00
Aditya @ArchLinux 1b7c887101 fix(script): added script envprisma in root package.json
dotenv loads all environment variable before running prisma:migrate-dev script
2023-11-06 13:02:18 +11:00
Aditya @ArchLinux 2e90eee4c2 style(ui/ux): added margin to dialogprimitive.content & dialogprimitive.close (m-4) 2023-11-06 13:02:18 +11:00
Sachin bdf31625a2 fix: non responsiveness of Add your sign modal 2023-11-06 13:02:18 +11:00
Ephraim Atta-Duncan e556db0386 docs: add render one click deploy for refresh 2023-11-06 13:02:18 +11:00
Anjy Gupta 7a8f5b8422 fix: sign up with existing account email bug (#517)
* fix: sign up with existing account email bug
2023-11-06 13:02:18 +11:00
zahid47 fa06038950 fix: add defaultValue to SignaturePad to persist signatures (#522)
* fix: add defaultValue to SignaturePad to persist signatures
2023-11-06 13:02:18 +11:00
hallidayo 66eda3321d changed text of stepper (#513) 2023-11-06 13:02:18 +11:00
Timur Ercan 6ec04c5df3 chore: typos
typos
2023-11-06 13:01:46 +11:00
David Nguyen e0662ce024 chore: add code of conduct (#521)
Update contributing guidelines
2023-11-06 13:01:46 +11:00
Udit Takkar 78d2401d44 feat: require old password for password reset (#488)
* feat: require old password for password reset
2023-11-06 13:01:46 +11:00
Anik Dhabal Babu 02bb7e4d68 fix: update icons (#468)
* fix: update icons
2023-11-06 13:01:46 +11:00
Timur Ercan 8fba1c43d3 chore: add new team members 2023-11-06 13:01:46 +11:00
Harsh Acharya 911cdcf170 fix: Error in Pricing Page Validation for Signup Now Modal (#497)
* fix: signup modal validation on close
* fix: restore auto focus input
2023-11-06 13:01:46 +11:00
Arjun Bharti 07fb6fa0e1 fix: signature text overflow truncated for longer signature texts (#489) 2023-11-06 13:01:46 +11:00
Timur Ercan 1d97c0898f chore: and now his watch has ended 2023-11-06 13:01:46 +11:00
Mythie 4132e45a9b fix: remove unused imports 2023-11-06 13:01:46 +11:00
Ollie Halliday b190802f65 capitalise 2023-11-06 13:01:46 +11:00
Ollie Halliday 830f10ac6f text changes on isSubmitting 2023-11-06 13:01:46 +11:00
Adithya Krishna 82f13e0ac3 fix: typo in script
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:01:46 +11:00
Timur Ercan efa0505276 Update README.md 2023-11-06 13:01:46 +11:00
Adithya Krishna 27287cd1d5 fix: sharp corners of signing card 2023-11-06 13:01:46 +11:00
Mythie a5e087f209 fix: dogfood resend transport 2023-11-06 13:01:46 +11:00
Timur Ercan 85924f49f7 chore: sync 2023-11-06 13:01:46 +11:00
Mythie 6d8c29c5c1 fix: update compose scripts 2023-11-06 13:01:46 +11:00
Mythie 051c96c92b fix: further readme updates 2023-11-06 13:01:46 +11:00
Ephraim Atta-Duncan 0aaf65266e docs: clearer readme? maybe? 2023-11-06 13:01:46 +11:00
PeterKwesiAnsah 5bb2f183c1 chore: added classname and changed typo 2023-11-06 13:01:46 +11:00
PeterKwesiAnsah 8c8bcb694a chore: add empty message 2023-11-06 13:01:46 +11:00
David Nguyen 2e12893e0c fix: add missing URL to email template 2023-11-06 13:01:46 +11:00
David Nguyen 8fb5fdc058 feat: add email forgot password action
Updated email template imports
2023-11-06 13:01:46 +11:00
David Nguyen 0c607e6b39 feat: update email templates
Add SPM email attachment
2023-11-06 13:01:46 +11:00
Mythie a622fd402f fix: add hack for root zod validation with hook form 2023-11-06 13:01:46 +11:00
Timur Ercan a219588287 chore: sync next 2023-11-06 13:01:46 +11:00
Mythie 5cba913c55 fix: resolve issues with signing document stickiness 2023-11-06 13:01:46 +11:00
Mythie adf1359527 fix: disable cancel button when there is no window history 2023-11-06 13:01:46 +11:00
Mythie 187485678a feat: add resend mail transport 2023-11-06 13:01:45 +11:00
David Nguyen 48f4289c09 fix: resolve document title inconsistency (#452) 2023-11-06 13:01:45 +11:00
Mythie e09c076241 fix: do not overwrite new names or emails for signers 2023-11-06 13:01:45 +11:00
David Nguyen 7ec8ce2a56 feat: add posthog reverse proxy (#449) 2023-11-06 13:01:45 +11:00
Timur Ercan cca1bb4639 chore: greetings 2023-11-06 13:01:45 +11:00
Mythie 5e00df1fba chore: add missing migrations 2023-11-06 13:01:45 +11:00
Ephraim Atta-Duncan e14f96485c fix: update for code review 2023-11-06 13:01:45 +11:00
Ephraim Atta-Duncan c107608aeb chore: remove undefined check 2023-11-06 13:01:45 +11:00
Mythie 704306fd21 chore: styling updates 2023-11-06 13:01:45 +11:00
Mythie a164fd1256 fix: lint errors 2023-11-06 13:01:45 +11:00
Mythie ef0ad8761c fix: faster tooltips 2023-11-06 13:01:45 +11:00
Mythie d4c23455d8 fix: share og updates 2023-11-06 13:01:45 +11:00
Mythie eeb0d7b60c fix: twitter images 2023-11-06 13:01:45 +11:00
Mythie c0edac317e fix: better share links 2023-11-06 13:01:45 +11:00
Timur Ercan 5fa7f447cd chore: sync shop article and add missing and updated assets 2023-11-06 13:01:45 +11:00
Timur Ercan c035e9ce0b chore: update mania shirt res 2023-11-06 13:01:45 +11:00
Timur Ercan e982c85e9a feat: shop Article 2023-11-06 13:01:45 +11:00
Timur Ercan d79981b4b2 chore: typos 2023-11-06 13:01:45 +11:00
Timur Ercan 1b57b6027b chore: fix lightmode logo 2023-11-06 13:01:45 +11:00
Timur Ercan 75de3f26b9 Update README.md with M̸͍͚̽͒A̴̯͊͌L̴͖͖͘F̵̗̻́̅U̶̲̅͠N̸͙̰̓C̵̙̮̾T̸̜̙͌Í̷͎̯Ö̵̘̜́N̴̳͊̈ͅ ̶͔́M̸̡͐̾A̵̞̚N̴̤̏́Ǐ̸̩͂Ă̸͓͝ 2023-11-06 13:01:45 +11:00
Mythie 6a5c5f7dd8 fix: redirectless authentication 2023-11-06 13:01:45 +11:00
Mythie c911253be0 chore: quieten dependabot 2023-11-06 13:01:45 +11:00
Mythie f1898b24fc fix: update share preview 2023-11-06 13:01:45 +11:00
Mythie fcf586f24d fix: styling updates 2023-11-06 13:01:45 +11:00
Ephraim Atta-Duncan 34ce0d084e chore: refactor function 2023-11-06 13:01:45 +11:00
Ephraim Atta-Duncan 127dc42dc5 feat: add dropdown to tweet or copy signing link 2023-11-06 13:01:45 +11:00
Timur Ercan 016ae6ec6f chore: fix typo 2023-11-06 13:01:45 +11:00
Timur Ercan ac3b4080c0 chore: typo 2023-11-06 13:01:45 +11:00
Timur Ercan df9c603a37 chore: alt text 2023-11-06 13:01:45 +11:00
Timur Ercan 9c8eb5e13b chore: update date 2023-11-06 13:01:45 +11:00
Timur Ercan da64c9af0d chore: remove unused image 2023-11-06 13:01:45 +11:00
Timur Ercan 990a7da940 chore: typo 2023-11-06 13:01:45 +11:00
Timur Ercan dc5cc74db9 fix: text 2023-11-06 13:01:45 +11:00
Timur Ercan 8bb90494d8 chore: staging 2023-11-06 13:01:45 +11:00
Timur Ercan adc0c31462 chore: update malfunction mania image 2023-11-06 13:01:45 +11:00
Timur Ercan 7385abae25 chore: grammerly, finetuned bounties 2023-11-06 13:01:45 +11:00
flō 6c4fc6b2b4 Fix typo 2023-11-06 13:01:45 +11:00
flō 0c16e0339f Fix typo 2023-11-06 13:01:45 +11:00
flō 779289ccd8 Fix punctuation for consistency 2023-11-06 13:01:45 +11:00
flō bac7cf464a Add keywords
added keywords in description for SEO optimizations
2023-11-06 13:01:45 +11:00
flō e77fd92d13 Fix typo
Fix typo and minor edits for consistency
2023-11-06 13:01:45 +11:00
Timur Ercan 97f995b00a feat: malfunction mania, first draft 2023-11-06 13:01:44 +11:00
Mythie 5904f6c5a8 chore: sign document 2023-11-06 13:01:44 +11:00
Mythie 9164d301fd fix: dark mode for generic mdx content 2023-11-06 13:01:44 +11:00
Mythie f0f67be624 fix: remove usage of buffer 2023-11-06 13:01:44 +11:00
Mythie 1536634fc6 fix: assorted updates 2023-11-06 13:01:44 +11:00
Mythie 0e486c7b58 fix: feature flag client endpoint 2023-11-06 13:01:44 +11:00
Mythie 0f6298cc8b chore: update env.example 2023-11-06 13:01:44 +11:00
Mythie b26bba8329 chore: remove console.log 2023-11-06 13:01:44 +11:00
Mythie 350c02ae98 fix: single player dark mode and animation updates 2023-11-06 13:01:44 +11:00
Mythie 80c9ab5d8c fix: improve dark mode background patterns 2023-11-06 13:01:44 +11:00
Mythie 965b5c0afd fix: cors for feature flags 2023-11-06 13:01:44 +11:00
David Nguyen 341c0df231 fix: firefox signing fields 2023-11-06 13:01:44 +11:00
David Nguyen 0ff044978f feat: update success page text 2023-11-06 13:01:44 +11:00
David Nguyen d20ad4217c feat: add uninserted field validation 2023-11-06 13:01:44 +11:00
David Nguyen ebebceea1f feat: add uninserted field validation 2023-11-06 13:01:44 +11:00
David Nguyen a9a719cd37 feat: utilise transport layer 2023-11-06 13:01:44 +11:00
David Nguyen db1d6478a1 fix: timeout issues 2023-11-06 13:01:44 +11:00
David Nguyen 34232c79e5 feat: add single player mode 2023-11-06 13:01:43 +11:00
Timur Ercan cdae3a9a45 fix: company name 2023-11-06 13:01:15 +11:00
Mythie 60ae674984 feat: darker dark theme 2023-11-06 13:01:15 +11:00
Mythie 986bab2ba4 fix: normalize recipients 2023-11-06 13:01:15 +11:00
captain-Akshay ded4f03e05 fix: cancel button handler 2023-11-06 13:01:15 +11:00
Lucas Smith 4306fbc0f3 fix: remove unused import 2023-11-06 13:01:15 +11:00
Lucas Smith 8fc53d5dac fix: remove unused import 2023-11-06 13:01:15 +11:00
Lucas Smith e97de71811 fix: use named export 2023-11-06 13:01:15 +11:00
Lucas Smith 8c23bea985 fix: tidy messaging 2023-11-06 13:01:15 +11:00
Lucas Smith a92624b255 fix: use ts-pattern 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 8b5d0f445e chore: fix eslint errors 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan f09638a4de chore: remove code from different branch 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan e5871da7b5 chore: fix eslint errors 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 73a9213088 chore: correct types on component 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 4879d2360b feat: add empty state for different status 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 5aff50b13b feat: add initial empty state for no results 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan b747334383 feat: redirect signed document to completed page 2023-11-06 13:01:15 +11:00
Lucas Smith abfa0812f0 fix: minor updates 2023-11-06 13:01:15 +11:00
Mythie 4072e2a200 fix: update styling 2023-11-06 13:01:15 +11:00
Adithya Krishna 4ec2bd7536 chore: updated wording
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:01:15 +11:00
Adithya Krishna 5dad463877 feat: added 404 page for web app
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:01:15 +11:00
Adithya Krishna fe4b3e0450 chore: updated 404 page for marketing app
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:01:15 +11:00
Adithya Krishna 401311acd9 feat: added 404 page for marketing app
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:01:15 +11:00
Mythie 7823100272 fix: reverse meta relation and tidy code 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 826a901c10 fix: document meta relation with document 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 164aa1cc03 fix: avoid creating document meta with empty strings 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 22f9400932 fix: persist newline in emails 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 345343f4b5 feat: replace template variables with values
Co-authored-by: Mythie <me@lucasjamessmith.me>
2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan da16f1ee07 feat: send custom email message 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan a3baf2ed8b feat: persist document metadata in database for a specific user 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 9521d1df4c feat: send custom email subjects 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 1e294fc933 feat: add prisma schema for document meta 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan fd4602faf8 fix: fitler only unsigned documents 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan c947c7d761 chore: match file name and method name 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan c58006b2d9 feat: avoid sending pending email to document with 1 recipients 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan f18010e1e1 refactor: pass document id as arguments 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan 94215fffbb feat: send email when all recipients have signed 2023-11-06 13:01:15 +11:00
Ephraim Atta-Duncan b17e73003e feat: send email when recipient is done signing 2023-11-06 13:01:14 +11:00
Mythie 49ce09f49b fix: support optimise imports 2023-11-06 13:01:14 +11:00
Mythie 53db1a5d19 fix: resolve issues with open graph asset loading 2023-11-06 13:01:14 +11:00
Mythie 15fd819132 fix: tidy code and update endpoints 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 8df5304b8e feat: update share page to match latest changes 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 794e575ae9 feat: move opengraph-image to next.js 13 implementation 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 57ff77b920 refactor: redirect using useRouter 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan c43843d226 chore: remove unused files 2023-11-06 13:01:14 +11:00
Mythie d0cedc489f fix: update og card 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan b12f5b62f1 feat: redirect share page to marketing page after 3 seconds 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan f2da49d0e8 feat: generate metatags for share page with og image 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 96264f67e4 feat: display signature text from params 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan ebcd7c78e4 feat: create sharing id for each recipient 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 1bce169228 feat: add initial og image for share link 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 023a91832a feat: add extra info for the early adopters 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 0a035f8b60 refactor: metrics into reusable component 2023-11-06 13:01:14 +11:00
Ephraim Atta-Duncan 7659c51980 feat: add early adopters graph 2023-11-06 13:01:14 +11:00
Timur Ercan 3377b55341 chore: rename community plan to early adopters 2023-11-06 13:01:14 +11:00
Timur Ercan f938db25d0 chore: grammerly 2023-11-06 13:01:14 +11:00
Timur Ercan db50dc71aa chore: links 2023-11-06 13:01:14 +11:00
nsylke b2ea5f0e47 fix: remove disallow property of _next from robots 2023-11-06 13:01:14 +11:00
Timur Ercan 32a21999ef feat: early adopter article 2023-11-06 13:01:14 +11:00
pit 0321f8505d chore: self-review 2023-11-06 13:01:14 +11:00
pit 90225574de chore: move fetching in data-table-users 2023-11-06 13:01:14 +11:00
pit 92b5111d7e chore: tidy up 2023-11-06 13:01:14 +11:00
pit 15a1c1da3f chore: remove generic data table 2023-11-06 13:01:14 +11:00
pit d54710ca07 chore: add transition and check for empty users array 2023-11-06 13:01:14 +11:00
pit 9682f8ea36 feat: filter users by name or email 2023-11-06 13:01:14 +11:00
pit 02b6f6a7b7 feat: subscriptions and documents page 2023-11-06 13:01:14 +11:00
pit 214dd7a7c8 feat: manage documents admin ui 2023-11-06 13:01:14 +11:00
pit ff58735153 feat: profile page done 2023-11-06 13:01:14 +11:00
pit 82c1ca13be feat: update user functionality 2023-11-06 13:01:13 +11:00
pit eef720bf8c chore: improve the ui 2023-11-06 13:01:13 +11:00
pit 550b7e1655 feat: build individual user page 2023-11-06 13:01:13 +11:00
pit 32b41386e5 feat: admin ui for managing instance 2023-11-06 13:01:13 +11:00
captain-Akshay 1db0d2eae6 feat: added the icon for theme 2023-11-06 13:01:13 +11:00
captain-Akshay 7a1dd47537 feat: removed unecessary code to the file and made few UI changes 2023-11-06 13:01:13 +11:00
captain-Akshay eec48e48ef feat: added a better theme change ability for user 2023-11-06 13:01:13 +11:00
Mythie 1ec91f6c68 chore: add visibility toggle to reset password 2023-11-06 13:01:13 +11:00
Mythie fbb8e887a8 fix: update colors 2023-11-06 13:01:13 +11:00
Mythie 6778d2a0da fix: update token validity check 2023-11-06 13:01:13 +11:00
Mythie 0ec5976397 fix: update reset token query 2023-11-06 13:01:13 +11:00
Mythie 8f03782fe3 fix: update email template 2023-11-06 13:01:13 +11:00
Mythie 6e791b6e91 fix: add layout and minor updates 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 34c652ab54 feat: add invalid reset token page 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan dec7a9cb38 feat: better error handling and better toast messages 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 1e0cde850a feat: better error handling in forgotPassword trpc router 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 9b025f0c9f fix: width reducing with screen size 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan dabeead57f feat: send email to user on successful password reset 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 84bc6eb4f3 feat: add reset functionality 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 9ef50f356e feat: send forgot password email 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 38e8611159 chore: rename email templates export 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 8c12789c03 feat: add reset password email template 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan ca165c8141 feat: add forgot password template 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 729d0c93fe feat: create a password reset token 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 3544e44c31 chore: remove unused error toast 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan b3a312df98 feat: add reset password page 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 8ba9a761d4 feat: add forgot passoword page 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan c4282ded57 feat: add password reset token to schema 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 352720a6ae feat: add deploying documenso with vercel, supabase and resend 2023-11-06 13:01:13 +11:00
Mythie a948adfbb7 chore: update devcontainer 2023-11-06 13:01:13 +11:00
Mythie 77833ba895 chore: update ci 2023-11-06 13:01:13 +11:00
Mythie f700016440 chore: tidy unused code 2023-11-06 13:01:13 +11:00
David Nguyen 771042c9ce feat: add vercel build script 2023-11-06 13:01:13 +11:00
nsylke 95a40400af feat: security headers 2023-11-06 13:01:13 +11:00
Ephraim Atta-Duncan 7d4bb09170 feat: use description of each blog post in og image (#380) 2023-11-06 13:01:13 +11:00
Mythie 269f97fa6b fix: final reference to created column 2023-11-06 13:01:13 +11:00
Mythie 666d682536 fix: remove further references to created column 2023-11-06 13:01:13 +11:00
Mythie 2e5d5bb462 fix: remove references to created column 2023-11-06 13:01:13 +11:00
Mythie 0835da45ef fix: implement feedback 2023-11-06 13:01:13 +11:00
Mythie 5a99a0b5eb fix: update migration for timestamp columns 2023-11-06 13:01:13 +11:00
Mythie 3afc35c40c feat: universal upload
Implementation of a universal upload allowing for multiple storage backends
starting with `database` and `s3`.

Allows clients to put and retrieve files from either client or server using
a blend of client and server actions.
2023-11-06 13:01:13 +11:00
Mythie 72bec7bc34 feat: separate document data from document 2023-11-06 13:01:13 +11:00
Timur Ercan 0f38be4e41 chore: update readme to main version 2023-11-06 13:01:13 +11:00
Timur Ercan 706909107f chore: moved rewrite article from next repo 2023-11-06 13:01:13 +11:00
Mythie 48ff16876b fix: add dashboard header border on scroll 2023-11-06 13:01:12 +11:00
David Nguyen a72248e871 refactor: organise recipient utils 2023-11-06 13:01:12 +11:00
David Nguyen 48e58d4675 feat: add avatar email fallback 2023-11-06 13:01:12 +11:00
David Nguyen 41d46c82d1 feat: update document table layout (#371)
* feat: update document table layout

- Removed dashboard page
- Removed redundant ID column
- Moved date to first column
- Added estimated locales for SSR dates
2023-11-06 13:01:12 +11:00
Mythie a849c6431f fix: data table links for recipients 2023-11-06 13:01:12 +11:00
Mythie 399826a6f5 fix: add removed layout guard 2023-11-06 13:01:12 +11:00
Lucas Smith 632b3bd1f4 fix: update layout and wording 2023-11-06 13:01:12 +11:00
Catalin Pit 4f40ce6003 chore: implemented feedback 2023-11-06 13:01:12 +11:00
Catalin Pit e752c0c60b chore: fix version in nextjs config 2023-11-06 13:01:12 +11:00
Catalin Pit 31ae6591dd chore: added app version 2023-11-06 13:01:12 +11:00
Catalin Pit d8bbfae30d chore: changed the cards titles 2023-11-06 13:01:12 +11:00
Catalin Pit f42f2b1aa0 chore: feedback improvements 2023-11-06 13:01:12 +11:00
Catalin Pit a2635851cf chore: rename files 2023-11-06 13:01:12 +11:00
Catalin Pit a94b80d07d chore: implemented feedback 2023-11-06 13:01:12 +11:00
Catalin Pit 766fc6410b feat: add the admin page 2023-11-06 13:01:12 +11:00
Catalin Pit 61df56c21e feat: creating the admin ui for metrics 2023-11-06 13:01:12 +11:00
Timur Ercan fdb542765a chore: phrasing 2023-11-06 13:01:12 +11:00
Timur Ercan dcde3317b8 Update apps/marketing/content/blog/building-documenso-pt1.mdx
Co-authored-by: Adithya Krishna  <aadithya794@gmail.com>
2023-11-06 13:01:12 +11:00
Timur Ercan 6be4a1d3e4 fix: update building documenso article description 2023-11-06 13:01:12 +11:00
flō 9c26faba0d fix typo 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan 1a48d194f7 refactor: replace whole implementation with a state 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan cb5df80a26 fix: hide popover when user selects a recipients 2023-11-06 13:01:12 +11:00
Mythie 661dfe8368 fix: update devcontainer 2023-11-06 13:01:12 +11:00
Mythie 90bacea7ed fix: update devcontainer 2023-11-06 13:01:12 +11:00
Mythie 0f3db459cb feat: add devcontainer 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan 9e4f1dfe1e feat: disable signing and editing for completed documents 2023-11-06 13:01:12 +11:00
David Nguyen d5753dfbb3 feat: update marketing mobile menu 2023-11-06 13:01:12 +11:00
Mythie a644e0134b fix: actually make timeouts clear 2023-11-06 13:01:12 +11:00
Mythie 6ad10f6317 fix: tidy up code 2023-11-06 13:01:12 +11:00
nafees nazik 05c7ac4c20 revert: fix: component style 2023-11-06 13:01:12 +11:00
nafees nazik 0f9ce3b5c7 chore: add comments 2023-11-06 13:01:12 +11:00
nafees nazik 45d6181042 fix: use toast 2023-11-06 13:01:12 +11:00
nafees nazik d3529374a6 fix: typo 2023-11-06 13:01:12 +11:00
nafees nazik a7fa9daada feat: add api error 2023-11-06 13:01:12 +11:00
nafees nazik 4267456857 fix: component style 2023-11-06 13:01:12 +11:00
nafees nazik d7f4f2b8eb fix: value 2023-11-06 13:01:12 +11:00
nafees nazik 07580b4640 fix: user name not updatable 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan c2fe7192fa chore: remove console.log 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan 0961246834 feat: add missing email field to document sign page 2023-11-06 13:01:12 +11:00
Catalin Pit 383f8d6e3c chore: removed one more console.log 2023-11-06 13:01:12 +11:00
Catalin Pit d1db20ff4a chore: removed console.logs and warn 2023-11-06 13:01:12 +11:00
Mythie 4146d71f9d feat: store signature on signup 2023-11-06 13:01:12 +11:00
Mythie e05eaffb61 feat: store profile signature 2023-11-06 13:01:12 +11:00
PeterKwesiAnsah 6b250b88ec feat: add error message to signature pad 2023-11-06 13:01:12 +11:00
Mythie 8a271ff8bc fix: fix eslint warnings 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan 930a80ae18 chore: unused console logs 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan 2aabe7ec60 feat: redirect to dashboard when document is sent 2023-11-06 13:01:12 +11:00
Adithya Krishna f64885d2c3 chore: removed console logs
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:12 +11:00
Mythie 3546155aab fix: tidying broke generation 2023-11-06 13:01:12 +11:00
Mythie 95886d67a6 fix: tidy code and expect jsx errors 2023-11-06 13:01:12 +11:00
Mythie 8980274f7a fix: center align heading 2023-11-06 13:01:12 +11:00
Mythie 64b399043c fix: use nextjs opengraph-image component 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan d536305c1c feat: add og image to blog posts 2023-11-06 13:01:12 +11:00
Ephraim Atta-Duncan 991411c9f5 feat: add blog og image 2023-11-06 13:01:11 +11:00
Mythie bc2da7834e fix: retain redirect to documents rather than dashboard 2023-11-06 13:01:11 +11:00
Ephraim Atta-Duncan 00143052fd fix: redirect root direcotory to dashboard 2023-11-06 13:01:11 +11:00
Ephraim Atta-Duncan ef79eb3ca4 fix: redirect signin page to dashboard when logged in 2023-11-06 13:01:11 +11:00
Mythie 63b21a8206 fix: minor updates 2023-11-06 13:01:11 +11:00
Adithya Krishna e243ba2564 fix: removed passHref and updated card
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna fd43ebb7c0 fix: reverted line change
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna a7e43c5d77 chore: made requested changes - v2
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna 5ca4a7e117 chore: made requested changes
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna a23e3c6068 feat: updated rendeing of items using map
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna cf799e63ca chore: updated footer component
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna 2b53f796bd chore: updated signing fields
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna ebdb6442c6 chore: updated dashboard page
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna c29d1463b0 chore: updated documents page
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Mythie a8a6e40174 fix: update import for feature-flag helpers 2023-11-06 13:01:11 +11:00
Mythie 81e612040a fix: extract feature-flag zod schema to separate file 2023-11-06 13:01:11 +11:00
Mythie 7f5ef8690b fix: further stash conflicts 2023-11-06 13:01:11 +11:00
Mythie d7bd8fcd37 fix: add items from stash 2023-11-06 13:01:11 +11:00
Mythie c3f11afaf9 feat: make billing page functional 2023-11-06 13:01:11 +11:00
Mythie 5cba252627 fix: minor tidying 2023-11-06 13:01:11 +11:00
Ephraim Atta-Duncan cd1b4796fc feat: avoid updating password with existing password 2023-11-06 13:01:11 +11:00
Ephraim Atta-Duncan 9524875e98 feat: prevent a user from updating password with the same password 2023-11-06 13:01:11 +11:00
Ephraim Atta-Duncan fba95a4402 feat: reset password from on submit 2023-11-06 13:01:11 +11:00
Mythie 2ba7df4881 fix: update eslint rules 2023-11-06 13:01:11 +11:00
Mythie 7811035384 feat: promise safety with eslint 2023-11-06 13:01:11 +11:00
Mythie 98c980a5c4 fix: remove further unused code 2023-11-06 13:01:11 +11:00
Mythie 7b2d3356f2 fix: remove unused code 2023-11-06 13:01:11 +11:00
Mythie dfe04018a0 feat: add data table actions 2023-11-06 13:01:11 +11:00
Mythie 62c4a89521 feat: onepage inbox 2023-11-06 13:01:11 +11:00
David Nguyen a99efdc916 feat: add inbox 2023-11-06 13:01:11 +11:00
nsylke 6959307f5e chore: change root package.json name 2023-11-06 13:01:11 +11:00
Adithya Krishna b512343bbb chore: removed eslint-plugin-import
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna 5100a61ab7 fix: updated eslint config
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna e6790d3a19 feat: added eslint plugin dependencies
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna 778ac63272 feat: added new eslint rules
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna e2fe7e900c fix: duplicate import
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna c098c58b2e fix: removed more unnecessary whitespace
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna 9c763e864b fix: removed unnecessary whitespace
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
nafees nazik 3ffee97e72 fix: style 2023-11-06 13:01:11 +11:00
nafees nazik 2ab0cd1308 fix: overflow and scroll ux 2023-11-06 13:01:11 +11:00
Ephraim Atta-Duncan 989146f98e chore: reduce open page caching time to 1 hour 2023-11-06 13:01:11 +11:00
nafees nazik faa280c02b fix: make signing form card responsive 2023-11-06 13:01:11 +11:00
Adithya Krishna 942d4bf905 fix: added eol to workflow file
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna 84c6a40815 chore: updated workflow name
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna ce67f8cc11 feat: added pr title validator
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
nsylke 44f59b07c5 feat: add autocomplete for password managers 2023-11-06 13:01:11 +11:00
nsylke b97a0380df feat: set min/max lengths for password 2023-11-06 13:01:11 +11:00
Mythie 289e3776fd fix: dependency ordering 2023-11-06 13:01:11 +11:00
Adithya Krishna 36b3b36ac8 feat: added sharp for image optimizations on nextjs
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
nafees nazik 7a74f3c77e fix: authentication 2023-11-06 13:01:11 +11:00
Adithya Krishna d0def823c8 fix: update icon sizes
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:11 +11:00
Adithya Krishna c902fb8412 fix: updated padding and set patterns
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna b733defcd9 feat: added show password feature
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 0cc05052fa chore: optimized images to save ~8mb
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Mythie 166271ac4b fix: add missing await on mail send 2023-11-06 13:01:10 +11:00
Mythie 7cfc525ddf fix: nicer dark mode for stack avatars 2023-11-06 13:01:10 +11:00
Mythie 3ea0ff6b81 chore: support direct urls for prisma 2023-11-06 13:01:10 +11:00
David Nguyen 97c7bd9792 refactor: remove whitespace 2023-11-06 13:01:10 +11:00
David Nguyen 215eaebc1a feat: update document flow
- Fixed z-index when dragging pre-existing fields
- Refactored document flow
- Added button spinner
- Added animation for document flow slider
- Updated drag and drop fields
- Updated document flow so it adjusts to the height of the PDF
- Updated claim plan dialog
2023-11-06 13:01:10 +11:00
nsylke 039cd11c49 fix: use -p cli option for next dev 2023-11-06 13:01:10 +11:00
Adithya Krishna 617c738e5b fix: dependabot workflow
Signed-off-by: Adithya Krishna <aadithya794@gmail.com>
2023-11-06 13:01:10 +11:00
Mythie 77ab168c03 feat: change document view upon completion 2023-11-06 13:01:10 +11:00
David Nguyen a0abf56833 refactor: extract common components into UI package 2023-11-06 13:01:10 +11:00
Nicholas Sylke 98862a6356 feat: robots.txt & sitemap.xml 2023-11-06 13:01:10 +11:00
Mythie 077bb4ac3c chore: resolve build errors 2023-11-06 13:01:10 +11:00
Adithya Krishna 399cf78508 fix: fixed build command in codeql workflow
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
David Nguyen 857a48b3d9 feat: update items 2023-11-06 13:01:10 +11:00
David Nguyen 371f0947fb feat: update items
Refactored billing flag name

Refactored FeatureFlag type

Disabled session recording by default
2023-11-06 13:01:10 +11:00
David Nguyen aa2969fd50 feat: feature flags 2023-11-06 13:01:10 +11:00
flō 64eefbd340 Add visual asset for blog post 2023-11-06 13:01:10 +11:00
flō 769a374cf6 Create blog post 2023-11-06 13:01:10 +11:00
Adithya Krishna 96ae622f95 fix: removed ts from codeql
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 10330872e1 feat: add codeql-analysis
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 3a77a50d56 chore: updated dependabot config
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 122e1dffc3 chore: update readme file with radix-ui
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 256f1c6c84 chore: update readme file
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 56aea04b80 chore: enable job concurrency
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 6e7deca0cb feat: add dependabot config
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Adithya Krishna 7975eb7a8d feat: Added Engines to Enforce Node v18.0.0 and above
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-11-06 13:01:10 +11:00
Timur Ercan 105eeec5df chore: add shop to footer 2023-11-06 13:01:10 +11:00
Timur Ercan efc2102b90 chore: remove double emtpy state add CTA 2023-11-06 13:01:10 +11:00
Timur Ercan 46bd501835 chore: status order to figma 2023-11-06 13:01:10 +11:00
Timur Ercan 759b864b14 remove dashboard and link bar 2023-11-06 13:01:10 +11:00
Mythie 94b9b1060b fix: styling updates 2023-11-06 13:01:10 +11:00
premiare 6c06337f6e add PORT number back to package.json 2023-11-06 13:01:10 +11:00
premiare 0b5a550cd1 move menu links to map, add window-size hook 2023-11-06 13:01:10 +11:00
premiare cd1af7b5d3 add reducedMotion check, center menu items 2023-11-06 13:01:10 +11:00
premiare bc87986a3d extract hamburger to own file 2023-11-06 13:01:10 +11:00
premiare a948441956 init mobileNavigation and added documenso symbol 2023-11-06 13:01:10 +11:00
Nicholas Sylke 1f75342b91 chore: ignore intellij editor settings and some of vscode 2023-11-06 13:01:10 +11:00
Ashutosh-Bhadauriya d36ca59b77 fix: commitlint 2023-11-06 13:01:10 +11:00
Ashutosh-Bhadauriya 1f79ca0a70 feat: add commitlint 2023-11-06 13:01:09 +11:00
Nicholas Sylke d4b130a731 ci: use built-in cache from setup-node action 2023-11-06 13:01:09 +11:00
Nicholas Sylke 77ad1d1b0b ci: remove --workspaces from the build script in ci workflow 2023-11-06 13:01:09 +11:00
Nicholas Sylke ab4d70ced1 ci: setup build workflow 2023-11-06 13:01:09 +11:00
flō 3632a5aef5 fix typo 2023-11-06 13:01:09 +11:00
flō 8fa31c8f05 fix typo for consistency 2023-11-06 13:01:09 +11:00
Ephraim Atta-Duncan 7fda1d2010 feat: reduce chart radius to add padding 2023-11-06 13:01:09 +11:00
Ephraim Atta-Duncan fac1d34d24 feat: change legend text color to black 2023-11-06 13:01:09 +11:00
Ephraim Atta-Duncan c841c989aa feat: Add legend to pie chart 2023-11-06 13:01:09 +11:00
Mythie 7a705e3b81 feat: document authoring 2023-11-06 13:01:09 +11:00
Timur Ercan c6327c77ea chore: remove funding rounding 2023-11-06 13:01:08 +11:00
Timur Ercan 258477fd8d fix: link 2023-11-06 13:01:08 +11:00
Timur Ercan baa7236864 fix: format heade 2023-11-06 13:01:08 +11:00
Timur Ercan 201b88544d chore: adding open to footer, naming 2023-11-06 13:01:08 +11:00
Timur Ercan ee595056ac fix: open page link 2023-11-06 13:01:08 +11:00
Timur Ercan 331944f34a chore: update date 2023-11-06 13:01:08 +11:00
Nicholas Sylke 01c88065ee fix: used wrong lockfile version when resolving conflicts 2023-11-06 13:01:08 +11:00
Nicholas Sylke 784188ff51 refactor: future proofing the prettier/lint-staged for js/ts filetypes 2023-11-06 13:01:08 +11:00
Nicholas Sylke 58d765924f refactor: use lint-staged.config.cjs as configuration for lint-staged 2023-11-06 13:01:08 +11:00
Nicholas Sylke 9783e16656 add husky and lint-staged to ensure commits are formatted 2023-11-06 13:01:08 +11:00
Timur Ercan e164fabb04 chore: review updates 2023-11-06 13:01:08 +11:00
Timur Ercan 2d9f996630 mention cals open startup article 2023-11-06 13:01:08 +11:00
Timur Ercan 7c0c082a40 feat: pre seed announce and link on /open page 2023-11-06 13:01:08 +11:00
Timur Ercan 57e9f88b93 chore: whitespace 2023-11-06 13:01:08 +11:00
flō 64ffa37ea7 fix typo 2023-11-06 13:01:08 +11:00
flō 1503536334 Optimize description 2023-11-06 13:01:08 +11:00
flō f3e8c62fad Edit content 2023-11-06 13:01:08 +11:00
Mythie f66934551e chore: add contributor license agreement 2023-11-06 13:01:08 +11:00
flō 739599810e Edit blog post description 2023-11-06 13:01:08 +11:00
flō 58e291bcf1 Update link to community (Discord) 2023-11-06 13:01:08 +11:00
flō 7e2b351390 Add blog post: Switching to Discord 2023-11-06 13:01:08 +11:00
flō 900a51ff06 Add profile picture: Flo 2023-11-06 13:01:08 +11:00
flō dcd275fa6b fix mailto 2023-11-06 13:01:08 +11:00
flō c5f35fb66d fix typo 2023-11-06 13:01:08 +11:00
flō 89bf7b6651 edit announcing-documenso.mdx
- fix typo for consistency in tone and voice
- edit filename for SEO
2023-11-06 13:01:08 +11:00
flō 51e55148c7 edit manifest.mdx
- fix typo in caption for consistency
- edit filename (URL) for SEO
2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 4bfbe039e8 fix: ssr hydration error in piechart 2023-11-06 13:01:08 +11:00
Timur Ercan 769b51c531 chore: add total 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 07492100d2 chore: fix ts errors in metrics 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 3407952a5e chore: refactor github charts into a single component 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan d59045c419 chore: use correct names on tooltip 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan b3a52c4aec feat: add other charts 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan b3969f0614 chore: fix eslint error in data 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 0da6e93a6a chore: format date 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan fdd3d7c6fe chore: fetch stargazers data from stargazers api 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan a78ed42f6c feat: add github stars cummulative 2023-11-06 13:01:08 +11:00
Timur Ercan 63d4216fd1 chore: team updates, funding cummulative 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan d5e7f6f4df feat: add team members engagement to open page 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 145abc0e51 --amend 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 63f37732cd feat: add tooltip to cap table 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 27b42814d8 Add initial cap table 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan f882be4338 feat: open page 2023-11-06 13:01:08 +11:00
Mythie dc58f77c61 chore: upgrade deps and linting 2023-11-06 13:01:08 +11:00
Mythie 0fca2e9f4e fix: improve typesafety 2023-11-06 13:01:08 +11:00
Mythie 664357d166 chore: add oss friends page 2023-11-06 13:01:08 +11:00
Mythie a1cb595f25 fix: type errors 2023-11-06 13:01:08 +11:00
Mythie d986a3cf77 fix: styling updates 2023-11-06 13:01:08 +11:00
David Nguyen 71e9f016b6 feat: update privacy content 2023-11-06 13:01:08 +11:00
David Nguyen 96fc2fb850 feat: update blog styling 2023-11-06 13:01:08 +11:00
David Nguyen 3f322d1d0c feat: update privacy content 2023-11-06 13:01:08 +11:00
David Nguyen 79684c9970 feat: remove whitespace 2023-11-06 13:01:08 +11:00
David Nguyen fcf9720a1e feat: add generic content page 2023-11-06 13:01:08 +11:00
David Nguyen 822624bc02 Update apps/marketing/content/blog/announcing-documenso.mdx
Co-authored-by: Joshua Sharp <joshuafsharp@gmail.com>
2023-11-06 13:01:08 +11:00
David Nguyen 154ef26465 feat: add content layer
Add blog pages

Add privacy page
2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan bc425abe35 refactor: read z-index values from an object 2023-11-06 13:01:08 +11:00
Ephraim Atta-Duncan 0de4fe5883 fix: update types from code review 2023-11-06 13:01:07 +11:00
Ephraim Atta-Duncan a94f447e9e feat: update stack avatar with changes from code review 2023-11-06 13:01:07 +11:00
Ephraim Atta-Duncan e1d57e6d29 feat: add recipients avatars on all tables 2023-11-06 13:01:07 +11:00
Ephraim Atta-Duncan 71c689eae8 chore: refactor stacked avatars into component 2023-11-06 13:01:07 +11:00
Ephraim Atta-Duncan 208e028226 feat: add tooltip on hover on stacked avatars 2023-11-06 13:01:07 +11:00
Ephraim Atta-Duncan 00a15124be feat: stack recipients avatars on dashboard 2023-11-06 13:01:07 +11:00
Mythie 12d8cebd4c feat: use server-actions for authoring flow
This change actually makes the authoring flow work for
the most part by tying in emailing and more.

We have also done a number of quality of life updates to
simplify the codebase overall making it easier to continue
work on the refresh.
2023-11-06 13:01:07 +11:00
Mythie 5e3752cdbf fix: add shadow to metric-card 2023-11-06 13:01:07 +11:00
Mythie ca0f4eefd2 feat: email templates
adds email templates using `react-email` which will be used for invites,
signing and document completion.

authored by @dephraiim
2023-11-06 13:01:07 +11:00
Mythie 0b1c4fadc6 chore: use jsonprotocol 2023-11-06 13:01:07 +11:00
Doug Andrade 56b6bc68c4 linking card metrics to filtered /documents 2023-11-06 13:01:07 +11:00
Mythie 3c73f030ac feat: persist fields and recipients for document editing 2023-11-06 13:01:07 +11:00
Mythie 115cae8365 fix: styling and semantic updates 2023-11-06 13:01:07 +11:00
Doug Andrade 814a6174e3 improved loading state for /document/id 2023-11-06 13:01:07 +11:00
Doug Andrade 5442e82ae7 clean up console.log() used for testing 2023-11-06 13:01:07 +11:00
Doug Andrade 6253c42ca1 feat: google auth without schema change 2023-11-06 13:01:07 +11:00
Doug Andrade 3377e29c72 fix: dark mode on signup and signin pages 2023-11-06 13:01:07 +11:00
Doug Andrade 4ea316a077 fix: signature pad in dark mode 2023-11-06 13:01:07 +11:00
Doug Andrade feefeea0c4 resolving eslint build errors 2023-11-06 13:01:07 +11:00
Doug Andrade 40115d33b4 adding dark mode to feat/refresh 2023-11-06 13:01:07 +11:00
Mythie cc182649c2 wip: create document workflow 2023-11-06 13:01:07 +11:00
Mythie 159bcade7b wip: refresh design 2023-11-06 13:01:06 +11:00
Pablo Hinojosa 76b2fb5edd docs: add docker compose instructions
Add docker section to Deployment section
2023-10-31 18:47:34 +11:00
Lucas Smith 7df83cc0ad Merge pull request #363 from mcnaveen/main
chore(docs): improve Docker instructions
2023-09-13 21:57:56 +10:00
MC Naveen f9d96c67d3 changes 2023-09-10 09:32:37 +05:30
MC Naveen beb9946f3b typo 2023-09-10 09:29:01 +05:30
MC Naveen 822b018eb0 chore(docs): improve Docker instructions 2023-09-10 09:28:15 +05:30
Lucas Smith d702710cb4 Merge pull request #325 from thara003/fix/typo-contributing.md
docs: fix typo in CONTRUBUTING.md
2023-08-30 11:19:21 +10:00
Nayan Thara M a9ed9c0316 docs: fix a typo in README.md 2023-08-30 00:27:44 +05:30
Nayan Thara M 1ea231e9af docs: fix a typo in CONTRUBUTING.md 2023-08-29 19:56:39 +05:30
Lucas Smith dfd28f4441 Merge pull request #322 from documenso/readme
feat: Add Repository Activity
2023-08-29 13:23:15 +10:00
flō fee107ff7a Add Repository Activity 2023-08-28 16:34:31 +02:00
Lucas Smith 19ea119009 Merge pull request #251 from documenso/docs/add-render-deploy
docs: add render one click deploy button
2023-08-24 10:49:03 +10:00
Lucas Smith aea20019bd Merge pull request #253 from documenso/docs/add-gitpod-setup
docs: add gitpod setup
2023-08-22 10:20:24 +10:00
Lucas Smith 3177a07d25 chore: update readme 2023-08-21 17:00:14 +10:00
Lucas Smith 0a1603b147 Merge pull request #276 from yashug/fix-null-name-email
Fixes Showing ask null from SigningEmail Template
2023-08-21 12:56:07 +10:00
yashug 82ccdd5104 fix-null-name-email 2023-08-20 10:15:26 +05:30
Mythie 2d5727cab2 chore: add issue and pr templates 2023-08-19 19:57:03 +10:00
Lucas Smith 8862edfd3e Merge pull request #269 from rishi-raj-jain/main
fix: [DOC-296] Add changesets for automatic release versioning
2023-08-18 18:35:19 +10:00
Rishi Raj Jain 2c9644ce9c remove changelog-github 2023-08-18 11:32:48 +05:30
Rishi Raj Jain 6b45e8ce3a update changeset config and contributing 2023-08-18 11:31:13 +05:30
Rishi Raj Jain a98eca3bf1 move to devDep 2023-08-18 11:18:27 +05:30
Rishi Raj Jain e1772da491 add publish & version 2023-08-18 11:17:08 +05:30
Rishi Raj Jain 9f1e0257e7 install changeset 2023-08-18 11:06:49 +05:30
Lucas Smith 4f4725f166 Merge pull request #265 from documenso/fix/discord
fix: update link to community
2023-08-18 08:56:54 +10:00
flō eb7d89a0cd Update link to community 2023-08-17 17:46:32 +02:00
flō 4a57f75228 update link to community 2023-08-17 17:45:39 +02:00
Lucas Smith adf92bb53d Merge pull request #254 from nsylke/nsylke-patch-1
Correct readme logo url
2023-08-14 21:34:18 +10:00
Nicholas Sylke adf3a11c5f Update README.md 2023-08-12 15:08:57 -05:00
Ephraim Atta-Duncan 652951ed7f Fix prisma studio opening twice 2023-08-11 20:48:42 +00:00
Ephraim Atta-Duncan 40db5baa17 Add gitpod button to readme 2023-08-11 20:44:09 +00:00
Ephraim Atta-Duncan 4cdfa2d1f0 Add gitpod config files 2023-08-11 20:38:41 +00:00
Mythie ba82b1fca8 chore: update dockerfile 2023-08-10 13:51:22 +10:00
Ephraim Atta-Duncan 6b3696d879 docs: add render deploy button to readme 2023-08-10 01:53:05 +00:00
Ephraim Atta-Duncan ca1522a31e Merge branch 'main' into feat/add-render-deploy 2023-08-10 01:52:25 +00:00
Ephraim Atta-Duncan 201c0ac22a chore: update start command for render deployment 2023-08-10 01:34:42 +00:00
Ephraim Atta-Duncan c573e15ac2 Add render.yaml 2023-08-10 00:52:43 +00:00
Lucas Smith e5a80a701f Merge pull request #242 from documenso/docs/railway-one-click-deploy
docs: add railway one click deploy
2023-08-09 16:23:40 +10:00
Lucas Smith f8aebbc484 Merge branch 'main' into docs/railway-one-click-deploy 2023-08-09 16:22:56 +10:00
Lucas Smith 4e60d4ac09 Merge pull request #247 from criadoperez/fix/criadoperez
Fixed typos
2023-08-04 12:36:49 +10:00
Alejandro Criado-Pérez b5328eebde Corrected ts and tsx files 2023-08-04 02:14:04 +02:00
Lucas Smith e87c57c97c Merge branch 'main' into docs/railway-one-click-deploy 2023-08-03 10:51:49 +10:00
Timur Ercan cef5c8e33f Merge pull request #245 from documenso/fix-comunity-link
Update link to community (Discord)
2023-08-02 15:13:48 +02:00
flō b03dd1553f Update link to community (Discord) 2023-08-02 15:12:31 +02:00
Ephraim Atta-Duncan f6d1b8c8a1 add hint that more deployment methods are coming 2023-08-01 14:12:33 +00:00
Ephraim Atta-Duncan cb29ffef37 docs: add railway one click deploy 2023-08-01 13:47:50 +00:00
Lucas Smith f7d0bb9823 Merge pull request #227 from documenso/docs-coventional-commits
docs: conventional commits
2023-07-31 13:00:55 +10:00
Timur Ercan fc2809c4cf Merge branch 'main' into docs-coventional-commits 2023-07-28 11:42:48 +02:00
Timur Ercan 14536bda1e Merge pull request #234 from documenso/chore-manifest-filename
Rename manifest.md to MANIFEST.md
2023-07-21 14:35:39 +02:00
Timur Ercan 5fcd54ae09 Rename manifest.md to MANIFEST.md 2023-07-21 14:35:19 +02:00
Timur Ercan 35fecfffa9 Merge pull request #231 from documenso/chore/claim
chore: claim
2023-07-21 14:26:12 +02:00
Timur Ercan da07bf135c chore: phrasing 2023-07-21 09:55:50 +02:00
Timur Ercan 1a06dd261c chore: claim 2023-07-21 09:47:05 +02:00
Timur Ercan 9c3fbbdb3a chore: dub slack slug 2023-07-19 07:54:03 +02:00
Timur Ercan c20123ec75 merge main 2023-07-18 12:03:14 +02:00
Timur Ercan 4c25e85f01 doc: branchformat 2023-07-18 11:59:59 +02:00
Lucas Smith ed3dbefbd7 Merge pull request #229 from fmerian/patch-1
fix: Update link to Slack in CONTRIBUTING.md
2023-07-15 12:19:04 +10:00
flō 7d8f83750b Update link to Slack in CONTRIBUTING.md 2023-07-14 16:40:31 +02:00
Timur Ercan fda3472e67 docs: conventional commits 2023-07-13 15:50:38 +02:00
Timur Ercan 2dd89b1bc1 Merge pull request #225 from documenso/feat/manifest
feat: added manifest
2023-07-13 12:08:02 +02:00
Timur Ercan cc87eeb8e0 feat: added manifest 2023-07-13 11:10:21 +02:00
Mythie 6c78332258 fix: await signing requests 2023-07-08 18:52:13 +10:00
Timur Ercan 5922725e1a Merge pull request #219 from PeerRich/patch-2
added new cal.com badge for booking link
2023-06-29 13:55:24 +02:00
Peer Richelsen 36232c7817 added new cal.com badge for booking link 2023-06-28 19:53:39 +02:00
Mythie 723339f812 fix: add smtp troubleshooting 2023-06-17 13:08:15 +10:00
Lucas Smith f279b41b89 Merge pull request #172 from doug-andrade/logo
added dynamic coloring of logo mark
2023-06-17 11:56:36 +10:00
Lucas Smith 3d0836c39c Merge branch 'main' into logo 2023-06-17 11:55:54 +10:00
Lucas Smith fcaa59699c Merge pull request #188 from Ashutosh-Bhadauriya/fix/selectbox-alignment
Fixed alignment of div containing selectboxes in mobile and tablet screens
2023-06-17 11:54:01 +10:00
Mythie 5063b60a8a fix: further style updates 2023-06-17 11:53:04 +10:00
Lucas Smith 1322f7333f Merge branch 'main' into fix/selectbox-alignment 2023-06-17 11:48:11 +10:00
Lucas Smith 0278495896 Merge pull request #190 from dephraiim/fix-typo
Fix typo in contributing.md
2023-06-17 11:47:19 +10:00
Lucas Smith ef34c4fe5d Merge pull request #179 from documenso/fix/improve-stripe-webhook-endpoint
fix: improve stripe webhook endpoint
2023-06-17 11:46:37 +10:00
Lucas Smith 6dad379943 Merge pull request #196 from documenso/feat/password-reset
feat: reset password
2023-06-17 11:45:38 +10:00
Mythie d3f82e1eb0 fix: code style updates and email wording changes 2023-06-17 11:44:34 +10:00
Lucas Smith 6838b953a8 Merge pull request #209 from documenso/fix/send-error-double-send
fix: don't double send error reponses when sending fails
2023-06-17 11:28:13 +10:00
Lucas Smith fdd5a6114e Merge pull request #211 from danieltonel/doc-210-fix-docker-compose-name-attribute-error
Doc 210 fix docker compose name attribute error
2023-06-16 18:24:54 +10:00
danieltonel b05ab9fbb4 set name using docker compose 2023-06-16 10:50:20 +03:00
danieltonel e34be16d3d fix: Docker Compose 'name' attribute causing invalid file issue 2023-06-16 10:44:23 +03:00
Mythie 9fd9bc2893 fix: reformat 2023-06-16 00:24:13 +10:00
Lucas Smith 3f14d8007a fix: don't double send error reponses when sending fails 2023-06-15 23:25:35 +10:00
Ephraim Atta-Duncan e3bc41934c Fixes from code review 2023-06-09 03:55:30 +00:00
Ephraim Atta-Duncan 13a840ff78 Password validation with zod 2023-06-07 12:33:33 +00:00
Ephraim Atta-Duncan fe6561f596 Set reset token expiry to 24 hours 2023-06-07 11:02:50 +00:00
Ephraim Atta-Duncan 9cfbb1dec9 Avoid leaking that a user has an account 2023-06-07 10:59:20 +00:00
Ephraim Atta-Duncan 9dd8c2842c Match emails with regex 2023-06-07 10:44:07 +00:00
Ephraim Atta-Duncan 54a965e2b4 Remove unused props from components 2023-06-07 10:37:47 +00:00
Ephraim Atta-Duncan 7cc1ae2de0 Refactor forgot password and reset component 2023-06-07 10:33:05 +00:00
Ephraim Atta-Duncan f08836216e Remove unused input fields 2023-06-07 10:12:05 +00:00
Ephraim Atta-Duncan 7184c47ac4 Rename component interfaces 2023-06-07 10:10:56 +00:00
Ephraim Atta-Duncan 79bd410687 Remove tokens on successful password reset 2023-06-05 17:15:41 +00:00
Ephraim Atta-Duncan 3a0648c85d Expire token after 1 hour 2023-06-05 16:54:12 +00:00
Ephraim Atta-Duncan 2b9a2ff250 Avoid user from setting the same old password 2023-06-05 16:36:16 +00:00
Ephraim Atta-Duncan 4136811e32 Avoid consecutive password reset requests 2023-06-05 16:01:01 +00:00
Ephraim Atta-Duncan e9cee23c15 Error handling for invalid users 2023-06-05 15:52:00 +00:00
Ephraim Atta-Duncan 5d2349086d Send email on password reset complete 2023-06-05 15:33:27 +00:00
Ephraim Atta-Duncan c47e01b2b8 Display sucessful password reset request 2023-06-05 14:59:50 +00:00
Ephraim Atta-Duncan 7c30ee0c3e Redirect to /login on password reset 2023-06-05 14:47:10 +00:00
Ephraim Atta-Duncan 6e2b05f835 Change password in database to new reset password 2023-06-05 14:36:20 +00:00
Ephraim Atta-Duncan 8dc9c9d72d Add reset password page 2023-06-05 14:17:45 +00:00
Ephraim Atta-Duncan 66b529a841 feat: send reset password email 2023-06-05 13:44:47 +00:00
Ephraim Atta-Duncan 8293b50195 Create reset password token for user 2023-06-05 13:05:25 +00:00
Ephraim Atta-Duncan 002b22b1a8 Add forgot password page 2023-06-05 12:53:51 +00:00
Ephraim Atta-Duncan 447bf0cb76 Add password reset to prisma schema 2023-06-05 12:23:52 +00:00
Ephraim Atta-Duncan 37c4e68aac Fix typo in contributing.md 2023-06-02 20:01:10 +00:00
AshutoshBhadauriya a07febef46 Fix: alignment of div containing selecboxes in mobile and tab screens 2023-06-02 15:06:26 +05:30
Doug Andrade 79c037216d Delete turbo-build.log 2023-06-01 22:13:45 -04:00
Doug Andrade aa584c1495 Merge branch 'logo' of https://github.com/doug-andrade/documenso into logo 2023-06-01 22:09:39 -04:00
Doug Andrade 8b9738f6d5 fix: updated all logo component to set color 2023-06-01 22:05:15 -04:00
Mythie 19e960f593 fix: improve stripe webhook endpoint
Improve the stripe webhook endpoint by checking for
subscriptions prior to performing an update to handle
cases where accounts have no created subscription.

This can happen in sitations such as when a checkout_session has been created but the payment fails.
2023-05-31 21:11:54 +10:00
Doug Andrade 37ded07a92 Merge branch 'main' into logo 2023-05-29 13:23:14 -04:00
Doug Andrade df2294b43b dynamic coloring of logo mark 2023-05-29 13:17:40 -04:00
179 changed files with 1455 additions and 4876 deletions
+6 -11
View File
@@ -2,11 +2,6 @@
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="secret"
# [[CRYPTO]]
# Application Key for symmetric encryption and decryption
# This should be a random string of at least 32 characters
NEXT_PRIVATE_ENCRYPTION_KEY="CAFEBABE"
# [[AUTH OPTIONAL]]
NEXT_PRIVATE_GOOGLE_CLIENT_ID=""
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=""
@@ -23,21 +18,21 @@ NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/
# [[E2E Tests]]
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_password"
# [[STORAGE]]
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
NEXT_PUBLIC_UPLOAD_TRANSPORT="database"
# OPTIONAL: Defines the endpoint to use for the S3 storage transport. Relevant when using third-party S3-compatible providers.
NEXT_PRIVATE_UPLOAD_ENDPOINT="http://127.0.0.1:9002"
NEXT_PRIVATE_UPLOAD_ENDPOINT=
# OPTIONAL: Defines the region to use for the S3 storage transport. Defaults to us-east-1.
NEXT_PRIVATE_UPLOAD_REGION="unknown"
NEXT_PRIVATE_UPLOAD_REGION=
# REQUIRED: Defines the bucket to use for the S3 storage transport.
NEXT_PRIVATE_UPLOAD_BUCKET="documenso"
NEXT_PRIVATE_UPLOAD_BUCKET=
# OPTIONAL: Defines the access key ID to use for the S3 storage transport.
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID="documenso"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID=
# OPTIONAL: Defines the secret access key to use for the S3 storage transport.
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY="password"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY=
# [[SMTP]]
# OPTIONAL: Defines the transport to use for sending emails. Available options: smtp-auth (default) | smtp-api | mailchannels
+2 -2
View File
@@ -1,5 +1,5 @@
name: "Bug Report"
labels: "bug"
labels: ["bug"]
description: Create a bug report to help us improve
body:
- type: markdown
@@ -45,4 +45,4 @@ body:
- label: I have included relevant environment information.
- label: I have included any relevant screenshots.
- label: I understand that this is a voluntary contribution and that there is no guarantee of resolution.
- label: I want to work on creating a PR for this issue if approved
- label: I want to work on creating a PR for this issue if approved
+3 -3
View File
@@ -5,7 +5,7 @@ updates:
directory: '/'
schedule:
interval: "weekly"
target-branch: "feat/refresh"
target-branch: "main"
labels:
- "ci dependencies"
- "ci"
@@ -15,7 +15,7 @@ updates:
directory: "/apps/marketing"
schedule:
interval: "weekly"
target-branch: "feat/refresh"
target-branch: "main"
labels:
- "npm dependencies"
- "frontend"
@@ -25,7 +25,7 @@ updates:
directory: "/apps/web"
schedule:
interval: "weekly"
target-branch: "feat/refresh"
target-branch: "main"
labels:
- "npm dependencies"
- "frontend"
+2 -2
View File
@@ -2,9 +2,9 @@ name: "Continuous Integration"
on:
push:
branches: [ "feat/refresh" ]
branches: [ "main" ]
pull_request:
branches: [ "feat/refresh" ]
branches: [ "main" ]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+2 -2
View File
@@ -3,9 +3,9 @@ name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [ feat/refresh ]
branches: [ "main" ]
pull_request:
branches: [ feat/refresh ]
branches: [ "main" ]
jobs:
analyze:
+2 -2
View File
@@ -1,9 +1,9 @@
name: Playwright Tests
on:
push:
branches: [feat/refresh]
branches: [ "main" ]
pull_request:
branches: [feat/refresh]
branches: [ "main" ]
jobs:
e2e_tests:
timeout-minutes: 60
-16
View File
@@ -1,16 +0,0 @@
node_modules
.next
public
**/**/node_modules
**/**/.next
**/**/public
*.lock
*.log
*.test.ts
.gitignore
.npmignore
.prettierignore
.DS_Store
.eslintignore
+1 -1
View File
@@ -37,7 +37,7 @@ The development branch is <code>main</code>. All pull requests should be made ag
- Create a new branch (include the issue id and something readable):
```sh
git checkout -b doc-999-my-feature-or-fix
git checkout -b feat/doc-999-somefeature-that-rocks
```
3. See the [Developer Setup](https://github.com/documenso/documenso/blob/main/README.md#developer-setup) for more setup details.
+6
View File
@@ -0,0 +1,6 @@
# The Documenso Manifest
Signing documents is a fundamental building block of private, economic, and government interactions. Access to easy and secure signing to participate in society should therefore be a fundamental right for everyone. The technology to enable this should be accessible and widespread.
We know that open source is the key to solving this need once and for all to benefit all humankind. Using open source kickstarts innovation by putting the open sharing of ideas and solutions first. With Documenso, we will create an open and globally accessible signing platform to empower users, customers, and developers to fulfill their needs. Documenso is built by and for the global community, listening and implementing what is needed. Being transparent with the code and the processes that use it brings trust and security to the platform.
We build Documenso for longevity and scale by embracing the capital efficiency and inclusiveness of the Commercial Open Source (COSS) movement. We are building a global commodity for the world.
+1 -5
View File
@@ -1,5 +1,3 @@
🚨 We are live on Product Hunt with Single Player Mode and the new free tier: [https://www.producthunt.com/products/documenso](https://www.producthunt.com/posts/documenso-singleplayer-mode)
<img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo">
<p align="center" style="margin-top: 20px">
@@ -141,13 +139,11 @@ npm run d
1. **App** - http://localhost:3000
2. **Incoming Mail Access** - http://localhost:9000
3. **Database Connection Details**
3. **Database Connection Details**
- **Port**: 54320
- **Connection**: Use your favorite database client to connect using the provided port.
4. **S3 Storage Dashboard** - http://localhost:9001
## Developer Setup
### Manual Setup
-1
View File
@@ -8,7 +8,6 @@
"build": "next build",
"start": "next start -p 3001",
"lint": "next lint",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},
@@ -18,14 +18,6 @@ export const revalidate = 3600;
export const dynamic = 'force-dynamic';
const GITHUB_HEADERS: Record<string, string> = {
accept: 'application/vnd.github.v3+json',
};
if (process.env.NEXT_PRIVATE_GITHUB_TOKEN) {
GITHUB_HEADERS.authorization = `Bearer ${process.env.NEXT_PRIVATE_GITHUB_TOKEN}`;
}
const ZGithubStatsResponse = z.object({
stargazers_count: z.number(),
forks_count: z.number(),
@@ -36,10 +28,6 @@ const ZMergedPullRequestsResponse = z.object({
total_count: z.number(),
});
const ZOpenIssuesResponse = z.object({
total_count: z.number(),
});
const ZStargazersLiveResponse = z.record(
z.object({
stars: z.number(),
@@ -60,76 +48,49 @@ const ZEarlyAdoptersResponse = z.record(
export type StargazersType = z.infer<typeof ZStargazersLiveResponse>;
export type EarlyAdoptersType = z.infer<typeof ZEarlyAdoptersResponse>;
const fetchGithubStats = async () => {
return await fetch('https://api.github.com/repos/documenso/documenso', {
headers: {
...GITHUB_HEADERS,
},
export default async function OpenPage() {
const GITHUB_HEADERS: Record<string, string> = {
accept: 'application/vnd.github.v3+json',
};
if (process.env.NEXT_PRIVATE_GITHUB_TOKEN) {
GITHUB_HEADERS.authorization = `Bearer ${process.env.NEXT_PRIVATE_GITHUB_TOKEN}`;
}
const {
forks_count: forksCount,
open_issues: openIssues,
stargazers_count: stargazersCount,
} = await fetch('https://api.github.com/repos/documenso/documenso', {
headers: GITHUB_HEADERS,
})
.then(async (res) => res.json())
.then((res) => ZGithubStatsResponse.parse(res));
};
const fetchOpenIssues = async () => {
return await fetch(
'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1',
{
headers: {
...GITHUB_HEADERS,
},
},
)
.then(async (res) => res.json())
.then((res) => ZOpenIssuesResponse.parse(res));
};
const fetchMergedPullRequests = async () => {
return await fetch(
const { total_count: mergedPullRequests } = await fetch(
'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1',
{
headers: {
...GITHUB_HEADERS,
},
headers: GITHUB_HEADERS,
},
)
.then(async (res) => res.json())
.then((res) => ZMergedPullRequestsResponse.parse(res));
};
const fetchStargazers = async () => {
return await fetch('https://stargrazer-live.onrender.com/api/stats', {
const STARGAZERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats', {
headers: {
accept: 'application/json',
},
})
.then(async (res) => res.json())
.then((res) => ZStargazersLiveResponse.parse(res));
};
const fetchEarlyAdopters = async () => {
return await fetch('https://stargrazer-live.onrender.com/api/stats/stripe', {
const EARLY_ADOPTERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats/stripe', {
headers: {
accept: 'application/json',
},
})
.then(async (res) => res.json())
.then((res) => ZEarlyAdoptersResponse.parse(res));
};
export default async function OpenPage() {
const [
{ forks_count: forksCount, stargazers_count: stargazersCount },
{ total_count: openIssues },
{ total_count: mergedPullRequests },
STARGAZERS_DATA,
EARLY_ADOPTERS_DATA,
] = await Promise.all([
fetchGithubStats(),
fetchOpenIssues(),
fetchMergedPullRequests(),
fetchStargazers(),
fetchEarlyAdopters(),
]);
const MONTHLY_USERS = await getUserMonthlyGrowth();
@@ -8,19 +8,18 @@ import { useRouter } from 'next/navigation';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { base64 } from '@documenso/lib/universal/base64';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentDataType, Prisma } from '@documenso/prisma/client';
import { DocumentDataType, Field, Prisma, Recipient } from '@documenso/prisma/client';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone';
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { AddSignatureFormPartial } from '@documenso/ui/primitives/document-flow/add-signature';
import type { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types';
import { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types';
import {
DocumentFlowFormContainer,
DocumentFlowFormContainerHeader,
} from '@documenso/ui/primitives/document-flow/document-flow-root';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -5,12 +5,13 @@ import { HTMLAttributes } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { FaXTwitter } from 'react-icons/fa6';
import { LiaDiscord } from 'react-icons/lia';
import { LuGithub } from 'react-icons/lu';
import { cn } from '@documenso/ui/lib/utils';
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
export type FooterProps = HTMLAttributes<HTMLDivElement>;
@@ -33,6 +34,8 @@ const FOOTER_LINKS = [
];
export const Footer = ({ className, ...props }: FooterProps) => {
const { setTheme } = useTheme();
return (
<div className={cn('border-t py-12', className)} {...props}>
<div className="mx-auto flex w-full max-w-screen-xl flex-wrap items-start justify-between gap-8 px-8">
@@ -74,13 +77,21 @@ export const Footer = ({ className, ...props }: FooterProps) => {
))}
</div>
</div>
<div className="mx-auto mt-4 flex w-full max-w-screen-xl flex-wrap items-center justify-between gap-4 px-8 md:mt-12 lg:mt-24">
<div className="mx-auto mt-4 flex w-full max-w-screen-xl flex-wrap justify-between gap-4 px-8 md:mt-12 lg:mt-24">
<p className="text-muted-foreground text-sm">
© {new Date().getFullYear()} Documenso, Inc. All rights reserved.
</p>
<div className="flex flex-wrap">
<ThemeSwitcher />
<div className="flex flex-wrap items-center gap-x-4 gap-y-2.5">
<button type="button" className="text-muted-foreground" onClick={() => setTheme('light')}>
<Sun className="h-5 w-5" />
<span className="sr-only">Light</span>
</button>
<button type="button" className="text-muted-foreground" onClick={() => setTheme('dark')}>
<Moon className="h-5 w-5" />
<span className="sr-only">Dark</span>
</button>
</div>
</div>
</div>
@@ -10,7 +10,6 @@ import { z } from 'zod';
import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email';
import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf';
import { alphaid } from '@documenso/lib/universal/id';
@@ -216,7 +215,7 @@ const mapField = (
signer: TCreateSinglePlayerDocumentSchema['signer'],
) => {
const customText = match(field.type)
.with(FieldType.DATE, () => DateTime.now().toFormat(DEFAULT_DOCUMENT_DATE_FORMAT))
.with(FieldType.DATE, () => DateTime.now().toFormat('yyyy-MM-dd hh:mm a'))
.with(FieldType.EMAIL, () => signer.email)
.with(FieldType.NAME, () => signer.name)
.otherwise(() => '');
@@ -1,4 +1,4 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextApiRequest, NextApiResponse } from 'next';
import { randomBytes } from 'crypto';
import { readFileSync } from 'fs';
@@ -7,8 +7,7 @@ import { buffer } from 'micro';
import { insertImageInPDF } from '@documenso/lib/server-only/pdf/insert-image-in-pdf';
import { insertTextInPDF } from '@documenso/lib/server-only/pdf/insert-text-in-pdf';
import { redis } from '@documenso/lib/server-only/redis';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { stripe } from '@documenso/lib/server-only/stripe';
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { updateFile } from '@documenso/lib/universal/upload/update-file';
import { prisma } from '@documenso/prisma';
-1
View File
@@ -12,7 +12,6 @@ ENV_FILES.forEach((file) => {
/** @type {import('next').NextConfig} */
const config = {
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
experimental: {
serverActionsBodySizeLimit: '50mb',
},
-2
View File
@@ -8,7 +8,6 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},
@@ -43,7 +42,6 @@
"sharp": "0.32.5",
"ts-pattern": "^5.0.5",
"typescript": "5.2.2",
"uqr": "^0.1.2",
"zod": "^3.22.4"
},
"devDependencies": {
@@ -2,7 +2,7 @@ import React from 'react';
import { redirect } from 'next/navigation';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { AdminNav } from './nav';
@@ -4,11 +4,12 @@ import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { ZUpdateProfileMutationByAdminSchema } from '@documenso/trpc/server/admin-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import { Combobox } from '@documenso/ui/primitives/combobox';
import {
Form,
FormControl,
@@ -18,7 +19,6 @@ import {
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multiselect-combobox';
import { useToast } from '@documenso/ui/primitives/use-toast';
const ZUserFormSchema = ZUpdateProfileMutationByAdminSchema.omit({ id: true });
@@ -117,7 +117,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
<fieldset className="flex flex-col gap-2">
<FormLabel className="text-muted-foreground">Roles</FormLabel>
<FormControl>
<MultiSelectCombobox
<Combobox
listValues={roles}
onChange={(values: string[]) => onChange(values)}
/>
@@ -4,21 +4,21 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation';
import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { DocumentData, Field, Recipient, User } from '@documenso/prisma/client';
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add-fields';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { AddSignersFormPartial } from '@documenso/ui/primitives/document-flow/add-signers';
import type { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
import { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
import { AddSubjectFormPartial } from '@documenso/ui/primitives/document-flow/add-subject';
import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
import {
DocumentFlowFormContainer,
DocumentFlowFormContainerHeader,
} from '@documenso/ui/primitives/document-flow/document-flow-root';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -117,16 +117,14 @@ export const EditDocumentForm = ({
};
const onAddSubjectFormSubmit = async (data: TAddSubjectFormSchema) => {
const { subject, message, timezone, dateFormat } = data.meta;
const { subject, message } = data.email;
try {
await completeDocument({
documentId: document.id,
meta: {
email: {
subject,
message,
timezone,
dateFormat,
},
});
@@ -3,7 +3,7 @@ import { redirect } from 'next/navigation';
import { ChevronLeft, Users2 } from 'lucide-react';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
@@ -1,185 +0,0 @@
'use client';
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { History } from 'lucide-react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import { type Document, type Recipient, SigningStatus } from '@documenso/prisma/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { DropdownMenuItem } from '@documenso/ui/primitives/dropdown-menu';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from '@documenso/ui/primitives/form/form';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { StackAvatar } from '~/components/(dashboard)/avatar/stack-avatar';
const FORM_ID = 'resend-email';
export type ResendDocumentActionItemProps = {
document: Document;
recipients: Recipient[];
};
export const ZResendDocumentFormSchema = z.object({
recipients: z.array(z.number()).min(1, {
message: 'You must select at least one item.',
}),
});
export type TResendDocumentFormSchema = z.infer<typeof ZResendDocumentFormSchema>;
export const ResendDocumentActionItem = ({
document,
recipients,
}: ResendDocumentActionItemProps) => {
const { toast } = useToast();
const [isOpen, setIsOpen] = useState(false);
const isDisabled =
document.status !== 'PENDING' ||
!recipients.some((r) => r.signingStatus === SigningStatus.NOT_SIGNED);
const { mutateAsync: resendDocument } = trpcReact.document.resendDocument.useMutation();
const form = useForm<TResendDocumentFormSchema>({
resolver: zodResolver(ZResendDocumentFormSchema),
defaultValues: {
recipients: [],
},
});
const {
handleSubmit,
formState: { isSubmitting },
} = form;
const onFormSubmit = async ({ recipients }: TResendDocumentFormSchema) => {
try {
await resendDocument({ documentId: document.id, recipients });
toast({
title: 'Document re-sent',
description: 'Your document has been re-sent successfully.',
duration: 5000,
});
setIsOpen(false);
} catch (err) {
toast({
title: 'Something went wrong',
description: 'This document could not be re-sent at this time. Please try again.',
variant: 'destructive',
duration: 7500,
});
}
};
return (
<>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<DropdownMenuItem disabled={isDisabled} onSelect={(e) => e.preventDefault()}>
<History className="mr-2 h-4 w-4" />
Resend
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-sm" hideClose>
<DialogHeader>
<DialogTitle>
<h1 className="text-center text-xl">Who do you want to remind?</h1>
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form id={FORM_ID} onSubmit={handleSubmit(onFormSubmit)} className="px-3">
<FormField
control={form.control}
name="recipients"
render={({ field: { value, onChange } }) => (
<>
{recipients.map((recipient) => (
<FormItem
key={recipient.id}
className="flex flex-row items-center justify-between gap-x-3"
>
<FormLabel
className={cn('my-2 flex items-center gap-2 font-normal', {
'opacity-50': !value.includes(recipient.id),
})}
>
<StackAvatar
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
{recipient.email}
</FormLabel>
<FormControl>
<Checkbox
className="h-5 w-5 rounded-full data-[state=checked]:border-black data-[state=checked]:bg-black "
checkClassName="text-white"
value={recipient.id}
checked={value.includes(recipient.id)}
onCheckedChange={(checked: boolean) =>
checked
? onChange([...value, recipient.id])
: onChange(value.filter((v) => v !== recipient.id))
}
/>
</FormControl>
</FormItem>
))}
</>
)}
/>
</form>
</Form>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<DialogClose asChild>
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
disabled={isSubmitting}
>
Cancel
</Button>
</DialogClose>
<Button className="flex-1" loading={isSubmitting} type="submit" form={FORM_ID}>
Send reminder
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
};
@@ -8,6 +8,7 @@ import {
Copy,
Download,
Edit,
History,
Loader,
MoreHorizontal,
Pencil,
@@ -18,9 +19,8 @@ import {
import { useSession } from 'next-auth/react';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import type { Document, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { Document, DocumentStatus, Recipient, User } from '@documenso/prisma/client';
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc as trpcClient } from '@documenso/trpc/client';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import {
@@ -31,7 +31,6 @@ import {
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { ResendDocumentActionItem } from './_action-items/resend-document';
import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog';
import { DuplicateDocumentDialog } from './duplicate-document-dialog';
@@ -97,7 +96,6 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
window.URL.revokeObjectURL(link.href);
};
const nonSignedRecipients = row.Recipient.filter((item) => item.signingStatus !== 'SIGNED');
return (
<DropdownMenu>
<DropdownMenuTrigger>
@@ -143,7 +141,10 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
<DropdownMenuLabel>Share</DropdownMenuLabel>
<ResendDocumentActionItem document={row} recipients={nonSignedRecipients} />
<DropdownMenuItem disabled>
<History className="mr-2 h-4 w-4" />
Resend
</DropdownMenuItem>
<DocumentShareButton
documentId={row.id}
@@ -26,11 +26,16 @@ export const DuplicateDocumentDialog = ({
const router = useRouter();
const { toast } = useToast();
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
const { data, isLoading } = trpcReact.document.getDocumentById.useQuery({
id,
});
const documentData = document?.documentData;
const documentData = data?.documentData
? {
...data.documentData,
data: data.documentData.initialData,
}
: undefined;
const { mutateAsync: duplicateDocument, isLoading: isDuplicateLoading } =
trpcReact.document.duplicateDocument.useMutation({
@@ -73,7 +78,7 @@ export const DuplicateDocumentDialog = ({
</div>
) : (
<div className="p-2 [&>div]:h-[50vh] [&>div]:overflow-y-scroll ">
<LazyPDFViewer key={document?.id} documentData={documentData} />
<LazyPDFViewer key={data?.id} documentData={documentData} />
</div>
)}
@@ -1,6 +1,6 @@
import Link from 'next/link';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { getStats } from '@documenso/lib/server-only/document/get-stats';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
@@ -8,8 +8,10 @@ import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-documen
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
import type { PeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
import { isPeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
import {
PeriodSelectorValue,
isPeriodSelectorValue,
} from '~/components/(dashboard)/period-selector/types';
import { DocumentStatus } from '~/components/formatter/document-status';
import { DocumentsDataTable } from './data-table';
@@ -6,7 +6,6 @@ import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
@@ -24,8 +23,6 @@ export type UploadDocumentProps = {
export const UploadDocument = ({ className }: UploadDocumentProps) => {
const router = useRouter();
const { data: session } = useSession();
const { toast } = useToast();
const { quota, remaining } = useLimits();
@@ -82,7 +79,7 @@ export const UploadDocument = ({ className }: UploadDocumentProps) => {
<div className={cn('relative', className)}>
<DocumentDropzone
className="min-h-[40vh]"
disabled={remaining.documents === 0 || !session?.user.emailVerified}
disabled={remaining.documents === 0}
onDrop={onFileDrop}
/>
+1 -5
View File
@@ -6,11 +6,9 @@ import { getServerSession } from 'next-auth';
import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/server';
import { NEXT_AUTH_OPTIONS } from '@documenso/lib/next-auth/auth-options';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { CommandMenu } from '~/components/(dashboard)/common/command-menu';
import { Header } from '~/components/(dashboard)/layout/header';
import { VerifyEmailBanner } from '~/components/(dashboard)/layout/verify-email-banner';
import { RefreshOnFocus } from '~/components/(dashboard)/refresh-on-focus/refresh-on-focus';
import { NextAuthProvider } from '~/providers/next-auth';
@@ -32,8 +30,6 @@ export default async function AuthenticatedDashboardLayout({
return (
<NextAuthProvider session={session}>
<LimitsProvider>
{!user.emailVerified && <VerifyEmailBanner email={user.email} />}
<CommandMenu />
<Header user={user} />
<main className="mt-8 pb-8 md:mt-12 md:pb-12">{children}</main>
@@ -5,14 +5,16 @@ import {
getStripeCustomerById,
} from '@documenso/ee/server-only/stripe/get-customer';
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { stripe } from '@documenso/lib/server-only/stripe';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
export const createBillingPortal = async () => {
const { user } = await getRequiredServerComponentSession();
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
let stripeCustomer: Stripe.Customer | null = null;
@@ -44,6 +46,6 @@ export const createBillingPortal = async () => {
return getPortalSession({
customerId: stripeCustomer.id,
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
});
};
@@ -7,9 +7,10 @@ import {
getStripeCustomerById,
} from '@documenso/ee/server-only/stripe/get-customer';
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { Stripe } from '@documenso/lib/server-only/stripe';
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
export type CreateCheckoutOptions = {
priceId: string;
@@ -18,6 +19,8 @@ export type CreateCheckoutOptions = {
export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
const { user } = await getRequiredServerComponentSession();
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
let stripeCustomer: Stripe.Customer | null = null;
@@ -32,7 +35,7 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
return getPortalSession({
customerId: stripeCustomer.id,
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
});
}
@@ -53,6 +56,6 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
return getCheckoutSession({
customerId: stripeCustomer.id,
priceId,
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
});
};
@@ -4,9 +4,9 @@ import { match } from 'ts-pattern';
import { getPricesByInterval } from '@documenso/ee/server-only/stripe/get-prices-by-interval';
import { getProductByPriceId } from '@documenso/ee/server-only/stripe/get-product-by-price-id';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { Stripe } from '@documenso/lib/server-only/stripe';
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
import { LocaleDate } from '~/components/formatter/locale-date';
@@ -41,7 +41,7 @@ export default async function BillingSettingsPage() {
return (
<div>
<h3 className="text-2xl font-semibold">Billing</h3>
<h3 className="text-lg font-medium">Billing</h3>
<div className="text-muted-foreground mt-2 text-sm">
{isMissingOrInactiveOrFreePlan && (
@@ -1,5 +1,19 @@
import { redirect } from 'next/navigation';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
export default function PasswordSettingsPage() {
redirect('/settings/security');
import { PasswordForm } from '~/components/forms/password';
export default async function PasswordSettingsPage() {
const { user } = await getRequiredServerComponentSession();
return (
<div>
<h3 className="text-lg font-medium">Password</h3>
<p className="text-muted-foreground mt-2 text-sm">Here you can update your password.</p>
<hr className="my-4" />
<PasswordForm user={user} className="max-w-xl" />
</div>
);
}
@@ -1,4 +1,4 @@
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { ProfileForm } from '~/components/forms/profile';
@@ -7,7 +7,7 @@ export default async function ProfileSettingsPage() {
return (
<div>
<h3 className="text-2xl font-semibold">Profile</h3>
<h3 className="text-lg font-medium">Profile</h3>
<p className="text-muted-foreground mt-2 text-sm">Here you can edit your personal details.</p>
@@ -1,46 +0,0 @@
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { AuthenticatorApp } from '~/components/forms/2fa/authenticator-app';
import { RecoveryCodes } from '~/components/forms/2fa/recovery-codes';
import { PasswordForm } from '~/components/forms/password';
export default async function SecuritySettingsPage() {
const { user } = await getRequiredServerComponentSession();
return (
<div>
<h3 className="text-2xl font-semibold">Security</h3>
<p className="text-muted-foreground mt-2 text-sm">
Here you can manage your password and security settings.
</p>
<hr className="my-4" />
<PasswordForm user={user} className="max-w-xl" />
<hr className="mb-4 mt-8" />
<h4 className="text-lg font-medium">Two Factor Authentication</h4>
<p className="text-muted-foreground mt-2 text-sm">
Add and manage your two factor security settings to add an extra layer of security to your
account!
</p>
<div className="mt-4 max-w-xl">
<h5 className="font-medium">Two-factor methods</h5>
<AuthenticatorApp isTwoFactorEnabled={user.twoFactorEnabled} />
</div>
{user.twoFactorEnabled && (
<div className="mt-4 max-w-xl">
<h5 className="font-medium">Recovery methods</h5>
<RecoveryCodes isTwoFactorEnabled={user.twoFactorEnabled} />
</div>
)}
</div>
);
}
@@ -2,7 +2,8 @@ import { Metadata } from 'next';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { APP_BASE_URL } from '@documenso/lib/constants/app';
import { appBaseUrl } from '@documenso/lib/constants/app';
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
type SharePageProps = {
params: { slug: string };
@@ -16,12 +17,12 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
title: 'Documenso - Join the open source signing revolution',
description: 'I just signed with Documenso!',
type: 'website',
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
images: [`${appBaseUrl()}/share/${slug}/opengraph`],
},
twitter: {
site: '@documenso',
card: 'summary_large_image',
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
images: [`${appBaseUrl()}/share/${slug}/opengraph`],
description: 'I just signed with Documenso!',
},
} satisfies Metadata;
@@ -30,10 +31,12 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
export default function SharePage() {
const userAgent = headers().get('User-Agent') ?? '';
const { NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
// https://stackoverflow.com/questions/47026171/how-to-detect-bots-for-open-graph-with-user-agent
if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) {
return null;
}
redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
redirect(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
}
@@ -6,13 +6,8 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import {
DEFAULT_DOCUMENT_DATE_FORMAT,
convertToLocalSystemFormat,
} from '@documenso/lib/constants/date-formats';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { Recipient } from '@documenso/prisma/client';
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -21,16 +16,9 @@ import { SigningFieldContainer } from './signing-field-container';
export type DateFieldProps = {
field: FieldWithSignature;
recipient: Recipient;
dateFormat?: string | null;
timezone?: string | null;
};
export const DateField = ({
field,
recipient,
dateFormat = DEFAULT_DOCUMENT_DATE_FORMAT,
timezone = DEFAULT_DOCUMENT_TIME_ZONE,
}: DateFieldProps) => {
export const DateField = ({ field, recipient }: DateFieldProps) => {
const router = useRouter();
const { toast } = useToast();
@@ -47,18 +35,12 @@ export const DateField = ({
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
const localDateString = convertToLocalSystemFormat(field.customText, dateFormat, timezone);
const isDifferentTimeZone = field.inserted && localDateString !== field.customText;
const tooltipText = `"${field.customText}" will appear on the document as it has a timezone of "${timezone}".`;
const onSign = async () => {
try {
await signFieldWithToken({
token: recipient.token,
fieldId: field.id,
value: dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
value: '',
});
startTransition(() => router.refresh());
@@ -93,13 +75,7 @@ export const DateField = ({
};
return (
<SigningFieldContainer
field={field}
onSign={onSign}
onRemove={onRemove}
type="Date"
tooltipText={isDifferentTimeZone ? tooltipText : undefined}
>
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove}>
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
@@ -111,7 +87,7 @@ export const DateField = ({
)}
{field.inserted && (
<p className="text-muted-foreground text-sm duration-200">{localDateString}</p>
<p className="text-muted-foreground text-sm duration-200">{field.customText}</p>
)}
</SigningFieldContainer>
);
@@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { Recipient } from '@documenso/prisma/client';
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -79,7 +79,7 @@ export const EmailField = ({ field, recipient }: EmailFieldProps) => {
};
return (
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Email">
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove}>
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
@@ -9,7 +9,7 @@ import { useForm } from 'react-hook-form';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
import type { Document, Field, Recipient } from '@documenso/prisma/client';
import { Document, Field, Recipient } from '@documenso/prisma/client';
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@@ -19,7 +19,6 @@ import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { useRequiredSigningContext } from './provider';
import { SignDialog } from './sign-dialog';
export type SigningFormProps = {
document: Document;
@@ -46,7 +45,6 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
const onFormSubmit = async () => {
setValidateUninsertedFields(true);
const isFieldsValid = validateFieldsInserted(fields);
if (!isFieldsValid) {
@@ -82,11 +80,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
disabled={isSubmitting}
className={cn('-mx-2 flex flex-1 flex-col overflow-hidden px-2')}
>
<div
className={cn(
'custom-scrollbar -mx-2 flex flex-1 flex-col overflow-y-auto overflow-x-hidden px-2',
)}
>
<div className={cn('flex flex-1 flex-col')}>
<h3 className="text-foreground text-2xl font-semibold">Sign Document</h3>
<p className="text-muted-foreground mt-2 text-sm">
@@ -138,12 +132,9 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
Cancel
</Button>
<SignDialog
isSubmitting={isSubmitting}
onSignatureComplete={handleSubmit(onFormSubmit)}
document={document}
fields={fields}
/>
<Button className="w-full" type="submit" size="lg" loading={isSubmitting}>
Complete
</Button>
</div>
</div>
</div>
@@ -1,6 +1,6 @@
import React from 'react';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
import { NextAuthProvider } from '~/providers/next-auth';
@@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { Recipient } from '@documenso/prisma/client';
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
@@ -98,7 +98,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
};
return (
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Name">
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove}>
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
@@ -2,12 +2,9 @@ import { notFound, redirect } from 'next/navigation';
import { match } from 'ts-pattern';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getDocumentMetaByDocumentId } from '@documenso/lib/server-only/document/get-document-meta-by-document-id';
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
@@ -43,8 +40,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
viewedDocument({ token }).catch(() => null),
]);
const documentMeta = await getDocumentMetaByDocumentId({ id: document!.id }).catch(() => null);
if (!document || !document.documentData || !recipient) {
return notFound();
}
@@ -102,13 +97,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
<NameField key={field.id} field={field} recipient={recipient} />
))
.with(FieldType.DATE, () => (
<DateField
key={field.id}
field={field}
recipient={recipient}
dateFormat={documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT}
timezone={documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE}
/>
<DateField key={field.id} field={field} recipient={recipient} />
))
.with(FieldType.EMAIL, () => (
<EmailField key={field.id} field={field} recipient={recipient} />
@@ -1,77 +0,0 @@
import { useState } from 'react';
import { Document, Field } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
export type SignDialogProps = {
isSubmitting: boolean;
document: Document;
fields: Field[];
onSignatureComplete: () => void | Promise<void>;
};
export const SignDialog = ({
isSubmitting,
document,
fields,
onSignatureComplete,
}: SignDialogProps) => {
const [showDialog, setShowDialog] = useState(false);
const isComplete = fields.every((field) => field.inserted);
return (
<Dialog open={showDialog} onOpenChange={setShowDialog}>
<DialogTrigger asChild>
<Button
className="w-full"
type="button"
size="lg"
disabled={!isComplete}
loading={isSubmitting}
>
Complete
</Button>
</DialogTrigger>
<DialogContent>
<div className="text-center">
<div className="text-xl font-semibold text-neutral-800">Sign Document</div>
<div className="text-muted-foreground mx-auto w-4/5 py-2 text-center">
You are about to finish signing "{document.title}". Are you sure?
</div>
</div>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
onClick={() => {
setShowDialog(false);
}}
>
Cancel
</Button>
<Button
type="button"
className="flex-1"
disabled={!isComplete}
loading={isSubmitting}
onClick={onSignatureComplete}
>
Sign
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
@@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { Recipient } from '@documenso/prisma/client';
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
@@ -121,7 +121,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
};
return (
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Signature">
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove}>
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
@@ -2,9 +2,8 @@
import React from 'react';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { FieldRootContainer } from '@documenso/ui/components/field/field';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
export type SignatureFieldProps = {
field: FieldWithSignature;
@@ -12,8 +11,6 @@ export type SignatureFieldProps = {
children: React.ReactNode;
onSign?: () => Promise<void> | void;
onRemove?: () => Promise<void> | void;
type?: 'Date' | 'Email' | 'Name' | 'Signature';
tooltipText?: string | null;
};
export const SigningFieldContainer = ({
@@ -22,8 +19,6 @@ export const SigningFieldContainer = ({
onSign,
onRemove,
children,
type,
tooltipText,
}: SignatureFieldProps) => {
const onSignFieldClick = async () => {
if (field.inserted) {
@@ -51,22 +46,7 @@ export const SigningFieldContainer = ({
/>
)}
{type === 'Date' && field.inserted && !loading && (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<button
className="text-destructive bg-background/40 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 backdrop-blur-sm duration-200 group-hover:opacity-100"
onClick={onRemoveSignedFieldClick}
>
Remove
</button>
</TooltipTrigger>
{tooltipText && <TooltipContent className="max-w-xs">{tooltipText}</TooltipContent>}
</Tooltip>
)}
{type !== 'Date' && field.inserted && !loading && (
{field.inserted && !loading && (
<button
className="text-destructive bg-background/40 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 backdrop-blur-sm duration-200 group-hover:opacity-100"
onClick={onRemoveSignedFieldClick}
@@ -1,97 +0,0 @@
import Link from 'next/link';
import { AlertTriangle, CheckCircle2, XCircle, XOctagon } from 'lucide-react';
import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
import { Button } from '@documenso/ui/primitives/button';
export type PageProps = {
params: {
token: string;
};
};
export default async function VerifyEmailPage({ params: { token } }: PageProps) {
if (!token) {
return (
<div className="w-full">
<div className="mb-4 text-red-300">
<XOctagon />
</div>
<h2 className="text-4xl font-semibold">No token provided</h2>
<p className="text-muted-foreground mt-2 text-base">
It seems that there is no token provided. Please check your email and try again.
</p>
</div>
);
}
const verified = await verifyEmail({ token });
if (verified === null) {
return (
<div className="flex w-full items-start">
<div className="mr-4 mt-1 hidden md:block">
<AlertTriangle className="h-10 w-10 text-yellow-500" strokeWidth={2} />
</div>
<div>
<h2 className="text-2xl font-bold md:text-4xl">Something went wrong</h2>
<p className="text-muted-foreground mt-4">
We were unable to verify your email. If your email is not verified already, please try
again.
</p>
<Button className="mt-4" asChild>
<Link href="/">Go back home</Link>
</Button>
</div>
</div>
);
}
if (!verified) {
return (
<div className="flex w-full items-start">
<div className="mr-4 mt-1 hidden md:block">
<XCircle className="text-destructive h-10 w-10" strokeWidth={2} />
</div>
<div>
<h2 className="text-2xl font-bold md:text-4xl">Your token has expired!</h2>
<p className="text-muted-foreground mt-4">
It seems that the provided token has expired. We've just sent you another token, please
check your email and try again.
</p>
<Button className="mt-4" asChild>
<Link href="/">Go back home</Link>
</Button>
</div>
</div>
);
}
return (
<div className="flex w-full items-start">
<div className="mr-4 mt-1 hidden md:block">
<CheckCircle2 className="h-10 w-10 text-green-500" strokeWidth={2} />
</div>
<div>
<h2 className="text-2xl font-bold md:text-4xl">Email Confirmed!</h2>
<p className="text-muted-foreground mt-4">
Your email has been successfully confirmed! You can now use all features of Documenso.
</p>
<Button className="mt-4" asChild>
<Link href="/">Go back home</Link>
</Button>
</div>
</div>
);
}
@@ -1,28 +0,0 @@
import Link from 'next/link';
import { XCircle } from 'lucide-react';
import { Button } from '@documenso/ui/primitives/button';
export default function EmailVerificationWithoutTokenPage() {
return (
<div className="flex w-full items-start">
<div className="mr-4 mt-1 hidden md:block">
<XCircle className="text-destructive h-10 w-10" strokeWidth={2} />
</div>
<div>
<h2 className="text-2xl font-bold md:text-4xl">Uh oh! Looks like you're missing a token</h2>
<p className="text-muted-foreground mt-4">
It seems that there is no token provided, if you are trying to verify your email please
follow the link in your email.
</p>
<Button className="mt-4" asChild>
<Link href="/">Go back home</Link>
</Button>
</div>
</div>
);
}
+43 -31
View File
@@ -6,6 +6,8 @@ import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/featur
import { LocaleProvider } from '@documenso/lib/client-only/providers/locale';
import { getServerComponentAllFlags } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
import { getLocale } from '@documenso/lib/server-only/headers/get-locale';
import { RuntimeEnvProvider } from '@documenso/lib/universal/runtime-env';
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
import { TrpcProvider } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Toaster } from '@documenso/ui/primitives/toaster';
@@ -17,31 +19,39 @@ import { PostHogPageview } from '~/providers/posthog';
import './globals.css';
export const dynamic = 'force-dynamic';
const fontInter = Inter({ subsets: ['latin'], variable: '--font-sans' });
const fontCaveat = Caveat({ subsets: ['latin'], variable: '--font-signature' });
export const metadata = {
title: 'Documenso - The Open Source DocuSign Alternative',
description:
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
keywords:
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
authors: { name: 'Documenso, Inc.' },
robots: 'index, follow',
openGraph: {
// We do this so NEXT_PUBLIC variables will be evaluated at runtime.
// eslint-disable-next-line @typescript-eslint/require-await
export const generateMetadata = async () => {
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
return {
title: 'Documenso - The Open Source DocuSign Alternative',
description:
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
type: 'website',
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
},
twitter: {
site: '@documenso',
card: 'summary_large_image',
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
description:
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
},
keywords:
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
authors: { name: 'Documenso, Inc.' },
robots: 'index, follow',
openGraph: {
title: 'Documenso - The Open Source DocuSign Alternative',
description:
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
type: 'website',
images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
},
twitter: {
site: '@documenso',
card: 'summary_large_image',
images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
description:
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.',
},
};
};
export default async function RootLayout({ children }: { children: React.ReactNode }) {
@@ -67,19 +77,21 @@ export default async function RootLayout({ children }: { children: React.ReactNo
</Suspense>
<body>
<LocaleProvider locale={locale}>
<FeatureFlagProvider initialFlags={flags}>
<PlausibleProvider>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<TooltipProvider>
<TrpcProvider>{children}</TrpcProvider>
</TooltipProvider>
</ThemeProvider>
</PlausibleProvider>
<RuntimeEnvProvider>
<LocaleProvider locale={locale}>
<FeatureFlagProvider initialFlags={flags}>
<PlausibleProvider>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<TooltipProvider>
<TrpcProvider>{children}</TrpcProvider>
</TooltipProvider>
</ThemeProvider>
</PlausibleProvider>
<Toaster />
</FeatureFlagProvider>
</LocaleProvider>
<Toaster />
</FeatureFlagProvider>
</LocaleProvider>
</RuntimeEnvProvider>
</body>
</html>
);
+1 -1
View File
@@ -1,6 +1,6 @@
import Link from 'next/link';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { Button } from '@documenso/ui/primitives/button';
import NotFoundPartial from '~/components/partials/not-found';
@@ -40,22 +40,61 @@ const SETTINGS_PAGES = [
{ label: 'Password', path: '/settings/password' },
];
export function CommandMenu() {
export type CommandMenuProps = {
open?: boolean;
onOpenChange?: (_open: boolean) => void;
};
export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
const { setTheme } = useTheme();
const { push } = useRouter();
const [open, setOpen] = useState(false);
const router = useRouter();
const [isOpen, setIsOpen] = useState(() => open ?? false);
const [search, setSearch] = useState('');
const [pages, setPages] = useState<string[]>([]);
const currentPage = pages[pages.length - 1];
const toggleOpen = () => {
setOpen((open) => !open);
setIsOpen((isOpen) => !isOpen);
onOpenChange?.(!isOpen);
if (isOpen) {
setPages([]);
setSearch('');
}
};
const setOpen = useCallback(
(open: boolean) => {
setIsOpen(open);
onOpenChange?.(open);
if (!open) {
setPages([]);
setSearch('');
}
},
[onOpenChange],
);
const push = useCallback(
(path: string) => {
router.push(path);
setOpen(false);
},
[router, setOpen],
);
const addPage = (page: string) => {
setPages((pages) => [...pages, page]);
setSearch('');
};
const goToSettings = useCallback(() => push(SETTINGS_PAGES[0].path), [push]);
const goToDocuments = useCallback(() => push(DOCUMENTS_PAGES[0].path), [push]);
useHotkeys('ctrl+k', toggleOpen);
useHotkeys(['ctrl+k', 'meta+k'], toggleOpen);
useHotkeys(SETTINGS_PAGE_SHORTCUT, goToSettings);
useHotkeys(DOCUMENTS_PAGE_SHORTCUT, goToDocuments);
@@ -64,9 +103,11 @@ export function CommandMenu() {
// Backspace goes to previous page when search is empty
if (e.key === 'Escape' || (e.key === 'Backspace' && !search)) {
e.preventDefault();
if (currentPage === undefined) {
setOpen(false);
}
setPages((pages) => pages.slice(0, -1));
}
};
@@ -78,6 +119,7 @@ export function CommandMenu() {
onValueChange={setSearch}
placeholder="Type a command or search..."
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{!currentPage && (
@@ -89,7 +131,7 @@ export function CommandMenu() {
<Commands push={push} pages={SETTINGS_PAGES} />
</CommandGroup>
<CommandGroup heading="Preferences">
<CommandItem onSelect={() => setPages([...pages, 'theme'])}>Change theme</CommandItem>
<CommandItem onSelect={() => addPage('theme')}>Change theme</CommandItem>
</CommandGroup>
</>
)}
@@ -1,16 +1,44 @@
'use client';
import { HTMLAttributes } from 'react';
import { HTMLAttributes, useState } from 'react';
import { Search } from 'lucide-react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { CommandMenu } from '../common/command-menu';
export type DesktopNavProps = HTMLAttributes<HTMLDivElement>;
export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
// const pathname = usePathname();
const [open, setOpen] = useState(false);
return (
<div className={cn('ml-8 hidden flex-1 gap-x-6 md:flex', className)} {...props}>
<div
className={cn('ml-8 hidden flex-1 gap-x-6 md:flex md:justify-center', className)}
{...props}
>
<CommandMenu open={open} onOpenChange={setOpen} />
<Button
variant="outline"
className="text-muted-foreground flex w-96 items-center justify-between rounded-lg"
onClick={() => setOpen((open) => !open)}
>
<div className="flex items-center">
<Search className="mr-2 h-5 w-5" />
Search
</div>
<div>
<div className="text-muted-foreground bg-muted rounded-md px-1.5 py-0.5 font-mono text-xs">
Ctrl+K
</div>
</div>
</Button>
{/* We have no other subpaths rn */}
{/* <Link
href="/documents"
@@ -4,7 +4,7 @@ import Link from 'next/link';
import {
CreditCard,
Lock,
Key,
LogOut,
User as LucideUser,
Monitor,
@@ -87,9 +87,9 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/settings/security" className="cursor-pointer">
<Lock className="mr-2 h-4 w-4" />
Security
<Link href="/settings/password" className="cursor-pointer">
<Key className="mr-2 h-4 w-4" />
Password
</Link>
</DropdownMenuItem>
@@ -1,123 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import { AlertTriangle } from 'lucide-react';
import { ONE_SECOND } from '@documenso/lib/constants/time';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type VerifyEmailBannerProps = {
email: string;
};
const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND;
export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
const { toast } = useToast();
const [isOpen, setIsOpen] = useState(false);
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
const { mutateAsync: sendConfirmationEmail, isLoading } =
trpc.profile.sendConfirmationEmail.useMutation();
const onResendConfirmationEmail = async () => {
try {
setIsButtonDisabled(true);
await sendConfirmationEmail({ email: email });
toast({
title: 'Success',
description: 'Verification email sent successfully.',
});
setIsOpen(false);
setTimeout(() => setIsButtonDisabled(false), RESEND_CONFIRMATION_EMAIL_TIMEOUT);
} catch (err) {
setIsButtonDisabled(false);
toast({
title: 'Error',
description: 'Something went wrong while sending the confirmation email.',
variant: 'destructive',
});
}
};
useEffect(() => {
// Check localStorage to see if we've recently automatically displayed the dialog
// if it was within the past 24 hours, don't show it again
// otherwise, show it again and update the localStorage timestamp
const emailVerificationDialogLastShown = localStorage.getItem(
'emailVerificationDialogLastShown',
);
if (emailVerificationDialogLastShown) {
const lastShownTimestamp = parseInt(emailVerificationDialogLastShown);
if (Date.now() - lastShownTimestamp < 24 * 60 * 60 * 1000) {
return;
}
}
setIsOpen(true);
localStorage.setItem('emailVerificationDialogLastShown', Date.now().toString());
}, []);
return (
<>
<div className="bg-yellow-200 dark:bg-yellow-400">
<div className="mx-auto flex max-w-screen-xl items-center justify-center gap-x-4 px-4 py-2 text-sm font-medium text-yellow-900">
<div className="flex items-center">
<AlertTriangle className="mr-2.5 h-5 w-5" />
Verify your email address to unlock all features.
</div>
<div>
<Button
variant="ghost"
className="h-auto px-2.5 py-1.5 text-yellow-900 hover:bg-yellow-100 hover:text-yellow-900 dark:hover:bg-yellow-500"
disabled={isButtonDisabled}
onClick={() => setIsOpen(true)}
size="sm"
>
{isButtonDisabled ? 'Verification Email Sent' : 'Verify Now'}
</Button>
</div>
</div>
</div>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
<DialogTitle>Verify your email address</DialogTitle>
<DialogDescription>
We've sent a confirmation email to <strong>{email}</strong>. Please check your inbox and
click the link in the email to verify your account.
</DialogDescription>
<div>
<Button
disabled={isButtonDisabled}
loading={isLoading}
onClick={onResendConfirmationEmail}
>
{isLoading ? 'Sending...' : 'Resend Confirmation Email'}
</Button>
</div>
</DialogContent>
</Dialog>
</>
);
};
@@ -5,7 +5,7 @@ import { HTMLAttributes } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { CreditCard, Lock, User } from 'lucide-react';
import { CreditCard, Key, User } from 'lucide-react';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -35,16 +35,16 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
</Button>
</Link>
<Link href="/settings/security">
<Link href="/settings/password">
<Button
variant="ghost"
className={cn(
'w-full justify-start',
pathname?.startsWith('/settings/security') && 'bg-secondary',
pathname?.startsWith('/settings/password') && 'bg-secondary',
)}
>
<Lock className="mr-2 h-5 w-5" />
Security
<Key className="mr-2 h-5 w-5" />
Password
</Button>
</Link>
@@ -5,7 +5,7 @@ import { HTMLAttributes } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { CreditCard, Lock, User } from 'lucide-react';
import { CreditCard, Key, User } from 'lucide-react';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -38,16 +38,16 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
</Button>
</Link>
<Link href="/settings/security">
<Link href="/settings/password">
<Button
variant="ghost"
className={cn(
'w-full justify-start',
pathname?.startsWith('/settings/security') && 'bg-secondary',
pathname?.startsWith('/settings/password') && 'bg-secondary',
)}
>
<Lock className="mr-2 h-5 w-5" />
Security
<Key className="mr-2 h-5 w-5" />
Password
</Button>
</Link>
@@ -1,9 +1,9 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import { CheckCircle2, Clock, File } from 'lucide-react';
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { SignatureIcon } from '@documenso/ui/icons/signature';
import { cn } from '@documenso/ui/lib/utils';
@@ -1,10 +1,8 @@
'use client';
import type { HTMLAttributes } from 'react';
import { useEffect, useState } from 'react';
import { HTMLAttributes, useEffect, useState } from 'react';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { DateTime, DateTimeFormatOptions } from 'luxon';
import { useLocale } from '@documenso/lib/client-only/providers/locale';
@@ -1,58 +0,0 @@
'use client';
import { useState } from 'react';
import { Button } from '@documenso/ui/primitives/button';
import { DisableAuthenticatorAppDialog } from './disable-authenticator-app-dialog';
import { EnableAuthenticatorAppDialog } from './enable-authenticator-app-dialog';
type AuthenticatorAppProps = {
isTwoFactorEnabled: boolean;
};
export const AuthenticatorApp = ({ isTwoFactorEnabled }: AuthenticatorAppProps) => {
const [modalState, setModalState] = useState<'enable' | 'disable' | null>(null);
const isEnableDialogOpen = modalState === 'enable';
const isDisableDialogOpen = modalState === 'disable';
return (
<>
<div className="mt-4 flex flex-col justify-between gap-4 rounded-lg border p-4 md:flex-row md:items-center md:gap-8">
<div className="flex-1">
<p>Authenticator app</p>
<p className="text-muted-foreground mt-2 max-w-[50ch] text-sm">
Create one-time passwords that serve as a secondary authentication method for confirming
your identity when requested during the sign-in process.
</p>
</div>
<div>
{isTwoFactorEnabled ? (
<Button variant="destructive" onClick={() => setModalState('disable')} size="sm">
Disable 2FA
</Button>
) : (
<Button onClick={() => setModalState('enable')} size="sm">
Enable 2FA
</Button>
)}
</div>
</div>
<EnableAuthenticatorAppDialog
key={isEnableDialogOpen ? 'open' : 'closed'}
open={isEnableDialogOpen}
onOpenChange={(open) => !open && setModalState(null)}
/>
<DisableAuthenticatorAppDialog
key={isDisableDialogOpen ? 'open' : 'closed'}
open={isDisableDialogOpen}
onOpenChange={(open) => !open && setModalState(null)}
/>
</>
);
};
@@ -1,161 +0,0 @@
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { flushSync } from 'react-dom';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZDisableTwoFactorAuthenticationForm = z.object({
password: z.string().min(6).max(72),
backupCode: z.string(),
});
export type TDisableTwoFactorAuthenticationForm = z.infer<
typeof ZDisableTwoFactorAuthenticationForm
>;
export type DisableAuthenticatorAppDialogProps = {
open: boolean;
onOpenChange: (_open: boolean) => void;
};
export const DisableAuthenticatorAppDialog = ({
open,
onOpenChange,
}: DisableAuthenticatorAppDialogProps) => {
const router = useRouter();
const { toast } = useToast();
const { mutateAsync: disableTwoFactorAuthentication } =
trpc.twoFactorAuthentication.disable.useMutation();
const disableTwoFactorAuthenticationForm = useForm<TDisableTwoFactorAuthenticationForm>({
defaultValues: {
password: '',
backupCode: '',
},
resolver: zodResolver(ZDisableTwoFactorAuthenticationForm),
});
const { isSubmitting: isDisableTwoFactorAuthenticationSubmitting } =
disableTwoFactorAuthenticationForm.formState;
const onDisableTwoFactorAuthenticationFormSubmit = async ({
password,
backupCode,
}: TDisableTwoFactorAuthenticationForm) => {
try {
await disableTwoFactorAuthentication({ password, backupCode });
toast({
title: 'Two-factor authentication disabled',
description:
'Two-factor authentication has been disabled for your account. You will no longer be required to enter a code from your authenticator app when signing in.',
});
flushSync(() => {
onOpenChange(false);
});
router.refresh();
} catch (_err) {
toast({
title: 'Unable to disable two-factor authentication',
description:
'We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again.',
variant: 'destructive',
});
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">
<DialogHeader>
<DialogTitle>Disable Authenticator App</DialogTitle>
<DialogDescription>
To disable the Authenticator App for your account, please enter your password and a
backup code. If you do not have a backup code available, please contact support.
</DialogDescription>
</DialogHeader>
<Form {...disableTwoFactorAuthenticationForm}>
<form
onSubmit={disableTwoFactorAuthenticationForm.handleSubmit(
onDisableTwoFactorAuthenticationFormSubmit,
)}
className="flex flex-col gap-y-4"
>
<FormField
name="password"
control={disableTwoFactorAuthenticationForm.control}
render={({ field }) => (
<FormItem>
<FormLabel className="text-muted-foreground">Password</FormLabel>
<FormControl>
<Input
{...field}
type="password"
autoComplete="current-password"
value={field.value ?? ''}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="backupCode"
control={disableTwoFactorAuthenticationForm.control}
render={({ field }) => (
<FormItem>
<FormLabel className="text-muted-foreground">Backup Code</FormLabel>
<FormControl>
<Input {...field} type="text" value={field.value ?? ''} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex w-full items-center justify-between">
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button
type="submit"
variant="destructive"
loading={isDisableTwoFactorAuthenticationSubmitting}
>
Disable 2FA
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
@@ -1,283 +0,0 @@
import { useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { flushSync } from 'react-dom';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { renderSVG } from 'uqr';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { RecoveryCodeList } from './recovery-code-list';
export const ZSetupTwoFactorAuthenticationForm = z.object({
password: z.string().min(6).max(72),
});
export type TSetupTwoFactorAuthenticationForm = z.infer<typeof ZSetupTwoFactorAuthenticationForm>;
export const ZEnableTwoFactorAuthenticationForm = z.object({
token: z.string(),
});
export type TEnableTwoFactorAuthenticationForm = z.infer<typeof ZEnableTwoFactorAuthenticationForm>;
export type EnableAuthenticatorAppDialogProps = {
open: boolean;
onOpenChange: (_open: boolean) => void;
};
export const EnableAuthenticatorAppDialog = ({
open,
onOpenChange,
}: EnableAuthenticatorAppDialogProps) => {
const router = useRouter();
const { toast } = useToast();
const { mutateAsync: setupTwoFactorAuthentication, data: setupTwoFactorAuthenticationData } =
trpc.twoFactorAuthentication.setup.useMutation();
const { mutateAsync: enableTwoFactorAuthentication, data: enableTwoFactorAuthenticationData } =
trpc.twoFactorAuthentication.enable.useMutation();
const setupTwoFactorAuthenticationForm = useForm<TSetupTwoFactorAuthenticationForm>({
defaultValues: {
password: '',
},
resolver: zodResolver(ZSetupTwoFactorAuthenticationForm),
});
const { isSubmitting: isSetupTwoFactorAuthenticationSubmitting } =
setupTwoFactorAuthenticationForm.formState;
const enableTwoFactorAuthenticationForm = useForm<TEnableTwoFactorAuthenticationForm>({
defaultValues: {
token: '',
},
resolver: zodResolver(ZEnableTwoFactorAuthenticationForm),
});
const { isSubmitting: isEnableTwoFactorAuthenticationSubmitting } =
enableTwoFactorAuthenticationForm.formState;
const step = useMemo(() => {
if (!setupTwoFactorAuthenticationData || isSetupTwoFactorAuthenticationSubmitting) {
return 'setup';
}
if (!enableTwoFactorAuthenticationData || isEnableTwoFactorAuthenticationSubmitting) {
return 'enable';
}
return 'view';
}, [
setupTwoFactorAuthenticationData,
isSetupTwoFactorAuthenticationSubmitting,
enableTwoFactorAuthenticationData,
isEnableTwoFactorAuthenticationSubmitting,
]);
const onSetupTwoFactorAuthenticationFormSubmit = async ({
password,
}: TSetupTwoFactorAuthenticationForm) => {
try {
await setupTwoFactorAuthentication({ password });
} catch (_err) {
toast({
title: 'Unable to setup two-factor authentication',
description:
'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.',
variant: 'destructive',
});
}
};
const onEnableTwoFactorAuthenticationFormSubmit = async ({
token,
}: TEnableTwoFactorAuthenticationForm) => {
try {
await enableTwoFactorAuthentication({ code: token });
toast({
title: 'Two-factor authentication enabled',
description:
'Two-factor authentication has been enabled for your account. You will now be required to enter a code from your authenticator app when signing in.',
});
} catch (_err) {
toast({
title: 'Unable to setup two-factor authentication',
description:
'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.',
variant: 'destructive',
});
}
};
const onCompleteClick = () => {
flushSync(() => {
onOpenChange(false);
});
router.refresh();
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">
<DialogHeader>
<DialogTitle>Enable Authenticator App</DialogTitle>
{step === 'setup' && (
<DialogDescription>
To enable two-factor authentication, please enter your password below.
</DialogDescription>
)}
{step === 'view' && (
<DialogDescription>
Your recovery codes are listed below. Please store them in a safe place.
</DialogDescription>
)}
</DialogHeader>
{match(step)
.with('setup', () => {
return (
<Form {...setupTwoFactorAuthenticationForm}>
<form
onSubmit={setupTwoFactorAuthenticationForm.handleSubmit(
onSetupTwoFactorAuthenticationFormSubmit,
)}
className="flex flex-col gap-y-4"
>
<FormField
name="password"
control={setupTwoFactorAuthenticationForm.control}
render={({ field }) => (
<FormItem>
<FormLabel className="text-muted-foreground">Password</FormLabel>
<FormControl>
<Input
{...field}
type="password"
autoComplete="current-password"
value={field.value ?? ''}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex w-full items-center justify-between">
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type="submit" loading={isSetupTwoFactorAuthenticationSubmitting}>
Continue
</Button>
</div>
</form>
</Form>
);
})
.with('enable', () => (
<Form {...enableTwoFactorAuthenticationForm}>
<form
onSubmit={enableTwoFactorAuthenticationForm.handleSubmit(
onEnableTwoFactorAuthenticationFormSubmit,
)}
className="flex flex-col gap-y-4"
>
<p className="text-muted-foreground text-sm">
To enable two-factor authentication, scan the following QR code using your
authenticator app.
</p>
<div
className="flex h-36 justify-center"
dangerouslySetInnerHTML={{
__html: renderSVG(setupTwoFactorAuthenticationData?.uri ?? ''),
}}
/>
<p className="text-muted-foreground text-sm">
If your authenticator app does not support QR codes, you can use the following
code instead:
</p>
<p className="bg-muted/60 text-muted-foreground rounded-lg p-2 text-center font-mono tracking-widest">
{setupTwoFactorAuthenticationData?.secret}
</p>
<p className="text-muted-foreground text-sm">
Once you have scanned the QR code or entered the code manually, enter the code
provided by your authenticator app below.
</p>
<FormField
name="token"
control={enableTwoFactorAuthenticationForm.control}
render={({ field }) => (
<FormItem>
<FormLabel className="text-muted-foreground">Token</FormLabel>
<FormControl>
<Input {...field} type="text" value={field.value ?? ''} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex w-full items-center justify-between">
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type="submit" loading={isEnableTwoFactorAuthenticationSubmitting}>
Enable 2FA
</Button>
</div>
</form>
</Form>
))
.with('view', () => (
<div>
{enableTwoFactorAuthenticationData?.recoveryCodes && (
<RecoveryCodeList recoveryCodes={enableTwoFactorAuthenticationData.recoveryCodes} />
)}
<div className="mt-4 flex w-full flex-row-reverse items-center justify-between">
<Button type="button" onClick={() => onCompleteClick()}>
Complete
</Button>
</div>
</div>
))
.exhaustive()}
</DialogContent>
</Dialog>
);
};
@@ -1,57 +0,0 @@
import { Copy } from 'lucide-react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type RecoveryCodeListProps = {
recoveryCodes: string[];
};
export const RecoveryCodeList = ({ recoveryCodes }: RecoveryCodeListProps) => {
const { toast } = useToast();
const [, copyToClipboard] = useCopyToClipboard();
const onCopyRecoveryCodeClick = async (code: string) => {
try {
const result = await copyToClipboard(code);
if (!result) {
throw new Error('Unable to copy recovery code');
}
toast({
title: 'Recovery code copied',
description: 'Your recovery code has been copied to your clipboard.',
});
} catch (_err) {
toast({
title: 'Unable to copy recovery code',
description:
'We were unable to copy your recovery code to your clipboard. Please try again.',
variant: 'destructive',
});
}
};
return (
<div className="grid grid-cols-2 gap-4">
{recoveryCodes.map((code) => (
<div
key={code}
className="bg-muted text-muted-foreground relative rounded-lg p-4 font-mono md:text-center"
>
<span>{code}</span>
<div className="absolute inset-y-0 right-4 flex items-center justify-center">
<button
className="opacity-60 hover:opacity-80"
onClick={() => void onCopyRecoveryCodeClick(code)}
>
<Copy className="h-5 w-5" />
</button>
</div>
</div>
))}
</div>
);
};
@@ -1,43 +0,0 @@
'use client';
import { useState } from 'react';
import { Button } from '@documenso/ui/primitives/button';
import { ViewRecoveryCodesDialog } from './view-recovery-codes-dialog';
type RecoveryCodesProps = {
// backupCodes: string[] | null;
isTwoFactorEnabled: boolean;
};
export const RecoveryCodes = ({ isTwoFactorEnabled }: RecoveryCodesProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<div className="mt-4 flex flex-col justify-between gap-4 rounded-lg border p-4 md:flex-row md:items-center md:gap-8">
<div className="flex-1">
<p>Recovery Codes</p>
<p className="text-muted-foreground mt-2 max-w-[50ch] text-sm">
Recovery codes are used to access your account in the event that you lose access to your
authenticator app.
</p>
</div>
<div>
<Button onClick={() => setIsOpen(true)} disabled={!isTwoFactorEnabled} size="sm">
View Codes
</Button>
</div>
</div>
<ViewRecoveryCodesDialog
key={isOpen ? 'open' : 'closed'}
open={isOpen}
onOpenChange={setIsOpen}
/>
</>
);
};
@@ -1,151 +0,0 @@
import { useMemo } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { RecoveryCodeList } from './recovery-code-list';
export const ZViewRecoveryCodesForm = z.object({
password: z.string().min(6).max(72),
});
export type TViewRecoveryCodesForm = z.infer<typeof ZViewRecoveryCodesForm>;
export type ViewRecoveryCodesDialogProps = {
open: boolean;
onOpenChange: (_open: boolean) => void;
};
export const ViewRecoveryCodesDialog = ({ open, onOpenChange }: ViewRecoveryCodesDialogProps) => {
const { toast } = useToast();
const { mutateAsync: viewRecoveryCodes, data: viewRecoveryCodesData } =
trpc.twoFactorAuthentication.viewRecoveryCodes.useMutation();
const viewRecoveryCodesForm = useForm<TViewRecoveryCodesForm>({
defaultValues: {
password: '',
},
resolver: zodResolver(ZViewRecoveryCodesForm),
});
const { isSubmitting: isViewRecoveryCodesSubmitting } = viewRecoveryCodesForm.formState;
const step = useMemo(() => {
if (!viewRecoveryCodesData || isViewRecoveryCodesSubmitting) {
return 'authenticate';
}
return 'view';
}, [viewRecoveryCodesData, isViewRecoveryCodesSubmitting]);
const onViewRecoveryCodesFormSubmit = async ({ password }: TViewRecoveryCodesForm) => {
try {
await viewRecoveryCodes({ password });
} catch (_err) {
toast({
title: 'Unable to view recovery codes',
description:
'We were unable to view your recovery codes. Please ensure that you have entered your password correctly and try again.',
variant: 'destructive',
});
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">
<DialogHeader>
<DialogTitle>View Recovery Codes</DialogTitle>
{step === 'authenticate' && (
<DialogDescription>
To view your recovery codes, please enter your password below.
</DialogDescription>
)}
{step === 'view' && (
<DialogDescription>
Your recovery codes are listed below. Please store them in a safe place.
</DialogDescription>
)}
</DialogHeader>
{match(step)
.with('authenticate', () => {
return (
<Form {...viewRecoveryCodesForm}>
<form
onSubmit={viewRecoveryCodesForm.handleSubmit(onViewRecoveryCodesFormSubmit)}
className="flex flex-col gap-y-4"
>
<FormField
name="password"
control={viewRecoveryCodesForm.control}
render={({ field }) => (
<FormItem>
<FormLabel className="text-muted-foreground">Password</FormLabel>
<FormControl>
<Input
{...field}
type="password"
autoComplete="current-password"
value={field.value ?? ''}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex w-full items-center justify-between">
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type="submit" loading={isViewRecoveryCodesSubmitting}>
Continue
</Button>
</div>
</form>
</Form>
);
})
.with('view', () => (
<div>
{viewRecoveryCodesData?.recoveryCodes && (
<RecoveryCodeList recoveryCodes={viewRecoveryCodesData.recoveryCodes} />
)}
<div className="mt-4 flex flex-row-reverse items-center justify-between">
<Button onClick={() => onOpenChange(false)}>Complete</Button>
</div>
</div>
))
.exhaustive()}
</DialogContent>
</Dialog>
);
};
@@ -1,8 +1,8 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
export type AddFieldsActionInput = TAddFieldsFormSchema & {
documentId: number;
@@ -1,8 +1,8 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import type { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
import { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
export type AddSignersActionInput = TAddSignersFormSchema & {
documentId: number;
@@ -1,26 +1,24 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
documentId: number;
};
export const completeDocument = async ({ documentId, meta }: CompleteDocumentActionInput) => {
export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => {
'use server';
const { user } = await getRequiredServerComponentSession();
if (meta.message || meta.subject || meta.dateFormat || meta.timezone) {
if (email.message || email.subject) {
await upsertDocumentMeta({
documentId,
subject: meta.subject,
message: meta.message,
timezone: meta.timezone,
dateFormat: meta.dateFormat,
subject: email.subject,
message: email.message,
});
}
+3 -4
View File
@@ -10,7 +10,6 @@ import { z } from 'zod';
import { User } from '@documenso/prisma/client';
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
@@ -21,9 +20,9 @@ import { FormErrorMessage } from '../form/form-error-message';
export const ZPasswordFormSchema = z
.object({
currentPassword: ZCurrentPasswordSchema,
password: ZPasswordSchema,
repeatedPassword: ZPasswordSchema,
currentPassword: z.string().min(6).max(72),
password: z.string().min(6).max(72),
repeatedPassword: z.string().min(6).max(72),
})
.refine((data) => data.password === data.repeatedPassword, {
message: 'Passwords do not match',
@@ -11,7 +11,6 @@ import { z } from 'zod';
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
@@ -21,8 +20,8 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZResetPasswordFormSchema = z
.object({
password: ZPasswordSchema,
repeatedPassword: ZPasswordSchema,
password: z.string().min(6).max(72),
repeatedPassword: z.string().min(6).max(72),
})
.refine((data) => data.password === data.repeatedPassword, {
path: ['repeatedPassword'],
+33 -135
View File
@@ -3,39 +3,32 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Eye, EyeOff } from 'lucide-react';
import { signIn } from 'next-auth/react';
import { useForm } from 'react-hook-form';
import { FcGoogle } from 'react-icons/fc';
import { z } from 'zod';
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
import { ZCurrentPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input, PasswordInput } from '@documenso/ui/primitives/input';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
const ERROR_MESSAGES: Partial<Record<keyof typeof ErrorCode, string>> = {
const ERROR_MESSAGES = {
[ErrorCode.CREDENTIALS_NOT_FOUND]: 'The email or password provided is incorrect',
[ErrorCode.INCORRECT_EMAIL_PASSWORD]: 'The email or password provided is incorrect',
[ErrorCode.USER_MISSING_PASSWORD]:
'This account appears to be using a social login method, please sign in using that method',
[ErrorCode.INCORRECT_TWO_FACTOR_CODE]: 'The two-factor authentication code provided is incorrect',
[ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE]: 'The backup code provided is incorrect',
};
const TwoFactorEnabledErrorCode = ErrorCode.TWO_FACTOR_MISSING_CREDENTIALS;
const LOGIN_REDIRECT_PATH = '/documents';
export const ZSignInFormSchema = z.object({
email: z.string().email().min(1),
password: ZCurrentPasswordSchema,
totpCode: z.string().trim().optional(),
backupCode: z.string().trim().optional(),
password: z.string().min(6).max(72),
});
export type TSignInFormSchema = z.infer<typeof ZSignInFormSchema>;
@@ -46,84 +39,33 @@ export type SignInFormProps = {
export const SignInForm = ({ className }: SignInFormProps) => {
const { toast } = useToast();
const [isTwoFactorAuthenticationDialogOpen, setIsTwoFactorAuthenticationDialogOpen] =
useState(false);
const [twoFactorAuthenticationMethod, setTwoFactorAuthenticationMethod] = useState<
'totp' | 'backup'
>('totp');
const [showPassword, setShowPassword] = useState(false);
const {
register,
handleSubmit,
setValue,
formState: { errors, isSubmitting },
} = useForm<TSignInFormSchema>({
values: {
email: '',
password: '',
totpCode: '',
backupCode: '',
},
resolver: zodResolver(ZSignInFormSchema),
});
const onCloseTwoFactorAuthenticationDialog = () => {
setValue('totpCode', '');
setValue('backupCode', '');
setIsTwoFactorAuthenticationDialogOpen(false);
};
const onToggleTwoFactorAuthenticationMethodClick = () => {
const method = twoFactorAuthenticationMethod === 'totp' ? 'backup' : 'totp';
if (method === 'totp') {
setValue('backupCode', '');
}
if (method === 'backup') {
setValue('totpCode', '');
}
setTwoFactorAuthenticationMethod(method);
};
const onFormSubmit = async ({ email, password, totpCode, backupCode }: TSignInFormSchema) => {
const onFormSubmit = async ({ email, password }: TSignInFormSchema) => {
try {
const credentials: Record<string, string> = {
const result = await signIn('credentials', {
email,
password,
};
if (totpCode) {
credentials.totpCode = totpCode;
}
if (backupCode) {
credentials.backupCode = backupCode;
}
const result = await signIn('credentials', {
...credentials,
callbackUrl: LOGIN_REDIRECT_PATH,
redirect: false,
});
if (result?.error && isErrorCode(result.error)) {
if (result.error === TwoFactorEnabledErrorCode) {
setIsTwoFactorAuthenticationDialogOpen(true);
return;
}
const errorMessage = ERROR_MESSAGES[result.error];
toast({
variant: 'destructive',
title: 'Unable to sign in',
description: errorMessage ?? 'An unknown error occurred',
description: ERROR_MESSAGES[result.error],
});
return;
@@ -176,14 +118,31 @@ export const SignInForm = ({ className }: SignInFormProps) => {
<span>Password</span>
</Label>
<PasswordInput
id="password"
minLength={6}
maxLength={72}
className="bg-background mt-2"
autoComplete="current-password"
{...register('password')}
/>
<div className="relative">
<Input
id="password"
type={showPassword ? 'text' : 'password'}
minLength={6}
maxLength={72}
autoComplete="current-password"
className="bg-background mt-2 pr-10"
{...register('password')}
/>
<Button
variant="link"
type="button"
className="absolute right-0 top-0 flex h-full items-center justify-center pr-3"
aria-label={showPassword ? 'Mask password' : 'Reveal password'}
onClick={() => setShowPassword((show) => !show)}
>
{showPassword ? (
<EyeOff className="text-muted-foreground h-5 w-5" />
) : (
<Eye className="text-muted-foreground h-5 w-5" />
)}
</Button>
</div>
<FormErrorMessage className="mt-1.5" error={errors.password} />
</div>
@@ -214,67 +173,6 @@ export const SignInForm = ({ className }: SignInFormProps) => {
<FcGoogle className="mr-2 h-5 w-5" />
Google
</Button>
<Dialog
open={isTwoFactorAuthenticationDialogOpen}
onOpenChange={onCloseTwoFactorAuthenticationDialog}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Two-Factor Authentication</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit(onFormSubmit)}>
{twoFactorAuthenticationMethod === 'totp' && (
<div>
<Label htmlFor="totpCode" className="text-muted-forground">
Authentication Token
</Label>
<Input
id="totpCode"
type="text"
className="bg-background mt-2"
{...register('totpCode')}
/>
<FormErrorMessage className="mt-1.5" error={errors.totpCode} />
</div>
)}
{twoFactorAuthenticationMethod === 'backup' && (
<div>
<Label htmlFor="backupCode" className="text-muted-forground">
Backup Code
</Label>
<Input
id="backupCode"
type="text"
className="bg-background mt-2"
{...register('backupCode')}
/>
<FormErrorMessage className="mt-1.5" error={errors.backupCode} />
</div>
)}
<div className="mt-4 flex items-center justify-between">
<Button
type="button"
variant="ghost"
onClick={onToggleTwoFactorAuthenticationMethodClick}
>
{twoFactorAuthenticationMethod === 'totp' ? 'Use Backup Code' : 'Use Authenticator'}
</Button>
<Button type="submit" loading={isSubmitting}>
Sign In
</Button>
</div>
</form>
</DialogContent>
</Dialog>
</form>
);
};
+6 -17
View File
@@ -10,7 +10,6 @@ import { z } from 'zod';
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
@@ -19,22 +18,12 @@ import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZSignUpFormSchema = z
.object({
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
email: z.string().email().min(1),
password: ZPasswordSchema,
signature: z.string().min(1, { message: 'We need your signature to sign documents' }),
})
.refine(
(data) => {
const { name, email, password } = data;
return !password.includes(name) && !password.includes(email.split('@')[0]);
},
{
message: 'Password should not be common or based on personal information',
},
);
export const ZSignUpFormSchema = z.object({
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
email: z.string().email().min(1),
password: z.string().min(6).max(72),
signature: z.string().min(1, { message: 'We need your signature to sign documents' }),
});
export type TSignUpFormSchema = z.infer<typeof ZSignUpFormSchema>;
+5 -1
View File
@@ -1,3 +1,5 @@
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
/**
* getAssetBuffer is used to retrieve array buffers for various assets
* that are hosted in the `public` folder.
@@ -8,7 +10,9 @@
* @param path The path to the asset, relative to the `public` folder.
*/
export const getAssetBuffer = async (path: string) => {
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
const baseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
return fetch(new URL(path, baseUrl)).then(async (res) => res.arrayBuffer());
};
+26 -59
View File
@@ -1,86 +1,53 @@
###########################
# BASE CONTAINER #
###########################
FROM node:18-alpine AS base
###########################
# BUILDER CONTAINER #
###########################
FROM base AS builder
# Install dependencies only when needed
FROM base AS production_deps
WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache jq
WORKDIR /app
# Copy our current monorepo
COPY . .
RUN TURBO_VERSION="$(npm list --package-lock-only --json turbo | jq -r '.dependencies.turbo.version')"
RUN npm install -g "turbo@$TURBO_VERSION"
RUN npm ci --production
# Outputs to the /out folder
# source: https://turbo.build/repo/docs/reference/command-line-reference/prune#--docker
RUN turbo prune --scope=@documenso/web --docker
###########################
# INSTALLER CONTAINER #
###########################
FROM base AS installer
# Install dependencies only when needed
FROM base AS builder
WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache jq
# Required for node_modules/aws-crt
RUN apk add --no-cache make cmake g++
WORKDIR /app
# Copy our current monorepo
COPY . .
# Disable husky from installing hooks
ENV HUSKY 0
ENV DOCKER_OUTPUT 1
ENV NEXT_TELEMETRY_DISABLED 1
# Uncomment and use build args to enable remote caching
# ARG TURBO_TEAM
# ENV TURBO_TEAM=$TURBO_TEAM
# ARG TURBO_TOKEN
# ENV TURBO_TOKEN=$TURBO_TOKEN
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/package-lock.json ./package-lock.json
RUN npm ci
# Then copy all the source code (as it changes more often)
COPY --from=builder /app/out/full/ .
# Finally copy the turbo.json file so that we can run turbo commands
COPY turbo.json turbo.json
RUN npm run build --workspaces
RUN TURBO_VERSION="$(npm list --package-lock-only --json turbo | jq -r '.dependencies.turbo.version')"
RUN npm install -g "turbo@$TURBO_VERSION"
RUN turbo run build --filter=@documenso/web...
###########################
# RUNNER CONTAINER #
###########################
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
# Don't run production as root
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=installer /app/apps/web/next.config.js .
COPY --from=installer /app/apps/web/package.json .
COPY --from=production_deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=production_deps --chown=nextjs:nodejs /app/package-lock.json ./package-lock.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next ./.next
CMD node apps/web/server.js
EXPOSE 3000
ENV PORT 3000
CMD ["npm", "run", "start"]
-17
View File
@@ -17,20 +17,3 @@ services:
- 9000:9000
- 2500:2500
- 1100:1100
minio:
image: minio/minio
container_name: minio
ports:
- 9002:9002
- 9001:9001
volumes:
- minio:/data
environment:
MINIO_ROOT_USER: documenso
MINIO_ROOT_PASSWORD: password
entrypoint: sh
command: -c 'mkdir -p /data/documenso && minio server /data --console-address ":9001" --address ":9002"'
volumes:
minio:
-32
View File
@@ -1,32 +0,0 @@
name: documenso_test
services:
database:
image: postgres:15
environment:
- POSTGRES_USER=documenso
- POSTGRES_PASSWORD=password
- POSTGRES_DB=documenso
ports:
- 54322:5432
inbucket:
image: inbucket/inbucket
# ports:
# - 9000:9000
# - 2500:2500
# - 1100:1100
documenso:
build:
context: ../
dockerfile: docker/Dockerfile
depends_on:
- database
- inbucket
env_file:
- ../.env.example
environment:
- NEXT_PRIVATE_DATABASE_URL=postgres://documenso:password@database:5432/documenso
- NEXT_PRIVATE_DIRECT_DATABASE_URL=postgres://documenso:password@database:5432/documenso
ports:
- 3000:3000
-1
View File
@@ -1,4 +1,3 @@
module.exports = {
'**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}': ['prettier --write'],
'**/*/package.json': ['npm run precommit'],
};
+665 -1100
View File
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -6,7 +6,6 @@
"dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing",
"start": "cd apps && cd web && next start",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
"prepare": "husky install",
"commitlint": "commitlint --edit",
@@ -21,8 +20,7 @@
"prisma:migrate-deploy": "npm run with:env -- npm run prisma:migrate-deploy -w @documenso/prisma",
"prisma:studio": "npm run with:env -- npx prisma studio --schema packages/prisma/schema.prisma",
"with:env": "dotenv -e .env -e .env.local --",
"reset:hard": "npm run clean && npm i && npm run prisma:generate",
"precommit": "npm install && git add package.json package-lock.json"
"reset:hard": "npm run clean && npm i && npm run prisma:generate"
},
"engines": {
"npm": ">=8.6.0",
@@ -48,7 +46,7 @@
"packages/*"
],
"dependencies": {
"recharts": "^2.7.2",
"react-hotkeys-hook": "^4.4.1"
"react-hotkeys-hook": "^4.4.1",
"recharts": "^2.7.2"
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ export type GetLimitsOptions = {
export const getLimits = async ({ headers }: GetLimitsOptions = {}) => {
const requestHeaders = headers ?? {};
const url = new URL(`${APP_BASE_URL}/api/limits`);
const url = new URL('/api/limits', APP_BASE_URL ?? 'http://localhost:3000');
return fetch(url, {
headers: {
+4 -4
View File
@@ -17,11 +17,11 @@
"worker:test": "tsup worker/index.ts --format esm"
},
"dependencies": {
"@documenso/nodemailer-resend": "2.0.0",
"@react-email/components": "^0.0.11",
"@documenso/nodemailer-resend": "1.0.0",
"@react-email/components": "^0.0.7",
"nodemailer": "^6.9.3",
"react-email": "^1.9.5",
"resend": "^2.0.0"
"react-email": "^1.9.4",
"resend": "^1.1.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
@@ -1,52 +0,0 @@
import { Button, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
import { TemplateDocumentImage } from './template-document-image';
export type TemplateConfirmationEmailProps = {
confirmationLink: string;
assetBaseUrl: string;
};
export const TemplateConfirmationEmail = ({
confirmationLink,
assetBaseUrl,
}: TemplateConfirmationEmailProps) => {
return (
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
<Section className="flex-row items-center justify-center">
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
Welcome to Documenso!
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Before you get started, please confirm your email address by clicking the button below:
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={confirmationLink}
>
Confirm email
</Button>
<Text className="mt-8 text-center text-sm italic text-slate-400">
You can also copy and paste this link into your browser: {confirmationLink} (link
expires in 1 hour)
</Text>
</Section>
</Section>
</Tailwind>
);
};
@@ -1,69 +0,0 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview,
Section,
Tailwind,
} from '@react-email/components';
import config from '@documenso/tailwind-config';
import {
TemplateConfirmationEmail,
TemplateConfirmationEmailProps,
} from '../template-components/template-confirmation-email';
import { TemplateFooter } from '../template-components/template-footer';
export const ConfirmEmailTemplate = ({
confirmationLink,
assetBaseUrl,
}: TemplateConfirmationEmailProps) => {
const previewText = `Please confirm your email address`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Body className="mx-auto my-auto bg-white font-sans">
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
<TemplateConfirmationEmail
confirmationLink={confirmationLink}
assetBaseUrl={assetBaseUrl}
/>
</Section>
</Container>
<div className="mx-auto mt-12 max-w-xl" />
<Container className="mx-auto max-w-xl">
<TemplateFooter isDocument={false} />
</Container>
</Section>
</Body>
</Tailwind>
</Html>
);
};
+4 -2
View File
@@ -14,8 +14,10 @@ import {
import config from '@documenso/tailwind-config';
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
import {
TemplateDocumentInvite,
TemplateDocumentInviteProps,
} from '../template-components/template-document-invite';
import { TemplateFooter } from '../template-components/template-footer';
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps> & {
+5 -23
View File
@@ -2,13 +2,14 @@ module.exports = {
extends: [
'next',
'turbo',
'prettier',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:package-json/recommended',
],
plugins: ['prettier', 'package-json', 'unused-imports'],
plugins: ['prettier', 'package-json'],
env: {
node: true,
@@ -29,22 +30,12 @@ module.exports = {
},
rules: {
'@next/next/no-html-link-for-pages': 'off',
'react/no-unescaped-entities': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-duplicate-imports': 'error',
'no-multi-spaces': [
'error',
{
@@ -76,14 +67,5 @@ module.exports = {
// To handle this we want this rule to catch usages and highlight them as
// warnings so we can write appropriate interfaces and guards later.
'@typescript-eslint/consistent-type-assertions': ['warn', { assertionStyle: 'never' }],
'@typescript-eslint/consistent-type-imports': [
'warn',
{
prefer: 'type-imports',
fixStyle: 'separate-type-imports',
disallowTypeAnnotations: false,
},
],
},
};
-1
View File
@@ -16,7 +16,6 @@
"eslint-plugin-package-json": "^0.1.4",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-unused-imports": "^3.0.0",
"typescript": "5.2.2"
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
import type { Recipient } from '@documenso/prisma/client';
import { ReadStatus, SendStatus, SigningStatus } from '@documenso/prisma/client';
import { ReadStatus, Recipient, SendStatus, SigningStatus } from '@documenso/prisma/client';
export const getRecipientType = (recipient: Recipient) => {
if (
+20
View File
@@ -1,3 +1,5 @@
import { getRuntimeEnv } from '../universal/runtime-env/get-runtime-env';
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
@@ -6,3 +8,21 @@ export const APP_FOLDER = IS_APP_MARKETING ? 'marketing' : 'web';
export const APP_BASE_URL = IS_APP_WEB
? process.env.NEXT_PUBLIC_WEBAPP_URL
: process.env.NEXT_PUBLIC_MARKETING_URL;
export const appBaseUrl = () => {
const { NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
if (IS_APP_WEB) {
return NEXT_PUBLIC_WEBAPP_URL;
}
if (IS_APP_MARKETING) {
return NEXT_PUBLIC_MARKETING_URL;
}
if (typeof window !== 'undefined') {
return window.location.origin;
}
return NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000';
};
-1
View File
@@ -1 +0,0 @@
export const DOCUMENSO_ENCRYPTION_KEY = process.env.NEXT_PRIVATE_ENCRYPTION_KEY;
-71
View File
@@ -1,71 +0,0 @@
import { DateTime } from 'luxon';
import { DEFAULT_DOCUMENT_TIME_ZONE } from './time-zones';
export const DEFAULT_DOCUMENT_DATE_FORMAT = 'yyyy-MM-dd hh:mm a';
export const DATE_FORMATS = [
{
key: 'YYYYMMDD',
label: 'YYYY-MM-DD',
value: DEFAULT_DOCUMENT_DATE_FORMAT,
},
{
key: 'DDMMYYYY',
label: 'DD/MM/YYYY',
value: 'dd/MM/yyyy hh:mm a',
},
{
key: 'MMDDYYYY',
label: 'MM/DD/YYYY',
value: 'MM/dd/yyyy hh:mm a',
},
{
key: 'YYYYMMDDHHmm',
label: 'YYYY-MM-DD HH:mm',
value: 'yyyy-MM-dd HH:mm',
},
{
key: 'YYMMDD',
label: 'YY-MM-DD',
value: 'yy-MM-dd hh:mm a',
},
{
key: 'YYYYMMDDhhmmss',
label: 'YYYY-MM-DD HH:mm:ss',
value: 'yyyy-MM-dd HH:mm:ss',
},
{
key: 'MonthDateYear',
label: 'Month Date, Year',
value: 'MMMM dd, yyyy hh:mm a',
},
{
key: 'DayMonthYear',
label: 'Day, Month Year',
value: 'EEEE, MMMM dd, yyyy hh:mm a',
},
{
key: 'ISO8601',
label: 'ISO 8601',
value: "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
},
];
export const convertToLocalSystemFormat = (
customText: string,
dateFormat: string | null = DEFAULT_DOCUMENT_DATE_FORMAT,
timeZone: string | null = DEFAULT_DOCUMENT_TIME_ZONE,
): string => {
const parsedDate = DateTime.fromFormat(customText, dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT, {
zone: timeZone ?? DEFAULT_DOCUMENT_TIME_ZONE,
});
if (!parsedDate.isValid) {
return 'Invalid date';
}
const formattedDate = parsedDate.toLocal().toFormat(dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT);
return formattedDate;
};
+2 -2
View File
@@ -1,4 +1,4 @@
import { APP_BASE_URL } from './app';
import { appBaseUrl } from './app';
/**
* The flag name for global session recording feature flag.
@@ -25,7 +25,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
*/
export function extractPostHogConfig(): { key: string; host: string } | null {
const postHogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
const postHogHost = `${APP_BASE_URL}/ingest`;
const postHogHost = `${appBaseUrl()}/ingest`;
if (!postHogKey || !postHogHost) {
return null;
+1 -3
View File
@@ -1,9 +1,7 @@
import { APP_BASE_URL } from './app';
export const DEFAULT_STANDARD_FONT_SIZE = 15;
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
export const MIN_STANDARD_FONT_SIZE = 8;
export const MIN_HANDWRITING_FONT_SIZE = 20;
export const CAVEAT_FONT_PATH = `${APP_BASE_URL}/fonts/caveat.ttf`;
export const CAVEAT_FONT_PATH = `/fonts/caveat.ttf`;
-44
View File
@@ -1,44 +0,0 @@
import { rawTimeZones, timeZonesNames } from '@vvo/tzdb';
export const TIME_ZONE_DATA = rawTimeZones;
export const DEFAULT_DOCUMENT_TIME_ZONE = 'Etc/UTC';
export type TimeZone = {
name: string;
rawOffsetInMinutes: number;
};
export const minutesToHours = (minutes: number): string => {
const hours = Math.abs(Math.floor(minutes / 60));
const min = Math.abs(minutes % 60);
const sign = minutes >= 0 ? '+' : '-';
return `${sign}${String(hours).padStart(2, '0')}:${String(min).padStart(2, '0')}`;
};
const getGMTOffsets = (timezones: TimeZone[]): string[] => {
const gmtOffsets: string[] = [];
for (const timezone of timezones) {
const offsetValue = minutesToHours(timezone.rawOffsetInMinutes);
const gmtText = `(${offsetValue})`;
gmtOffsets.push(`${timezone.name} ${gmtText}`);
}
return gmtOffsets;
};
export const splitTimeZone = (input: string | null): string => {
if (input === null) {
return '';
}
const [timeZone] = input.split('(');
return timeZone.trim();
};
export const TIME_ZONES_FULL = getGMTOffsets(TIME_ZONE_DATA);
export const TIME_ZONES = ['Etc/UTC', ...timeZonesNames];
+3 -30
View File
@@ -1,15 +1,12 @@
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import { compare } from 'bcrypt';
import { DateTime } from 'luxon';
import type { AuthOptions, Session, User } from 'next-auth';
import { AuthOptions, Session, User } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import type { GoogleProfile } from 'next-auth/providers/google';
import GoogleProvider from 'next-auth/providers/google';
import GoogleProvider, { GoogleProfile } from 'next-auth/providers/google';
import { prisma } from '@documenso/prisma';
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa';
import { getUserByEmail } from '../server-only/user/get-user-by-email';
import { ErrorCode } from './error-codes';
@@ -25,19 +22,13 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
totpCode: {
label: 'Two-factor Code',
type: 'input',
placeholder: 'Code from authenticator app',
},
backupCode: { label: 'Backup Code', type: 'input', placeholder: 'Two-factor backup code' },
},
authorize: async (credentials, _req) => {
if (!credentials) {
throw new Error(ErrorCode.CREDENTIALS_NOT_FOUND);
}
const { email, password, backupCode, totpCode } = credentials;
const { email, password } = credentials;
const user = await getUserByEmail({ email }).catch(() => {
throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
@@ -53,20 +44,6 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
}
const is2faEnabled = isTwoFactorAuthenticationEnabled({ user });
if (is2faEnabled) {
const isValid = await validateTwoFactorAuthentication({ backupCode, totpCode, user });
if (!isValid) {
throw new Error(
totpCode
? ErrorCode.INCORRECT_TWO_FACTOR_CODE
: ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE,
);
}
}
return {
id: Number(user.id),
email: user.email,
@@ -111,7 +88,6 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
merged.id = retrieved.id;
merged.name = retrieved.name;
merged.email = retrieved.email;
merged.emailVerified = retrieved.emailVerified;
}
if (
@@ -136,7 +112,6 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
name: merged.name,
email: merged.email,
lastSignedIn: merged.lastSignedIn,
emailVerified: merged.emailVerified,
};
},
@@ -148,8 +123,6 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
id: Number(token.id),
name: token.name,
email: token.email,
emailVerified:
typeof token.emailVerified === 'string' ? new Date(token.emailVerified) : null,
},
} satisfies Session;
}
-11
View File
@@ -8,15 +8,4 @@ export const ErrorCode = {
INCORRECT_EMAIL_PASSWORD: 'INCORRECT_EMAIL_PASSWORD',
USER_MISSING_PASSWORD: 'USER_MISSING_PASSWORD',
CREDENTIALS_NOT_FOUND: 'CREDENTIALS_NOT_FOUND',
INTERNAL_SEVER_ERROR: 'INTERNAL_SEVER_ERROR',
TWO_FACTOR_ALREADY_ENABLED: 'TWO_FACTOR_ALREADY_ENABLED',
TWO_FACTOR_SETUP_REQUIRED: 'TWO_FACTOR_SETUP_REQUIRED',
TWO_FACTOR_MISSING_SECRET: 'TWO_FACTOR_MISSING_SECRET',
TWO_FACTOR_MISSING_CREDENTIALS: 'TWO_FACTOR_MISSING_CREDENTIALS',
INCORRECT_TWO_FACTOR_CODE: 'INCORRECT_TWO_FACTOR_CODE',
INCORRECT_TWO_FACTOR_BACKUP_CODE: 'INCORRECT_TWO_FACTOR_BACKUP_CODE',
INCORRECT_IDENTITY_PROVIDER: 'INCORRECT_IDENTITY_PROVIDER',
INCORRECT_PASSWORD: 'INCORRECT_PASSWORD',
MISSING_ENCRYPTION_KEY: 'MISSING_ENCRYPTION_KEY',
MISSING_BACKUP_CODE: 'MISSING_BACKUP_CODE',
} as const;
@@ -1,35 +0,0 @@
'use server';
import { cache } from 'react';
import { getServerSession as getNextAuthServerSession } from 'next-auth';
import { prisma } from '@documenso/prisma';
import { NEXT_AUTH_OPTIONS } from './auth-options';
export const getServerComponentSession = cache(async () => {
const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS);
if (!session || !session.user?.email) {
return { user: null, session: null };
}
const user = await prisma.user.findFirstOrThrow({
where: {
email: session.user.email,
},
});
return { user, session };
});
export const getRequiredServerComponentSession = cache(async () => {
const { user, session } = await getServerComponentSession();
if (!user || !session) {
throw new Error('No session found');
}
return { user, session };
});
+27 -3
View File
@@ -1,6 +1,4 @@
'use server';
import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';
import { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';
import { getServerSession as getNextAuthServerSession } from 'next-auth';
@@ -28,3 +26,29 @@ export const getServerSession = async ({ req, res }: GetServerSessionOptions) =>
return { user, session };
};
export const getServerComponentSession = async () => {
const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS);
if (!session || !session.user?.email) {
return { user: null, session: null };
}
const user = await prisma.user.findFirstOrThrow({
where: {
email: session.user.email,
},
});
return { user, session };
};
export const getRequiredServerComponentSession = async () => {
const { user, session } = await getServerComponentSession();
if (!user || !session) {
throw new Error('No session found');
}
return { user, session };
};
-6
View File
@@ -11,8 +11,6 @@
"next-auth/"
],
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"clean": "rimraf node_modules"
},
"dependencies": {
@@ -24,19 +22,15 @@
"@documenso/prisma": "*",
"@documenso/signing": "*",
"@next-auth/prisma-adapter": "1.0.7",
"@noble/ciphers": "0.4.0",
"@noble/hashes": "1.3.2",
"@pdf-lib/fontkit": "^1.1.1",
"@scure/base": "^1.1.3",
"@sindresorhus/slugify": "^2.2.1",
"@upstash/redis": "^1.20.6",
"@vvo/tzdb": "^6.117.0",
"bcrypt": "^5.1.0",
"luxon": "^3.4.0",
"nanoid": "^4.0.2",
"next": "14.0.0",
"next-auth": "4.24.3",
"oslo": "^0.17.0",
"pdf-lib": "^1.17.1",
"react": "18.2.0",
"remeda": "^1.27.1",
@@ -1,48 +0,0 @@
import { compare } from 'bcrypt';
import { prisma } from '@documenso/prisma';
import { User } from '@documenso/prisma/client';
import { ErrorCode } from '../../next-auth/error-codes';
import { validateTwoFactorAuthentication } from './validate-2fa';
type DisableTwoFactorAuthenticationOptions = {
user: User;
backupCode: string;
password: string;
};
export const disableTwoFactorAuthentication = async ({
backupCode,
user,
password,
}: DisableTwoFactorAuthenticationOptions) => {
if (!user.password) {
throw new Error(ErrorCode.USER_MISSING_PASSWORD);
}
const isCorrectPassword = await compare(password, user.password);
if (!isCorrectPassword) {
throw new Error(ErrorCode.INCORRECT_PASSWORD);
}
const isValid = await validateTwoFactorAuthentication({ backupCode, user });
if (!isValid) {
throw new Error(ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE);
}
await prisma.user.update({
where: {
id: user.id,
},
data: {
twoFactorEnabled: false,
twoFactorBackupCodes: null,
twoFactorSecret: null,
},
});
return true;
};
@@ -1,47 +0,0 @@
import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
import { prisma } from '@documenso/prisma';
import { User } from '@documenso/prisma/client';
import { getBackupCodes } from './get-backup-code';
import { verifyTwoFactorAuthenticationToken } from './verify-2fa-token';
type EnableTwoFactorAuthenticationOptions = {
user: User;
code: string;
};
export const enableTwoFactorAuthentication = async ({
user,
code,
}: EnableTwoFactorAuthenticationOptions) => {
if (user.identityProvider !== 'DOCUMENSO') {
throw new Error(ErrorCode.INCORRECT_IDENTITY_PROVIDER);
}
if (user.twoFactorEnabled) {
throw new Error(ErrorCode.TWO_FACTOR_ALREADY_ENABLED);
}
if (!user.twoFactorSecret) {
throw new Error(ErrorCode.TWO_FACTOR_SETUP_REQUIRED);
}
const isValidToken = await verifyTwoFactorAuthenticationToken({ user, totpCode: code });
if (!isValidToken) {
throw new Error(ErrorCode.INCORRECT_TWO_FACTOR_CODE);
}
const updatedUser = await prisma.user.update({
where: {
id: user.id,
},
data: {
twoFactorEnabled: true,
},
});
const recoveryCodes = getBackupCodes({ user: updatedUser });
return { recoveryCodes };
};
@@ -1,38 +0,0 @@
import { z } from 'zod';
import { User } from '@documenso/prisma/client';
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
import { symmetricDecrypt } from '../../universal/crypto';
interface GetBackupCodesOptions {
user: User;
}
const ZBackupCodeSchema = z.array(z.string());
export const getBackupCodes = ({ user }: GetBackupCodesOptions) => {
const key = DOCUMENSO_ENCRYPTION_KEY;
if (!user.twoFactorEnabled) {
throw new Error('User has not enabled 2FA');
}
if (!user.twoFactorBackupCodes) {
throw new Error('User has no backup codes');
}
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorBackupCodes })).toString(
'utf-8',
);
const data = JSON.parse(secret);
const result = ZBackupCodeSchema.safeParse(data);
if (result.success) {
return result.data;
}
return null;
};
@@ -1,17 +0,0 @@
import { User } from '@documenso/prisma/client';
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
type IsTwoFactorAuthenticationEnabledOptions = {
user: User;
};
export const isTwoFactorAuthenticationEnabled = ({
user,
}: IsTwoFactorAuthenticationEnabledOptions) => {
return (
user.twoFactorEnabled &&
user.identityProvider === 'DOCUMENSO' &&
typeof DOCUMENSO_ENCRYPTION_KEY === 'string'
);
};
-76
View File
@@ -1,76 +0,0 @@
import { base32 } from '@scure/base';
import { compare } from 'bcrypt';
import crypto from 'crypto';
import { createTOTPKeyURI } from 'oslo/otp';
import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
import { prisma } from '@documenso/prisma';
import { User } from '@documenso/prisma/client';
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
import { symmetricEncrypt } from '../../universal/crypto';
type SetupTwoFactorAuthenticationOptions = {
user: User;
password: string;
};
const ISSUER = 'Documenso';
export const setupTwoFactorAuthentication = async ({
user,
password,
}: SetupTwoFactorAuthenticationOptions) => {
const key = DOCUMENSO_ENCRYPTION_KEY;
if (!key) {
throw new Error(ErrorCode.MISSING_ENCRYPTION_KEY);
}
if (user.identityProvider !== 'DOCUMENSO') {
throw new Error(ErrorCode.INCORRECT_IDENTITY_PROVIDER);
}
if (!user.password) {
throw new Error(ErrorCode.USER_MISSING_PASSWORD);
}
const isCorrectPassword = await compare(password, user.password);
if (!isCorrectPassword) {
throw new Error(ErrorCode.INCORRECT_PASSWORD);
}
const secret = crypto.randomBytes(10);
const backupCodes = new Array(10)
.fill(null)
.map(() => crypto.randomBytes(5).toString('hex'))
.map((code) => `${code.slice(0, 5)}-${code.slice(5)}`.toUpperCase());
const accountName = user.email;
const uri = createTOTPKeyURI(ISSUER, accountName, secret);
const encodedSecret = base32.encode(secret);
await prisma.user.update({
where: {
id: user.id,
},
data: {
twoFactorEnabled: false,
twoFactorBackupCodes: symmetricEncrypt({
data: JSON.stringify(backupCodes),
key: key,
}),
twoFactorSecret: symmetricEncrypt({
data: encodedSecret,
key: key,
}),
},
});
return {
secret: encodedSecret,
uri,
};
};

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