diff --git a/.prettierignore b/.prettierignore index 0263490d1..273b8eb09 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,6 @@ node_modules/ .next/ .react-email/ .vercel/ -.github/ \ No newline at end of file +.github/ +*.min.js +*.min.css \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index 95e47e0bb..24a340beb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,46 +18,46 @@ "default": { "annotated-types": { "hashes": [ - "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", - "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.7.0" }, "black": { "hashes": [ - "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f", - "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93", - "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11", - "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0", - "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9", - "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5", - "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213", - "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d", - "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7", - "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837", - "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f", - "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395", - "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995", - "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f", - "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597", - "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959", - "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5", - "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb", - "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4", - "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7", - "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd", - "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7" - ], - "version": "==24.3.0" + "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474", + "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1", + "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0", + "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8", + "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96", + "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1", + "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04", + "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021", + "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94", + "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d", + "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c", + "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7", + "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c", + "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc", + "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7", + "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d", + "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c", + "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741", + "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce", + "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb", + "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063", + "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e" + ], + "version": "==24.4.2" }, "certifi": { "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", + "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" ], "markers": "python_version >= '3.6'", - "version": "==2024.2.2" + "version": "==2024.6.2" }, "cffi": { "hashes": [ @@ -223,24 +223,25 @@ }, "clickhouse-toolset": { "hashes": [ - "sha256:2d19599d68e62fd0d318e2b661f69190cddf571503acbb277c465b40753ea2a5", - "sha256:44c246da0824c73a78dcba5543349e6afd316083abbecc586a5c50d55b691b55", - "sha256:45e760786f9c39e457f0fe88ec9373547ff2aa65f0f0975423500a7044186f18", - "sha256:557a50c94383925b3f8013f30785fedd6466514bedfed2e81f02574f3367c3d9", - "sha256:6884d233aad27ba5fe9ca9312d9650229ea9b4d5eb930a5215773eb17edbb5d6", - "sha256:8a7ccad5e6d4f5226aec0907e78e48ed5abfe2d6941a1727e3036f55d4791cd6", - "sha256:8fbf3f967803c0845f7f2e4bde8b589f88c49547484a4ada1ab7873ae79b02fe", - "sha256:b3ba92a89649f4ff0c2abdea7e74376161f8457f897fb59dd0a18083103f4354", - "sha256:b45c81b858ed5ef4174b041e22fef57198e3de2e8e5682025df1eef6c7550361", - "sha256:ba16258fba7f20e8165ba96f536ca4448c75e321058a03323fb333f8e52d5377", - "sha256:bf84e5e1c034f70ea6546b0ae55fc21bd220f8c3985367e7eb6b16a3a80b8db8", - "sha256:d0377766858be833de52e3e09bb5fd722b692a161f528977d7b898c63716e6f1", - "sha256:d70d8584bb8c4724692139189bfb33329e4ad532e58b73e83fc925c5232efe73", - "sha256:e6cbb96ad3212aa3eb9ce0295da4be9b80f516922e19bbc37ca167f727d5903c", - "sha256:f992302bb632aee9d36c86e2bc30c672bb449bc3ca3fe0f097cda3e16ef31864" + "sha256:0a47851ad42575f1d77c8d6931d2fcaeb1d33798c97ee503944e9c787c99b175", + "sha256:0a4a510224ef3cf788e3766329a50200c54e8b34b40a62d7104f801302fe7907", + "sha256:0ae905fcaa6032d023941681be38571a1949403c3f414d006e150a7db8945e08", + "sha256:1c34e48ed4e4c91915917b2b9bd897158983a18371becb93b7147b925f7fd7d3", + "sha256:33a869fb693cce36b4af6e739712c0895e6f9ff0cb66c72c30e1398d3c6b4ac5", + "sha256:49ce2d91fb76e02e371a3904b341c37847340213011a393e417fdb8aaa57b38d", + "sha256:7c0d6d2075413f448bd03c49b3339355d29b75f09c2b0a2ec1e0bae05e3d171f", + "sha256:a43788583ee38a0b5adb169742e949c591939d4d52aa58cd61a87ff8d04d488f", + "sha256:a5a423df8c65b7d9c35d403da788211ab6e527a0bff5db239be903e0501a961c", + "sha256:ad4c6aa5f3cfd10aaada015e2c452aede5432db4a4371957fc1a70ee13843221", + "sha256:b6c11f8f16a1d059d1fbecdb686ce7cf03eb90006b0021218bd7ff4ed81ce308", + "sha256:b6d61b46f325c5ba3e0086b4a9660218c0b8c34aab8aab7427b05b6de29b828f", + "sha256:c05fe1565edc4f28611c4b09febd19a224754bc545a3191fa396d0f91449da4f", + "sha256:e35e91e59f1a88a89b8355b6978e880242d4a93c9ab71d9a1b165acee614704c", + "sha256:f3a35aee052b2f630d866fe0fcfc258112585447fc9fe17e9342ce9e3d65b3e4", + "sha256:f90b40531cb75ce63ee0cebed95efc72de151c9afc79592366563c50a3eb4c37" ], "markers": "python_version < '3.13' and python_version >= '3.8'", - "version": "==0.29.dev0" + "version": "==0.30.dev0" }, "colorama": { "hashes": [ @@ -260,41 +261,41 @@ }, "cryptography": { "hashes": [ - "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee", - "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576", - "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d", - "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30", - "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413", - "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb", - "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da", - "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4", - "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd", - "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc", - "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8", - "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1", - "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc", - "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e", - "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8", - "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940", - "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400", - "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7", - "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16", - "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278", - "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74", - "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec", - "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1", - "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2", - "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c", - "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922", - "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a", - "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6", - "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1", - "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e", - "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac", - "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7" + "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", + "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", + "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", + "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", + "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", + "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", + "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", + "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", + "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", + "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", + "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", + "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", + "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", + "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", + "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", + "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", + "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", + "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", + "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", + "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", + "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", + "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", + "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", + "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", + "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", + "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", + "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", + "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", + "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", + "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", + "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", + "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" ], "markers": "python_version >= '3.7'", - "version": "==42.0.5" + "version": "==42.0.8" }, "gitdb": { "hashes": [ @@ -322,11 +323,11 @@ }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], "markers": "python_version >= '3.5'", - "version": "==3.6" + "version": "==3.7" }, "mypy-extensions": { "hashes": [ @@ -362,10 +363,11 @@ }, "pycparser": { "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], - "version": "==2.21" + "markers": "python_version >= '3.8'", + "version": "==2.22" }, "pydantic": { "hashes": [ @@ -559,11 +561,11 @@ }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.3" }, "shandy-sqlfmt": { "hashes": [ @@ -591,12 +593,12 @@ }, "tinybird-cli": { "hashes": [ - "sha256:8ad9432898b7ab7866624bfee876c5ce30ec2f4ea30c84ed1c036d5c05a7c9df", - "sha256:948c26fb04d359e7b8c66208eb22c06127d032f1e96ba5d2124b1a396ce03571" + "sha256:2a2c5a5d4ce757f488d4266fd48fdc368cf83975564947fea7fd982665cd542b", + "sha256:f088097f6d6c75dd962401959c436752d1fa963c6da44753341680003e3e230f" ], "index": "pypi", "markers": "python_version < '3.12' and python_version >= '3.8'", - "version": "==3.6.0" + "version": "==4.1.1" }, "toposort": { "hashes": [ @@ -620,19 +622,19 @@ }, "tqdm": { "hashes": [ - "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9", - "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531" + "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644", + "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb" ], "markers": "python_version >= '3.7'", - "version": "==4.66.2" + "version": "==4.66.4" }, "typing-extensions": { "hashes": [ - "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", - "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "markers": "python_version >= '3.8'", - "version": "==4.10.0" + "version": "==4.12.2" }, "urllib3": { "hashes": [ diff --git a/components/NotionPage.tsx b/components/NotionPage.tsx index 4432aa703..1a123a3c6 100644 --- a/components/NotionPage.tsx +++ b/components/NotionPage.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from "react"; +import React from "react"; import { Brand, DataroomBrand } from "@prisma/client"; import { ExtendedRecordMap } from "notion-types"; @@ -6,6 +7,7 @@ import { NotionRenderer } from "react-notion-x"; // core styles shared by all of react-notion-x (required) import "react-notion-x/src/styles.css"; +import { TDocumentData } from "./view/dataroom/dataroom-view"; import Nav from "./view/nav"; export const NotionPage = ({ @@ -29,7 +31,7 @@ export const NotionPage = ({ documentName?: string; brand?: Partial | Partial | null; dataroomId?: string; - setDocumentData?: (data: any) => void; + setDocumentData?: React.Dispatch>; }) => { const [pageNumber, setPageNumber] = useState(1); // start on first page const [maxScrollPercentage, setMaxScrollPercentage] = useState(0); diff --git a/components/datarooms/add-dataroom-document-modal.tsx b/components/datarooms/add-dataroom-document-modal.tsx deleted file mode 100644 index 3d996e5f4..000000000 --- a/components/datarooms/add-dataroom-document-modal.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useRouter } from "next/router"; - -import { useState } from "react"; - -import { useTeam } from "@/context/team-context"; -import { toast } from "sonner"; -import { mutate } from "swr"; - -import { SidebarFolderTreeSelection } from "@/components/sidebar-folders"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; - -export function AddDataroomDocumentModal({ - open, - setOpen, - dataroomId, - documentName, -}: { - open: boolean; - setOpen: React.Dispatch>; - dataroomId?: string; - documentName?: string; -}) { - const router = useRouter(); - const [folderId, setFolderId] = useState(""); - const [documentId, setDocumentId] = useState(""); - const [loading, setLoading] = useState(false); - - const teamInfo = useTeam(); - - /** current folder name */ - const currentFolderPath = router.query.name as string[] | undefined; - - const handleSubmit = async (event: any) => { - event.preventDefault(); - event.stopPropagation(); - - if (folderId === "") return; - - setLoading(true); - try { - const response = await fetch( - `/api/teams/${teamInfo?.currentTeam?.id}/datarooms/${dataroomId}/documents`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - documentId: documentId, - folderPathName: currentFolderPath?.join("/"), - }), - }, - ); - - if (!response.ok) { - const { message } = await response.json(); - setLoading(false); - toast.error(message); - return; - } - - const { newPath, oldPath } = (await response.json()) as { - newPath: string; - oldPath: string; - }; - - toast.success("Document moved successfully!"); - - // mutate(`/api/teams/${teamInfo?.currentTeam?.id}/folders`); - // mutate(`/api/teams/${teamInfo?.currentTeam?.id}/folders${oldPath}`); - // mutate(`/api/teams/${teamInfo?.currentTeam?.id}/folders${newPath}`).then( - // () => { - // router.push(`/documents/tree${newPath}`); - // }, - // ); - } catch (error) { - console.error("Error moving document", error); - toast.error("Failed to move document. Try again."); - } finally { - setLoading(false); - setOpen(false); - } - }; - - return ( - - - - Add Document - Add your document to dataroom - -
-
- -
- - - - -
-
-
- ); -} diff --git a/components/datarooms/dataroom-trial-modal.tsx b/components/datarooms/dataroom-trial-modal.tsx index c87a1efa7..3e3f5d97a 100644 --- a/components/datarooms/dataroom-trial-modal.tsx +++ b/components/datarooms/dataroom-trial-modal.tsx @@ -43,7 +43,7 @@ export function DataroomTrialModal({ const [companySize, setCompanySize] = useState(""); const [name, setName] = useState(""); const [companyName, setCompanyName] = useState(""); - const [phoneNumber, setPhoneNumber] = useState(""); + const [phoneNumber, setPhoneNumber] = useState(null); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); diff --git a/components/datarooms/folders/selection-tree.tsx b/components/datarooms/folders/selection-tree.tsx index d3588bd93..1252d19b0 100644 --- a/components/datarooms/folders/selection-tree.tsx +++ b/components/datarooms/folders/selection-tree.tsx @@ -1,5 +1,6 @@ import { memo, useMemo } from "react"; +import { TSelectedFolder } from "@/components/documents/move-folder-modal"; import { FileTree } from "@/components/ui/nextra-filetree"; import { @@ -12,12 +13,12 @@ import { buildNestedFolderStructure } from "./utils"; const FolderComponentSelection = memo( ({ folder, - selectedFolderId, - setFolderId, + selectedFolder, + setSelectedFolder, }: { folder: DataroomFolderWithDocuments; - selectedFolderId: string; - setFolderId: React.Dispatch>; + selectedFolder: TSelectedFolder; + setSelectedFolder: React.Dispatch>; }) => { // Recursively render child folders if they exist const childFolders = useMemo( @@ -26,11 +27,16 @@ const FolderComponentSelection = memo( )), - [folder.childFolders, selectedFolderId, setFolderId], + [folder.childFolders, selectedFolder, setSelectedFolder], + ); + + const isActive = folder.id === selectedFolder?.id; + const isChildActive = folder.childFolders.some( + (childFolder) => childFolder.id === selectedFolder?.id, ); return ( @@ -38,13 +44,17 @@ const FolderComponentSelection = memo( onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setFolderId(folder.id); + setSelectedFolder({ id: folder.id, name: folder.name }); }} > + setSelectedFolder({ id: folder.id, name: folder.name }) + } > {childFolders} @@ -56,12 +66,12 @@ FolderComponentSelection.displayName = "FolderComponentSelection"; const SidebarFoldersSelectipon = ({ folders, - selectedFolderId, - setFolderId, + selectedFolder, + setSelectedFolder, }: { folders: DataroomFolderWithDocuments[]; - selectedFolderId: string; - setFolderId: React.Dispatch>; + selectedFolder: TSelectedFolder; + setSelectedFolder: React.Dispatch>; }) => { const nestedFolders = useMemo(() => { if (folders) { @@ -76,8 +86,8 @@ const SidebarFoldersSelectipon = ({ ))} @@ -86,12 +96,12 @@ const SidebarFoldersSelectipon = ({ export function SidebarFolderTreeSelection({ dataroomId, - selectedFolderId, - setFolderId, + selectedFolder, + setSelectedFolder, }: { dataroomId: string; - selectedFolderId: string; - setFolderId: React.Dispatch>; + selectedFolder: TSelectedFolder; + setSelectedFolder: React.Dispatch>; }) { const { folders, error } = useDataroomFoldersTree({ dataroomId }); @@ -100,8 +110,8 @@ export function SidebarFolderTreeSelection({ return ( ); } diff --git a/components/datarooms/folders/sidebar-tree.tsx b/components/datarooms/folders/sidebar-tree.tsx index 220817b15..6b8f5d253 100644 --- a/components/datarooms/folders/sidebar-tree.tsx +++ b/components/datarooms/folders/sidebar-tree.tsx @@ -47,17 +47,30 @@ const FolderComponent = memo( [folder.childFolders, dataroomId], ); + const isActive = + folder.path === "/" + (router.query.name as string[])?.join("/"); + const isChildActive = folder.childFolders.some( + (childFolder) => + childFolder.path === "/" + (router.query.name as string[])?.join("/"), + ); + + const handleFolderClick = () => { + router.push( + `/datarooms/${dataroomId}/documents${folder.path}`, + `/datarooms/${dataroomId}/documents${folder.path}`, + { + scroll: false, + }, + ); + }; + return ( - router.push(`/datarooms/${dataroomId}/documents${folder.path}`) - } + active={isActive} + childActive={isChildActive} + onToggle={handleFolderClick} > {childFolders} {documents} diff --git a/components/datarooms/folders/view-tree.tsx b/components/datarooms/folders/view-tree.tsx index aa6e0a9f0..fee7c6b59 100644 --- a/components/datarooms/folders/view-tree.tsx +++ b/components/datarooms/folders/view-tree.tsx @@ -69,6 +69,11 @@ const FolderComponent = memo( [folder.childFolders, folderId, setFolderId], ); + const isActive = folder.id === folderId; + const isChildActive = folder.childFolders.some( + (childFolder) => childFolder.id === folderId, + ); + return (
{ @@ -80,11 +85,9 @@ const FolderComponent = memo( - // router.push(`/datarooms/${dataroomId}/documents${folder.path}`) - // } + active={isActive} + childActive={isChildActive} + onToggle={() => setFolderId(folder.id)} > {childFolders} {documents} diff --git a/components/datarooms/move-dataroom-folder-modal.tsx b/components/datarooms/move-dataroom-folder-modal.tsx index c128b020b..35fcda192 100644 --- a/components/datarooms/move-dataroom-folder-modal.tsx +++ b/components/datarooms/move-dataroom-folder-modal.tsx @@ -5,6 +5,7 @@ import { useState } from "react"; import { useTeam } from "@/context/team-context"; import { toast } from "sonner"; import { mutate } from "swr"; +import { set } from "ts-pattern/dist/patterns"; import { SidebarFolderTreeSelection } from "@/components/datarooms/folders"; import { Button } from "@/components/ui/button"; @@ -17,6 +18,8 @@ import { DialogTitle, } from "@/components/ui/dialog"; +import { TSelectedFolder } from "../documents/move-folder-modal"; + export function MoveToDataroomFolderModal({ open, setOpen, @@ -31,10 +34,11 @@ export function MoveToDataroomFolderModal({ documentName?: string; }) { const router = useRouter(); - const [folderId, setFolderId] = useState(""); + const [selectedFolder, setSelectedFolder] = useState(null); const [loading, setLoading] = useState(false); const teamInfo = useTeam(); + const teamId = teamInfo?.currentTeam?.id; const currentPath = router.query.name ? (router.query.name as string[]).join("/") @@ -44,19 +48,19 @@ export function MoveToDataroomFolderModal({ event.preventDefault(); event.stopPropagation(); - if (folderId === "") return; + if (!selectedFolder) return; setLoading(true); try { const response = await fetch( - `/api/teams/${teamInfo?.currentTeam?.id}/datarooms/${dataroomId}/documents/${documentId}`, + `/api/teams/${teamId}/datarooms/${dataroomId}/documents/${documentId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ - folderId: folderId, + folderId: selectedFolder.id, currentPathName: "/" + currentPath, }), }, @@ -76,14 +80,10 @@ export function MoveToDataroomFolderModal({ toast.success("Document moved successfully!"); + mutate(`/api/teams/${teamId}/datarooms/${dataroomId}/folders`); + mutate(`/api/teams/${teamId}/datarooms/${dataroomId}/folders${oldPath}`); mutate( - `/api/teams/${teamInfo?.currentTeam?.id}/datarooms/${dataroomId}/folders`, - ); - mutate( - `/api/teams/${teamInfo?.currentTeam?.id}/datarooms/${dataroomId}/folders${oldPath}`, - ); - mutate( - `/api/teams/${teamInfo?.currentTeam?.id}/datarooms/${dataroomId}/folders${newPath}`, + `/api/teams/${teamId}/datarooms/${dataroomId}/folders${newPath}`, ).then(() => { router.push(`/datarooms/${dataroomId}/documents${newPath}`); }); @@ -109,14 +109,26 @@ export function MoveToDataroomFolderModal({
- diff --git a/components/datarooms/settings/duplicate-dataroom.tsx b/components/datarooms/settings/duplicate-dataroom.tsx new file mode 100644 index 000000000..005e39747 --- /dev/null +++ b/components/datarooms/settings/duplicate-dataroom.tsx @@ -0,0 +1,104 @@ +import { useState } from "react"; + +import { toast } from "sonner"; +import { mutate } from "swr"; + +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; + +export default function DuplicateDataroom({ + dataroomId, + teamId, +}: { + dataroomId: string; + teamId?: string; +}) { + const [loading, setLoading] = useState(false); + + const handleDuplicateDataroom = async ( + e: React.MouseEvent, + ) => { + e.preventDefault(); + e.stopPropagation(); + + if (!teamId) { + return; + } + + setLoading(true); + + try { + toast.promise( + fetch(`/api/teams/${teamId}/datarooms/${dataroomId}/duplicate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }), + { + loading: "Copying dataroom...", + success: () => { + mutate(`/api/teams/${teamId}/datarooms`); + return "Dataroom copied successfully."; + }, + error: (error) => { + console.log(error); + return error.message || "An error occurred while copying dataroom."; + }, + }, + ); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + Duplicate Dataroom + + Create a new data room with the same content (folders and files) as + this data room. + + + + +
+ +
+
+
+
+ //
+ //
+ //

Duplicate Dataroom

+ //

+ // Create a new data room with the same content (folders and files) as + // this data room. + //

+ //
+ //
+ + //
+ // + //
+ //
+ ); +} diff --git a/components/document-upload.tsx b/components/document-upload.tsx index 21ae5905d..c0162708f 100644 --- a/components/document-upload.tsx +++ b/components/document-upload.tsx @@ -1,10 +1,10 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; -import { useTeam } from "@/context/team-context"; import { Upload as ArrowUpTrayIcon, File as DocumentIcon, FileText as DocumentTextIcon, + FileSpreadsheetIcon, Image as PhotoIcon, Presentation as PresentationChartBarIcon, } from "lucide-react"; @@ -15,6 +15,13 @@ import { usePlan } from "@/lib/swr/use-billing"; import { bytesToSize } from "@/lib/utils"; import { getPagesCount } from "@/lib/utils/get-page-number-count"; +const fileSizeLimits: { [key: string]: number } = { + "application/vnd.ms-excel": 100, // 30 MB + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": 30, // 30 MB + "text/csv": 30, // 30 MB + "application/vnd.oasis.opendocument.spreadsheet": 30, // 30 MB +}; + function fileIcon(fileType: string) { switch (fileType) { case "application/pdf": @@ -29,6 +36,11 @@ function fileIcon(fileType: string) { case "application/vnd.ms-powerpoint": case "application/msword": return ; + case "application/vnd.ms-excel": + case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + case "text/csv": + case "application/vnd.oasis.opendocument.spreadsheet": + return ; default: return ; } @@ -46,16 +58,34 @@ export default function DocumentUpload({ const { getRootProps, getInputProps } = useDropzone({ accept: { "application/pdf": [], // ".pdf" + "application/vnd.ms-excel": [], // ".xls" + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [], // ".xlsx" + "text/csv": [], // ".csv" + "application/vnd.oasis.opendocument.spreadsheet": [], // ".ods" }, multiple: false, maxSize: maxSize * 1024 * 1024, // 30 MB onDropAccepted: (acceptedFiles) => { const file = acceptedFiles[0]; + const fileType = file.type; + const fileSizeLimit = fileSizeLimits[fileType] * 1024 * 1024; + + if (file.size > fileSizeLimit) { + toast.error( + `File size too big for ${fileType} (max. ${fileSizeLimits[fileType]} MB)`, + ); + return; + } + + if (file.type !== "application/pdf") { + setCurrentFile(file); + return; + } file .arrayBuffer() .then((buffer) => { getPagesCount(buffer).then((numPages) => { - if (numPages > 100) { + if (numPages > 250) { toast.error("File has too many pages (max. 100)"); } else { setCurrentFile(file); diff --git a/components/documents/add-document-modal.tsx b/components/documents/add-document-modal.tsx index c37a62b52..3027938e5 100644 --- a/components/documents/add-document-modal.tsx +++ b/components/documents/add-document-modal.tsx @@ -29,6 +29,7 @@ import { } from "@/lib/documents/create-document"; import { putFile } from "@/lib/files/put-file"; import { copyToClipboard } from "@/lib/utils"; +import { getSupportedContentType } from "@/lib/utils/get-content-type"; export function AddDocumentModal({ newVersion, @@ -69,6 +70,16 @@ export function AddDocumentModal({ try { setUploading(true); + const contentType = getSupportedContentType(currentFile.type); + + if (!contentType) { + setUploading(false); + toast.error( + "Unsupported file format. Please upload a PDF or Excel file.", + ); + return; + } + const { type, data, numPages } = await putFile({ file: currentFile, teamId, @@ -78,6 +89,7 @@ export function AddDocumentModal({ name: currentFile.name, key: data!, storageType: type!, + contentType: contentType, }; let response: Response | undefined; // create a document or new version in the database @@ -115,7 +127,7 @@ export function AddDocumentModal({ name: document.name, numPages: document.numPages, path: router.asPath, - type: "pdf", + type: document.type, teamId: teamId, dataroomId: dataroomId, }); @@ -137,7 +149,7 @@ export function AddDocumentModal({ name: document.name, numPages: document.numPages, path: router.asPath, - type: "pdf", + type: document.type, teamId: teamId, }); analytics.capture("Link Added", { @@ -157,7 +169,7 @@ export function AddDocumentModal({ name: document.name, numPages: document.numPages, path: router.asPath, - type: "pdf", + type: document.type, newVersion: true, teamId: teamId, }); diff --git a/components/documents/document-card.tsx b/components/documents/document-card.tsx index b387e0a02..a401383af 100644 --- a/components/documents/document-card.tsx +++ b/components/documents/document-card.tsx @@ -1,10 +1,16 @@ import Image from "next/image"; import Link from "next/link"; +import { useRouter } from "next/router"; import { useEffect, useRef, useState } from "react"; import { TeamContextType } from "@/context/team-context"; -import { FolderInputIcon, MoreVertical, TrashIcon } from "lucide-react"; +import { + FolderInputIcon, + Layers2Icon, + MoreVertical, + TrashIcon, +} from "lucide-react"; import { useTheme } from "next-themes"; import { toast } from "sonner"; import { mutate } from "swr"; @@ -37,6 +43,7 @@ export default function DocumentsCard({ document: prismaDocument, teamInfo, }: DocumentsCardProps) { + const router = useRouter(); const { theme, systemTheme } = useTheme(); const isLight = theme === "light" || (theme === "system" && systemTheme === "light"); @@ -47,6 +54,9 @@ export default function DocumentsCard({ const [moveFolderOpen, setMoveFolderOpen] = useState(false); const dropdownRef = useRef(null); + /** current folder name */ + const currentFolderPath = router.query.name as string[] | undefined; + function handleCopyToClipboard(id: string) { copyToClipboard( `${process.env.NEXT_PUBLIC_BASE_URL}/view/${id}`, @@ -125,6 +135,30 @@ export default function DocumentsCard({ } }; + const handleDuplicateDocument = async (event: any) => { + event.stopPropagation(); + event.preventDefault(); + + toast.promise( + fetch( + `/api/teams/${teamInfo?.currentTeam?.id}/documents/${prismaDocument.id}/duplicate`, + { + method: "POST", + }, + ).then(() => { + mutate(`/api/teams/${teamInfo?.currentTeam?.id}/documents`); + mutate( + `/api/teams/${teamInfo?.currentTeam?.id}/folders/documents/${currentFolderPath?.join("/")}`, + ); + }), + { + loading: "Duplicating document...", + success: "Document duplicated successfully.", + error: "Failed to duplicate document. Try again.", + }, + ); + }; + return ( <>
  • @@ -218,6 +252,10 @@ export default function DocumentsCard({ Move to folder + handleDuplicateDocument(e)}> + + Duplicate document + handleButtonClick(event, prismaDocument.id)} diff --git a/components/documents/document-header.tsx b/components/documents/document-header.tsx index 3396d5ac7..5506c5e88 100644 --- a/components/documents/document-header.tsx +++ b/components/documents/document-header.tsx @@ -27,8 +27,10 @@ import { } from "@/components/ui/dropdown-menu"; import { DocumentWithLinksAndLinkCountAndViewCount } from "@/lib/types"; -import { getExtension } from "@/lib/utils"; +import { cn, getExtension } from "@/lib/utils"; +import PortraitLandscape from "../shared/icons/portrait-landscape"; +import LoadingSpinner from "../ui/loading-spinner"; import { AddDocumentModal } from "./add-document-modal"; export default function DocumentHeader({ @@ -51,6 +53,7 @@ export default function DocumentHeader({ const [isEditingName, setIsEditingName] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const [isFirstClick, setIsFirstClick] = useState(false); + const [orientationLoading, setOrientationLoading] = useState(false); const nameRef = useRef(null); const enterPressedRef = useRef(false); @@ -168,6 +171,44 @@ export default function DocumentHeader({ }); }; + const changeDocumentOrientation = async () => { + setOrientationLoading(true); + try { + const response = await fetch( + "/api/teams/" + + teamId + + "/documents/" + + prismaDocument.id + + "/change-orientation", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + versionId: primaryVersion.id, + isVertical: primaryVersion.isVertical ? false : true, + }), + }, + ); + + if (response.ok) { + const { message } = await response.json(); + toast.success(message); + + mutate(`/api/teams/${teamId}/documents/${prismaDocument.id}`); + } else { + const { message } = await response.json(); + toast.error(message); + } + } catch (error) { + console.error("Error:", error); + toast.error("An error occurred. Please try again."); + } finally { + setOrientationLoading(false); + } + }; + useEffect(() => { function handleClickOutside(event: { target: any }) { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { @@ -250,7 +291,7 @@ export default function DocumentHeader({ ) : (
    File icon
    + {!orientationLoading ? ( + + ) : ( +
    + +
    + )} + {primaryVersion.type !== "notion" && ( diff --git a/components/emails/dataroom-notification.tsx b/components/emails/dataroom-notification.tsx index 8518bdbd0..1810644a0 100644 --- a/components/emails/dataroom-notification.tsx +++ b/components/emails/dataroom-notification.tsx @@ -5,12 +5,12 @@ import { Button, Container, Head, - Text, Hr, Html, Preview, Section, Tailwind, + Text, } from "@react-email/components"; export default function DataroomNotification({ diff --git a/components/emails/viewed-dataroom.tsx b/components/emails/viewed-dataroom.tsx new file mode 100644 index 000000000..82953b9bb --- /dev/null +++ b/components/emails/viewed-dataroom.tsx @@ -0,0 +1,89 @@ +import React from "react"; + +import { + Body, + Button, + Container, + Head, + Hr, + Html, + Preview, + Section, + Tailwind, + Text, +} from "@react-email/components"; + +export default function ViewedDataroom({ + dataroomId = "123", + dataroomName = "Example Dataroom", + viewerEmail, +}: { + dataroomId: string; + dataroomName: string; + viewerEmail: string | null; +}) { + return ( + + + See who visited your dataroom + + + + + Papermark + + + New Dataroom Visitor + + + Your dataroom{" "} + {dataroomName} was just + viewed by{" "} + + {viewerEmail ? `${viewerEmail}` : `someone`} + + . + + + You can get the detailed engagement analytics like time-spent per + document page and total duration for this dataroom on Papermark. + +
    + +
    + + Stay informed, stay ahead with Papermark. + +
    +
    + + © {new Date().getFullYear()}{" "} + + papermark.io + + + + If you have any feedback or questions about this email, simply + reply to it. I'd love to hear from you! + + + To stop email notifications for this link, edit the link and + uncheck "Receive email notification". + +
    +
    + +
    + + ); +} diff --git a/components/links/link-sheet/agreement-panel/index.tsx b/components/links/link-sheet/agreement-panel/index.tsx new file mode 100644 index 000000000..8dbd5eecc --- /dev/null +++ b/components/links/link-sheet/agreement-panel/index.tsx @@ -0,0 +1,229 @@ +import { + Dispatch, + FormEvent, + SetStateAction, + useEffect, + useState, +} from "react"; + +import { useTeam } from "@/context/team-context"; +import { toast } from "sonner"; +import { mutate } from "swr"; + +import DocumentUpload from "@/components/document-upload"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; + +import { + DocumentData, + createAgreementDocument, +} from "@/lib/documents/create-document"; +import { putFile } from "@/lib/files/put-file"; +import { getSupportedContentType } from "@/lib/utils/get-content-type"; + +export default function AgreementSheet({ + isOpen, + setIsOpen, +}: { + isOpen: boolean; + setIsOpen: Dispatch>; +}) { + const teamInfo = useTeam(); + const teamId = teamInfo?.currentTeam?.id; + const [data, setData] = useState({ name: "", link: "" }); + const [isLoading, setIsLoading] = useState(false); + const [currentFile, setCurrentFile] = useState(null); + const [currentLink, setCurrentLink] = useState(null); + + const handleBrowserUpload = async () => { + // event.preventDefault(); + // event.stopPropagation(); + + // Check if the file is chosen + if (!currentFile) { + toast.error("Please select a file to upload."); + return; // prevent form from submitting + } + + try { + setIsLoading(true); + + const contentType = getSupportedContentType(currentFile.type); + + if (!contentType || currentFile.type !== "application/pdf") { + toast.error("Unsupported file format. Please upload a PDF file."); + return; + } + + const { type, data, numPages } = await putFile({ + file: currentFile, + teamId: teamId!, + }); + + const documentData: DocumentData = { + name: currentFile.name, + key: data!, + storageType: type!, + contentType: contentType, + }; + // create a document in the database + const response = await createAgreementDocument({ + documentData, + teamId: teamId!, + numPages, + }); + + if (response) { + const document = await response.json(); + const linkId = document.links[0].id; + setData((prevData) => ({ + ...prevData, + link: "https://www.papermark.io/view/" + linkId, + })); + } + } catch (error) { + console.error("An error occurred while uploading the file: ", error); + } finally { + setCurrentFile(null); + setIsLoading(false); + } + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + e.stopPropagation(); + + setIsLoading(true); + + try { + const response = await fetch(`/api/teams/${teamId}/agreements`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...data, + }), + }); + + if (!response.ok) { + // handle error with toast message + toast.error("Error creating agreement"); + return; + } + + // Update the agreements list + mutate(`/api/teams/${teamId}/agreements`); + } catch (error) { + console.error(error); + toast.error("An error occurred. Please try again."); + } finally { + setIsLoading(false); + setIsOpen(false); + setData({ name: "", link: "" }); + } + }; + + useEffect(() => { + if (currentFile) { + handleBrowserUpload(); + } + }, [currentFile]); + + return ( + + + + Create a new agreement + + An agreement is a special document that visitors must accept before + accessing your link. You can create a new agreement here. + + + +
    +
    +
    + + + setData({ + ...data, + name: e.target.value, + }) + } + /> +
    + +
    +
    + + + setData({ + ...data, + link: e.target.value, + }) + } + onInvalid={(e) => + e.currentTarget.setCustomValidity( + "Please enter a valid URL starting with https://", + ) + } + /> +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + ); +} diff --git a/components/links/link-sheet/agreement-section.tsx b/components/links/link-sheet/agreement-section.tsx new file mode 100644 index 000000000..cd7c81d3e --- /dev/null +++ b/components/links/link-sheet/agreement-section.tsx @@ -0,0 +1,153 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; + +import { motion } from "framer-motion"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; + +import { FADE_IN_ANIMATION_SETTINGS } from "@/lib/constants"; +import { useAgreements } from "@/lib/swr/use-agreements"; +import { cn } from "@/lib/utils"; + +import { DEFAULT_LINK_TYPE } from "."; +import AgreementSheet from "./agreement-panel"; + +export default function AgreementSection({ + data, + setData, + hasFreePlan, + handleUpgradeStateChange, +}: { + data: DEFAULT_LINK_TYPE; + setData: Dispatch>; + hasFreePlan: boolean; + handleUpgradeStateChange: ( + state: boolean, + trigger: string, + plan?: "Pro" | "Business" | "Data Rooms", + ) => void; +}) { + const { agreements } = useAgreements(); + const { enableAgreement, agreementId, emailProtected } = data; + const [enabled, setEnabled] = useState(false); + const [isAgreementSheetVisible, setIsAgreementSheetVisible] = + useState(false); + + useEffect(() => { + setEnabled(enableAgreement!); + }, [enableAgreement]); + + const handleAgreement = async () => { + const updatedAgreement = !enabled; + + setData({ + ...data, + enableAgreement: updatedAgreement, + emailProtected: updatedAgreement ? true : emailProtected, + }); + setEnabled(updatedAgreement); + }; + + const handleAgreementChange = (value: string) => { + if (value === "add_agreement") { + // Open the agreement sheet + setIsAgreementSheetVisible(true); + return; + } + + setData({ ...data, agreementId: value }); + }; + + return ( +
    +
    +
    +

    + handleUpgradeStateChange( + true, + "link_sheet_agreement_section", + "Data Rooms", + ) + : undefined + } + > + Require NDA to view + {/* + + */} + {hasFreePlan && ( + + Datarooms + + )} +

    +
    + + handleUpgradeStateChange( + true, + "link_sheet_agreement_section", + "Data Rooms", + ) + : undefined + } + className={hasFreePlan ? "opacity-50" : undefined} + onCheckedChange={hasFreePlan ? undefined : handleAgreement} + /> +
    + + {enabled && ( + +
    +
    + +
    +
    +
    + )} + + +
    + ); +} diff --git a/components/links/link-sheet/email-protection-section.tsx b/components/links/link-sheet/email-protection-section.tsx index db9c8447f..aa64ae87e 100644 --- a/components/links/link-sheet/email-protection-section.tsx +++ b/components/links/link-sheet/email-protection-section.tsx @@ -26,6 +26,7 @@ export default function EmailProtectionSection({ ...data, emailProtected: updatedEmailProtection, emailAuthenticated: !updatedEmailProtection && false, + enableAgreement: !updatedEmailProtection && false, }); setEnabled(updatedEmailProtection); }; diff --git a/components/links/link-sheet/index.tsx b/components/links/link-sheet/index.tsx index d3ef18aed..89c870e89 100644 --- a/components/links/link-sheet/index.tsx +++ b/components/links/link-sheet/index.tsx @@ -51,6 +51,8 @@ export const DEFAULT_LINK_PROPS = { enabledQuestion: false, questionText: null, questionType: null, + enableAgreement: false, + agreementId: null, }; export type DEFAULT_LINK_TYPE = { @@ -75,6 +77,8 @@ export type DEFAULT_LINK_TYPE = { enableQuestion?: boolean; // feedback question questionText: string | null; questionType: string | null; + enableAgreement: boolean; // agreement + agreementId: string | null; }; export default function LinkSheet({ @@ -196,7 +200,7 @@ export default function LinkSheet({ return ( setIsOpen(open)}> - + {currentLink ? "Edit link" : "Create a new link"} diff --git a/components/links/link-sheet/link-options.tsx b/components/links/link-sheet/link-options.tsx index 5ba0ce29f..ac7a12b85 100644 --- a/components/links/link-sheet/link-options.tsx +++ b/components/links/link-sheet/link-options.tsx @@ -21,6 +21,7 @@ import { import { usePlan } from "@/lib/swr/use-billing"; +import AgreementSection from "./agreement-section"; import QuestionSection from "./question-section"; import ScreenshotProtectionSection from "./screenshot-protection-section"; @@ -101,6 +102,11 @@ export const LinkOptions = ({ hasFreePlan={isNotBusiness && isNotDatarooms} handleUpgradeStateChange={handleUpgradeStateChange} /> + diff --git a/components/links/links-table.tsx b/components/links/links-table.tsx index 416d68dae..1bc77d7c3 100644 --- a/components/links/links-table.tsx +++ b/components/links/links-table.tsx @@ -97,6 +97,8 @@ export default function LinksTable({ metaTitle: link.metaTitle, metaDescription: link.metaDescription, metaImage: link.metaImage, + enableAgreement: link.enableAgreement ? link.enableAgreement : false, + agreementId: link.agreementId, }); //wait for dropdown to close before opening the link sheet setTimeout(() => { @@ -104,6 +106,39 @@ export default function LinksTable({ }, 0); }; + const handleDuplicateLink = async (link: LinkWithViews) => { + setIsLoading(true); + + const response = await fetch( + `/api/links/${link.id}/duplicate?teamId=${teamInfo?.currentTeam?.id}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }, + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const duplicatedLink = await response.json(); + const endpointTargetType = `${targetType.toLowerCase()}s`; // "documents" or "datarooms" + + // Update the duplicated link in the list of links + mutate( + `/api/teams/${teamInfo?.currentTeam?.id}/${endpointTargetType}/${encodeURIComponent( + link.documentId ?? link.dataroomId ?? "", + )}/links`, + (links || []).concat(duplicatedLink), + false, + ); + + toast.success("Link duplicated successfully"); + setIsLoading(false); + }; + const handleArchiveLink = async ( linkId: string, targetId: string, @@ -199,7 +234,7 @@ export default function LinksTable({ > {/* Progress bar */} {primaryVersion && - primaryVersion.type !== "notion" && + primaryVersion.type === "pdf" && !primaryVersion.hasPages ? ( - @@ -301,6 +339,11 @@ export default function LinksTable({ > Edit Link + handleDuplicateLink(link)} + > + Duplicate Link + diff --git a/components/mdx/components/feature-table.tsx b/components/mdx/components/feature-table.tsx new file mode 100644 index 000000000..ea251fa76 --- /dev/null +++ b/components/mdx/components/feature-table.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +type Column = { + header: string; + key: string; +}; + +type Row = { + [key: string]: string; +}; + +type TableProps = { + columns: Column[]; + rows: Row[]; +}; + +export const Table: React.FC = ({ columns, rows }) => { + return ( +
    +
    +
    + + + + {columns.map((column) => ( + + ))} + + + + {rows.map((row, rowIndex) => ( + + {columns.map((column) => ( + + ))} + + ))} + +
    + {column.header} +
    + {row[column.key]} +
    +
    +
    +
    + ); +}; diff --git a/components/mdx/components/index.tsx b/components/mdx/components/index.tsx index 674811ebc..3dd7f39d4 100644 --- a/components/mdx/components/index.tsx +++ b/components/mdx/components/index.tsx @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; +import { Table } from "./feature-table"; import { MDXImage } from "./mdx-image"; export const mdxComponents: MDXComponents = { @@ -85,5 +86,11 @@ export const mdxComponents: MDXComponents = { {children} ), + Table: ({ columns, rows, ...props }) => { + if (!columns || !rows || columns.length === 0 || rows.length === 0) + return null; + + return ; + }, // any other components you want to use in your markdown }; diff --git a/components/shared/icons/portrait-landscape.tsx b/components/shared/icons/portrait-landscape.tsx new file mode 100644 index 000000000..e40e71ee7 --- /dev/null +++ b/components/shared/icons/portrait-landscape.tsx @@ -0,0 +1,26 @@ +export default function PortraitLandscape({ + className, + fill, +}: { + className?: string; + fill?: string; +}) { + return ( + + + + + + ); +} diff --git a/components/sidebar-folders.tsx b/components/sidebar-folders.tsx index b1c98d754..533cfee11 100644 --- a/components/sidebar-folders.tsx +++ b/components/sidebar-folders.tsx @@ -5,6 +5,9 @@ import { memo, useMemo } from "react"; import { FileTree } from "@/components/ui/nextra-filetree"; import { FolderWithDocuments, useFolders } from "@/lib/swr/use-documents"; +import { cn } from "@/lib/utils"; + +import { TSelectedFolder } from "./documents/move-folder-modal"; // Helper function to build nested folder structure const buildNestedFolderStructure = (folders: FolderWithDocuments[]) => { @@ -54,13 +57,31 @@ const FolderComponent = memo(({ folder }: { folder: FolderWithDocuments }) => { [folder.childFolders], ); + const isActive = + folder.path === "/" + (router.query.name as string[])?.join("/"); + const isChildActive = folder.childFolders.some( + (childFolder) => + childFolder.path === "/" + (router.query.name as string[])?.join("/"), + ); + + const handleFolderClick = () => { + router.push( + `/documents/tree${folder.path}`, + `/documents/tree${folder.path}`, + { + scroll: false, + }, + ); + }; + return ( router.push(`/documents/tree${folder.path}`)} + active={isActive} + childActive={isChildActive} + onToggle={handleFolderClick} + className={cn("hover:bg-gray-200", isActive && "bg-gray-200")} > {childFolders} {documents} @@ -72,12 +93,12 @@ FolderComponent.displayName = "FolderComponent"; const FolderComponentSelection = memo( ({ folder, - selectedFolderId, - setFolderId, + selectedFolder, + setSelectedFolder, }: { folder: FolderWithDocuments; - selectedFolderId: string; - setFolderId: React.Dispatch>; + selectedFolder: TSelectedFolder; + setSelectedFolder: React.Dispatch>; }) => { // Recursively render child folders if they exist const childFolders = useMemo( @@ -86,11 +107,16 @@ const FolderComponentSelection = memo( )), - [folder.childFolders, selectedFolderId, setFolderId], + [folder.childFolders, selectedFolder, setSelectedFolder], + ); + + const isActive = folder.id === selectedFolder?.id; + const isChildActive = folder.childFolders.some( + (childFolder) => childFolder.id === selectedFolder?.id, ); return ( @@ -98,13 +124,17 @@ const FolderComponentSelection = memo( onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setFolderId(folder.id); + setSelectedFolder({ id: folder.id, name: folder.name }); }} > + setSelectedFolder({ id: folder.id, name: folder.name }) + } > {childFolders} @@ -139,14 +169,14 @@ export default function SidebarFolderTree() { return ; } -const SidebarFoldersSelectipon = ({ +const SidebarFoldersSelection = ({ folders, - selectedFolderId, - setFolderId, + selectedFolder, + setSelectedFolder, }: { folders: FolderWithDocuments[]; - selectedFolderId: string; - setFolderId: React.Dispatch>; + selectedFolder: TSelectedFolder; + setSelectedFolder: React.Dispatch>; }) => { const nestedFolders = useMemo(() => { if (folders) { @@ -161,8 +191,8 @@ const SidebarFoldersSelectipon = ({ ))} @@ -170,21 +200,21 @@ const SidebarFoldersSelectipon = ({ }; export function SidebarFolderTreeSelection({ - selectedFolderId, - setFolderId, + selectedFolder, + setSelectedFolder, }: { - selectedFolderId: string; - setFolderId: React.Dispatch>; + selectedFolder: TSelectedFolder; + setSelectedFolder: React.Dispatch>; }) { const { folders, error } = useFolders(); if (!folders || error) return null; return ( - ); } diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 000000000..db1f232b1 --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,91 @@ +import { InputHTMLAttributes, ReactNode, useMemo, useState } from "react"; + +import { cn } from "@/lib/utils"; + +import { Button } from "./button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "./card"; +import { Input } from "./input"; + +export function Form({ + title, + description, + inputAttrs, + helpText, + buttonText = "Save Changes", + disabledTooltip, + handleSubmit, +}: { + title: string; + description: string; + inputAttrs: InputHTMLAttributes; + helpText?: string | ReactNode; + buttonText?: string; + disabledTooltip?: string | ReactNode; + handleSubmit: (data: any) => Promise; +}) { + const [value, setValue] = useState(inputAttrs.defaultValue); + const [saving, setSaving] = useState(false); + const saveDisabled = useMemo(() => { + return saving || !value || value === inputAttrs.defaultValue; + }, [saving, value, inputAttrs.defaultValue]); + + return ( +
    { + e.preventDefault(); + setSaving(true); + await handleSubmit({ + [inputAttrs.name as string]: value, + }); + setSaving(false); + }} + className="rounded-lg bg-background" + > + + + {title} + {description} + + + {typeof inputAttrs.defaultValue === "string" ? ( + setValue(e.target.value)} + className={cn( + "w-full max-w-md focus:border-gray-500 focus:outline-none focus:ring-gray-500 ", + { + "cursor-not-allowed bg-gray-100 text-gray-400": + disabledTooltip, + }, + )} + data-1p-ignore + /> + ) : ( +
    + )} + + +

    + {helpText || ""} +

    + +
    + +
    +
    + + + ); +} diff --git a/components/ui/nextra-filetree.tsx b/components/ui/nextra-filetree.tsx index 2bd402f7d..d985342e1 100644 --- a/components/ui/nextra-filetree.tsx +++ b/components/ui/nextra-filetree.tsx @@ -1,23 +1,29 @@ +"use client"; + /** * This component is based on the nextra's filetree component from @shuding * https://github.com/shuding/nextra/blob/main/packages/nextra/src/components/file-tree.tsx * */ -import { createContext, memo, useCallback, useContext, useState } from "react"; +import { + createContext, + memo, + useCallback, + useContext, + useEffect, + useState, +} from "react"; import type { ReactElement, ReactNode } from "react"; import { + ChevronRightIcon, FileIcon, - FolderClosedIcon, FolderIcon, FolderOpenIcon, } from "lucide-react"; import { cn } from "@/lib/utils"; -import ChevronDown from "../shared/icons/chevron-down"; -import ChevronRight from "../shared/icons/chevron-right"; - const ctx = createContext(0); function useIndent() { @@ -32,6 +38,7 @@ interface FolderProps { active?: boolean; childActive?: boolean; onToggle?: (open: boolean) => void; + className?: string; children: ReactNode; } @@ -71,50 +78,60 @@ const Folder = memo( childActive, defaultOpen = false, onToggle, + className, }) => { const indent = useIndent(); - const [isOpen, setIsOpen] = useState(defaultOpen || active || childActive); + const [isOpen, setIsOpen] = useState(defaultOpen || childActive); + + useEffect(() => { + if (childActive) { + setIsOpen(true); + } + }, [childActive]); + + const handleFolderClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + onToggle?.(!isOpen); + }, + [isOpen, onToggle], + ); - const toggle = useCallback(() => { - onToggle?.(!isOpen); - setIsOpen(!isOpen); - }, [isOpen, onToggle]); + const handleChevronClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + setIsOpen(!isOpen); + }, + [isOpen], + ); const isFolderOpen = open === undefined ? isOpen : open; return (
  • -
  • {isFolderOpen ? (
    ";var n=document.createElement("CAPTION");n.innerHTML="c
    c
    c
    c",n.style.padding="0",n.style.margin="0",e.insertBefore(n,t),document.body.appendChild(e),r=e.offsetHeight<2*e.lastChild.offsetHeight,document.body.removeChild(e)}();return r},t.getComparisonFunction=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(i)return i;i="object"===("undefined"==typeof Intl?"undefined":o(Intl))?new Intl.Collator(e,t).compare:"function"==typeof String.prototype.localeCompare?function(e,t){return"".concat(e).localeCompare(t)}:function(e,t){return e===t?0:e>t?-1:1};return i};for(var r,i,a=0,s=["ms","moz","webkit","o"],l=window.requestAnimationFrame,u=window.cancelAnimationFrame,c=0;c1&&void 0!==arguments[1]?arguments[1]:{};return"".concat(e).replace(/(?:\\)?\[([^[\]]+)]/g,function(e,n){return"\\"===e.charAt(0)?e.substr(1,e.length-1):void 0===t[n]?"":t[n]})},t.stripTags=function(e){return"".concat(e).replace(r,"")};var o=n(14);var r=/<\/?\w+\/?>|<\w+[\s|/][^>]*>/gi},function(e,t,n){"use strict";t.__esModule=!0,t.getValidator=function(e){if("function"==typeof e)return e;if(!h(e))throw Error('No registered validator found under "'.concat(e,'" name'));return f(e)},t.getRegisteredValidators=t.getRegisteredValidatorNames=t.hasValidator=t.registerValidator=void 0;var o=l(n(46)),r=l(n(631)),i=l(n(632)),a=l(n(633)),s=l(n(634));function l(e){return e&&e.__esModule?e:{default:e}}var u=(0,o.default)("validators"),c=u.register,f=u.getItem,h=u.hasItem,d=u.getNames,p=u.getValues;t.getRegisteredValidators=p,t.getRegisteredValidatorNames=d,t.hasValidator=h,t.registerValidator=c,c("autocomplete",r.default),c("date",i.default),c("numeric",a.default),c("time",s.default)},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t){var n=0,o=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+o).toString(36))}},function(e,t){e.exports=!1},function(e,t,n){var o=n(132),r=n(96);e.exports=Object.keys||function(e){return o(e,r)}},function(e,t,n){var o=n(33),r=Math.max,i=Math.min;e.exports=function(e,t){return(e=o(e))<0?r(e+t,0):i(e,t)}},function(e,t,n){var o=n(5),r=n(133),i=n(96),a=n(95)("IE_PROTO"),s=function(){},l=function(){var e,t=n(92)("iframe"),o=i.length;for(t.style.display="none",n(98).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write("
    t
    t