realtimex-crm 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +104 -0
- package/dist/assets/DealList-DqDrFeDV.js +59 -0
- package/dist/assets/DealList-DqDrFeDV.js.map +1 -0
- package/dist/assets/index-BiQoGq1P.css +1 -0
- package/dist/assets/index-CDIy4x-0.js +152 -0
- package/dist/assets/index-CDIy4x-0.js.map +1 -0
- package/dist/auth-callback.html +140 -0
- package/dist/favicon.ico +0 -0
- package/dist/img/adding-users.png +0 -0
- package/dist/img/empty.svg +42 -0
- package/dist/index.html +1 -0
- package/dist/logo192.png +0 -0
- package/dist/logo512.png +0 -0
- package/dist/logos/0.png +0 -0
- package/dist/logos/1.png +0 -0
- package/dist/logos/10.png +0 -0
- package/dist/logos/11.png +0 -0
- package/dist/logos/12.png +0 -0
- package/dist/logos/13.png +0 -0
- package/dist/logos/14.png +0 -0
- package/dist/logos/15.png +0 -0
- package/dist/logos/16.png +0 -0
- package/dist/logos/17.png +0 -0
- package/dist/logos/18.png +0 -0
- package/dist/logos/19.png +0 -0
- package/dist/logos/2.png +0 -0
- package/dist/logos/20.png +0 -0
- package/dist/logos/21.png +0 -0
- package/dist/logos/22.png +0 -0
- package/dist/logos/23.png +0 -0
- package/dist/logos/24.png +0 -0
- package/dist/logos/25.png +0 -0
- package/dist/logos/26.png +0 -0
- package/dist/logos/27.png +0 -0
- package/dist/logos/28.png +0 -0
- package/dist/logos/29.png +0 -0
- package/dist/logos/3.png +0 -0
- package/dist/logos/30.png +0 -0
- package/dist/logos/31.png +0 -0
- package/dist/logos/32.png +0 -0
- package/dist/logos/33.png +0 -0
- package/dist/logos/34.png +0 -0
- package/dist/logos/35.png +0 -0
- package/dist/logos/36.png +0 -0
- package/dist/logos/37.png +0 -0
- package/dist/logos/38.png +0 -0
- package/dist/logos/39.png +0 -0
- package/dist/logos/4.png +0 -0
- package/dist/logos/40.png +0 -0
- package/dist/logos/41.png +0 -0
- package/dist/logos/42.png +0 -0
- package/dist/logos/43.png +0 -0
- package/dist/logos/44.png +0 -0
- package/dist/logos/45.png +0 -0
- package/dist/logos/46.png +0 -0
- package/dist/logos/47.png +0 -0
- package/dist/logos/48.png +0 -0
- package/dist/logos/49.png +0 -0
- package/dist/logos/5.png +0 -0
- package/dist/logos/50.png +0 -0
- package/dist/logos/51.png +0 -0
- package/dist/logos/52.png +0 -0
- package/dist/logos/53.png +0 -0
- package/dist/logos/54.png +0 -0
- package/dist/logos/55.png +0 -0
- package/dist/logos/6.png +0 -0
- package/dist/logos/7.png +0 -0
- package/dist/logos/8.png +0 -0
- package/dist/logos/9.png +0 -0
- package/dist/logos/Readme.md +1 -0
- package/dist/logos/logo_atomic_crm.svg +14 -0
- package/dist/logos/logo_atomic_crm_dark.svg +14 -0
- package/dist/logos/logo_atomic_crm_light.svg +14 -0
- package/dist/manifest.json +25 -0
- package/dist/robots.txt +3 -0
- package/dist/stats.html +4949 -0
- package/package.json +152 -0
- package/public/auth-callback.html +140 -0
- package/public/favicon.ico +0 -0
- package/public/img/adding-users.png +0 -0
- package/public/img/empty.svg +42 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/logos/0.png +0 -0
- package/public/logos/1.png +0 -0
- package/public/logos/10.png +0 -0
- package/public/logos/11.png +0 -0
- package/public/logos/12.png +0 -0
- package/public/logos/13.png +0 -0
- package/public/logos/14.png +0 -0
- package/public/logos/15.png +0 -0
- package/public/logos/16.png +0 -0
- package/public/logos/17.png +0 -0
- package/public/logos/18.png +0 -0
- package/public/logos/19.png +0 -0
- package/public/logos/2.png +0 -0
- package/public/logos/20.png +0 -0
- package/public/logos/21.png +0 -0
- package/public/logos/22.png +0 -0
- package/public/logos/23.png +0 -0
- package/public/logos/24.png +0 -0
- package/public/logos/25.png +0 -0
- package/public/logos/26.png +0 -0
- package/public/logos/27.png +0 -0
- package/public/logos/28.png +0 -0
- package/public/logos/29.png +0 -0
- package/public/logos/3.png +0 -0
- package/public/logos/30.png +0 -0
- package/public/logos/31.png +0 -0
- package/public/logos/32.png +0 -0
- package/public/logos/33.png +0 -0
- package/public/logos/34.png +0 -0
- package/public/logos/35.png +0 -0
- package/public/logos/36.png +0 -0
- package/public/logos/37.png +0 -0
- package/public/logos/38.png +0 -0
- package/public/logos/39.png +0 -0
- package/public/logos/4.png +0 -0
- package/public/logos/40.png +0 -0
- package/public/logos/41.png +0 -0
- package/public/logos/42.png +0 -0
- package/public/logos/43.png +0 -0
- package/public/logos/44.png +0 -0
- package/public/logos/45.png +0 -0
- package/public/logos/46.png +0 -0
- package/public/logos/47.png +0 -0
- package/public/logos/48.png +0 -0
- package/public/logos/49.png +0 -0
- package/public/logos/5.png +0 -0
- package/public/logos/50.png +0 -0
- package/public/logos/51.png +0 -0
- package/public/logos/52.png +0 -0
- package/public/logos/53.png +0 -0
- package/public/logos/54.png +0 -0
- package/public/logos/55.png +0 -0
- package/public/logos/6.png +0 -0
- package/public/logos/7.png +0 -0
- package/public/logos/8.png +0 -0
- package/public/logos/9.png +0 -0
- package/public/logos/Readme.md +1 -0
- package/public/logos/logo_atomic_crm.svg +14 -0
- package/public/logos/logo_atomic_crm_dark.svg +14 -0
- package/public/logos/logo_atomic_crm_light.svg +14 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/App.css +42 -0
- package/src/App.tsx +58 -0
- package/src/assets/react.svg +1 -0
- package/src/components/admin/Readme.md +40 -0
- package/src/components/admin/admin.tsx +132 -0
- package/src/components/admin/app-sidebar.tsx +166 -0
- package/src/components/admin/array-field.tsx +59 -0
- package/src/components/admin/array-input.tsx +201 -0
- package/src/components/admin/authentication.tsx +86 -0
- package/src/components/admin/autocomplete-array-input.tsx +254 -0
- package/src/components/admin/autocomplete-input.tsx +296 -0
- package/src/components/admin/badge-field.tsx +65 -0
- package/src/components/admin/boolean-input.tsx +116 -0
- package/src/components/admin/breadcrumb.tsx +135 -0
- package/src/components/admin/bulk-actions-toolbar.tsx +83 -0
- package/src/components/admin/bulk-delete-button.tsx +70 -0
- package/src/components/admin/bulk-export-button.tsx +76 -0
- package/src/components/admin/cancel-button.tsx +46 -0
- package/src/components/admin/columns-button.tsx +345 -0
- package/src/components/admin/confirm.tsx +166 -0
- package/src/components/admin/count.tsx +94 -0
- package/src/components/admin/create-button.tsx +58 -0
- package/src/components/admin/create.tsx +132 -0
- package/src/components/admin/data-table.tsx +520 -0
- package/src/components/admin/date-field.tsx +136 -0
- package/src/components/admin/date-input.tsx +317 -0
- package/src/components/admin/date-time-input.tsx +331 -0
- package/src/components/admin/delete-button.tsx +113 -0
- package/src/components/admin/edit-button.tsx +64 -0
- package/src/components/admin/edit-guesser.tsx +157 -0
- package/src/components/admin/edit.tsx +152 -0
- package/src/components/admin/email-field.tsx +74 -0
- package/src/components/admin/error.tsx +111 -0
- package/src/components/admin/export-button.tsx +126 -0
- package/src/components/admin/field-toggle.tsx +164 -0
- package/src/components/admin/file-field.tsx +123 -0
- package/src/components/admin/file-input.tsx +361 -0
- package/src/components/admin/filter-form.tsx +510 -0
- package/src/components/admin/form.tsx +312 -0
- package/src/components/admin/icon-button-with-tooltip.tsx +85 -0
- package/src/components/admin/index.ts +73 -0
- package/src/components/admin/input-helper-text.tsx +29 -0
- package/src/components/admin/layout.tsx +69 -0
- package/src/components/admin/list-guesser.tsx +239 -0
- package/src/components/admin/list-pagination.tsx +247 -0
- package/src/components/admin/list.tsx +178 -0
- package/src/components/admin/loading.tsx +40 -0
- package/src/components/admin/locales-menu-button.tsx +60 -0
- package/src/components/admin/login-page.tsx +104 -0
- package/src/components/admin/notification.tsx +114 -0
- package/src/components/admin/number-field.tsx +84 -0
- package/src/components/admin/number-input.tsx +124 -0
- package/src/components/admin/radio-button-group-input.tsx +184 -0
- package/src/components/admin/ready.tsx +55 -0
- package/src/components/admin/record-field.tsx +132 -0
- package/src/components/admin/reference-array-field.tsx +152 -0
- package/src/components/admin/reference-array-input.tsx +68 -0
- package/src/components/admin/reference-field.tsx +153 -0
- package/src/components/admin/reference-input.tsx +46 -0
- package/src/components/admin/reference-many-count.tsx +92 -0
- package/src/components/admin/reference-many-field.tsx +132 -0
- package/src/components/admin/refresh-button.tsx +31 -0
- package/src/components/admin/saved-queries.tsx +174 -0
- package/src/components/admin/search-input.tsx +57 -0
- package/src/components/admin/select-field.tsx +111 -0
- package/src/components/admin/select-input.tsx +323 -0
- package/src/components/admin/show-button.tsx +57 -0
- package/src/components/admin/show-guesser.tsx +215 -0
- package/src/components/admin/show.tsx +184 -0
- package/src/components/admin/simple-form-iterator.tsx +582 -0
- package/src/components/admin/simple-form.tsx +95 -0
- package/src/components/admin/simple-show-layout.tsx +8 -0
- package/src/components/admin/single-field-list.tsx +67 -0
- package/src/components/admin/sort-button.tsx +152 -0
- package/src/components/admin/spinner.tsx +46 -0
- package/src/components/admin/text-field.tsx +60 -0
- package/src/components/admin/text-input.tsx +77 -0
- package/src/components/admin/theme-mode-toggle.tsx +48 -0
- package/src/components/admin/theme-provider.tsx +74 -0
- package/src/components/admin/toggle-filter-button.tsx +77 -0
- package/src/components/admin/url-field.tsx +83 -0
- package/src/components/admin/user-menu.tsx +84 -0
- package/src/components/atomic-crm/activity/ActivityLog.tsx +54 -0
- package/src/components/atomic-crm/activity/ActivityLogCompanyCreated.tsx +50 -0
- package/src/components/atomic-crm/activity/ActivityLogContactCreated.tsx +42 -0
- package/src/components/atomic-crm/activity/ActivityLogContactNoteCreated.tsx +71 -0
- package/src/components/atomic-crm/activity/ActivityLogContext.tsx +11 -0
- package/src/components/atomic-crm/activity/ActivityLogDealCreated.tsx +41 -0
- package/src/components/atomic-crm/activity/ActivityLogDealNoteCreated.tsx +84 -0
- package/src/components/atomic-crm/activity/ActivityLogIterator.tsx +80 -0
- package/src/components/atomic-crm/activity/ActivityLogNote.tsx +36 -0
- package/src/components/atomic-crm/companies/AutocompleteCompanyInput.tsx +43 -0
- package/src/components/atomic-crm/companies/CompanyAside.tsx +207 -0
- package/src/components/atomic-crm/companies/CompanyAvatar.tsx +29 -0
- package/src/components/atomic-crm/companies/CompanyCard.tsx +88 -0
- package/src/components/atomic-crm/companies/CompanyCreate.tsx +41 -0
- package/src/components/atomic-crm/companies/CompanyEdit.tsx +33 -0
- package/src/components/atomic-crm/companies/CompanyEmpty.tsx +26 -0
- package/src/components/atomic-crm/companies/CompanyInputs.tsx +160 -0
- package/src/components/atomic-crm/companies/CompanyList.tsx +54 -0
- package/src/components/atomic-crm/companies/CompanyListFilter.tsx +55 -0
- package/src/components/atomic-crm/companies/CompanyShow.tsx +241 -0
- package/src/components/atomic-crm/companies/GridList.tsx +46 -0
- package/src/components/atomic-crm/companies/index.ts +11 -0
- package/src/components/atomic-crm/companies/sizes.ts +7 -0
- package/src/components/atomic-crm/consts.ts +5 -0
- package/src/components/atomic-crm/contacts/Avatar.tsx +40 -0
- package/src/components/atomic-crm/contacts/ContactAside.tsx +187 -0
- package/src/components/atomic-crm/contacts/ContactCreate.tsx +34 -0
- package/src/components/atomic-crm/contacts/ContactEdit.tsx +32 -0
- package/src/components/atomic-crm/contacts/ContactEmpty.tsx +28 -0
- package/src/components/atomic-crm/contacts/ContactImportButton.tsx +213 -0
- package/src/components/atomic-crm/contacts/ContactInputs.tsx +209 -0
- package/src/components/atomic-crm/contacts/ContactList.tsx +116 -0
- package/src/components/atomic-crm/contacts/ContactListContent.tsx +107 -0
- package/src/components/atomic-crm/contacts/ContactListFilter.tsx +126 -0
- package/src/components/atomic-crm/contacts/ContactMergeButton.tsx +263 -0
- package/src/components/atomic-crm/contacts/ContactShow.tsx +76 -0
- package/src/components/atomic-crm/contacts/ExportVCardButton.tsx +79 -0
- package/src/components/atomic-crm/contacts/TagsList.tsx +33 -0
- package/src/components/atomic-crm/contacts/TagsListEdit.tsx +155 -0
- package/src/components/atomic-crm/contacts/contacts_export.csv +3 -0
- package/src/components/atomic-crm/contacts/exportToVCard.ts +104 -0
- package/src/components/atomic-crm/contacts/index.tsx +14 -0
- package/src/components/atomic-crm/contacts/useContactImport.tsx +206 -0
- package/src/components/atomic-crm/dashboard/Dashboard.tsx +66 -0
- package/src/components/atomic-crm/dashboard/DashboardActivityLog.tsx +22 -0
- package/src/components/atomic-crm/dashboard/DashboardStepper.tsx +72 -0
- package/src/components/atomic-crm/dashboard/DealsChart.tsx +202 -0
- package/src/components/atomic-crm/dashboard/DealsPipeline.tsx +90 -0
- package/src/components/atomic-crm/dashboard/HotContacts.tsx +92 -0
- package/src/components/atomic-crm/dashboard/LatestNotes.tsx +116 -0
- package/src/components/atomic-crm/dashboard/TasksList.tsx +69 -0
- package/src/components/atomic-crm/dashboard/TasksListEmpty.tsx +22 -0
- package/src/components/atomic-crm/dashboard/TasksListFilter.tsx +72 -0
- package/src/components/atomic-crm/dashboard/Welcome.tsx +41 -0
- package/src/components/atomic-crm/deals/ContactList.tsx +31 -0
- package/src/components/atomic-crm/deals/DealArchivedList.tsx +105 -0
- package/src/components/atomic-crm/deals/DealCard.tsx +78 -0
- package/src/components/atomic-crm/deals/DealColumn.tsx +52 -0
- package/src/components/atomic-crm/deals/DealCreate.tsx +95 -0
- package/src/components/atomic-crm/deals/DealEdit.tsx +81 -0
- package/src/components/atomic-crm/deals/DealEmpty.tsx +63 -0
- package/src/components/atomic-crm/deals/DealInputs.tsx +103 -0
- package/src/components/atomic-crm/deals/DealList.tsx +95 -0
- package/src/components/atomic-crm/deals/DealListContent.tsx +245 -0
- package/src/components/atomic-crm/deals/DealShow.tsx +260 -0
- package/src/components/atomic-crm/deals/OnlyMineInput.tsx +30 -0
- package/src/components/atomic-crm/deals/deal.ts +5 -0
- package/src/components/atomic-crm/deals/dealUtils.ts +26 -0
- package/src/components/atomic-crm/deals/index.ts +6 -0
- package/src/components/atomic-crm/deals/stages.ts +28 -0
- package/src/components/atomic-crm/filters/FilterCategory.tsx +20 -0
- package/src/components/atomic-crm/layout/FormToolbar.tsx +12 -0
- package/src/components/atomic-crm/layout/Header.tsx +134 -0
- package/src/components/atomic-crm/layout/Layout.tsx +21 -0
- package/src/components/atomic-crm/layout/TopToolbar.tsx +24 -0
- package/src/components/atomic-crm/login/LoginSkeleton.tsx +18 -0
- package/src/components/atomic-crm/login/SignupPage.tsx +150 -0
- package/src/components/atomic-crm/login/StartPage.tsx +27 -0
- package/src/components/atomic-crm/misc/AsideSection.tsx +21 -0
- package/src/components/atomic-crm/misc/ContactOption.tsx +26 -0
- package/src/components/atomic-crm/misc/ImageEditorField.tsx +206 -0
- package/src/components/atomic-crm/misc/RelativeDate.tsx +5 -0
- package/src/components/atomic-crm/misc/Status.tsx +28 -0
- package/src/components/atomic-crm/misc/fetchWithTimeout.ts +19 -0
- package/src/components/atomic-crm/misc/isLinkedInUrl.ts +15 -0
- package/src/components/atomic-crm/misc/unsupportedDomains.const.ts +105 -0
- package/src/components/atomic-crm/misc/useAppBarHeight.ts +9 -0
- package/src/components/atomic-crm/misc/usePapaParse.tsx +144 -0
- package/src/components/atomic-crm/notes/Note.tsx +187 -0
- package/src/components/atomic-crm/notes/NoteAttachments.tsx +56 -0
- package/src/components/atomic-crm/notes/NoteCreate.tsx +112 -0
- package/src/components/atomic-crm/notes/NoteInputs.tsx +92 -0
- package/src/components/atomic-crm/notes/NotesIterator.tsx +37 -0
- package/src/components/atomic-crm/notes/StatusSelector.tsx +39 -0
- package/src/components/atomic-crm/notes/index.ts +3 -0
- package/src/components/atomic-crm/notes/utils.ts +13 -0
- package/src/components/atomic-crm/providers/commons/activity.ts +174 -0
- package/src/components/atomic-crm/providers/commons/canAccess.ts +26 -0
- package/src/components/atomic-crm/providers/commons/getCompanyAvatar.spec.ts +20 -0
- package/src/components/atomic-crm/providers/commons/getCompanyAvatar.ts +21 -0
- package/src/components/atomic-crm/providers/commons/getContactAvatar.spec.ts +80 -0
- package/src/components/atomic-crm/providers/commons/getContactAvatar.ts +70 -0
- package/src/components/atomic-crm/providers/commons/mergeContacts.ts +185 -0
- package/src/components/atomic-crm/providers/fakerest/authProvider.ts +74 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/companies.ts +53 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/contactNotes.ts +25 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/contacts.ts +103 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/dealNotes.ts +19 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/deals.ts +53 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/finalize.ts +10 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts +25 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/sales.ts +37 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/tags.ts +14 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/tasks.ts +55 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts +21 -0
- package/src/components/atomic-crm/providers/fakerest/dataGenerator/utils.ts +28 -0
- package/src/components/atomic-crm/providers/fakerest/dataProvider.ts +518 -0
- package/src/components/atomic-crm/providers/fakerest/index.ts +2 -0
- package/src/components/atomic-crm/providers/fakerest/internal/listParser.ts +48 -0
- package/src/components/atomic-crm/providers/fakerest/internal/supabaseAdapter.spec.ts +721 -0
- package/src/components/atomic-crm/providers/fakerest/internal/supabaseAdapter.ts +49 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformContainsFilter.spec.ts +35 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformContainsFilter.ts +17 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformFilter.ts +57 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformInFilter.spec.ts +32 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformInFilter.ts +17 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformOrFilter.spec.ts +23 -0
- package/src/components/atomic-crm/providers/fakerest/internal/transformOrFilter.ts +17 -0
- package/src/components/atomic-crm/providers/supabase/authProvider.ts +121 -0
- package/src/components/atomic-crm/providers/supabase/dataProvider.ts +407 -0
- package/src/components/atomic-crm/providers/supabase/index.ts +2 -0
- package/src/components/atomic-crm/providers/supabase/supabase.ts +34 -0
- package/src/components/atomic-crm/providers/types.ts +1 -0
- package/src/components/atomic-crm/root/CRM.tsx +167 -0
- package/src/components/atomic-crm/root/ConfigurationContext.tsx +80 -0
- package/src/components/atomic-crm/root/defaultConfiguration.ts +64 -0
- package/src/components/atomic-crm/root/i18nProvider.tsx +25 -0
- package/src/components/atomic-crm/sales/SaleName.tsx +13 -0
- package/src/components/atomic-crm/sales/SalesCreate.tsx +51 -0
- package/src/components/atomic-crm/sales/SalesEdit.tsx +82 -0
- package/src/components/atomic-crm/sales/SalesInputs.tsx +31 -0
- package/src/components/atomic-crm/sales/SalesList.tsx +62 -0
- package/src/components/atomic-crm/sales/index.ts +12 -0
- package/src/components/atomic-crm/settings/DatabaseSettings.tsx +169 -0
- package/src/components/atomic-crm/settings/SettingsPage.tsx +259 -0
- package/src/components/atomic-crm/setup/SupabaseSetupWizard.tsx +215 -0
- package/src/components/atomic-crm/simple-list/ListNoResults.tsx +53 -0
- package/src/components/atomic-crm/simple-list/ListPlaceholder.tsx +9 -0
- package/src/components/atomic-crm/simple-list/SimpleList.tsx +245 -0
- package/src/components/atomic-crm/simple-list/SimpleListItem.tsx +138 -0
- package/src/components/atomic-crm/simple-list/SimpleListLoading.tsx +60 -0
- package/src/components/atomic-crm/tags/RoundButton.tsx +10 -0
- package/src/components/atomic-crm/tags/TagChip.tsx +45 -0
- package/src/components/atomic-crm/tags/TagCreateModal.tsx +39 -0
- package/src/components/atomic-crm/tags/TagDialog.tsx +118 -0
- package/src/components/atomic-crm/tags/TagEditModal.tsx +42 -0
- package/src/components/atomic-crm/tags/colors.ts +12 -0
- package/src/components/atomic-crm/tasks/AddTask.tsx +191 -0
- package/src/components/atomic-crm/tasks/Task.tsx +184 -0
- package/src/components/atomic-crm/tasks/TaskEdit.tsx +96 -0
- package/src/components/atomic-crm/tasks/TasksIterator.tsx +30 -0
- package/src/components/atomic-crm/types.ts +226 -0
- package/src/components/supabase/forgot-password-page.tsx +86 -0
- package/src/components/supabase/layout.tsx +27 -0
- package/src/components/supabase/set-password-page.tsx +119 -0
- package/src/components/ui/README.md +34 -0
- package/src/components/ui/accordion.tsx +64 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar.tsx +99 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button.tsx +59 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/command.tsx +175 -0
- package/src/components/ui/dialog.tsx +133 -0
- package/src/components/ui/drawer.tsx +133 -0
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/progress.tsx +29 -0
- package/src/components/ui/radio-group.tsx +43 -0
- package/src/components/ui/select.tsx +183 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +137 -0
- package/src/components/ui/sidebar.tsx +724 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +38 -0
- package/src/components/ui/spinner.tsx +51 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/table.tsx +114 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/hooks/saved-queries.tsx +67 -0
- package/src/hooks/simple-form-iterator-context.tsx +70 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/hooks/useBulkExport.tsx +61 -0
- package/src/hooks/useSupportCreateSuggestion.tsx +188 -0
- package/src/hooks/user-menu-context.tsx +24 -0
- package/src/index.css +170 -0
- package/src/lib/field.type.ts +22 -0
- package/src/lib/genericMemo.ts +18 -0
- package/src/lib/i18nProvider.ts +9 -0
- package/src/lib/sanitizeInputRestProps.ts +46 -0
- package/src/lib/supabase-config.ts +123 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/src/setupTests.js +5 -0
- package/src/vite-env.d.ts +1 -0
- package/supabase/config.toml +157 -0
- package/supabase/functions/.env.development +7 -0
- package/supabase/functions/_shared/db.ts +187 -0
- package/supabase/functions/_shared/supabaseAdmin.ts +13 -0
- package/supabase/functions/_shared/utils.ts +13 -0
- package/supabase/functions/mergeContacts/index.ts +215 -0
- package/supabase/functions/postmark/addNoteToContact.ts +129 -0
- package/supabase/functions/postmark/extractMailContactData.ts +41 -0
- package/supabase/functions/postmark/getExpectedAuthorization.ts +4 -0
- package/supabase/functions/postmark/getNoteContent.ts +6 -0
- package/supabase/functions/postmark/index.ts +210 -0
- package/supabase/functions/updatePassword/index.ts +50 -0
- package/supabase/functions/users/index.ts +206 -0
- package/supabase/migrations/20240730075029_init_db.sql +600 -0
- package/supabase/migrations/20240730075425_init_triggers.sql +57 -0
- package/supabase/migrations/20240806124555_task_sales_id.sql +1 -0
- package/supabase/migrations/20240807082449_remove-aquisition.sql +20 -0
- package/supabase/migrations/20240808141826_init_state_configure.sql +9 -0
- package/supabase/migrations/20240813084010_tags_policy.sql +18 -0
- package/supabase/migrations/20241104153231_sales_policies.sql +7 -0
- package/supabase/migrations/20250109152531_email_jsonb.sql +43 -0
- package/supabase/migrations/20250113132531_phone_jsonb.sql +67 -0
- package/supabase/migrations/20251204172855_merge_contacts_function.sql +153 -0
- package/supabase/migrations/20251204201317_drop_merge_contacts_function.sql +2 -0
- package/supabase/seed.sql +0 -0
- package/supabase/templates/invite.html +70 -0
- package/supabase/templates/recovery.html +75 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
2
|
+
|
|
3
|
+
export const addNoteToContact = async ({
|
|
4
|
+
salesEmail,
|
|
5
|
+
email,
|
|
6
|
+
domain,
|
|
7
|
+
firstName,
|
|
8
|
+
lastName,
|
|
9
|
+
noteContent,
|
|
10
|
+
}: {
|
|
11
|
+
salesEmail: string;
|
|
12
|
+
email: string;
|
|
13
|
+
domain: string;
|
|
14
|
+
firstName: string;
|
|
15
|
+
lastName: string;
|
|
16
|
+
noteContent: string;
|
|
17
|
+
}) => {
|
|
18
|
+
const { data: sales, error: fetchSalesError } = await supabaseAdmin
|
|
19
|
+
.from("sales")
|
|
20
|
+
.select("*")
|
|
21
|
+
.eq("email", salesEmail)
|
|
22
|
+
.neq("disabled", true)
|
|
23
|
+
.maybeSingle();
|
|
24
|
+
if (fetchSalesError)
|
|
25
|
+
return new Response(
|
|
26
|
+
`Could not fetch sales from database, email: ${salesEmail}`,
|
|
27
|
+
{ status: 500 },
|
|
28
|
+
);
|
|
29
|
+
if (!sales) {
|
|
30
|
+
// Return a 403 to let Postmark know that it's no use to retry this request
|
|
31
|
+
// https://postmarkapp.com/developer/webhooks/inbound-webhook#errors-and-retries
|
|
32
|
+
return new Response(
|
|
33
|
+
`Unable to find (active) sales in database, email: ${salesEmail}`,
|
|
34
|
+
{ status: 403 },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if the contact already exists
|
|
39
|
+
const { data: existingContact, error: fetchContactError } =
|
|
40
|
+
await supabaseAdmin
|
|
41
|
+
.from("contacts")
|
|
42
|
+
.select("*")
|
|
43
|
+
.contains("email_jsonb", JSON.stringify([{ email }]))
|
|
44
|
+
.maybeSingle();
|
|
45
|
+
if (fetchContactError)
|
|
46
|
+
return new Response(
|
|
47
|
+
`Could not fetch contact from database, email: ${email}`,
|
|
48
|
+
{ status: 500 },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// deno-lint-ignore no-explicit-any
|
|
52
|
+
let contact: any = undefined;
|
|
53
|
+
if (existingContact) {
|
|
54
|
+
contact = existingContact;
|
|
55
|
+
} else {
|
|
56
|
+
// If the contact does not exist, we need to create it, along with the company if needed
|
|
57
|
+
|
|
58
|
+
// Check if the company already exists
|
|
59
|
+
const { data: existingCompany, error: fetchCompanyError } =
|
|
60
|
+
await supabaseAdmin
|
|
61
|
+
.from("companies")
|
|
62
|
+
.select("*")
|
|
63
|
+
.eq("name", domain)
|
|
64
|
+
.maybeSingle();
|
|
65
|
+
if (fetchCompanyError)
|
|
66
|
+
return new Response(
|
|
67
|
+
`Could not fetch companies from database, name: ${domain}`,
|
|
68
|
+
{ status: 500 },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// deno-lint-ignore no-explicit-any
|
|
72
|
+
let company: any = undefined;
|
|
73
|
+
if (existingCompany) {
|
|
74
|
+
company = existingCompany;
|
|
75
|
+
} else {
|
|
76
|
+
const { data: newCompanies, error: createCompanyError } =
|
|
77
|
+
await supabaseAdmin
|
|
78
|
+
.from("companies")
|
|
79
|
+
.insert({ name: domain, sales_id: sales.id })
|
|
80
|
+
.select();
|
|
81
|
+
if (createCompanyError)
|
|
82
|
+
return new Response(
|
|
83
|
+
`Could not create company in database, name: ${domain}`,
|
|
84
|
+
{ status: 500 },
|
|
85
|
+
);
|
|
86
|
+
company = newCompanies[0];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Create the contact
|
|
90
|
+
const { data: newContacts, error: createContactError } = await supabaseAdmin
|
|
91
|
+
.from("contacts")
|
|
92
|
+
.insert({
|
|
93
|
+
first_name: firstName,
|
|
94
|
+
last_name: lastName,
|
|
95
|
+
email_jsonb: [{ email, type: "Work" }],
|
|
96
|
+
company_id: company.id,
|
|
97
|
+
sales_id: sales.id,
|
|
98
|
+
first_seen: new Date(),
|
|
99
|
+
last_seen: new Date(),
|
|
100
|
+
tags: [],
|
|
101
|
+
})
|
|
102
|
+
.select();
|
|
103
|
+
if (createContactError || !newContacts[0])
|
|
104
|
+
return new Response(
|
|
105
|
+
`Could not create contact in database, email: ${email}`,
|
|
106
|
+
{ status: 500 },
|
|
107
|
+
);
|
|
108
|
+
contact = newContacts[0];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add note to contact
|
|
112
|
+
const { error: createNoteError } = await supabaseAdmin
|
|
113
|
+
.from("contactNotes")
|
|
114
|
+
.insert({
|
|
115
|
+
contact_id: contact.id,
|
|
116
|
+
text: noteContent,
|
|
117
|
+
sales_id: sales.id,
|
|
118
|
+
});
|
|
119
|
+
if (createNoteError)
|
|
120
|
+
return new Response(
|
|
121
|
+
`Could not add note to contact ${email}, sales ${salesEmail}`,
|
|
122
|
+
{ status: 500 },
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await supabaseAdmin
|
|
126
|
+
.from("contacts")
|
|
127
|
+
.update({ last_seen: new Date() })
|
|
128
|
+
.eq("id", contact.id);
|
|
129
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the first name, last name, email, and domain from a mail contact.
|
|
3
|
+
*
|
|
4
|
+
* Example:
|
|
5
|
+
* "ToFull": [
|
|
6
|
+
* {
|
|
7
|
+
* "Email": "firstname.lastname@marmelab.com",
|
|
8
|
+
* "Name": "Firstname Lastname"
|
|
9
|
+
* }
|
|
10
|
+
* ]
|
|
11
|
+
*
|
|
12
|
+
* Return Value:
|
|
13
|
+
* {
|
|
14
|
+
* firstName: "Firstname",
|
|
15
|
+
* lastName: "Lastname",
|
|
16
|
+
* email: "firstname.lastname@marmelab.com",
|
|
17
|
+
* domain: "marmelab.com"
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
export const extractMailContactData = (
|
|
22
|
+
ToFull: {
|
|
23
|
+
Email: string;
|
|
24
|
+
Name: string;
|
|
25
|
+
}[],
|
|
26
|
+
) => {
|
|
27
|
+
return ToFull.map((contact) => {
|
|
28
|
+
const domain = contact.Email.split("@").at(-1)!;
|
|
29
|
+
const fullName =
|
|
30
|
+
contact.Name ||
|
|
31
|
+
contact.Email.split("@").slice(0, -1).join(" ").split(".").join(" ");
|
|
32
|
+
let firstName = "";
|
|
33
|
+
let lastName = fullName;
|
|
34
|
+
if (fullName && fullName.includes(" ")) {
|
|
35
|
+
const parts = fullName.split(" ");
|
|
36
|
+
firstName = parts[0];
|
|
37
|
+
lastName = parts.slice(1).join(" ");
|
|
38
|
+
}
|
|
39
|
+
return { firstName, lastName, email: contact.Email, domain };
|
|
40
|
+
});
|
|
41
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Follow this setup guide to integrate the Deno language server with your editor:
|
|
2
|
+
// https://deno.land/manual/getting_started/setup_your_environment
|
|
3
|
+
// This enables autocomplete, go to definition, etc.
|
|
4
|
+
|
|
5
|
+
// Setup type definitions for built-in Supabase Runtime APIs
|
|
6
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
7
|
+
import { addNoteToContact } from "./addNoteToContact.ts";
|
|
8
|
+
import { extractMailContactData } from "./extractMailContactData.ts";
|
|
9
|
+
import { getExpectedAuthorization } from "./getExpectedAuthorization.ts";
|
|
10
|
+
import { getNoteContent } from "./getNoteContent.ts";
|
|
11
|
+
|
|
12
|
+
const webhookUser = Deno.env.get("POSTMARK_WEBHOOK_USER");
|
|
13
|
+
const webhookPassword = Deno.env.get("POSTMARK_WEBHOOK_PASSWORD");
|
|
14
|
+
if (!webhookUser || !webhookPassword) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Missing POSTMARK_WEBHOOK_USER or POSTMARK_WEBHOOK_PASSWORD env variable",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const rawAuthorizedIPs = Deno.env.get("POSTMARK_WEBHOOK_AUTHORIZED_IPS");
|
|
21
|
+
if (!rawAuthorizedIPs) {
|
|
22
|
+
throw new Error("Missing POSTMARK_WEBHOOK_AUTHORIZED_IPS env variable");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Deno.serve(async (req) => {
|
|
26
|
+
let response: Response | undefined;
|
|
27
|
+
|
|
28
|
+
response = checkRequestTypeAndHeaders(req);
|
|
29
|
+
if (response) return response;
|
|
30
|
+
|
|
31
|
+
const json = await req.json();
|
|
32
|
+
response = checkBody(json);
|
|
33
|
+
if (response) return response;
|
|
34
|
+
|
|
35
|
+
const { ToFull, FromFull, Subject, TextBody } = json;
|
|
36
|
+
|
|
37
|
+
const noteContent = getNoteContent(Subject, TextBody);
|
|
38
|
+
|
|
39
|
+
const { Email: salesEmail } = FromFull;
|
|
40
|
+
if (!salesEmail) {
|
|
41
|
+
// Return a 403 to let Postmark know that it's no use to retry this request
|
|
42
|
+
// https://postmarkapp.com/developer/webhooks/inbound-webhook#errors-and-retries
|
|
43
|
+
return new Response(
|
|
44
|
+
`Could not extract sales email from FromFull: ${FromFull}`,
|
|
45
|
+
{ status: 403 },
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const contacts = extractMailContactData(ToFull);
|
|
50
|
+
|
|
51
|
+
for (const { firstName, lastName, email, domain } of contacts) {
|
|
52
|
+
if (!email) {
|
|
53
|
+
// Return a 403 to let Postmark know that it's no use to retry this request
|
|
54
|
+
// https://postmarkapp.com/developer/webhooks/inbound-webhook#errors-and-retries
|
|
55
|
+
return new Response(`Could not extract email from ToFull: ${ToFull}`, {
|
|
56
|
+
status: 403,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await addNoteToContact({
|
|
61
|
+
salesEmail,
|
|
62
|
+
email,
|
|
63
|
+
domain,
|
|
64
|
+
firstName,
|
|
65
|
+
lastName,
|
|
66
|
+
noteContent,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new Response("OK");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const checkRequestTypeAndHeaders = (req: Request) => {
|
|
74
|
+
// Only allow known IP addresses
|
|
75
|
+
// We can use the x-forwarded-for header as it is populated by Supabase
|
|
76
|
+
// https://supabase.com/docs/guides/api/securing-your-api#accessing-request-information
|
|
77
|
+
const forwardedFor = req.headers.get("x-forwarded-for");
|
|
78
|
+
if (!forwardedFor) {
|
|
79
|
+
return new Response("Unauthorized", { status: 401 });
|
|
80
|
+
}
|
|
81
|
+
const ips = forwardedFor.split(",").map((ip) => ip.trim());
|
|
82
|
+
const authorizedIPs = rawAuthorizedIPs.split(",").map((ip) => ip.trim());
|
|
83
|
+
if (!ips.some((ip) => authorizedIPs.includes(ip))) {
|
|
84
|
+
return new Response("Unauthorized", { status: 401 });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Only allow POST requests
|
|
88
|
+
if (req.method !== "POST") {
|
|
89
|
+
return new Response(null, { status: 405 });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check the Authorization header
|
|
93
|
+
const expectedAuthorization = getExpectedAuthorization(
|
|
94
|
+
webhookUser,
|
|
95
|
+
webhookPassword,
|
|
96
|
+
);
|
|
97
|
+
const authorization = req.headers.get("Authorization");
|
|
98
|
+
if (authorization !== expectedAuthorization) {
|
|
99
|
+
return new Response("Unauthorized", { status: 401 });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// deno-lint-ignore no-explicit-any
|
|
104
|
+
const checkBody = (json: any) => {
|
|
105
|
+
const { ToFull, FromFull, Subject, TextBody } = json;
|
|
106
|
+
|
|
107
|
+
// In case of incorrect request data, we
|
|
108
|
+
// return a 403 to let Postmark know that it's no use to retry this request
|
|
109
|
+
// https://postmarkapp.com/developer/webhooks/inbound-webhook#errors-and-retries
|
|
110
|
+
if (!ToFull || !ToFull.length)
|
|
111
|
+
return new Response("Missing parameter: ToFull", { status: 403 });
|
|
112
|
+
if (!FromFull)
|
|
113
|
+
return new Response("Missing parameter: FromFull", { status: 403 });
|
|
114
|
+
if (!Subject)
|
|
115
|
+
return new Response("Missing parameter: Subject", { status: 403 });
|
|
116
|
+
if (!TextBody)
|
|
117
|
+
return new Response("Missing parameter: TextBody", {
|
|
118
|
+
status: 403,
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/* To invoke locally:
|
|
123
|
+
1. Run `make start`
|
|
124
|
+
2. Make sure to have a Sales with email "support@postmarkapp.com" (create it if needed)
|
|
125
|
+
3. OPTIONAL: Create a Contact with email "firstname.lastname@marmelab.com"
|
|
126
|
+
4. In another terminal, run `make start-supabase-functions`
|
|
127
|
+
5. In another terminal, make an HTTP request:
|
|
128
|
+
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/postmark' \
|
|
129
|
+
--header 'Content-Type: application/json' \
|
|
130
|
+
--header 'Authorization: Basic dGVzdHVzZXI6dGVzdHB3ZA==' \
|
|
131
|
+
--data '{
|
|
132
|
+
"FromName": "Postmarkapp Support",
|
|
133
|
+
"From": "support@postmarkapp.com",
|
|
134
|
+
"FromFull": {
|
|
135
|
+
"Email": "support@postmarkapp.com",
|
|
136
|
+
"Name": "Postmarkapp Support",
|
|
137
|
+
"MailboxHash": ""
|
|
138
|
+
},
|
|
139
|
+
"To": "\"Firstname Lastname\" <firstname.lastname@marmelab.com>",
|
|
140
|
+
"ToFull": [
|
|
141
|
+
{
|
|
142
|
+
"Email": "firstname.lastname@marmelab.com",
|
|
143
|
+
"Name": "Firstname Lastname",
|
|
144
|
+
"MailboxHash": "SampleHash"
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"Cc": "\"First Cc\" <firstcc@postmarkapp.com>, secondCc@postmarkapp.com",
|
|
148
|
+
"CcFull": [
|
|
149
|
+
{
|
|
150
|
+
"Email": "firstcc@postmarkapp.com",
|
|
151
|
+
"Name": "First Cc",
|
|
152
|
+
"MailboxHash": ""
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"Email": "secondCc@postmarkapp.com",
|
|
156
|
+
"Name": "",
|
|
157
|
+
"MailboxHash": ""
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"Bcc": "\"First Bcc\" <firstbcc@postmarkapp.com>, secondbcc@postmarkapp.com",
|
|
161
|
+
"BccFull": [
|
|
162
|
+
{
|
|
163
|
+
"Email": "firstbcc@postmarkapp.com",
|
|
164
|
+
"Name": "First Bcc",
|
|
165
|
+
"MailboxHash": ""
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"Email": "secondbcc@postmarkapp.com",
|
|
169
|
+
"Name": "",
|
|
170
|
+
"MailboxHash": ""
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
"OriginalRecipient": "firstname.lastname@marmelab.com",
|
|
174
|
+
"Subject": "Test subject",
|
|
175
|
+
"MessageID": "73e6d360-66eb-11e1-8e72-a8904824019b",
|
|
176
|
+
"ReplyTo": "replyto@postmarkapp.com",
|
|
177
|
+
"MailboxHash": "SampleHash",
|
|
178
|
+
"Date": "Fri, 1 Aug 2014 16:45:32 -04:00",
|
|
179
|
+
"TextBody": "This is a test text body.",
|
|
180
|
+
"HtmlBody": "<html><body><p>This is a test html body.</p></body></html>",
|
|
181
|
+
"StrippedTextReply": "This is the reply text",
|
|
182
|
+
"Tag": "TestTag",
|
|
183
|
+
"Headers": [
|
|
184
|
+
{
|
|
185
|
+
"Name": "X-Header-Test",
|
|
186
|
+
"Value": ""
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"Name": "X-Spam-Status",
|
|
190
|
+
"Value": "No"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"Name": "X-Spam-Score",
|
|
194
|
+
"Value": "-0.1"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"Name": "X-Spam-Tests",
|
|
198
|
+
"Value": "DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_PASS"
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"Attachments": [
|
|
202
|
+
{
|
|
203
|
+
"Name": "test.txt",
|
|
204
|
+
"Content": "VGhpcyBpcyBhdHRhY2htZW50IGNvbnRlbnRzLCBiYXNlLTY0IGVuY29kZWQu",
|
|
205
|
+
"ContentType": "text/plain",
|
|
206
|
+
"ContentLength": 45
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
}'
|
|
210
|
+
*/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
3
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
4
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
5
|
+
|
|
6
|
+
async function updatePassword(user: any) {
|
|
7
|
+
const { data, error } = await supabaseAdmin.auth.resetPasswordForEmail(
|
|
8
|
+
user.email,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
if (!data || error) {
|
|
12
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new Response(
|
|
16
|
+
JSON.stringify({
|
|
17
|
+
data,
|
|
18
|
+
}),
|
|
19
|
+
{
|
|
20
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Deno.serve(async (req: Request) => {
|
|
26
|
+
if (req.method === "OPTIONS") {
|
|
27
|
+
return new Response(null, {
|
|
28
|
+
status: 204,
|
|
29
|
+
headers: corsHeaders,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const authHeader = req.headers.get("Authorization")!;
|
|
34
|
+
const localClient = createClient(
|
|
35
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
36
|
+
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
|
|
37
|
+
{ global: { headers: { Authorization: authHeader } } },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const { data } = await localClient.auth.getUser();
|
|
41
|
+
if (!data?.user) {
|
|
42
|
+
return createErrorResponse(401, "Unauthorized");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (req.method === "PATCH") {
|
|
46
|
+
return updatePassword(data.user);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return createErrorResponse(405, "Method Not Allowed");
|
|
50
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
3
|
+
import { supabaseAdmin } from "../_shared/supabaseAdmin.ts";
|
|
4
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
5
|
+
|
|
6
|
+
async function updateSaleDisabled(user_id: string, disabled: boolean) {
|
|
7
|
+
return await supabaseAdmin
|
|
8
|
+
.from("sales")
|
|
9
|
+
.update({ disabled: disabled ?? false })
|
|
10
|
+
.eq("user_id", user_id);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function updateSaleAdministrator(
|
|
14
|
+
user_id: string,
|
|
15
|
+
administrator: boolean,
|
|
16
|
+
) {
|
|
17
|
+
const { data: sales, error: salesError } = await supabaseAdmin
|
|
18
|
+
.from("sales")
|
|
19
|
+
.update({ administrator })
|
|
20
|
+
.eq("user_id", user_id)
|
|
21
|
+
.select("*");
|
|
22
|
+
|
|
23
|
+
if (!sales?.length || salesError) {
|
|
24
|
+
console.error("Error updating user:", salesError);
|
|
25
|
+
throw salesError ?? new Error("Failed to update sale");
|
|
26
|
+
}
|
|
27
|
+
return sales.at(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function updateSaleAvatar(user_id: string, avatar: string) {
|
|
31
|
+
const { data: sales, error: salesError } = await supabaseAdmin
|
|
32
|
+
.from("sales")
|
|
33
|
+
.update({ avatar })
|
|
34
|
+
.eq("user_id", user_id)
|
|
35
|
+
.select("*");
|
|
36
|
+
|
|
37
|
+
if (!sales?.length || salesError) {
|
|
38
|
+
console.error("Error updating user:", salesError);
|
|
39
|
+
throw salesError ?? new Error("Failed to update sale");
|
|
40
|
+
}
|
|
41
|
+
return sales.at(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function inviteUser(req: Request, currentUserSale: any) {
|
|
45
|
+
const { email, password, first_name, last_name, disabled, administrator } =
|
|
46
|
+
await req.json();
|
|
47
|
+
|
|
48
|
+
if (!currentUserSale.administrator) {
|
|
49
|
+
return createErrorResponse(401, "Not Authorized");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { data, error: userError } = await supabaseAdmin.auth.admin.createUser({
|
|
53
|
+
email,
|
|
54
|
+
password,
|
|
55
|
+
user_metadata: { first_name, last_name },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const { error: emailError } =
|
|
59
|
+
await supabaseAdmin.auth.admin.inviteUserByEmail(email);
|
|
60
|
+
|
|
61
|
+
if (!data?.user || userError) {
|
|
62
|
+
console.error(`Error inviting user: user_error=${userError}`);
|
|
63
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!data?.user || userError || emailError) {
|
|
67
|
+
console.error(`Error inviting user, email_error=${emailError}`);
|
|
68
|
+
return createErrorResponse(500, "Failed to send invitation mail");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await updateSaleDisabled(data.user.id, disabled);
|
|
73
|
+
const sale = await updateSaleAdministrator(data.user.id, administrator);
|
|
74
|
+
|
|
75
|
+
return new Response(
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
data: sale,
|
|
78
|
+
}),
|
|
79
|
+
{
|
|
80
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error("Error patching sale:", e);
|
|
85
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function patchUser(req: Request, currentUserSale: any) {
|
|
90
|
+
const {
|
|
91
|
+
sales_id,
|
|
92
|
+
email,
|
|
93
|
+
first_name,
|
|
94
|
+
last_name,
|
|
95
|
+
avatar,
|
|
96
|
+
administrator,
|
|
97
|
+
disabled,
|
|
98
|
+
} = await req.json();
|
|
99
|
+
const { data: sale } = await supabaseAdmin
|
|
100
|
+
.from("sales")
|
|
101
|
+
.select("*")
|
|
102
|
+
.eq("id", sales_id)
|
|
103
|
+
.single();
|
|
104
|
+
|
|
105
|
+
if (!sale) {
|
|
106
|
+
return createErrorResponse(404, "Not Found");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Users can only update their own profile unless they are an administrator
|
|
110
|
+
if (!currentUserSale.administrator && currentUserSale.id !== sale.id) {
|
|
111
|
+
return createErrorResponse(401, "Not Authorized");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { data, error: userError } =
|
|
115
|
+
await supabaseAdmin.auth.admin.updateUserById(sale.user_id, {
|
|
116
|
+
email,
|
|
117
|
+
ban_duration: disabled ? "87600h" : "none",
|
|
118
|
+
user_metadata: { first_name, last_name },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!data?.user || userError) {
|
|
122
|
+
console.error("Error patching user:", userError);
|
|
123
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (avatar) {
|
|
127
|
+
await updateSaleAvatar(data.user.id, avatar);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Only administrators can update the administrator and disabled status
|
|
131
|
+
if (!currentUserSale.administrator) {
|
|
132
|
+
const { data: new_sale } = await supabaseAdmin
|
|
133
|
+
.from("sales")
|
|
134
|
+
.select("*")
|
|
135
|
+
.eq("id", sales_id)
|
|
136
|
+
.single();
|
|
137
|
+
return new Response(
|
|
138
|
+
JSON.stringify({
|
|
139
|
+
data: new_sale,
|
|
140
|
+
}),
|
|
141
|
+
{
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
...corsHeaders,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await updateSaleDisabled(data.user.id, disabled);
|
|
152
|
+
const sale = await updateSaleAdministrator(data.user.id, administrator);
|
|
153
|
+
return new Response(
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
data: sale,
|
|
156
|
+
}),
|
|
157
|
+
{
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
...corsHeaders,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.error("Error patching sale:", e);
|
|
166
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Deno.serve(async (req: Request) => {
|
|
171
|
+
if (req.method === "OPTIONS") {
|
|
172
|
+
return new Response(null, {
|
|
173
|
+
status: 204,
|
|
174
|
+
headers: corsHeaders,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const authHeader = req.headers.get("Authorization")!;
|
|
179
|
+
const localClient = createClient(
|
|
180
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
181
|
+
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
|
|
182
|
+
{ global: { headers: { Authorization: authHeader } } },
|
|
183
|
+
);
|
|
184
|
+
const { data } = await localClient.auth.getUser();
|
|
185
|
+
if (!data?.user) {
|
|
186
|
+
return createErrorResponse(401, "Unauthorized");
|
|
187
|
+
}
|
|
188
|
+
const currentUserSale = await supabaseAdmin
|
|
189
|
+
.from("sales")
|
|
190
|
+
.select("*")
|
|
191
|
+
.eq("user_id", data.user.id)
|
|
192
|
+
.single();
|
|
193
|
+
|
|
194
|
+
if (!currentUserSale?.data) {
|
|
195
|
+
return createErrorResponse(401, "Unauthorized");
|
|
196
|
+
}
|
|
197
|
+
if (req.method === "POST") {
|
|
198
|
+
return inviteUser(req, currentUserSale.data);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (req.method === "PATCH") {
|
|
202
|
+
return patchUser(req, currentUserSale.data);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return createErrorResponse(405, "Method Not Allowed");
|
|
206
|
+
});
|