├── .cursor └── rules │ ├── rule-claude-sonnet-37.mdc │ └── rule-trigger-typescript.mdc ├── .cursorignore ├── .env.example ├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── oss-gg-hack-template.yml ├── images │ ├── papermark-logo.svg │ └── papermark-welcome.gif └── workflows │ └── cla.yml ├── .gitignore ├── .prettierignore ├── .vercelignore ├── CLA.md ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── app ├── (auth) │ ├── auth │ │ └── confirm-email-change │ │ │ └── [token] │ │ │ ├── page-client.tsx │ │ │ ├── page.tsx │ │ │ └── utils.ts │ ├── layout.tsx │ ├── login │ │ ├── page-client.tsx │ │ └── page.tsx │ ├── register │ │ ├── page-client.tsx │ │ └── page.tsx │ └── verify │ │ ├── invitation │ │ ├── AcceptInvitationButton.tsx │ │ ├── InvitationStatusContent.tsx │ │ ├── page.tsx │ │ └── status │ │ │ └── ClientRedirect.tsx │ │ └── page.tsx ├── api │ ├── cron │ │ ├── domains │ │ │ ├── route.ts │ │ │ └── utils.ts │ │ ├── upgrade │ │ │ └── route.ts │ │ └── year-in-review │ │ │ └── route.ts │ ├── csp-report │ │ └── route.ts │ ├── feature-flags │ │ └── route.ts │ ├── help │ │ └── route.ts │ ├── links │ │ └── [id] │ │ │ └── upload │ │ │ └── route.ts │ ├── og │ │ ├── route.tsx │ │ └── yir │ │ │ └── route.tsx │ ├── views-dataroom │ │ └── route.ts │ ├── views │ │ └── route.ts │ └── webhooks │ │ └── callback │ │ └── route.ts ├── layout.tsx └── robots.txt ├── components.json ├── components ├── EmailForm.tsx ├── Skeleton.tsx ├── account │ ├── account-header.tsx │ ├── update-subscription.tsx │ └── upload-avatar.tsx ├── agreements │ └── agreement-card.tsx ├── analytics │ ├── analytics-card.tsx │ ├── dashboard-views-chart.tsx │ ├── documents-table.tsx │ ├── links-table.tsx │ ├── time-range-select.tsx │ ├── views-table.tsx │ └── visitors-table.tsx ├── billing │ ├── add-seat-modal.tsx │ ├── plan-badge.tsx │ ├── pro-annual-banner.tsx │ ├── pro-banner.tsx │ ├── upgrade-plan-container.tsx │ ├── upgrade-plan-modal-old.tsx │ └── upgrade-plan-modal.tsx ├── blur-image.tsx ├── charts │ ├── bar-chart-tooltip.tsx │ ├── bar-chart.tsx │ └── utils.ts ├── chat │ ├── chat-input.tsx │ ├── chat-list.tsx │ ├── chat-message-actions.tsx │ ├── chat-message.tsx │ ├── chat-scroll-anchor.tsx │ ├── chat.tsx │ └── empty-screen.tsx ├── conversations │ └── index.tsx ├── datarooms │ ├── actions │ │ ├── download-dataroom.tsx │ │ ├── generate-index-button.tsx │ │ ├── generate-index-dialog.tsx │ │ └── remove-document-modal.tsx │ ├── add-dataroom-modal.tsx │ ├── add-viewer-modal.tsx │ ├── analytics │ │ ├── analytics-overview.tsx │ │ ├── document-analytics-tree.tsx │ │ └── mock-analytics-table.tsx │ ├── dataroom-breadcrumb.tsx │ ├── dataroom-document-card.tsx │ ├── dataroom-header.tsx │ ├── dataroom-items-list.tsx │ ├── dataroom-navigation.tsx │ ├── dataroom-trial-modal.tsx │ ├── empty-dataroom.tsx │ ├── folders │ │ ├── index.tsx │ │ ├── selection-tree.tsx │ │ ├── sidebar-tree.tsx │ │ ├── utils.ts │ │ └── view-tree.tsx │ ├── groups │ │ ├── add-group-modal.tsx │ │ ├── add-member-modal.tsx │ │ ├── delete-group │ │ │ ├── delete-group-modal.tsx │ │ │ └── index.tsx │ │ ├── group-card-placeholder.tsx │ │ ├── group-card.tsx │ │ ├── group-header.tsx │ │ ├── group-member-table.tsx │ │ ├── group-navigation.tsx │ │ ├── group-permissions.tsx │ │ └── set-group-permissions-modal.tsx │ ├── move-dataroom-folder-modal.tsx │ ├── settings │ │ ├── delete-dataroooom │ │ │ ├── delete-dataroom-modal.tsx │ │ │ └── index.tsx │ │ ├── duplicate-dataroom.tsx │ │ ├── notification-settings.tsx │ │ └── settings-tabs.tsx │ ├── sortable │ │ ├── sortable-item.tsx │ │ └── sortable-list.tsx │ └── stats-card.tsx ├── document-upload.tsx ├── documents │ ├── actions │ │ ├── delete-documents-modal.tsx │ │ └── delete-folder-modal.tsx │ ├── add-document-modal.tsx │ ├── add-document-to-dataroom-modal.tsx │ ├── add-folder-to-dataroom-modal.tsx │ ├── alert.tsx │ ├── breadcrumb.tsx │ ├── delete-folder-modal.tsx │ ├── document-card.tsx │ ├── document-header.tsx │ ├── documents-list.tsx │ ├── drag-and-drop │ │ ├── draggable-item.tsx │ │ └── droppable-folder.tsx │ ├── empty-document.tsx │ ├── file-process-status-bar.tsx │ ├── filters │ │ └── sort-button.tsx │ ├── folder-card.tsx │ ├── loading-document.tsx │ ├── move-folder-modal.tsx │ ├── pagination.tsx │ ├── stats-card.tsx │ ├── stats-chart-dummy.tsx │ ├── stats-chart-skeleton.tsx │ ├── stats-chart.tsx │ ├── stats-element.tsx │ ├── stats.tsx │ ├── video-analytics.tsx │ └── video-chart-placeholder.tsx ├── domains │ ├── add-domain-modal.tsx │ ├── delete-domain-modal.tsx │ ├── domain-card.tsx │ ├── domain-configuration.tsx │ └── use-domain-status.ts ├── emails │ ├── dataroom-notification.tsx │ ├── dataroom-trial-end.tsx │ ├── dataroom-trial-welcome.tsx │ ├── dataroom-viewer-invitation.tsx │ ├── deleted-domain.tsx │ ├── email-updated.tsx │ ├── email-verification.tsx │ ├── invalid-domain.tsx │ ├── onboarding-1.tsx │ ├── onboarding-2.tsx │ ├── onboarding-3.tsx │ ├── onboarding-4.tsx │ ├── onboarding-5.tsx │ ├── otp-verification.tsx │ ├── team-invitation.tsx │ ├── trial-end-final-reminder.tsx │ ├── trial-end-reminder.tsx │ ├── upgrade-plan.tsx │ ├── verification-email-change.tsx │ ├── verification-link.tsx │ ├── viewed-dataroom.tsx │ ├── viewed-document.tsx │ ├── welcome.tsx │ └── year-in-review-papermark.tsx ├── folders │ ├── add-folder-modal.tsx │ └── edit-folder-modal.tsx ├── hooks │ ├── use-optimistic-update.ts │ └── useLastUsed.tsx ├── layouts │ ├── app.tsx │ ├── blocking-modal.tsx │ ├── breadcrumb.tsx │ └── trial-banner.tsx ├── links │ ├── embed-code-modal.tsx │ ├── link-active-controls.tsx │ ├── link-sheet │ │ ├── agreement-panel │ │ │ └── index.tsx │ │ ├── agreement-section.tsx │ │ ├── allow-download-section.tsx │ │ ├── allow-list-section.tsx │ │ ├── allow-notification-section.tsx │ │ ├── conversation-section.tsx │ │ ├── custom-fields-panel │ │ │ ├── custom-field.tsx │ │ │ └── index.tsx │ │ ├── custom-fields-section.tsx │ │ ├── deny-list-section.tsx │ │ ├── domain-section.tsx │ │ ├── email-authentication-section.tsx │ │ ├── email-protection-section.tsx │ │ ├── expiration-section.tsx │ │ ├── expirationIn-section.tsx │ │ ├── feedback-section.tsx │ │ ├── index-file-section.tsx │ │ ├── index.tsx │ │ ├── link-item.tsx │ │ ├── link-options.tsx │ │ ├── og-section.tsx │ │ ├── password-section.tsx │ │ ├── pro-banner-section.tsx │ │ ├── question-section.tsx │ │ ├── screenshot-protection-section.tsx │ │ ├── tags │ │ │ ├── tag-badge.tsx │ │ │ ├── tag-details.tsx │ │ │ └── tag-section.tsx │ │ ├── upload-section │ │ │ └── index.tsx │ │ ├── watermark-panel │ │ │ └── index.tsx │ │ └── watermark-section.tsx │ ├── links-table.tsx │ └── links-visitors.tsx ├── navigation-menu.tsx ├── profile-menu.tsx ├── profile-search-trigger.tsx ├── providers │ └── posthog-provider.tsx ├── search-box.tsx ├── search-command.tsx ├── settings │ ├── delete-team-modal.tsx │ ├── delete-team.tsx │ ├── og-preview.tsx │ └── settings-header.tsx ├── shared │ ├── icons │ │ ├── advanced-sheet.tsx │ │ ├── alert-circle.tsx │ │ ├── arrow-up.tsx │ │ ├── badge-check.tsx │ │ ├── bar-chart.tsx │ │ ├── check-cirlce-2.tsx │ │ ├── check.tsx │ │ ├── chevron-down.tsx │ │ ├── chevron-right.tsx │ │ ├── chevron-up.tsx │ │ ├── circle.tsx │ │ ├── cloud-download-off.tsx │ │ ├── copy-right.tsx │ │ ├── copy.tsx │ │ ├── external-link.tsx │ │ ├── eye-off.tsx │ │ ├── eye.tsx │ │ ├── facebook.tsx │ │ ├── file-up.tsx │ │ ├── files │ │ │ ├── cad.tsx │ │ │ ├── docs.tsx │ │ │ ├── image.tsx │ │ │ ├── map.tsx │ │ │ ├── notion.tsx │ │ │ ├── pdf.tsx │ │ │ ├── sheet.tsx │ │ │ ├── slides.tsx │ │ │ └── video.tsx │ │ ├── folder.tsx │ │ ├── github.tsx │ │ ├── globe.tsx │ │ ├── google.tsx │ │ ├── grip-vertical.tsx │ │ ├── home.tsx │ │ ├── index.tsx │ │ ├── linkedin.tsx │ │ ├── menu.tsx │ │ ├── moon.tsx │ │ ├── more-horizontal.tsx │ │ ├── more-vertical.tsx │ │ ├── papermark-sparkle.tsx │ │ ├── passkey.tsx │ │ ├── pie-chart.tsx │ │ ├── portrait-landscape.tsx │ │ ├── producthunt.tsx │ │ ├── search.tsx │ │ ├── settings.tsx │ │ ├── sparkle.tsx │ │ ├── sun.tsx │ │ ├── teams.tsx │ │ ├── twitter.tsx │ │ ├── user-round.tsx │ │ ├── x-circle.tsx │ │ └── x.tsx │ └── logo-cloud.tsx ├── sidebar-folders.tsx ├── sidebar │ ├── app-sidebar.tsx │ ├── nav-main.tsx │ ├── nav-user.tsx │ └── team-switcher.tsx ├── tab-menu.tsx ├── tags │ └── add-tag-modal.tsx ├── teams │ ├── add-team-member-modal.tsx │ ├── add-team-modal.tsx │ ├── delete-team-modal.tsx │ └── select-team.tsx ├── theme-provider.tsx ├── theme-toggle.tsx ├── ui │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── alert.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── bar-list.tsx │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── calendar.tsx │ ├── card.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── devices.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── feature-preview.tsx │ ├── file-upload.tsx │ ├── form.tsx │ ├── gauge.tsx │ ├── input-otp.tsx │ ├── input.tsx │ ├── label.tsx │ ├── loading-dots.module.css │ ├── loading-dots.tsx │ ├── loading-spinner.module.css │ ├── loading-spinner.tsx │ ├── modal.tsx │ ├── multi-select-v2.tsx │ ├── nextra-filetree.tsx │ ├── pagination.tsx │ ├── phone-input.tsx │ ├── popover.tsx │ ├── portal.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── sidebar.tsx │ ├── skeleton.tsx │ ├── smart-date-time-picker.tsx │ ├── sonner.tsx │ ├── status-badge.tsx │ ├── switch.tsx │ ├── tab-select.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ └── tooltip.tsx ├── upload-notification.tsx ├── upload-zone.tsx ├── user-agent-icon.tsx ├── view │ ├── ScreenProtection.tsx │ ├── access-form │ │ ├── agreement-section.tsx │ │ ├── custom-fields-section.tsx │ │ ├── email-section.tsx │ │ ├── email-verification-form.tsx │ │ ├── index.tsx │ │ ├── name-section.tsx │ │ └── password-section.tsx │ ├── conversations │ │ └── sidebar.tsx │ ├── custom-metatag.tsx │ ├── dataroom │ │ ├── dataroom-document-view.tsx │ │ ├── dataroom-view.tsx │ │ ├── document-card.tsx │ │ ├── document-upload-modal.tsx │ │ ├── folder-card.tsx │ │ ├── index-file-dialog.tsx │ │ └── nav-dataroom.tsx │ ├── document-view.tsx │ ├── nav.tsx │ ├── powered-by.tsx │ ├── question.tsx │ ├── report-form.tsx │ ├── toolbar.tsx │ ├── view-data.tsx │ ├── viewer │ │ ├── advanced-excel-viewer.tsx │ │ ├── away-poster.tsx │ │ ├── dataroom-viewer.tsx │ │ ├── download-only-viewer.tsx │ │ ├── excel-viewer.tsx │ │ ├── image-viewer.tsx │ │ ├── notion-page.tsx │ │ ├── pages-horizontal-viewer.tsx │ │ ├── pages-vertical-viewer.tsx │ │ ├── pdf-default-viewer.tsx │ │ ├── video-player.tsx │ │ └── video-viewer.tsx │ ├── visitor-graph.tsx │ └── watermark-svg.tsx ├── viewer-upload-component.tsx ├── viewer-upload-zone.tsx ├── visitors │ ├── contacts-document-table.tsx │ ├── contacts-table.tsx │ ├── data-table-pagination.tsx │ ├── dataroom-viewers.tsx │ ├── dataroom-visitor-custom-fields.tsx │ ├── dataroom-visitor-useragent.tsx │ ├── dataroom-visitors-history.tsx │ ├── dataroom-visitors-table.tsx │ ├── visitor-avatar.tsx │ ├── visitor-chart.tsx │ ├── visitor-clicks.tsx │ ├── visitor-custom-fields.tsx │ ├── visitor-useragent-base.tsx │ ├── visitor-useragent-placeholder.tsx │ ├── visitor-useragent.tsx │ ├── visitor-video-chart.tsx │ └── visitors-table.tsx ├── webhooks │ └── webhook-events.tsx └── welcome │ ├── containers │ ├── link-option-container.tsx │ ├── onboarding-dataroom-link-options.tsx │ ├── onboarding-link-options.tsx │ └── upload-container.tsx │ ├── dataroom-trial.tsx │ ├── dataroom-upload.tsx │ ├── dataroom.tsx │ ├── intro.tsx │ ├── next.tsx │ ├── notion-form.tsx │ ├── select.tsx │ ├── special-upload.tsx │ └── upload.tsx ├── context └── team-context.tsx ├── ee ├── LICENSE ├── features │ └── conversations │ │ ├── api │ │ ├── conversations-route.ts │ │ ├── send-conversation-new-message-notification.ts │ │ ├── team-conversations-route.ts │ │ └── toggle-conversations-route.ts │ │ ├── components │ │ ├── conversation-list-item.tsx │ │ ├── conversation-message.tsx │ │ ├── conversation-view-sidebar.tsx │ │ ├── conversations-not-enabled-banner.tsx │ │ └── link-option-conversation-section.tsx │ │ ├── emails │ │ ├── components │ │ │ └── conversation-notification.tsx │ │ └── lib │ │ │ └── send-conversation-notification.ts │ │ ├── lib │ │ ├── api │ │ │ ├── conversations │ │ │ │ └── index.ts │ │ │ ├── messages │ │ │ │ └── index.ts │ │ │ └── notifications │ │ │ │ └── index.ts │ │ └── trigger │ │ │ └── conversation-message-notification.ts │ │ └── pages │ │ ├── conversation-detail.tsx │ │ └── conversation-overview.tsx ├── limits │ ├── constants.ts │ ├── handler.ts │ ├── server.ts │ └── swr-handler.ts └── stripe │ ├── client.ts │ ├── constants.ts │ ├── functions │ ├── get-price-id-from-plan.ts │ ├── get-quantity-from-plan.ts │ └── get-subscription-item.ts │ ├── index.ts │ ├── utils.ts │ └── webhooks │ ├── checkout-session-completed.ts │ ├── customer-subscription-deleted.ts │ └── customer-subscription-updated.ts ├── lib ├── analytics │ └── index.ts ├── api │ ├── auth │ │ ├── passkey.ts │ │ └── token.ts │ ├── documents │ │ └── process-document.ts │ ├── domains.ts │ ├── links │ │ └── link-data.ts │ ├── notification-helper.ts │ └── views │ │ └── send-webhook-event.ts ├── auth │ ├── dataroom-auth.ts │ └── preview-auth.ts ├── constants.ts ├── cron │ ├── index.ts │ └── verify-qstash.ts ├── dataroom │ └── index-generator.ts ├── documents │ ├── create-document.ts │ ├── get-file-helper.ts │ ├── move-dataroom-documents.ts │ ├── move-dataroom-folders.ts │ ├── move-documents.ts │ └── move-folder.ts ├── domains.ts ├── edge-config │ ├── blacklist.ts │ └── custom-email.ts ├── emails │ ├── send-dataroom-info.ts │ ├── send-dataroom-notification.ts │ ├── send-dataroom-trial-end.ts │ ├── send-dataroom-trial.ts │ ├── send-dataroom-viewer-invite.ts │ ├── send-deleted-domain.ts │ ├── send-email-otp-verification.ts │ ├── send-email-verification.ts │ ├── send-invalid-domain.ts │ ├── send-mail-verification.ts │ ├── send-onboarding.ts │ ├── send-teammate-invite.ts │ ├── send-trial-end-final-reminder.ts │ ├── send-trial-end-reminder.ts │ ├── send-upgrade-plan.ts │ ├── send-verification-request.ts │ ├── send-viewed-dataroom.ts │ ├── send-viewed-document.ts │ └── send-welcome.ts ├── errorHandler.ts ├── featureFlags │ └── index.ts ├── files │ ├── aws-client.ts │ ├── bulk-download.ts │ ├── copy-file-server.ts │ ├── copy-file-to-bucket-server.ts │ ├── delete-file-server.ts │ ├── delete-team-files-server.ts │ ├── get-file.ts │ ├── put-file-server.ts │ ├── put-file.ts │ ├── stream-file-server.ts │ ├── tus-redis-locker.ts │ ├── tus-upload.ts │ └── viewer-tus-upload.ts ├── folders │ └── create-folder.ts ├── hanko.ts ├── hooks │ └── use-mobile.tsx ├── id-helper.ts ├── incoming-webhooks │ └── index.ts ├── middleware │ ├── app.ts │ ├── domain.ts │ ├── incoming-webhooks.ts │ └── posthog.ts ├── notion │ ├── config.ts │ ├── index.ts │ └── utils.ts ├── openai.ts ├── posthog.ts ├── prisma.ts ├── redis.ts ├── resend.ts ├── sheet │ └── index.ts ├── swr │ ├── use-agreements.ts │ ├── use-billing.ts │ ├── use-brand.ts │ ├── use-dataroom-document-stats.ts │ ├── use-dataroom-groups.ts │ ├── use-dataroom-stats.ts │ ├── use-dataroom.ts │ ├── use-datarooms.ts │ ├── use-document-stats.ts │ ├── use-document.ts │ ├── use-documents.ts │ ├── use-domains.ts │ ├── use-folders.ts │ ├── use-invitations.ts │ ├── use-limits.ts │ ├── use-link.ts │ ├── use-stats.ts │ ├── use-tags.ts │ ├── use-team.ts │ ├── use-teams.ts │ ├── use-viewer.ts │ └── use-viewers.ts ├── team │ └── helper.ts ├── tinybird │ ├── README.md │ ├── datasources │ │ ├── click_events.datasource │ │ ├── page_views.datasource │ │ ├── pm_click_events.datasource │ │ ├── video_views.datasource │ │ └── webhook_events.datasource │ ├── endpoints │ │ ├── get_click_events_by_view.pipe │ │ ├── get_document_duration_per_viewer.pipe │ │ ├── get_page_duration_per_view.pipe │ │ ├── get_total_average_page_duration.pipe │ │ ├── get_total_dataroom_duration.pipe │ │ ├── get_total_document_duration.pipe │ │ ├── get_total_link_duration.pipe │ │ ├── get_total_viewer_duration.pipe │ │ ├── get_useragent_per_view.pipe │ │ ├── get_video_events_by_document.pipe │ │ ├── get_video_events_by_view.pipe │ │ └── get_webhook_events.pipe │ ├── index.ts │ ├── pipes.ts │ └── publish.ts ├── tracking │ ├── record-link-view.ts │ ├── safe-page-view-tracker.ts │ ├── tracking-config.ts │ └── video-tracking.ts ├── trigger │ ├── conversation-message-notification.ts │ ├── convert-files.ts │ ├── dataroom-change-notification.ts │ ├── optimize-video-files.ts │ ├── pdf-to-image-route.ts │ └── send-scheduled-email.ts ├── types.ts ├── types │ └── index-file.ts ├── unsend.ts ├── utils.ts ├── utils │ ├── csv.ts │ ├── decode-base64url.ts │ ├── determine-text-color.ts │ ├── generate-checksum.ts │ ├── generate-jwt.ts │ ├── generate-otp.ts │ ├── generate-trigger-auth-token.ts │ ├── generate-trigger-status.ts │ ├── geo.ts │ ├── get-content-type.ts │ ├── get-file-icon.tsx │ ├── get-file-size-limits.ts │ ├── get-page-number-count.ts │ ├── get-search-params.ts │ ├── ip.ts │ ├── reliable-tracking.ts │ ├── resize-image.ts │ ├── sanitize-html.ts │ ├── sort-items-by-index-name.ts │ ├── trigger-utils.ts │ ├── unsubscribe.ts │ ├── use-at-bottom.ts │ ├── use-copy-to-clipboard.ts │ ├── use-enter-submit.ts │ ├── use-media-query.ts │ ├── use-progress-status.ts │ ├── user-agent.ts │ └── validate-email.ts ├── webhook │ ├── constants.ts │ ├── send-webhooks.ts │ ├── signature.ts │ ├── transform.ts │ ├── triggers │ │ ├── document-created.ts │ │ └── link-created.ts │ └── types.ts ├── webstorage.ts ├── year-in-review │ ├── calculate-percentile.ts │ ├── get-stats.ts │ ├── index.ts │ └── send-emails.ts └── zod │ └── schemas │ ├── notifications.ts │ ├── presets.ts │ └── webhooks.ts ├── middleware.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── pages ├── 404.tsx ├── _app.tsx ├── _document.tsx ├── account │ ├── general.tsx │ └── security.tsx ├── api │ ├── account │ │ └── index.ts │ ├── analytics │ │ └── index.ts │ ├── assistants │ │ ├── chat.ts │ │ ├── index.ts │ │ └── threads │ │ │ └── index.ts │ ├── auth-plus │ │ └── set-cookie.ts │ ├── auth │ │ └── [...nextauth].ts │ ├── conversations │ │ └── [[...conversations]].ts │ ├── feedback │ │ └── index.ts │ ├── file │ │ ├── browser-upload.ts │ │ ├── image-upload.ts │ │ ├── notion │ │ │ └── index.ts │ │ ├── s3 │ │ │ ├── get-presigned-get-url.ts │ │ │ └── get-presigned-post-url.ts │ │ ├── tus-viewer │ │ │ └── [[...file]].ts │ │ └── tus │ │ │ └── [[...file]].ts │ ├── health.ts │ ├── jobs │ │ ├── get-thumbnail.ts │ │ ├── send-conversation-new-message-notification.ts │ │ ├── send-dataroom-new-document-notification.ts │ │ ├── send-dataroom-view-invitation.ts │ │ └── send-notification.ts │ ├── links │ │ ├── [id] │ │ │ ├── archive.ts │ │ │ ├── dataroom.ts │ │ │ ├── documents │ │ │ │ └── [documentId].ts │ │ │ ├── duplicate.ts │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ └── visits.ts │ │ ├── domains │ │ │ └── [...domainSlug].ts │ │ ├── download │ │ │ ├── bulk.ts │ │ │ ├── dataroom-document.ts │ │ │ ├── dataroom-folder.ts │ │ │ └── index.ts │ │ ├── generate-index.ts │ │ └── index.ts │ ├── mupdf │ │ ├── annotate-document.ts │ │ ├── convert-page.ts │ │ └── get-pages.ts │ ├── passkeys │ │ └── register.ts │ ├── progress-token.ts │ ├── record_click.ts │ ├── record_reaction.ts │ ├── record_video_view.ts │ ├── record_view.ts │ ├── report.ts │ ├── revalidate.ts │ ├── stripe │ │ ├── webhook-old.ts │ │ └── webhook.ts │ ├── teams │ │ ├── [teamId] │ │ │ ├── agreements │ │ │ │ ├── [agreementId] │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── billing │ │ │ │ ├── index.ts │ │ │ │ ├── manage.ts │ │ │ │ ├── plan.ts │ │ │ │ └── upgrade.ts │ │ │ ├── branding.ts │ │ │ ├── change-role.ts │ │ │ ├── datarooms │ │ │ │ ├── [id] │ │ │ │ │ ├── branding.ts │ │ │ │ │ ├── conversations │ │ │ │ │ │ ├── [[...conversations]].ts │ │ │ │ │ │ └── toggle-conversations.ts │ │ │ │ │ ├── documents │ │ │ │ │ │ ├── [documentId] │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── stats.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── move.ts │ │ │ │ │ ├── download │ │ │ │ │ │ └── bulk.ts │ │ │ │ │ ├── duplicate.ts │ │ │ │ │ ├── export-visits.ts │ │ │ │ │ ├── folders │ │ │ │ │ │ ├── [...name].ts │ │ │ │ │ │ ├── documents │ │ │ │ │ │ │ └── [...name].ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── manage │ │ │ │ │ │ │ ├── [folderId] │ │ │ │ │ │ │ │ ├── dataroom-to-dataroom.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── move.ts │ │ │ │ │ │ └── parents │ │ │ │ │ │ │ └── [...name].ts │ │ │ │ │ ├── generate-index.ts │ │ │ │ │ ├── groups │ │ │ │ │ │ ├── [groupId] │ │ │ │ │ │ │ ├── export-visits.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── links.ts │ │ │ │ │ │ │ ├── members │ │ │ │ │ │ │ │ ├── [memberId].ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── permissions.ts │ │ │ │ │ │ │ └── views │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── links.ts │ │ │ │ │ ├── reorder.ts │ │ │ │ │ ├── stats.ts │ │ │ │ │ ├── users │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── viewers │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── views │ │ │ │ │ │ ├── [viewId] │ │ │ │ │ │ ├── custom-fields.ts │ │ │ │ │ │ ├── history.ts │ │ │ │ │ │ └── user-agent.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── create-from-folder.ts │ │ │ │ ├── index.ts │ │ │ │ └── trial.ts │ │ │ ├── documents │ │ │ │ ├── [id] │ │ │ │ │ ├── add-to-dataroom.ts │ │ │ │ │ ├── advanced-mode.ts │ │ │ │ │ ├── change-orientation.ts │ │ │ │ │ ├── duplicate.ts │ │ │ │ │ ├── export-visits.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── links.ts │ │ │ │ │ ├── stats.ts │ │ │ │ │ ├── toggle-dark-mode.ts │ │ │ │ │ ├── toggle-download-only.ts │ │ │ │ │ ├── update-name.ts │ │ │ │ │ ├── versions │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── video-analytics.ts │ │ │ │ │ └── views │ │ │ │ │ │ ├── [viewId] │ │ │ │ │ │ ├── click-events.ts │ │ │ │ │ │ ├── custom-fields.ts │ │ │ │ │ │ ├── stats.ts │ │ │ │ │ │ ├── user-agent.ts │ │ │ │ │ │ └── video-stats.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── agreement.ts │ │ │ │ ├── document-processing-status.ts │ │ │ │ ├── index.ts │ │ │ │ ├── move.ts │ │ │ │ ├── search.ts │ │ │ │ └── update.ts │ │ │ ├── domains │ │ │ │ ├── [domain] │ │ │ │ │ ├── index.ts │ │ │ │ │ └── verify.ts │ │ │ │ └── index.ts │ │ │ ├── enable-advanced-mode.ts │ │ │ ├── folders │ │ │ │ ├── [...name].ts │ │ │ │ ├── documents │ │ │ │ │ └── [...name].ts │ │ │ │ ├── index.ts │ │ │ │ ├── manage │ │ │ │ │ ├── [folderId] │ │ │ │ │ │ ├── add-to-dataroom.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── move.ts │ │ │ │ └── parents │ │ │ │ │ └── [...name].ts │ │ │ ├── incoming-webhooks │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── invitations │ │ │ │ ├── accept.ts │ │ │ │ ├── index.ts │ │ │ │ └── resend.ts │ │ │ ├── invite.ts │ │ │ ├── limits.ts │ │ │ ├── presets │ │ │ │ ├── [id].ts │ │ │ │ └── index.ts │ │ │ ├── remove-teammate.ts │ │ │ ├── tags │ │ │ │ ├── [id] │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── tokens │ │ │ │ └── index.ts │ │ │ ├── update-advanced-mode.ts │ │ │ ├── update-name.ts │ │ │ ├── viewers │ │ │ │ ├── [id] │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── views │ │ │ │ └── [id] │ │ │ │ │ └── archive.ts │ │ │ └── webhooks │ │ │ │ ├── [id] │ │ │ │ ├── events.ts │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ └── index.ts │ ├── unsubscribe │ │ ├── dataroom │ │ │ └── index.ts │ │ └── yir │ │ │ └── index.ts │ └── webhooks │ │ └── services │ │ └── [...path] │ │ └── index.ts ├── branding.tsx ├── dashboard.tsx ├── datarooms │ ├── [id] │ │ ├── analytics │ │ │ └── index.tsx │ │ ├── branding │ │ │ └── index.tsx │ │ ├── conversations │ │ │ ├── [conversationId] │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── documents │ │ │ ├── [...name].tsx │ │ │ └── index.tsx │ │ ├── groups │ │ │ ├── [groupId] │ │ │ │ ├── group-analytics.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── links.tsx │ │ │ │ ├── members.tsx │ │ │ │ └── permissions.tsx │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── permissions │ │ │ └── index.tsx │ │ ├── settings │ │ │ ├── index.tsx │ │ │ └── notifications.tsx │ │ └── users │ │ │ └── index.tsx │ └── index.tsx ├── documents │ ├── [id] │ │ ├── chat.tsx │ │ ├── index.tsx │ │ └── settings.tsx │ ├── index.tsx │ ├── new.tsx │ └── tree │ │ └── [...name].tsx ├── entrance_ppreview_demo.tsx ├── nav_ppreview_demo.tsx ├── room_ppreview_demo.tsx ├── settings │ ├── agreements.tsx │ ├── billing.tsx │ ├── domains.tsx │ ├── general.tsx │ ├── incoming-webhooks.tsx │ ├── people.tsx │ ├── presets │ │ ├── [id].tsx │ │ ├── index.tsx │ │ └── new.tsx │ ├── tags.tsx │ ├── tokens.tsx │ ├── upgrade.tsx │ └── webhooks │ │ ├── [id] │ │ └── index.tsx │ │ ├── index.tsx │ │ └── new.tsx ├── unsubscribe.tsx ├── view │ ├── [linkId] │ │ ├── chat.tsx │ │ ├── d │ │ │ └── [documentId].tsx │ │ ├── embed.tsx │ │ └── index.tsx │ └── domains │ │ └── [domain] │ │ └── [slug] │ │ ├── d │ │ └── [documentId].tsx │ │ └── index.tsx ├── visitors │ ├── [id] │ │ └── index.tsx │ └── index.tsx └── welcome.tsx ├── pkgx.yaml ├── postcss.config.js ├── prettier.config.js ├── prisma ├── README.md ├── add-migration.sh ├── migrations │ ├── 20230912150657_initialize │ │ └── migration.sql │ ├── 202310122339_NewColumnInLinkTable │ │ └── migration.sql │ ├── 20231013165123_create_document_version │ │ └── migration.sql │ ├── 20231014200337_create_document_pages │ │ └── migration.sql │ ├── 202310311254_NewColumnEnableNotificationLinkTable │ │ └── migration.sql │ ├── 20231105152632_create_team │ │ └── migration.sql │ ├── 20231113051339_create_sent_email │ │ └── migration.sql │ ├── 20231114054509_add_domain_to_sent_emails │ │ └── migration.sql │ ├── 20231116093816_update_invitations │ │ └── migration.sql │ ├── 20231127062841_add_conversation │ │ └── migration.sql │ ├── 20231128064540_add_indices │ │ └── migration.sql │ ├── 20231204070250_remove_trial │ │ └── migration.sql │ ├── 20231207081407_add_reactions │ │ └── migration.sql │ ├── 20240110233134_add_disable_feedback │ │ └── migration.sql │ ├── 20240117020456_add_branding │ │ └── migration.sql │ ├── 20240202052149_add_email_authentication_to_link_and_view │ │ └── migration.sql │ ├── 20240205170242_embedded_links │ │ └── migration.sql │ ├── 20240212081614_add_downloaded_time_to_view │ │ └── migration.sql │ ├── 20240215035046_add_allow_deny_list_to_links │ │ └── migration.sql │ ├── 20240221042933_add_document_storage_type_enum │ │ └── migration.sql │ ├── 20240313100203_add_folders │ │ └── migration.sql │ ├── 20240327102407_add_dataroom │ │ └── migration.sql │ ├── 20240330062000_add_viewtype │ │ └── migration.sql │ ├── 20240401000000_add_dataroom_brand │ │ └── migration.sql │ ├── 20240408000000_add_viewer │ │ └── migration.sql │ ├── 20240415000000_add_feedback │ │ └── migration.sql │ ├── 20240424152839_add_screenprotection_to_link │ │ └── migration.sql │ ├── 20240511000000_add_team_limits │ │ └── migration.sql │ ├── 20240520000000_add_vertical_to_document_version │ │ └── migration.sql │ ├── 20240521000000_add_manager_role │ │ └── migration.sql │ ├── 20240611000000_add_agreements │ │ └── migration.sql │ ├── 20240712000000_add_page_metadata │ │ └── migration.sql │ ├── 20240720000000_change_owner_dependecy │ │ └── migration.sql │ ├── 20240730000000_update_link_defaults │ │ └── migration.sql │ ├── 20240731000000_add_link_show_banner │ │ └── migration.sql │ ├── 20240809000000_add_dataroom_order_index │ │ └── migration.sql │ ├── 20240821000000_add_require_name │ │ └── migration.sql │ ├── 20240830000000_add_watermarks │ │ └── migration.sql │ ├── 20240901000000_add_domain_default │ │ └── migration.sql │ ├── 20240902000000_add_link_presets │ │ └── migration.sql │ ├── 20240911000000_add_dataroom_groups_permissions │ │ └── migration.sql │ ├── 20240915000000_add_advanced_mode │ │ └── migration.sql │ ├── 20240916000000_add_content_type_to_document │ │ └── migration.sql │ ├── 20240921000000_add_viewer_migration │ │ └── migration.sql │ ├── 20241004024010_add_favicon_column │ │ └── migration.sql │ ├── 20241020000000_add_teamid_to_link_and_view │ │ └── migration.sql │ ├── 20241029000000_add_archived_view │ │ └── migration.sql │ ├── 20241107000000_add_tokens_and_webhooks │ │ └── migration.sql │ ├── 20241118000000_add_filesize_and_downloadonly │ │ └── migration.sql │ ├── 20241123000000_add_viewer_notification_preferences │ │ └── migration.sql │ ├── 20241126000000_add_screen_shield │ │ └── migration.sql │ ├── 20241208000000_add_webhooks │ │ └── migration.sql │ ├── 20241212000000_add_yir │ │ └── migration.sql │ ├── 20250110000000_add_length_to_document_version │ │ └── migration.sql │ ├── 20250113000000_add_custom_fields │ │ └── migration.sql │ ├── 20250204000000_add_anonymous_group │ │ └── migration.sql │ ├── 20250217000000_add_contactid_to_user │ │ └── migration.sql │ ├── 20250217000000_remove_link_column │ │ └── migration.sql │ ├── 20250310000000_rename_conversation_table │ │ └── migration.sql │ ├── 20250404000000_add_questions_answer_conversation │ │ └── migration.sql │ ├── 20250413000000_add_dataroom_upload │ │ └── migration.sql │ ├── 20250425000000_update_link_presets │ │ └── migration.sql │ ├── 20250428000000_add_tags │ │ └── migration.sql │ ├── 20250502000000_add_additional_present_fields │ │ └── migration.sql │ ├── 20250511000000_add_index_file_to_links │ │ └── migration.sql │ ├── 20250513000000_add_notification_to_dataroom │ │ └── migration.sql │ ├── 20250516000000_add_advanced_mode_to_team │ │ └── migration.sql │ ├── 20250526000000_add_status_to_users │ │ └── migration.sql │ └── migration_lock.toml └── schema │ ├── conversation.prisma │ ├── dataroom.prisma │ ├── link.prisma │ └── schema.prisma ├── public ├── _example │ ├── papermark-example-document.pdf │ └── papermark-example-page.png ├── _icons │ ├── doc.svg │ ├── gif.svg │ ├── jpg.svg │ ├── other.svg │ ├── pdf-light.svg │ ├── pdf.svg │ ├── png.svg │ ├── ppt.svg │ ├── sheet-light.svg │ ├── sheet.svg │ └── xls.svg ├── _static │ ├── Inter-Bold.ttf │ ├── blank.gif │ ├── macos.png │ ├── meta-image.png │ ├── papermark-banner.png │ ├── papermark-logo-light.svg │ ├── papermark-logo.svg │ ├── papermark-p.svg │ └── testimonials │ │ ├── backtrace.jpeg │ │ ├── jaski.jpeg │ │ └── steven.jpeg ├── favicon.ico └── vendor │ └── handsontable │ ├── handsontable.full.min.css │ └── handsontable.full.min.js ├── styles ├── Inter-Regular.ttf ├── custom-notion-styles.css ├── custom-viewer-styles.css └── globals.css ├── tailwind.config.js ├── trigger.config.ts ├── tsconfig.json └── vercel.json /.cursorignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "@next/next/no-img-element": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Learn how to add code owners here: 2 | # https://help.github.com/en/articles/about-code-owners 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | # Order is important; the last matching pattern takes the most precedence. 6 | 7 | * @mfts 8 | /.github/ @mfts -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/oss-gg-hack-template.yml: -------------------------------------------------------------------------------- 1 | name: oss.gg hack submission 🕹️ 2 | description: "Submit your contribution for the for the oss.gg hackathon" 3 | title: "[🕹️]" 4 | labels: 🕹️ oss.gg, player submission, hacktoberfest 5 | assignees: [] 6 | body: 7 | - type: textarea 8 | id: contribution-name 9 | attributes: 10 | label: What side quest or challenge are you solving? 11 | description: Add the name of the side quest or challenge. 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: points 16 | attributes: 17 | label: Points 18 | description: How many points are assigned to this contribution? 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: description 23 | attributes: 24 | label: Description 25 | description: What's the task your performed? 26 | validations: 27 | - type: textarea 28 | id: proof 29 | attributes: 30 | label: Provide proof that you've completed the task 31 | description: Screenshots, loom recordings, links to the content you shared or interacted with. 32 | validations: 33 | required: true 34 | -------------------------------------------------------------------------------- /.github/images/papermark-welcome.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfts/papermark/bcdbdf1c3b5cfdc12928cacaf23972fb7a88f04a/.github/images/papermark-welcome.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | 12 | # nvm 13 | .npmrc 14 | .nvmrc 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # local env files 33 | .env*.local 34 | .env 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | # react-email 44 | .react-email 45 | 46 | # Tinybird config for the cli is stored in this file 47 | .tinyb 48 | 49 | # Internal scripts 50 | pages/api/scripts 51 | scripts/ 52 | 53 | # marketing emails 54 | components/emails/marketing 55 | lib/emails/marketing 56 | 57 | # vscode configs 58 | .vscode 59 | 60 | # trigger.dev 61 | .trigger 62 | 63 | # changelog 64 | changelog -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next/ 3 | .react-email/ 4 | .vercel/ 5 | .github/ 6 | *.min.js 7 | *.min.css -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | oss-gg -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | tinybird-cli = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.11" 13 | -------------------------------------------------------------------------------- /app/(auth)/auth/confirm-email-change/[token]/page-client.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | 5 | import { useEffect, useRef } from "react"; 6 | 7 | import { useSession } from "next-auth/react"; 8 | import { toast } from "sonner"; 9 | 10 | import LoadingSpinner from "@/components/ui/loading-spinner"; 11 | 12 | export default function ConfirmEmailChangePageClient() { 13 | const router = useRouter(); 14 | const { update, status } = useSession(); 15 | const hasUpdatedSession = useRef(false); 16 | 17 | useEffect(() => { 18 | if (status !== "authenticated" || hasUpdatedSession.current) { 19 | return; 20 | } 21 | 22 | async function updateSession() { 23 | hasUpdatedSession.current = true; 24 | await update(); 25 | toast.success("Email update successful!"); 26 | router.replace("/account/general"); 27 | } 28 | 29 | updateSession(); 30 | }, [status, update]); 31 | 32 | return ( 33 |
34 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /app/(auth)/auth/confirm-email-change/[token]/utils.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from "@/pages/api/auth/[...nextauth]"; 2 | import { getServerSession } from "next-auth"; 3 | 4 | export const getSession = async () => { 5 | return getServerSession(authOptions); 6 | }; 7 | -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SessionProvider } from "next-auth/react"; 4 | import { Toaster } from "sonner"; 5 | 6 | import { ThemeProvider } from "@/components/theme-provider"; 7 | 8 | export default function Layout({ children }: { children: React.ReactNode }) { 9 | return ( 10 | 11 | 12 |
13 | 14 |
{children}
15 |
16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | 3 | import LoginClient from "./page-client"; 4 | 5 | const data = { 6 | description: "Login to Papermark", 7 | title: "Login | Papermark", 8 | url: "/login", 9 | }; 10 | 11 | export const metadata: Metadata = { 12 | metadataBase: new URL("https://www.papermark.com"), 13 | title: data.title, 14 | description: data.description, 15 | openGraph: { 16 | title: data.title, 17 | description: data.description, 18 | url: data.url, 19 | siteName: "Papermark", 20 | images: [ 21 | { 22 | url: "/_static/meta-image.png", 23 | width: 800, 24 | height: 600, 25 | }, 26 | ], 27 | locale: "en_US", 28 | type: "website", 29 | }, 30 | twitter: { 31 | card: "summary_large_image", 32 | title: data.title, 33 | description: data.description, 34 | creator: "@papermarkio", 35 | images: ["/_static/meta-image.png"], 36 | }, 37 | }; 38 | 39 | export default function LoginPage() { 40 | return ; 41 | } 42 | -------------------------------------------------------------------------------- /app/(auth)/register/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | 3 | import RegisterClient from "./page-client"; 4 | 5 | const data = { 6 | description: "Signup to Papermark", 7 | title: "Sign up | Papermark", 8 | url: "/register", 9 | }; 10 | 11 | export const metadata: Metadata = { 12 | metadataBase: new URL("https://www.papermark.com"), 13 | title: data.title, 14 | description: data.description, 15 | openGraph: { 16 | title: data.title, 17 | description: data.description, 18 | url: data.url, 19 | siteName: "Papermark", 20 | images: [ 21 | { 22 | url: "/_static/meta-image.png", 23 | width: 800, 24 | height: 600, 25 | }, 26 | ], 27 | locale: "en_US", 28 | type: "website", 29 | }, 30 | twitter: { 31 | card: "summary_large_image", 32 | title: data.title, 33 | description: data.description, 34 | creator: "@papermarkio", 35 | images: ["/_static/meta-image.png"], 36 | }, 37 | }; 38 | 39 | export default function RegisterPage() { 40 | return ; 41 | } 42 | -------------------------------------------------------------------------------- /app/(auth)/verify/invitation/AcceptInvitationButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | 5 | import { useState } from "react"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | 9 | interface AcceptInvitationButtonProps { 10 | verificationUrl: string; 11 | } 12 | 13 | export default function AcceptInvitationButton({ 14 | verificationUrl, 15 | }: AcceptInvitationButtonProps) { 16 | const [isLoading, setIsLoading] = useState(false); 17 | 18 | const handleAccept = () => { 19 | setIsLoading(true); 20 | }; 21 | 22 | return ( 23 | 24 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /app/(auth)/verify/invitation/status/ClientRedirect.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | 5 | export default function CleanUrlOnExpire({ 6 | shouldClean, 7 | }: { 8 | shouldClean: boolean; 9 | }) { 10 | useEffect(() => { 11 | if (shouldClean && typeof window !== "undefined") { 12 | const url = new URL(window.location.href); 13 | url.search = ""; 14 | window.history.replaceState({}, "", url.toString()); 15 | } 16 | }, [shouldClean]); 17 | 18 | return null; 19 | } 20 | -------------------------------------------------------------------------------- /app/api/cron/year-in-review/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { receiver } from "@/lib/cron"; 4 | import { log } from "@/lib/utils"; 5 | import { processEmailQueue } from "@/lib/year-in-review/send-emails"; 6 | 7 | // Runs every hour (0 * * * *) 8 | export const maxDuration = 300; // 5 minutes in seconds 9 | 10 | export async function POST(req: Request) { 11 | const body = await req.json(); 12 | if (process.env.VERCEL === "1") { 13 | const isValid = await receiver.verify({ 14 | signature: req.headers.get("Upstash-Signature") || "", 15 | body: JSON.stringify(body), 16 | }); 17 | if (!isValid) { 18 | return new Response("Unauthorized", { status: 401 }); 19 | } 20 | } 21 | 22 | try { 23 | await processEmailQueue(); 24 | return NextResponse.json({ success: true }); 25 | } catch (error) { 26 | await log({ 27 | message: `Year in review email cron failed. \n\nError: ${(error as Error).message}`, 28 | type: "cron", 29 | mention: true, 30 | }); 31 | return NextResponse.json({ error: (error as Error).message }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/api/csp-report/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | export async function POST(request: Request) { 4 | const report = await request.json(); 5 | 6 | // Log the report or send to your logging service 7 | // console.log("CSP Violation:", report); 8 | 9 | // You could send this to your logging service 10 | // await fetch('your-logging-service', { 11 | // method: 'POST', 12 | // body: JSON.stringify(report) 13 | // }) 14 | 15 | return NextResponse.json({ success: true }); 16 | } 17 | -------------------------------------------------------------------------------- /app/api/feature-flags/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { getFeatureFlags } from "@/lib/featureFlags"; 4 | 5 | export const runtime = "edge"; 6 | 7 | export async function GET(request: Request) { 8 | const { searchParams } = new URL(request.url); 9 | const teamId = searchParams.get("teamId"); 10 | 11 | try { 12 | const features = await getFeatureFlags({ teamId: teamId || undefined }); 13 | return NextResponse.json(features); 14 | } catch (error) { 15 | console.error("Error fetching feature flags:", error); 16 | return NextResponse.json( 17 | { error: "Failed to fetch feature flags" }, 18 | { status: 500 }, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: /login 3 | Disallow: /register 4 | Disallow: /verify/ 5 | Disallow: /auth/ 6 | Disallow: /unsubscribe 7 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "styles/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils", 15 | "ui": "@/components/ui", 16 | "lib": "@/lib", 17 | "hooks": "@/lib/hooks" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { classNames } from "@/lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export default Skeleton; 16 | -------------------------------------------------------------------------------- /components/account/account-header.tsx: -------------------------------------------------------------------------------- 1 | import { NavMenu } from "../navigation-menu"; 2 | 3 | export function AccountHeader() { 4 | return ( 5 |
6 |
7 |
8 |

9 | User Account 10 |

11 |

Manage your profile

12 |
13 |
14 | 15 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/billing/plan-badge.tsx: -------------------------------------------------------------------------------- 1 | import { CrownIcon } from "lucide-react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export default function PlanBadge({ 6 | plan, 7 | className, 8 | }: { 9 | plan: string; 10 | className?: string; 11 | }) { 12 | return ( 13 | 19 | {plan} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /components/blur-image.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Image, { ImageProps } from "next/image"; 4 | 5 | import { useEffect, useState } from "react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | export function BlurImage(props: ImageProps) { 10 | const [loading, setLoading] = useState(true); 11 | const [src, setSrc] = useState(props.src); 12 | useEffect(() => setSrc(props.src), [props.src]); // update the `src` value when the `prop.src` value changes 13 | 14 | const handleLoad = (e: React.SyntheticEvent) => { 15 | setLoading(false); 16 | const target = e.target as HTMLImageElement; 17 | if (target.naturalWidth <= 16 && target.naturalHeight <= 16) { 18 | setSrc(`https://avatar.vercel.sh/${encodeURIComponent(props.alt)}`); 19 | } 20 | }; 21 | 22 | return ( 23 | {props.alt} { 30 | setSrc(`https://avatar.vercel.sh/${encodeURIComponent(props.alt)}`); // if the image fails to load, use the default avatar 31 | }} 32 | unoptimized 33 | /> 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /components/chat/chat-message-actions.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { type Message } from "ai"; 4 | 5 | import { Button } from "@/components/ui/button"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | import { useCopyToClipboard } from "@/lib/utils/use-copy-to-clipboard"; 9 | 10 | import Check from "../shared/icons/check"; 11 | import Copy from "../shared/icons/copy"; 12 | 13 | interface ChatMessageActionsProps extends React.ComponentProps<"div"> { 14 | message: Message; 15 | } 16 | 17 | export function ChatMessageActions({ 18 | message, 19 | className, 20 | ...props 21 | }: ChatMessageActionsProps) { 22 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); 23 | 24 | const onCopy = () => { 25 | if (isCopied) return; 26 | copyToClipboard(message.content); 27 | }; 28 | 29 | return ( 30 |
37 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /components/chat/chat-scroll-anchor.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { useInView } from "react-intersection-observer"; 6 | 7 | import { useAtBottom } from "@/lib/utils/use-at-bottom"; 8 | 9 | interface ChatScrollAnchorProps { 10 | trackVisibility?: boolean; 11 | } 12 | 13 | export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) { 14 | const isAtBottom = useAtBottom(); 15 | const { ref, entry, inView } = useInView({ 16 | trackVisibility, 17 | delay: 100, 18 | rootMargin: "0px 0px -100px 0px", 19 | }); 20 | 21 | React.useEffect(() => { 22 | if (isAtBottom && trackVisibility && !inView) { 23 | entry?.target.scrollIntoView({ 24 | block: "start", 25 | }); 26 | } 27 | }, [inView, entry, isAtBottom, trackVisibility]); 28 | 29 | return
; 30 | } 31 | -------------------------------------------------------------------------------- /components/conversations/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ConversationListItem as ConversationListItemEE } from "@/ee/features/conversations/components/conversation-list-item"; 4 | import { ConversationMessage as ConversationMessageEE } from "@/ee/features/conversations/components/conversation-message"; 5 | 6 | export function ConversationListItem(props: any) { 7 | return ; 8 | } 9 | 10 | export function ConversationMessage(props: any) { 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /components/datarooms/actions/generate-index-button.tsx: -------------------------------------------------------------------------------- 1 | import GenerateIndexDialog from "./generate-index-dialog"; 2 | 3 | interface GenerateIndexButtonProps { 4 | teamId: string; 5 | dataroomId: string; 6 | disabled?: boolean; 7 | } 8 | 9 | export default function GenerateIndexButton({ 10 | teamId, 11 | dataroomId, 12 | disabled = false, 13 | }: GenerateIndexButtonProps) { 14 | return ( 15 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/datarooms/empty-dataroom.tsx: -------------------------------------------------------------------------------- 1 | import { ServerIcon } from "lucide-react"; 2 | 3 | export function EmptyDataroom() { 4 | return ( 5 |
6 | 10 |

11 | No datarooms here 12 |

13 |

14 | Get started by creating a new dataroom. 15 |

16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/datarooms/folders/index.tsx: -------------------------------------------------------------------------------- 1 | import { SidebarFolderTreeSelection } from "./selection-tree"; 2 | import { SidebarFolderTree } from "./sidebar-tree"; 3 | import { ViewFolderTree } from "./view-tree"; 4 | 5 | export { SidebarFolderTree, SidebarFolderTreeSelection, ViewFolderTree }; 6 | -------------------------------------------------------------------------------- /components/datarooms/groups/group-card-placeholder.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardContent, 4 | CardFooter, 5 | CardHeader, 6 | } from "@/components/ui/card"; 7 | import { Skeleton } from "@/components/ui/skeleton"; 8 | 9 | export function GroupCardPlaceholder() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/datarooms/groups/group-header.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { ChevronRightIcon } from "lucide-react"; 4 | 5 | export const GroupHeader = ({ 6 | dataroomId, 7 | groupName, 8 | }: { 9 | dataroomId: string; 10 | groupName: string; 11 | }) => { 12 | return ( 13 |
14 | 15 |

16 | All Groups 17 |

18 | 19 | 20 |

21 | {groupName ?? "Management Team"} 22 |

23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /components/datarooms/sortable/sortable-item.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useSortable } from "@dnd-kit/sortable"; 4 | import { CSS } from "@dnd-kit/utilities"; 5 | 6 | export type ItemCategory = "folder" | "document"; 7 | 8 | interface SortableItemProps { 9 | id: string; 10 | category: ItemCategory; 11 | children: React.ReactElement; 12 | } 13 | 14 | export const SortableItem: React.FC = ({ 15 | id, 16 | category, 17 | children, 18 | }) => { 19 | const { attributes, listeners, setNodeRef, transform, isDragging } = 20 | useSortable({ 21 | id: id, 22 | data: { 23 | category: category, 24 | id: id.replace(category, ""), 25 | }, 26 | }); 27 | 28 | const style = { 29 | transform: CSS.Transform.toString(transform), 30 | opacity: isDragging ? 0.5 : 1, 31 | }; 32 | 33 | const childWithProps = React.cloneElement(children, { 34 | isDragging, 35 | }); 36 | 37 | return ( 38 |
  • 45 | {childWithProps} 46 |
  • 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /components/documents/alert.tsx: -------------------------------------------------------------------------------- 1 | import { AlertCircleIcon } from "lucide-react"; 2 | 3 | import { 4 | Alert, 5 | AlertClose, 6 | AlertDescription, 7 | AlertTitle, 8 | } from "@/components/ui/alert"; 9 | 10 | interface AlertProps { 11 | id: string; 12 | variant: "default" | "destructive"; 13 | title: string; 14 | description: React.ReactNode; 15 | onClose?: () => void; 16 | } 17 | 18 | const AlertBanner: React.FC = ({ 19 | id, 20 | variant, 21 | title, 22 | description, 23 | onClose, 24 | }) => { 25 | return ( 26 | 27 | 28 | {title} 29 | {description} 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default AlertBanner; 36 | -------------------------------------------------------------------------------- /components/documents/drag-and-drop/droppable-folder.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useDroppable } from "@dnd-kit/core"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | interface DroppableFolderProps { 8 | id: string; 9 | disabledFolder: string[]; 10 | children: React.ReactElement; 11 | path: string; 12 | } 13 | 14 | export function DroppableFolder({ 15 | id, 16 | disabledFolder, 17 | children, 18 | path, 19 | }: DroppableFolderProps) { 20 | const { isOver, setNodeRef } = useDroppable({ 21 | id: id, 22 | data: { type: "folder", id, path }, 23 | }); 24 | 25 | const childWithProps = React.cloneElement(children, { 26 | isOver, 27 | }); 28 | 29 | return ( 30 |
    38 | {childWithProps} 39 |
    40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /components/documents/empty-document.tsx: -------------------------------------------------------------------------------- 1 | import { FilePlusIcon, PlusIcon } from "lucide-react"; 2 | 3 | import { Button } from "../ui/button"; 4 | import { AddDocumentModal } from "./add-document-modal"; 5 | 6 | export function EmptyDocuments() { 7 | return ( 8 |
    9 | 13 |

    14 | No documents here 15 |

    16 |

    17 | Get started by uploading a new document. 18 |

    19 | {/*
    20 | 21 | 28 | 29 |
    */} 30 |
    31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /components/documents/loading-document.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "../ui/skeleton"; 2 | 3 | export function LoadingDocuments({ count }: { count: number }) { 4 | return ( 5 |
      6 | {Array.from({ length: count }).map((_, i) => ( 7 |
    • 11 | 12 |
      13 | 14 | 15 |
      16 | 20 |
    • 21 | ))} 22 |
    23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/documents/stats-chart-dummy.tsx: -------------------------------------------------------------------------------- 1 | import BarChartComponent from "../charts/bar-chart"; 2 | 3 | export default function StatsChartDummy({ 4 | totalPagesMax = 0, 5 | }: { 6 | totalPagesMax?: number; 7 | }) { 8 | let durationData = Array.from({ length: totalPagesMax }, (_, i) => ({ 9 | pageNumber: (i + 1).toString(), 10 | data: [ 11 | { 12 | versionNumber: 1, 13 | avg_duration: 16000 / (i + 1), 14 | }, 15 | ], 16 | })); 17 | 18 | return ( 19 |
    20 | 21 |
    22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/documents/stats-chart-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | import { Skeleton } from "../ui/skeleton"; 4 | 5 | const StatsChartSkeleton = ({ className }: { className?: string }) => { 6 | return ( 7 |
    8 |
    9 | 10 |
    11 |
    12 |
    13 | {Array.from({ length: 5 }).map((_, i) => ( 14 | 15 | ))} 16 |
    17 |
    18 | {[250, 200, 150, 100, 50, 20].map((item, i) => ( 19 | 24 | ))} 25 |
    26 |
    27 |
    28 | ); 29 | }; 30 | 31 | export default StatsChartSkeleton; 32 | -------------------------------------------------------------------------------- /components/domains/use-domain-status.ts: -------------------------------------------------------------------------------- 1 | import { useTeam } from "@/context/team-context"; 2 | import useSWR from "swr"; 3 | import useSWRImmutable from "swr/immutable"; 4 | 5 | import { 6 | DomainConfigResponse, 7 | DomainResponse, 8 | DomainVerificationStatusProps, 9 | } from "@/lib/types"; 10 | import { fetcher } from "@/lib/utils"; 11 | 12 | export function useDomainStatus({ domain }: { domain: string }) { 13 | const teamInfo = useTeam(); 14 | 15 | const { data, isValidating, mutate } = useSWR<{ 16 | status: DomainVerificationStatusProps; 17 | response: { 18 | domainJson: DomainResponse & { error: { code: string; message: string } }; 19 | configJson: DomainConfigResponse; 20 | }; 21 | }>( 22 | `/api/teams/${teamInfo?.currentTeam?.id}/domains/${domain}/verify`, 23 | fetcher, 24 | ); 25 | 26 | return { 27 | status: data?.status, 28 | domainJson: data?.response.domainJson, 29 | configJson: data?.response.configJson, 30 | loading: isValidating, 31 | mutate, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /components/emails/dataroom-trial-welcome.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Body, Head, Html, Tailwind, Text } from "@react-email/components"; 4 | 5 | interface WelcomeEmailProps { 6 | name: string | null | undefined; 7 | } 8 | 9 | const DataroomTrialWelcomeEmail = ({ name }: WelcomeEmailProps) => { 10 | return ( 11 | 12 | 13 | 14 | 15 | Hi {name}, 16 | 17 | I am Marc, founder of Papermark. Thanks for creating a trial. Do you 18 | need any help with Data Rooms setup? 19 | 20 | Marc 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default DataroomTrialWelcomeEmail; 28 | -------------------------------------------------------------------------------- /components/hooks/use-optimistic-update.ts: -------------------------------------------------------------------------------- 1 | import { toast } from "sonner"; 2 | import useSWR from "swr"; 3 | 4 | import { fetcher } from "@/lib/utils"; 5 | 6 | export function useOptimisticUpdate( 7 | url: string, 8 | toastCopy?: { loading: string; success: string; error: string }, 9 | ) { 10 | const { data, isLoading, mutate } = useSWR(url, fetcher); 11 | 12 | return { 13 | data, 14 | isLoading, 15 | update: async (fn: (data: T) => Promise, optimisticData: T) => { 16 | return toast.promise( 17 | mutate(fn(data as T), { 18 | optimisticData, 19 | rollbackOnError: true, 20 | populateCache: true, 21 | revalidate: true, 22 | }), 23 | { 24 | loading: toastCopy?.loading || "Updating...", 25 | success: toastCopy?.success || "Successfully updated", 26 | error: toastCopy?.error || "Failed to update", 27 | }, 28 | ); 29 | }, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /components/links/link-sheet/allow-download-section.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { DEFAULT_LINK_TYPE } from "."; 4 | import LinkItem from "./link-item"; 5 | 6 | export default function AllowDownloadSection({ 7 | data, 8 | setData, 9 | }: { 10 | data: DEFAULT_LINK_TYPE; 11 | setData: React.Dispatch>; 12 | }) { 13 | const { allowDownload } = data; 14 | const [enabled, setEnabled] = useState(false); 15 | 16 | useEffect(() => { 17 | setEnabled(allowDownload); 18 | }, [allowDownload]); 19 | 20 | const handleAllowDownload = () => { 21 | const updatedAllowDownload = !enabled; 22 | setData({ ...data, allowDownload: updatedAllowDownload }); 23 | setEnabled(updatedAllowDownload); 24 | }; 25 | 26 | return ( 27 |
    28 | 35 |
    36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /components/links/link-sheet/allow-notification-section.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { DEFAULT_LINK_TYPE } from "."; 4 | import LinkItem from "./link-item"; 5 | 6 | export default function AllowNotificationSection({ 7 | data, 8 | setData, 9 | }: { 10 | data: DEFAULT_LINK_TYPE; 11 | setData: React.Dispatch>; 12 | }) { 13 | const { enableNotification } = data; 14 | const [enabled, setEnabled] = useState(true); 15 | 16 | useEffect(() => { 17 | setEnabled(enableNotification); 18 | }, [enableNotification]); 19 | 20 | const handleEnableNotification = () => { 21 | const updatedEnableNotification = !enabled; 22 | setData({ ...data, enableNotification: updatedEnableNotification }); 23 | setEnabled(updatedEnableNotification); 24 | }; 25 | 26 | return ( 27 |
    28 | 35 |
    36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /components/links/link-sheet/conversation-section.tsx: -------------------------------------------------------------------------------- 1 | import ConversationSection from "@/ee/features/conversations/components/link-option-conversation-section"; 2 | 3 | export default ConversationSection; 4 | -------------------------------------------------------------------------------- /components/links/link-sheet/feedback-section.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { DEFAULT_LINK_TYPE } from "."; 4 | import LinkItem from "./link-item"; 5 | 6 | export default function FeedbackSection({ 7 | data, 8 | setData, 9 | }: { 10 | data: DEFAULT_LINK_TYPE; 11 | setData: React.Dispatch>; 12 | }) { 13 | const { enableFeedback } = data; 14 | const [enabled, setEnabled] = useState(true); 15 | 16 | useEffect(() => { 17 | setEnabled(enableFeedback); 18 | }, [enableFeedback]); 19 | 20 | const handleEnableFeedback = () => { 21 | const updatedEnableFeedback = !enabled; 22 | setData({ ...data, enableFeedback: updatedEnableFeedback }); 23 | setEnabled(updatedEnableFeedback); 24 | }; 25 | 26 | return ( 27 |
    28 | 34 |
    35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /components/profile-search-trigger.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Search } from "lucide-react"; 4 | 5 | interface ProfileSearchTriggerProps { 6 | onClick: () => void; 7 | } 8 | 9 | export function ProfileSearchTrigger({ onClick }: ProfileSearchTriggerProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/alert-circle.tsx: -------------------------------------------------------------------------------- 1 | export default function AlertCircle({ 2 | className, 3 | fill, 4 | }: { 5 | className?: string; 6 | fill?: string; 7 | }) { 8 | return ( 9 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/shared/icons/arrow-up.tsx: -------------------------------------------------------------------------------- 1 | export default function ArrowUp({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/badge-check.tsx: -------------------------------------------------------------------------------- 1 | export default function BadgeCheck({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/bar-chart.tsx: -------------------------------------------------------------------------------- 1 | export default function BarChart({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/shared/icons/check-cirlce-2.tsx: -------------------------------------------------------------------------------- 1 | export default function CheckCircle2({ 2 | className, 3 | fill, 4 | }: { 5 | className?: string; 6 | fill?: string; 7 | }) { 8 | return ( 9 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /components/shared/icons/check.tsx: -------------------------------------------------------------------------------- 1 | export default function Check({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/shared/icons/chevron-down.tsx: -------------------------------------------------------------------------------- 1 | export default function ChevronDown({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/shared/icons/chevron-right.tsx: -------------------------------------------------------------------------------- 1 | export default function ChevronRight({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/shared/icons/chevron-up.tsx: -------------------------------------------------------------------------------- 1 | export default function ChevronUp({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/shared/icons/circle.tsx: -------------------------------------------------------------------------------- 1 | export default function Circle({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/shared/icons/copy-right.tsx: -------------------------------------------------------------------------------- 1 | export default function CopyRight({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/copy.tsx: -------------------------------------------------------------------------------- 1 | export default function Copy({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/external-link.tsx: -------------------------------------------------------------------------------- 1 | export default function CheckCircle2({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/shared/icons/eye-off.tsx: -------------------------------------------------------------------------------- 1 | export default function EyeOff({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/shared/icons/eye.tsx: -------------------------------------------------------------------------------- 1 | export default function Eye({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/facebook.tsx: -------------------------------------------------------------------------------- 1 | export function Facebook({ 2 | className, 3 | fill = "#1977f3", 4 | }: { 5 | className?: string; 6 | fill?: string; 7 | }) { 8 | return ( 9 | 16 | 17 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/shared/icons/file-up.tsx: -------------------------------------------------------------------------------- 1 | export default function FileUp({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/shared/icons/folder.tsx: -------------------------------------------------------------------------------- 1 | export default function Folder({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/github.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function GitHubIcon( 4 | props: React.JSX.IntrinsicAttributes & React.SVGProps, 5 | ) { 6 | return ( 7 | 8 | 13 | 14 | ); 15 | } 16 | 17 | export default GitHubIcon; 18 | -------------------------------------------------------------------------------- /components/shared/icons/globe.tsx: -------------------------------------------------------------------------------- 1 | export default function Globe({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/shared/icons/google.tsx: -------------------------------------------------------------------------------- 1 | export default function Google({ className }: { className?: string }) { 2 | return ( 3 | 10 | 14 | 18 | 22 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /components/shared/icons/grip-vertical.tsx: -------------------------------------------------------------------------------- 1 | export default function GripVertical({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/shared/icons/home.tsx: -------------------------------------------------------------------------------- 1 | export default function Home({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ComponentType, SVGProps } from "react"; 4 | 5 | import { LucideIcon } from "lucide-react"; 6 | 7 | export type Icon = LucideIcon | ComponentType>; 8 | -------------------------------------------------------------------------------- /components/shared/icons/linkedin.tsx: -------------------------------------------------------------------------------- 1 | export default function LinkedIn({ 2 | className, 3 | color = true, 4 | }: { 5 | className?: string; 6 | color?: boolean; 7 | }) { 8 | return ( 9 | 17 | 18 | 22 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /components/shared/icons/menu.tsx: -------------------------------------------------------------------------------- 1 | export default function Menu({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/shared/icons/moon.tsx: -------------------------------------------------------------------------------- 1 | export default function Moon({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/shared/icons/more-horizontal.tsx: -------------------------------------------------------------------------------- 1 | export default function MoreHorizontal({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/shared/icons/more-vertical.tsx: -------------------------------------------------------------------------------- 1 | export default function MoreVertical({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/shared/icons/passkey.tsx: -------------------------------------------------------------------------------- 1 | export default function Passkey({ className }: { className?: string }) { 2 | return ( 3 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/shared/icons/pie-chart.tsx: -------------------------------------------------------------------------------- 1 | export default function PieChart({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/portrait-landscape.tsx: -------------------------------------------------------------------------------- 1 | export default function PortraitLandscape({ 2 | className, 3 | fill, 4 | }: { 5 | className?: string; 6 | fill?: string; 7 | }) { 8 | return ( 9 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/shared/icons/producthunt.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function ProductHuntIcon( 4 | props: React.JSX.IntrinsicAttributes & React.SVGProps, 5 | ) { 6 | return ( 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | export default ProductHuntIcon; 24 | -------------------------------------------------------------------------------- /components/shared/icons/search.tsx: -------------------------------------------------------------------------------- 1 | export default function Search({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/settings.tsx: -------------------------------------------------------------------------------- 1 | export default function Settings({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/sparkle.tsx: -------------------------------------------------------------------------------- 1 | export default function Sparkle({ className }: { className?: string }) { 2 | return ( 3 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /components/shared/icons/sun.tsx: -------------------------------------------------------------------------------- 1 | export default function Sun({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/shared/icons/teams.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TeamsIcon = () => { 4 | return ( 5 | 13 | 18 | 19 | ); 20 | }; 21 | 22 | export default TeamsIcon; 23 | -------------------------------------------------------------------------------- /components/shared/icons/user-round.tsx: -------------------------------------------------------------------------------- 1 | export default function UserRound({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/shared/icons/x-circle.tsx: -------------------------------------------------------------------------------- 1 | export default function XCircle({ 2 | className, 3 | fill, 4 | }: { 5 | className?: string; 6 | fill?: string; 7 | }) { 8 | return ( 9 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/shared/icons/x.tsx: -------------------------------------------------------------------------------- 1 | export default function X({ className }: { className?: string }) { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { 6 | ThemeProvider as NextThemesProvider, 7 | type ThemeProviderProps, 8 | } from "next-themes"; 9 | 10 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 11 | return {children}; 12 | } 13 | -------------------------------------------------------------------------------- /components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 6 | 7 | import Check from "@/components/shared/icons/check"; 8 | 9 | import { cn } from "@/lib/utils"; 10 | 11 | const Checkbox = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | 26 | 27 | 28 | 29 | )); 30 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 31 | 32 | export { Checkbox }; 33 | -------------------------------------------------------------------------------- /components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 2 | 3 | const Collapsible = CollapsiblePrimitive.Root; 4 | 5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 6 | 7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 8 | 9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 10 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import { type VariantProps, cva } from "class-variance-authority"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const labelVariants = cva( 9 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 10 | ); 11 | 12 | const Label = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef & 15 | VariantProps 16 | >(({ className, ...props }, ref) => ( 17 | 22 | )); 23 | Label.displayName = LabelPrimitive.Root.displayName; 24 | 25 | export { Label }; 26 | -------------------------------------------------------------------------------- /components/ui/loading-dots.module.css: -------------------------------------------------------------------------------- 1 | .loading { 2 | display: inline-flex; 3 | align-items: center; 4 | } 5 | 6 | .loading .spacer { 7 | margin-right: 2px; 8 | } 9 | 10 | .loading span { 11 | animation-name: blink; 12 | animation-duration: 1.4s; 13 | animation-iteration-count: infinite; 14 | animation-fill-mode: both; 15 | width: 5px; 16 | height: 5px; 17 | border-radius: 50%; 18 | display: inline-block; 19 | margin: 0 1px; 20 | } 21 | 22 | .loading span:nth-of-type(2) { 23 | animation-delay: 0.2s; 24 | } 25 | 26 | .loading span:nth-of-type(3) { 27 | animation-delay: 0.4s; 28 | } 29 | 30 | @keyframes blink { 31 | 0% { 32 | opacity: 0.2; 33 | } 34 | 20% { 35 | opacity: 1; 36 | } 37 | 100% { 38 | opacity: 0.2; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /components/ui/loading-dots.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./loading-dots.module.css"; 2 | 3 | const LoadingDots = ({ color = "#000" }: { color?: string }) => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default LoadingDots; 14 | -------------------------------------------------------------------------------- /components/ui/loading-spinner.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | import styles from "./loading-spinner.module.css"; 4 | 5 | export default function LoadingSpinner({ className }: { className?: string }) { 6 | return ( 7 |
    8 |
    9 | {[...Array(12)].map((_, i) => ( 10 |
    11 | ))} 12 |
    13 |
    14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/ui/portal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import * as PortalPrimitive from "@radix-ui/react-portal"; 4 | 5 | const Portal = ({ 6 | containerId, 7 | className, 8 | children, 9 | }: { 10 | containerId?: string | null; 11 | children: React.ReactElement; 12 | className?: string; 13 | }) => { 14 | const [mounted, setMounted] = React.useState(false); 15 | React.useEffect(() => setMounted(true), []); 16 | 17 | return ( 18 | 26 | {children} 27 | 28 | ); 29 | }; 30 | Portal.displayName = PortalPrimitive.Root.displayName; 31 | 32 | export { Portal }; 33 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Separator = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >( 13 | ( 14 | { className, orientation = "horizontal", decorative = true, ...props }, 15 | ref, 16 | ) => ( 17 | 28 | ), 29 | ); 30 | Separator.displayName = SeparatorPrimitive.Root.displayName; 31 | 32 | export { Separator }; 33 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
    15 | ); 16 | } 17 | 18 | export { Skeleton }; 19 | -------------------------------------------------------------------------------- /components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner } from "sonner"; 5 | 6 | type ToasterProps = React.ComponentProps; 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme(); 10 | 11 | return ( 12 | 30 | ); 31 | }; 32 | 33 | export { Toaster }; 34 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |