├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .storybook ├── main.js ├── preview-head.html └── preview.tsx ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── email-templates ├── messages │ ├── action-notification.hbs │ ├── confirm-email.hbs │ ├── forgot-password.hbs │ ├── invite-new-user.hbs │ └── notification.hbs └── partials │ ├── action-button.hbs │ └── layout.hbs ├── index.html ├── interval.d.ts ├── jest.config.js ├── package.json ├── playwright.config.ts ├── postcss.config.js ├── prisma ├── clean-vite-cache-generator.js ├── init.sql ├── schema.prisma ├── seed-consts.ts └── seed.ts ├── public ├── favicon.png ├── open-graph-twitter-1600w.png └── open-graph.png ├── scripts ├── STEPS_TO_RELEASE.md ├── docker-build.sh ├── drop-local-db.sh ├── pkg.sh └── pub.sh ├── src ├── App.tsx ├── components │ ├── APIKeyButton │ │ └── index.tsx │ ├── AccountSettings │ │ ├── EnrollMFAForm.tsx │ │ └── index.tsx │ ├── ActionBreadcrumbs.tsx │ ├── ActionSettings │ │ ├── ArchiveDialog.tsx │ │ ├── General.tsx │ │ ├── Notifications.tsx │ │ ├── Permissions.tsx │ │ └── Schedule.tsx │ ├── ActionsList │ │ ├── ActionsListGroup.tsx │ │ ├── ActionsListItem.tsx │ │ └── index.tsx │ ├── AuthLoadingState.tsx │ ├── AuthPageHeader.tsx │ ├── ChoiceButtons │ │ └── index.tsx │ ├── ChronologicalScrollableFeed.tsx │ ├── CommandBar │ │ ├── MultilineKBarSearch.tsx │ │ ├── index.tsx │ │ └── useCommandBarActions.ts │ ├── Console │ │ └── index.tsx │ ├── ConsoleOnboarding │ │ └── index.tsx │ ├── DashboardContext.tsx │ ├── DashboardLayout │ │ └── index.tsx │ ├── DashboardNav │ │ ├── ControlPanel.tsx │ │ ├── MobileNav.tsx │ │ ├── ModeSwitch.tsx │ │ ├── OrgSwitcher.tsx │ │ ├── index.tsx │ │ └── useControlPanelNav.ts │ ├── DotsSpinner │ │ └── index.tsx │ ├── DropdownMenu │ │ ├── dropdown.css │ │ └── index.tsx │ ├── EmptyState │ │ └── index.tsx │ ├── EnvironmentColor.tsx │ ├── ErrorMessage │ │ └── index.tsx │ ├── FileUploadButton │ │ ├── FileUploadButton.stories.tsx │ │ └── index.tsx │ ├── HelpText │ │ ├── HelpText.stories.tsx │ │ └── index.tsx │ ├── HighlightedCodeBlock │ │ ├── editorTheme.scss │ │ └── index.tsx │ ├── HostStatusIndicator │ │ └── index.tsx │ ├── IVAPIError │ │ └── index.tsx │ ├── IVAlert │ │ └── index.tsx │ ├── IVAvatar │ │ ├── IVAvatar.stories.tsx │ │ └── index.tsx │ ├── IVButton │ │ ├── IVButton.stories.tsx │ │ └── index.tsx │ ├── IVCheckbox │ │ ├── IVCheckbox.stories.tsx │ │ └── index.tsx │ ├── IVConstraintsIndicator │ │ └── index.tsx │ ├── IVDatePicker │ │ ├── IVDatePicker.stories.tsx │ │ ├── datepicker.scss │ │ └── index.tsx │ ├── IVDateTimePicker │ │ ├── CalendarPopover.tsx │ │ ├── DateInput.tsx │ │ ├── IVDateTimePicker.stories.tsx │ │ ├── TimeInput.tsx │ │ ├── datePickerUtils.ts │ │ ├── index.tsx │ │ └── useDateTimePickerState.ts │ ├── IVDialog │ │ ├── IVDialog.stories.tsx │ │ ├── dialog.css │ │ └── index.tsx │ ├── IVInputField │ │ └── index.tsx │ ├── IVMediaGrid │ │ ├── MediaGridItem.tsx │ │ └── index.tsx │ ├── IVRadio │ │ ├── IVRadio.stories.tsx │ │ └── index.tsx │ ├── IVRichTextEditor │ │ ├── IVRichTextEditor.stories.tsx │ │ ├── index.tsx │ │ ├── iv-rich-text-editor.css │ │ └── lazy.tsx │ ├── IVSelect │ │ ├── IVSelect.stories.tsx │ │ ├── async.tsx │ │ └── index.tsx │ ├── IVSpinner │ │ ├── IVSpinner │ │ │ ├── IVSpinner.stories.tsx │ │ │ └── index.tsx │ │ └── index.tsx │ ├── IVStatusPill.tsx │ ├── IVTable │ │ ├── DesktopTable.tsx │ │ ├── IVTable.stories.tsx │ │ ├── TableDownloader.tsx │ │ ├── TableRowMenu.tsx │ │ ├── VerticalTable.tsx │ │ ├── index.tsx │ │ ├── table.css │ │ ├── useTable.ts │ │ └── useTableSerializer.ts │ ├── IVTextArea │ │ ├── IVTextArea.stories.tsx │ │ └── index.tsx │ ├── IVTextInput │ │ ├── IVTextInput.stories.tsx │ │ └── index.tsx │ ├── IVTimePicker │ │ ├── IVTimePicker.stories.tsx │ │ └── index.tsx │ ├── IVToggle │ │ └── index.tsx │ ├── IVTooltip │ │ ├── IVTooltip.stories.tsx │ │ ├── index.tsx │ │ └── tooltip.css │ ├── IVUnstyledButtonOrLink.tsx │ ├── KeyValueTable │ │ ├── KeyValueTable.stories.tsx │ │ └── index.tsx │ ├── KeysList │ │ └── index.tsx │ ├── ListViewToggle.tsx │ ├── LoginRedirect.tsx │ ├── Logo.tsx │ ├── MFAInput.tsx │ ├── MeContext.tsx │ ├── MetadataCardList │ │ ├── MetadataValue.tsx │ │ └── index.tsx │ ├── NavTabs │ │ └── index.tsx │ ├── NotFound.tsx │ ├── NotificationCenter.tsx │ ├── ObjectViewer │ │ ├── index.tsx │ │ └── object-viewer.css │ ├── PageHeading │ │ └── index.tsx │ ├── PageLayout │ │ └── index.tsx │ ├── PageUI │ │ ├── Layout │ │ │ ├── Basic │ │ │ │ └── index.tsx │ │ │ ├── MobileSubnav.tsx │ │ │ ├── Subnav.tsx │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── usePage.ts │ ├── PermissionSelector │ │ └── index.tsx │ ├── QueuedActionsList │ │ ├── QueuedActionsList.stories.tsx │ │ └── index.tsx │ ├── RenderContext.tsx │ ├── RenderHTML │ │ └── index.tsx │ ├── RenderIOCall │ │ ├── ComponentError.tsx │ │ ├── ComponentRenderer.tsx │ │ ├── index.tsx │ │ └── useInitialValues.ts │ ├── RenderMarkdown │ │ └── index.tsx │ ├── RenderValue │ │ └── index.tsx │ ├── SectionHeading │ │ └── index.tsx │ ├── Sidebar │ │ └── OrgSwitcher.tsx │ ├── SimpleTable │ │ └── index.tsx │ ├── TeamsSelector │ │ └── index.tsx │ ├── TransactionHistory │ │ └── index.tsx │ ├── TransactionUI │ │ ├── ConfirmIdentity │ │ │ └── index.tsx │ │ ├── InputSpreadsheet │ │ │ ├── Importer │ │ │ │ └── index.tsx │ │ │ ├── InputSpreadsheet.stories.tsx │ │ │ ├── SpreadsheetEditor.tsx │ │ │ ├── helpers.ts │ │ │ ├── index.tsx │ │ │ ├── lazy.tsx │ │ │ └── react-contextmenu.css │ │ ├── _presentation │ │ │ ├── CompletionState │ │ │ │ ├── CompletionState.stories.tsx │ │ │ │ └── index.tsx │ │ │ ├── ComponentError │ │ │ │ └── index.tsx │ │ │ ├── ControlPanel │ │ │ │ └── index.tsx │ │ │ ├── ErrorState │ │ │ │ └── index.tsx │ │ │ ├── InlineAction │ │ │ │ └── index.tsx │ │ │ ├── LoadingState │ │ │ │ ├── Indeterminate.stories.tsx │ │ │ │ ├── Indeterminate.tsx │ │ │ │ ├── InlineLoading.stories.tsx │ │ │ │ ├── InlineLoading.tsx │ │ │ │ ├── Progress.stories.tsx │ │ │ │ ├── Progress.tsx │ │ │ │ └── index.tsx │ │ │ ├── Logs │ │ │ │ ├── Logs.stories.tsx │ │ │ │ └── index.tsx │ │ │ ├── Notifications │ │ │ │ └── index.tsx │ │ │ ├── Result │ │ │ │ └── index.tsx │ │ │ ├── TransactionHeader │ │ │ │ └── index.tsx │ │ │ └── TransactionLayout │ │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── useConsole.ts │ │ ├── useInput.ts │ │ ├── useTransaction.ts │ │ └── useWebSocketClient.tsx │ ├── TransactionsList │ │ └── index.tsx │ ├── Transition.tsx │ ├── Truncate.tsx │ ├── UsersList │ │ └── index.tsx │ ├── WrapOnUnderscores.tsx │ ├── examples │ │ └── FormValidation.stories.tsx │ └── io-methods │ │ ├── Confirm │ │ ├── Confirm.stories.tsx │ │ └── index.tsx │ │ ├── DisplayCode │ │ ├── DisplayCode.stories.tsx │ │ └── index.tsx │ │ ├── DisplayGrid │ │ └── index.tsx │ │ ├── DisplayHTML │ │ ├── DisplayHTML.stories.tsx │ │ ├── index.tsx │ │ ├── lazy.tsx │ │ └── stub.tsx │ │ ├── DisplayHeading │ │ ├── DisplayHeading.stories.tsx │ │ └── index.tsx │ │ ├── DisplayImage │ │ ├── DisplayImage.stories.tsx │ │ └── index.tsx │ │ ├── DisplayLink │ │ ├── DisplayLink.stories.tsx │ │ └── index.tsx │ │ ├── DisplayMarkdown │ │ ├── DisplayMarkdown.stories.tsx │ │ ├── index.tsx │ │ ├── lazy.tsx │ │ └── stub.tsx │ │ ├── DisplayMetadata │ │ ├── DisplayMetadata.stories.tsx │ │ └── index.tsx │ │ ├── DisplayObject │ │ ├── DisplayObject.stories.tsx │ │ └── index.tsx │ │ ├── DisplayProgressIndeterminate │ │ ├── DisplayProgressIndeterminate.stories.tsx │ │ └── index.tsx │ │ ├── DisplayProgressSteps │ │ ├── DisplayProgressSteps.stories.tsx │ │ └── index.tsx │ │ ├── DisplayTable │ │ ├── DisplayTable.stories.tsx │ │ ├── index.tsx │ │ └── useDisplayTableState.ts │ │ ├── DisplayVideo │ │ ├── DisplayVideo.stories.tsx │ │ └── index.tsx │ │ ├── InputBoolean │ │ ├── InputBoolean.stories.tsx │ │ └── index.tsx │ │ ├── InputDate │ │ └── index.tsx │ │ ├── InputDateTime │ │ └── index.tsx │ │ ├── InputEmail │ │ ├── InputEmail.stories.tsx │ │ └── index.tsx │ │ ├── InputNumber │ │ ├── InputNumber.stories.tsx │ │ └── index.tsx │ │ ├── InputRichText │ │ ├── InputRichText.stories.tsx │ │ └── index.tsx │ │ ├── InputSlider │ │ ├── InputSlider.stories.tsx │ │ └── index.tsx │ │ ├── InputText │ │ ├── InputText.stories.tsx │ │ └── index.tsx │ │ ├── InputTime │ │ └── index.tsx │ │ ├── InputURL │ │ └── index.tsx │ │ ├── ListProgress │ │ ├── ListProgress.stories.tsx │ │ └── index.tsx │ │ ├── Search │ │ ├── Search.stories.tsx │ │ └── index.tsx │ │ ├── SelectMultiple │ │ ├── SelectMultiple.stories.tsx │ │ └── index.tsx │ │ ├── SelectSingle │ │ ├── SelectSingle.stories.tsx │ │ └── index.tsx │ │ ├── SelectTable │ │ ├── SelectTable.stories.tsx │ │ ├── index.tsx │ │ └── useSelectTableState.ts │ │ └── UploadFile │ │ ├── UploadFile.stories.tsx │ │ └── index.tsx ├── emails │ ├── action-notification │ │ ├── index.ts │ │ └── preview.ts │ ├── confirm-email │ │ ├── index.ts │ │ └── preview.ts │ ├── forgot-password │ │ ├── index.ts │ │ └── preview.ts │ ├── index.ts │ ├── invite-new-user │ │ ├── index.ts │ │ └── preview.ts │ ├── nodemon.json │ ├── notification │ │ ├── index.ts │ │ └── preview.ts │ ├── preview-server.ts │ └── sender.ts ├── entry.ts ├── env.ts ├── icons │ ├── compiled │ │ ├── Actions.tsx │ │ ├── AddRow.tsx │ │ ├── AddRowAbove.tsx │ │ ├── BulletedList.tsx │ │ ├── Calendar.tsx │ │ ├── Cancel.tsx │ │ ├── CaretDown.tsx │ │ ├── Check.tsx │ │ ├── CheckCircle.tsx │ │ ├── CheckCircleOutline.tsx │ │ ├── ChevronDown.tsx │ │ ├── ChevronLeft.tsx │ │ ├── ChevronRight.tsx │ │ ├── ChevronUp.tsx │ │ ├── CircledPlay.tsx │ │ ├── ClearFormatting.tsx │ │ ├── Clipboard.tsx │ │ ├── Close.tsx │ │ ├── Code.tsx │ │ ├── Copy.tsx │ │ ├── Delete.tsx │ │ ├── DeleteRow.tsx │ │ ├── DownloadsFolder.tsx │ │ ├── ErrorCircle.tsx │ │ ├── ExclamationWarn.tsx │ │ ├── ExternalLink.tsx │ │ ├── Eye.tsx │ │ ├── Folder.tsx │ │ ├── FrownFace.tsx │ │ ├── Github.tsx │ │ ├── Google.tsx │ │ ├── IconSortUp.tsx │ │ ├── Image.tsx │ │ ├── Info.tsx │ │ ├── Invisible.tsx │ │ ├── Link.tsx │ │ ├── Lock.tsx │ │ ├── Menu.tsx │ │ ├── MinusMini.tsx │ │ ├── More.tsx │ │ ├── NumberedList.tsx │ │ ├── Offline.tsx │ │ ├── Ok.tsx │ │ ├── Online.tsx │ │ ├── Play.tsx │ │ ├── PlusMini.tsx │ │ ├── Puzzled.tsx │ │ ├── QuoteLeft.tsx │ │ ├── Redirect.tsx │ │ ├── Redo.tsx │ │ ├── Refresh.tsx │ │ ├── RightArrow.tsx │ │ ├── Rocket.tsx │ │ ├── Schedule.tsx │ │ ├── Search.tsx │ │ ├── Settings.tsx │ │ ├── Slack.tsx │ │ ├── SortDown.tsx │ │ ├── SortUp.tsx │ │ ├── Sparkling.tsx │ │ ├── Subtract.tsx │ │ ├── TailwindChevronDown.tsx │ │ ├── ThumbsDown.tsx │ │ ├── ThumbsUp.tsx │ │ ├── TwitterCircled.tsx │ │ ├── Undo.tsx │ │ ├── UploadsFolder.tsx │ │ ├── User.tsx │ │ └── XCircle.tsx │ ├── manifest.json │ ├── src │ │ ├── actions.svg │ │ ├── add-row-above.svg │ │ ├── add-row.svg │ │ ├── bulleted-list.svg │ │ ├── calendar.svg │ │ ├── cancel.svg │ │ ├── caret-down.svg │ │ ├── check-circle-outline.svg │ │ ├── check-circle.svg │ │ ├── check.svg │ │ ├── chevron-down.svg │ │ ├── chevron-left.svg │ │ ├── chevron-right.svg │ │ ├── chevron-up.svg │ │ ├── circled-play.svg │ │ ├── clear-formatting.svg │ │ ├── clipboard.svg │ │ ├── close.svg │ │ ├── code.svg │ │ ├── copy.svg │ │ ├── delete-row.svg │ │ ├── delete.svg │ │ ├── downloads-folder.svg │ │ ├── error-circle.svg │ │ ├── exclamation-warn.svg │ │ ├── external-link.svg │ │ ├── eye.svg │ │ ├── folder.svg │ │ ├── frown-face.svg │ │ ├── github.svg │ │ ├── google.svg │ │ ├── image.svg │ │ ├── info.svg │ │ ├── invisible.svg │ │ ├── link.svg │ │ ├── lock.svg │ │ ├── menu.svg │ │ ├── minus-mini.svg │ │ ├── more.svg │ │ ├── numbered-list.svg │ │ ├── offline.svg │ │ ├── ok.svg │ │ ├── online.svg │ │ ├── play.svg │ │ ├── plus-mini.svg │ │ ├── puzzled.svg │ │ ├── quote-left.svg │ │ ├── redirect.svg │ │ ├── redo.svg │ │ ├── refresh.svg │ │ ├── right-arrow.svg │ │ ├── rocket.svg │ │ ├── schedule.svg │ │ ├── search.svg │ │ ├── settings.svg │ │ ├── slack.svg │ │ ├── sort-down.svg │ │ ├── sort-up.svg │ │ ├── sparkling.svg │ │ ├── subtract.svg │ │ ├── tailwind-chevron-down.svg │ │ ├── thumbs-down.svg │ │ ├── thumbs-up.svg │ │ ├── twitter-circled.svg │ │ ├── undo.svg │ │ ├── uploads-folder.svg │ │ ├── user.svg │ │ └── x-circle.svg │ └── svgr-template.js ├── main.tsx ├── pages │ ├── accept-invitation.tsx │ ├── authentication-confirmed.tsx │ ├── authentication-not-confirmed.tsx │ ├── component-preview.tsx │ ├── confirm-email.tsx │ ├── confirm-signup │ │ └── [orgSlug].tsx │ ├── dashboard │ │ ├── [orgSlug] │ │ │ ├── [...fallback].tsx │ │ │ ├── account.tsx │ │ │ ├── actions │ │ │ │ └── [...actionSlug].tsx │ │ │ ├── configure │ │ │ │ └── [...actionSlug].tsx │ │ │ ├── details │ │ │ │ └── [...actionSlug].tsx │ │ │ ├── develop │ │ │ │ ├── actions │ │ │ │ │ └── [...actionSlug].tsx │ │ │ │ ├── console │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── keys │ │ │ │ │ └── index.tsx │ │ │ │ └── serverless-endpoints │ │ │ │ │ └── index.tsx │ │ │ ├── history │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── new-organization.tsx │ │ │ ├── organization │ │ │ │ ├── actions.tsx │ │ │ │ ├── environments.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── settings.tsx │ │ │ │ ├── teams │ │ │ │ │ ├── [groupId].tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── users.tsx │ │ │ └── transactions │ │ │ │ ├── [transactionId].tsx │ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── develop │ │ ├── GhostModeConsoleLayout.tsx │ │ └── [orgSlug] │ │ │ ├── actions │ │ │ └── [...actionSlug].tsx │ │ │ └── index.tsx │ ├── enroll-mfa.tsx │ ├── forgot-password.tsx │ ├── index.tsx │ ├── login.tsx │ ├── not-found.tsx │ ├── reset-password.tsx │ ├── signup.tsx │ └── verify-mfa.tsx ├── routes.ts ├── server │ ├── __test__ │ │ ├── auth.test.ts │ │ └── user.test.ts │ ├── api │ │ ├── actions.ts │ │ ├── auth │ │ │ ├── ghost │ │ │ │ ├── generateRandomSlug.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── login.ts │ │ │ ├── logout.ts │ │ │ ├── oauth │ │ │ │ ├── index.ts │ │ │ │ └── slack.ts │ │ │ ├── reset.ts │ │ │ ├── session.ts │ │ │ └── sso │ │ │ │ ├── auth.ts │ │ │ │ ├── callback.ts │ │ │ │ ├── index.ts │ │ │ │ └── sign-in-with-google.ts │ │ ├── hosts.ts │ │ ├── notify.ts │ │ └── workosWebhooks.ts │ ├── auth.ts │ ├── healthCheck.ts │ ├── index.ts │ ├── middleware │ │ └── requestLogger.ts │ ├── prisma.ts │ ├── proxyServerForTesting.ts │ ├── trpc │ │ ├── action.ts │ │ ├── actionGroup.ts │ │ ├── apiKeys.ts │ │ ├── auth.ts │ │ ├── dashboard.ts │ │ ├── environments.ts │ │ ├── group.ts │ │ ├── httpHosts.ts │ │ ├── index.ts │ │ ├── organization.ts │ │ ├── transaction.ts │ │ ├── uploads.ts │ │ ├── user.ts │ │ └── util.ts │ ├── user.ts │ └── utils │ │ ├── __test__ │ │ └── actions.ts │ │ ├── actionSchedule.ts │ │ ├── actions.ts │ │ ├── apiKey.ts │ │ ├── email.ts │ │ ├── featureFlags.ts │ │ ├── ghostMode.ts │ │ ├── hash.ts │ │ ├── hosts.ts │ │ ├── logger.ts │ │ ├── notify.ts │ │ ├── organizations.ts │ │ ├── routes.ts │ │ ├── sdkAlerts.ts │ │ ├── slack.ts │ │ ├── sleep.ts │ │ ├── slugs.ts │ │ ├── transactions.ts │ │ ├── uploads.ts │ │ └── wss.ts ├── styles │ ├── combobox.scss │ ├── context-menu.css │ ├── forms.scss │ └── globals.css ├── utils │ ├── IOComponentError.ts │ ├── __test__ │ │ ├── date.test.ts │ │ ├── slug.test.ts │ │ ├── text.test.ts │ │ └── validate.test.ts │ ├── actionSchedule.ts │ ├── actions.ts │ ├── auth.ts │ ├── color.ts │ ├── componentNameMap.ts │ ├── currency.ts │ ├── date.ts │ ├── deserialize.ts │ ├── email.ts │ ├── environments.ts │ ├── examples.ts │ ├── extractOrgSlug.ts │ ├── featureFlags.ts │ ├── formatters.ts │ ├── getQueuedActionId.ts │ ├── getTextWidth.ts │ ├── isomorphicConsts.ts │ ├── localStorage.ts │ ├── logger.ts │ ├── mockData.ts │ ├── navigation.ts │ ├── navigationHooks.ts │ ├── notify.ts │ ├── number.ts │ ├── organization.ts │ ├── parseActionResult.ts │ ├── permissions.ts │ ├── preventDefaultInputEnter.ts │ ├── queuedActions.ts │ ├── referralSchema.ts │ ├── routes.ts │ ├── sessionStorage.ts │ ├── stringify.ts │ ├── superjson.ts │ ├── text.ts │ ├── throttle.ts │ ├── timezones.ts │ ├── transactions.ts │ ├── trpc │ │ ├── index.ts │ │ └── utils.ts │ ├── types.ts │ ├── ui.ts │ ├── uniqueId.ts │ ├── uploads.ts │ ├── url.ts │ ├── useActionUrlBuilder.ts │ ├── useAnchorScroll.ts │ ├── useBackPath.ts │ ├── useCopyToClipboard.ts │ ├── useDashboardStructure.ts │ ├── useDebounce.ts │ ├── useDisableSelectionWithShiftKey.ts │ ├── useHasSession.ts │ ├── useImageLoaded.ts │ ├── useInput.ts │ ├── useInterval.ts │ ├── useIsFeatureEnabled.ts │ ├── useIsomorphicLocation.ts │ ├── useOrgEnvSwitcher.ts │ ├── useOrgSwitcher.ts │ ├── usePageMenuItems.ts │ ├── usePageVisibility.ts │ ├── usePlatform.ts │ ├── usePrevious.ts │ ├── useQueryParameterState.ts │ ├── useScrollToTop.ts │ ├── useSearchParams.ts │ ├── useShouldWarnOnNavigation.ts │ ├── useStartTransactionUser.ts │ ├── useSubmitFormWithShortcut.ts │ ├── useTransactionAutoFocus.ts │ ├── useTransactionAutoScroll.ts │ ├── useTransactionNavigationWarning.ts │ ├── useWindowSize.ts │ ├── user.ts │ └── validate.ts ├── vite-env.d.ts └── wss │ ├── actionSchedule.ts │ ├── consts.ts │ ├── inApp │ └── wss_state │ │ ├── clients.ts │ │ ├── hosts.ts │ │ └── index.ts │ ├── index.ts │ ├── notify.ts │ ├── processVars.ts │ ├── transactions.ts │ └── wss.ts ├── svgr.config.js ├── tailwind.config.js ├── test ├── _fixtures.ts ├── _setup.ts ├── actions │ ├── displayTable.test.ts │ └── spreadsheet.test.ts ├── app │ ├── actionAccess.test.ts │ ├── console.test.ts │ ├── live.test.ts │ ├── mfa.test.ts │ ├── organizationEnvironments.test.ts │ ├── queuedActions.test.ts │ ├── reconnect.test.ts │ ├── serverless.test.ts │ ├── signup.test.ts │ └── users.test.ts ├── classes │ ├── Signup.ts │ └── Transaction.ts ├── data │ ├── canyon.mp4 │ ├── fail.gif │ ├── mockDb.ts │ ├── spreadsheet.csv │ └── table.ts ├── index.ts ├── releases │ ├── index.ts │ └── main │ │ ├── host.ts │ │ └── transactions.test.ts └── utils │ ├── date.ts │ ├── table.ts │ └── uploads.ts ├── tsconfig.json ├── tsconfig.server.json ├── tsconfig.wss.json ├── vite.config.ts └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | node_modules 3 | dist 4 | .env -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /.next/ 3 | /out/ 4 | /build 5 | 6 | *.pem 7 | 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | .env* 13 | .env.* 14 | .vercel 15 | 16 | dist 17 | dist-ssr 18 | 19 | test/.session.json 20 | test/.session-support.json 21 | test/results 22 | 23 | # Test file for dev 24 | build-oss.sh 25 | 26 | *.tsbuildinfo 27 | 28 | release 29 | release.tar.gz -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | singleQuote: true 3 | printWidth: 80 4 | semi: false 5 | arrowParens: "avoid" 6 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 5 | 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["workos"] 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.18.0-alpine AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json yarn.lock ./ 6 | 7 | RUN yarn install --frozen-lockfile 8 | 9 | COPY . . 10 | 11 | RUN yarn build 12 | 13 | FROM node:18.18.0-alpine AS runner 14 | 15 | WORKDIR /app 16 | 17 | COPY --from=builder /app/dist ./dist 18 | COPY --from=builder /app/node_modules ./node_modules 19 | COPY --from=builder /app/prisma ./prisma 20 | COPY --from=builder /app/email-templates ./email-templates 21 | COPY --from=builder /app/public ./public 22 | 23 | CMD [ "node", "dist/src/entry.js", "start" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Interval 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /email-templates/messages/action-notification.hbs: -------------------------------------------------------------------------------- 1 | {{#>layout}} 2 | {{#*inline "content"}} 3 | 4 | {{#if title}} 5 |

{{title}}

6 | {{else}} 7 |

{{metadata.actionName}} notification

8 | {{/if}} 9 | 10 |

Your {{metadata.actionName}} action on Interval triggered a notification.

11 | 12 |
13 | {{#if title}} 14 | {{title}} 15 |
16 | {{/if}} 17 | {{message}} 18 |
19 | 20 |

View the transaction that sent this notification here.

21 | 22 | 26 | 27 | {{#unless_equals failedDetails.length 0}} 28 |
29 |

⚠️ Delivery failed for this notification to the following destinations:

30 | 35 |
36 | {{/unless_equals}} 37 | 38 | {{/inline}} 39 | {{/layout}} 40 | -------------------------------------------------------------------------------- /email-templates/messages/confirm-email.hbs: -------------------------------------------------------------------------------- 1 | {{#>layout}} 2 | {{#*inline "content"}} 3 | 4 |

Please confirm your email

5 | 6 | {{#if isEmailChange}} 7 |

We received a request to change your email address on Interval. Click the button below to confirm your new email:

8 | {{else}} 9 |

Click the button below to confirm your email address on Interval:

10 | {{/if}} 11 | 12 | {{> actionButton url=confirmUrl label='Confirm email' }} 13 | 14 | {{#if isEmailChange}} 15 |

If you didn't request to change your email address, you can safely ignore this email.

16 | {{/if}} 17 | 18 | 19 | {{/inline}} 20 | {{/layout}} 21 | -------------------------------------------------------------------------------- /email-templates/messages/forgot-password.hbs: -------------------------------------------------------------------------------- 1 | {{#>layout}} 2 | {{#*inline "content"}} 3 | 4 |

Password reset request

5 | 6 |

7 | We received a request to reset your password for 8 | Interval. 9 |

10 | 11 |

12 | If you made this request, please click the button below to reset your password. 13 | If you did not make this request, you can safely ignore this email. 14 |

15 | 16 | {{> actionButton url=resetUrl label='Reset password' }} 17 | 18 | {{/inline}} 19 | {{/layout}} 20 | -------------------------------------------------------------------------------- /email-templates/messages/invite-new-user.hbs: -------------------------------------------------------------------------------- 1 | {{#>layout}} 2 | {{#*inline "content"}} 3 | 4 |

Join {{organizationName}} on Interval

5 | 6 |

7 | You've been invited to join {{organizationName}} on Interval. Click the button below to set up your account: 8 |

9 | 10 | {{> actionButton url=signupUrl label='Join your team' }} 11 | 12 | 13 | {{/inline}} 14 | {{/layout}} 15 | -------------------------------------------------------------------------------- /email-templates/messages/notification.hbs: -------------------------------------------------------------------------------- 1 | {{#>layout}} 2 | {{#*inline "content"}} 3 | 4 | {{#if title}} 5 |

{{title}}

6 | {{else}} 7 |

Interval notification

8 | {{/if}} 9 | 10 |

A notification has been triggered via Interval.

11 | 12 |
13 | {{#if title}} 14 | {{title}} 15 |
16 | {{/if}} 17 | {{message}} 18 |
19 | 20 | 23 | 24 | {{#unless_equals failedDetails.length 0}} 25 |
26 |

⚠️ Delivery failed for this notification to the following destinations:

27 | 32 |
33 | {{/unless_equals}} 34 | 35 | {{/inline}} 36 | {{/layout}} 37 | -------------------------------------------------------------------------------- /email-templates/partials/action-button.hbs: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 32 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Interval 7 | 12 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /interval.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | interface Window { 3 | __INTERVAL_ORGANIZATION_ID?: string 4 | __INTERVAL_ORGANIZATION_ENVIRONMENT_ID?: string 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | const { pathsToModuleNameMapper } = require('ts-jest') 5 | const { compilerOptions } = require('./tsconfig.json') 6 | 7 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 8 | module.exports = { 9 | preset: 'ts-jest', 10 | testEnvironment: 'node', 11 | testPathIgnorePatterns: ['/dist/', '/web\\/test/'], 12 | testMatch: ['**/__test__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], 13 | modulePaths: [''], 14 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 15 | prefix: '', 16 | }), 17 | } 18 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | const tailwind = require('tailwindcss') 5 | const autoprefixer = require('autoprefixer') 6 | const tailwindConfig = require('./tailwind.config') 7 | 8 | module.exports = { 9 | plugins: [ 10 | require('postcss-import'), 11 | require('tailwindcss/nesting'), 12 | tailwind(tailwindConfig), 13 | autoprefixer, 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /prisma/init.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS pgcrypto; 2 | 3 | CREATE OR REPLACE FUNCTION nanoid(size int DEFAULT 21) 4 | RETURNS text AS $$ 5 | DECLARE 6 | id text := ''; 7 | i int := 0; 8 | urlAlphabet char(64) := 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; 9 | bytes bytea := gen_random_bytes(size); 10 | byte int; 11 | pos int; 12 | BEGIN 13 | WHILE i < size LOOP 14 | byte := get_byte(bytes, i); 15 | pos := (byte & 63) + 1; -- + 1 because substr starts at 1 for some reason 16 | id := id || substr(urlAlphabet, pos, 1); 17 | i = i + 1; 18 | END LOOP; 19 | RETURN id; 20 | END 21 | $$ LANGUAGE PLPGSQL STABLE; 22 | -------------------------------------------------------------------------------- /prisma/seed-consts.ts: -------------------------------------------------------------------------------- 1 | // these are shared between `web` and `playgrounds/server` 2 | export const ALEX_USER_ID = 'Z-bMgZZ1IY1NkgwYzE1n6' 3 | export const INTERVAL_USER_ID = '_iVAogMEwtfuhEKczhv_t' 4 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interval/server/e83ae439fb87cbc68fc0c765c4a7719dcfc16be9/public/favicon.png -------------------------------------------------------------------------------- /public/open-graph-twitter-1600w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interval/server/e83ae439fb87cbc68fc0c765c4a7719dcfc16be9/public/open-graph-twitter-1600w.png -------------------------------------------------------------------------------- /public/open-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interval/server/e83ae439fb87cbc68fc0c765c4a7719dcfc16be9/public/open-graph.png -------------------------------------------------------------------------------- /scripts/STEPS_TO_RELEASE.md: -------------------------------------------------------------------------------- 1 | 1. `yarn install` 2 | 2. Bump version in `package.json` to new version number 3 | 3. `yarn build` compiles + type checks 4 | 4. `yarn pkg` creates a "release" folder w/ the contents of the npm package 5 | 5. `./pub.sh` publishes to npm registry. Note! This isn't a `yarn` command like the others because npm doesn't think you're logged in when running from the yarn script runner. 6 | 6. `yarn docker` creates a Docker image + publishes to the Docker registry 7 | -------------------------------------------------------------------------------- /scripts/docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Any subsequent(*) commands which fail will cause the shell script to exit immediately 3 | docker buildx create --use 4 | pkgV=`node -e "console.log(require('./package.json').version.trim())"` 5 | specificVersionTag=alexarena/interval-server:$pkgV 6 | # Build the docker image + pushes it to registry 7 | docker buildx build --output=type=registry --platform=linux/amd64,linux/arm64 -t alexarena/interval-server:latest -t $specificVersionTag . -------------------------------------------------------------------------------- /scripts/drop-local-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Any subsequent(*) commands which fail will cause the shell script to exit immediately 3 | 4 | psql -c 'DROP database "interval";' -------------------------------------------------------------------------------- /scripts/pkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Any subsequent(*) commands which fail will cause the shell script to exit immediately 3 | 4 | rm -rf release 5 | mkdir release 6 | 7 | cp -r dist release/dist 8 | cp package.json release/package.json 9 | 10 | mkdir release/prisma 11 | cp prisma/schema.prisma release/prisma/schema.prisma 12 | cp prisma/clean-vite-cache-generator.js release/prisma/clean-vite-cache-generator.js 13 | 14 | cp -r email-templates release/email-templates 15 | cp -r LICENSE release/license 16 | cp README.md release/README.md 17 | 18 | # tar -czf release.tar.gz release 19 | -------------------------------------------------------------------------------- /scripts/pub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # Any subsequent(*) commands which fail will cause the shell script to exit immediately 3 | 4 | npm publish ./release --access public -------------------------------------------------------------------------------- /src/components/APIKeyButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { trpc } from '~/utils/trpc' 2 | import useCopyToClipboard from '~/utils/useCopyToClipboard' 3 | import IVButton from '~/components/IVButton' 4 | import { notify } from '~/components/NotificationCenter' 5 | import { useEffect } from 'react' 6 | import IconClipboard from '~/icons/compiled/Clipboard' 7 | 8 | export default function ApiKeyButton() { 9 | const key = trpc.useQuery(['key.dev']) 10 | 11 | const { isCopied, onCopyClick } = useCopyToClipboard() 12 | 13 | useEffect(() => { 14 | if (isCopied) { 15 | notify.success('Copied token to clipboard') 16 | } 17 | }, [isCopied]) 18 | 19 | return ( 20 | onCopyClick(key?.data?.key || '')} 23 | label={ 24 | 25 | {key?.data?.key} 26 | 27 | 28 | } 29 | /> 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/components/AuthLoadingState.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet-async' 2 | import IVButton from '~/components/IVButton' 3 | import IVSpinner from '~/components/IVSpinner' 4 | 5 | export default function AuthLoadingState({ 6 | pageTitle, 7 | children, 8 | buttonLabel = 'Go back', 9 | buttonHref = '/login', 10 | }: { 11 | pageTitle?: string 12 | children?: React.ReactNode 13 | buttonLabel?: string | null 14 | buttonHref?: string 15 | }) { 16 | return ( 17 |
18 | {pageTitle && ( 19 | 20 | {pageTitle} | Interval 21 | 22 | )} 23 | 24 |
25 | {children ?? } 26 | {children && ( 27 | 33 | )} 34 |
35 |
36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/AuthPageHeader.tsx: -------------------------------------------------------------------------------- 1 | import Logo from './Logo' 2 | 3 | export default function AuthPageHeader({ title }: { title: string }) { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 |
11 |

12 | {title} 13 |

14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/DashboardLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | import { Outlet } from 'react-router-dom' 3 | import { MeProvider } from '~/components/MeContext' 4 | import { DashboardProvider } from '../DashboardContext' 5 | import IVSpinner from '~/components/IVSpinner' 6 | import { WebSocketClientProvider } from '../TransactionUI/useWebSocketClient' 7 | 8 | // This contains a base Suspense wrapper as a fallback, but should 9 | // likely add one closer to any lazy components if used. 10 | 11 | export default function DashboardLayout() { 12 | return ( 13 | 14 | 15 | 16 | }> 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/DotsSpinner/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | 3 | export default function DotsSpinner() { 4 | const [value, setValue] = useState<[number, string]>([0, '⠋']) 5 | 6 | useEffect(() => { 7 | const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] 8 | 9 | const t = setInterval(function () { 10 | setValue(([frame]) => { 11 | frame = frame + 1 === frames.length ? 0 : frame + 1 12 | return [frame, frames[frame]] 13 | }) 14 | }, 80) 15 | 16 | return () => clearInterval(t) 17 | }, []) 18 | 19 | return <>{value[1]} 20 | } 21 | -------------------------------------------------------------------------------- /src/components/DropdownMenu/dropdown.css: -------------------------------------------------------------------------------- 1 | .iv-menu > div, 2 | .iv-menu[data-leave] > div { 3 | @apply opacity-0 -translate-y-2 transition-all duration-150 mt-1; 4 | } 5 | .iv-menu[data-enter] > div { 6 | @apply opacity-100 translate-y-0; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/EmptyState/index.tsx: -------------------------------------------------------------------------------- 1 | import IVButton, { IVButtonProps } from '~/components/IVButton' 2 | 3 | export default function EmptyState({ 4 | title, 5 | children, 6 | actions = [], 7 | Icon, 8 | }: { 9 | title: string 10 | children?: React.ReactNode 11 | actions?: IVButtonProps[] 12 | Icon?: React.ComponentType> 13 | }) { 14 | return ( 15 |
16 |
17 | {Icon && } 18 |

{title}

19 |
{children}
20 |
21 | {actions.map((button, key) => ( 22 | 23 | ))} 24 |
25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/index.tsx: -------------------------------------------------------------------------------- 1 | export default function ErrorMessage({ 2 | id, 3 | message, 4 | }: { 5 | id?: string 6 | message: React.ReactNode 7 | }) { 8 | return ( 9 |

14 | {message} 15 |

16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/HelpText/HelpText.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StoryFn, Meta } from '@storybook/react' 3 | import Component from '.' 4 | 5 | export default { 6 | title: 'Components/HelpText', 7 | component: Component, 8 | } as Meta 9 | 10 | const Template: StoryFn = args => 11 | 12 | export const Default = Template.bind({}) 13 | Default.args = { 14 | children: 15 | 'This is help text, it typically appears below a form field.\n\nLine breaks are supported.', 16 | } 17 | -------------------------------------------------------------------------------- /src/components/HelpText/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import RenderMarkdown, { ALLOWED_INLINE_ELEMENTS } from '../RenderMarkdown' 3 | 4 | export interface ComponentHelpTextProps { 5 | id?: string 6 | className?: string 7 | children: string | React.ReactNode 8 | } 9 | 10 | export default function ComponentHelpText(props: ComponentHelpTextProps) { 11 | const isMarkdown = typeof props.children === 'string' 12 | 13 | return ( 14 |
20 | {isMarkdown ? ( 21 | 25 | ) : ( 26 | props.children 27 | )} 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/components/IVAvatar/IVAvatar.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StoryFn, Meta } from '@storybook/react' 3 | import IVAvatar from '.' 4 | 5 | const Component = IVAvatar 6 | 7 | export default { 8 | title: 'Components/IVAvatar', 9 | component: Component, 10 | } as Meta 11 | 12 | const Template: StoryFn = args => 13 | 14 | export const WithoutImage = Template.bind({}) 15 | WithoutImage.args = { 16 | name: 'Alex Arena', 17 | } 18 | 19 | export const WithImage = Template.bind({}) 20 | WithImage.args = { 21 | name: 'Alex Arena', 22 | imageUrl: 23 | 'https://interval-public.s3.us-west-1.amazonaws.com/user-photos/fa049063-7606-4b54-a577-dc0c6ed4efcb-1629131221197.jpg', 24 | } 25 | 26 | export const Larger = Template.bind({}) 27 | Larger.args = { 28 | name: 'Alex Arena', 29 | textSizeClassName: 'text-xl', 30 | className: 'w-14 h-14', 31 | } 32 | 33 | export const Roundrect = Template.bind({}) 34 | Roundrect.args = { 35 | name: 'Interval Corp', 36 | shape: 'roundrect', 37 | } 38 | -------------------------------------------------------------------------------- /src/components/IVConstraintsIndicator/index.tsx: -------------------------------------------------------------------------------- 1 | import IVTooltip, { TooltipPlacement } from '../IVTooltip' 2 | import classNames from 'classnames' 3 | import InfoIcon from '~/icons/compiled/Info' 4 | import ErrorCircleIcon from '~/icons/compiled/ErrorCircle' 5 | 6 | export default function IVConstraintsIndicator({ 7 | constraints, 8 | placement, 9 | id, 10 | error, 11 | }: { 12 | constraints: React.ReactNode 13 | id: string 14 | placement?: TooltipPlacement 15 | error?: boolean 16 | }) { 17 | return ( 18 | 28 | {error ? ( 29 | 30 | ) : ( 31 | 32 | )} 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/IVDialog/dialog.css: -------------------------------------------------------------------------------- 1 | /* 2 | Reakit uses data attributes to assist with modal animations, so we apply the 3 | CSS changes here instead of using Tailwind classes. 4 | */ 5 | 6 | .dialog, 7 | .dialog[data-leave] { 8 | @apply opacity-0 scale-95; 9 | } 10 | .dialog[data-enter] { 11 | @apply opacity-100 scale-100; 12 | } 13 | 14 | /* note: you must use the `background` property here, not tailwind's bg-* and bg-opacity-*. */ 15 | /* the tailwind directives don't trigger the animation events that reakit relies on for show/hide. */ 16 | .dialog-backdrop--shade, 17 | .dialog-backdrop--shade[data-leave] { 18 | background: rgba(0, 0, 0, 0); 19 | } 20 | .dialog-backdrop--shade[data-enter] { 21 | background: rgba(0, 0, 0, 0.5); 22 | } 23 | 24 | .dialog-backdrop--white, 25 | .dialog-backdrop--white[data-leave] { 26 | background: rgba(255, 255, 255, 0); 27 | } 28 | .dialog-backdrop--white[data-enter] { 29 | background: rgba(255, 255, 255, 1); 30 | } 31 | -------------------------------------------------------------------------------- /src/components/IVRichTextEditor/IVRichTextEditor.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StoryFn, Meta } from '@storybook/react' 3 | import IVRichTextEditor from '.' 4 | 5 | export default { 6 | title: 'Components/IVRichTextEditor', 7 | component: IVRichTextEditor, 8 | } as Meta 9 | 10 | const Template: StoryFn = args => ( 11 | 12 | ) 13 | 14 | export const Default = Template.bind({}) 15 | Default.args = { 16 | defaultValue: '

Hello!

', 17 | onChange: () => { 18 | /* */ 19 | }, 20 | } 21 | 22 | export const Disabled = Template.bind({}) 23 | Disabled.args = { 24 | disabled: true, 25 | onChange: () => { 26 | /* */ 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /src/components/IVRichTextEditor/iv-rich-text-editor.css: -------------------------------------------------------------------------------- 1 | /* Remove browser-default outline in root element */ 2 | .ProseMirror { 3 | outline: none !important; 4 | min-height: 150px; 5 | font-size: 14px; 6 | } 7 | 8 | /* Enforce larger clickable area for empty input */ 9 | .ProseMirror p.is-editor-empty:first-child { 10 | min-height: 150px; 11 | } 12 | 13 | /* Enable placeholder plugin */ 14 | .ProseMirror p.is-editor-empty:first-child::before { 15 | content: attr(data-placeholder); 16 | float: left; 17 | color: #adb5bd; 18 | pointer-events: none; 19 | height: 0; 20 | } 21 | 22 | /* Disable default .prose margins at top and bottom */ 23 | .ProseMirror > :first-child { 24 | margin-top: 0; 25 | } 26 | 27 | .ProseMirror > :last-child { 28 | margin-bottom: 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/IVRichTextEditor/lazy.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | 3 | const IVRichTextEditor = lazy(() => import('.')) 4 | 5 | export { IVRichTextEditor as default } 6 | -------------------------------------------------------------------------------- /src/components/IVSelect/IVSelect.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StoryFn, Meta } from '@storybook/react' 3 | import IVSelect from '.' 4 | 5 | const Component = IVSelect 6 | 7 | export default { 8 | title: 'Components/IVSelect', 9 | component: Component, 10 | } as Meta 11 | 12 | const Template: StoryFn = args => 13 | 14 | const options = [ 15 | { 16 | label: 'Alex Arena', 17 | value: 'alex-arena', 18 | }, 19 | { 20 | label: 'Dan Philibin', 21 | value: 'dan-philibin', 22 | }, 23 | { 24 | label: 'Jacob Mischka', 25 | value: 'jacob-mischka', 26 | }, 27 | { 28 | label: 'Ryan Coppolo', 29 | value: 'ryan-coppolo', 30 | }, 31 | ] 32 | 33 | export const Default = Template.bind({}) 34 | Default.args = { 35 | options, 36 | disabled: false, 37 | } 38 | 39 | export const Disabled = Template.bind({}) 40 | Disabled.args = { 41 | options, 42 | disabled: true, 43 | } 44 | -------------------------------------------------------------------------------- /src/components/IVSpinner/IVSpinner/IVSpinner.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StoryFn, Meta } from '@storybook/react' 3 | import IVSpinner from '.' 4 | 5 | export default { 6 | title: 'Components/IVSpinner', 7 | component: IVSpinner, 8 | } as Meta 9 | 10 | const Template: StoryFn = args => 11 | 12 | export const Default = Template.bind({}) 13 | Default.args = {} 14 | -------------------------------------------------------------------------------- /src/components/IVStatusPill.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | 3 | export interface IVStatusPillProps { 4 | kind: 'danger' | 'success' | 'warn' | 'info' 5 | label: string 6 | } 7 | 8 | export default function IVStatusPill({ kind, label }: IVStatusPillProps) { 9 | return ( 10 | 21 | {label} 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/IVTextArea/IVTextArea.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StoryFn, Meta } from '@storybook/react' 3 | import IVTextArea from '.' 4 | 5 | export default { 6 | title: 'Components/IVTextArea', 7 | component: IVTextArea, 8 | } as Meta 9 | 10 | const Template: StoryFn = args => 11 | 12 | export const Default = Template.bind({}) 13 | Default.args = {} 14 | 15 | export const Disabled = Template.bind({}) 16 | Disabled.args = { disabled: true } 17 | -------------------------------------------------------------------------------- /src/components/IVTextArea/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { TextareaHTMLAttributes } from 'react' 3 | 4 | type IVTextAreaProps = TextareaHTMLAttributes 5 | 6 | export default function IVTextArea(props: IVTextAreaProps) { 7 | const { className, ...rest } = props 8 | 9 | return ( 10 |