├── .cursorrules ├── .do └── deploy.template.yaml ├── .dockerignore ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── deployment.yml │ └── flyio.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── .yarnrc.yml ├── CONTRIBUTING.md ├── DOCUMENTS.md ├── LICENSE ├── README.md ├── SECURITY.md ├── apps ├── backend │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── caches │ │ │ ├── OrganizationCache.ts │ │ │ ├── StorageCache.ts │ │ │ └── TaskCache.ts │ │ ├── checkhealth.ts │ │ ├── core │ │ │ ├── AppRoutes.ts │ │ │ ├── Controller.ts │ │ │ ├── Mapper.ts │ │ │ ├── UseMiddleware.ts │ │ │ ├── abstracts │ │ │ │ └── Controller.ts │ │ │ ├── decorators │ │ │ │ └── Auth.ts │ │ │ ├── index.ts │ │ │ ├── methods │ │ │ │ ├── Delete.ts │ │ │ │ ├── Get.ts │ │ │ │ ├── Method.ts │ │ │ │ ├── Post.ts │ │ │ │ └── Put.ts │ │ │ ├── params │ │ │ │ ├── Body.ts │ │ │ │ ├── Next.ts │ │ │ │ ├── Param.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── Req.ts │ │ │ │ └── Res.ts │ │ │ └── type.ts │ │ ├── events │ │ │ ├── index.ts │ │ │ ├── notification.event.ts │ │ │ ├── reminder.event.ts │ │ │ └── stats.day.event.ts │ │ ├── exceptions │ │ │ ├── ApiNotFoundException.ts │ │ │ ├── BadRequestException.ts │ │ │ ├── CredentialInvalidException.ts │ │ │ ├── DataAccessException.ts │ │ │ ├── InactiveAccountException.ts │ │ │ ├── IncorrectConfigurationException.ts │ │ │ ├── InternalErrorException.ts │ │ │ ├── InternalServerException.ts │ │ │ ├── MaxStorageSizeException.ts │ │ │ └── StorageConfigurationNotFoundException.ts │ │ ├── jobs │ │ │ ├── reminder.job.ts │ │ │ ├── status.pusher.job.ts │ │ │ └── task.pusher.job.ts │ │ ├── lib │ │ │ ├── buzzer.ts │ │ │ ├── email.ts │ │ │ ├── firebase-admin.ts │ │ │ ├── jwt.ts │ │ │ ├── log.ts │ │ │ ├── password.ts │ │ │ ├── pusher-server.ts │ │ │ ├── redis.ts │ │ │ ├── url.ts │ │ │ └── utils.ts │ │ ├── main.ts │ │ ├── middlewares │ │ │ ├── authMiddleware.ts │ │ │ ├── beProjectMemberMiddleware.ts │ │ │ └── index.ts │ │ ├── providers │ │ │ ├── JwtProvider.ts │ │ │ ├── auth │ │ │ │ ├── BaseAuthProvider.ts │ │ │ │ ├── EmailAuthProvider.ts │ │ │ │ └── GoogleAuthProvider.ts │ │ │ └── storage │ │ │ │ ├── AwsS3StorageProvider.ts │ │ │ │ ├── DigitalOceanStorageProvider.ts │ │ │ │ └── IStorageProvider.ts │ │ ├── queues │ │ │ ├── BaseJob.ts │ │ │ ├── BaseQueue.ts │ │ │ ├── Common │ │ │ │ ├── FieldSortableJob.ts │ │ │ │ └── index.ts │ │ │ ├── Stats │ │ │ │ ├── DoneTasksByMemberJob.ts │ │ │ │ ├── UnDoneTasksByProjectJob.ts │ │ │ │ └── index.ts │ │ │ ├── Task │ │ │ │ ├── MoveToAnotherBoardJob.ts │ │ │ │ ├── ReorderJob.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── routes │ │ │ ├── activity │ │ │ │ └── index.ts │ │ │ ├── apps │ │ │ │ └── index.controller.ts │ │ │ ├── auth │ │ │ │ ├── index.controller.ts │ │ │ │ ├── index.ts │ │ │ │ ├── password.ts │ │ │ │ └── permission.controller.ts │ │ │ ├── automation │ │ │ │ └── index.ts │ │ │ ├── buzzer │ │ │ │ └── index.ts │ │ │ ├── comment │ │ │ │ └── index.ts │ │ │ ├── dashboard │ │ │ │ └── index.ts │ │ │ ├── event │ │ │ │ └── index.controller.ts │ │ │ ├── example │ │ │ │ └── index.ts │ │ │ ├── favorite │ │ │ │ └── index.ts │ │ │ ├── fields │ │ │ │ └── index.ts │ │ │ ├── grid │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── meeting │ │ │ │ └── index.ts │ │ │ ├── member │ │ │ │ └── index.ts │ │ │ ├── organization │ │ │ │ ├── index.controller.ts │ │ │ │ ├── member.controller.ts │ │ │ │ └── storage.controller.ts │ │ │ ├── profile │ │ │ │ └── profile.controller.ts │ │ │ ├── project │ │ │ │ ├── index.ts │ │ │ │ ├── pin.ts │ │ │ │ ├── point.ts │ │ │ │ ├── project.controller.ts │ │ │ │ ├── setting.controller.ts │ │ │ │ ├── status.ts │ │ │ │ ├── tag.ts │ │ │ │ └── view.ts │ │ │ ├── report │ │ │ │ └── index.ts │ │ │ ├── scheduler │ │ │ │ └── index.controller.ts │ │ │ ├── storage │ │ │ │ └── index.ts │ │ │ ├── task │ │ │ │ ├── checklist.controller.ts │ │ │ │ ├── index.ts │ │ │ │ └── reorder.controller.ts │ │ │ ├── test │ │ │ │ ├── index.ts │ │ │ │ └── loadtest.controller.ts │ │ │ ├── timer │ │ │ │ └── timer.controller.ts │ │ │ └── vision │ │ │ │ └── index.ts │ │ ├── services │ │ │ ├── activity.service.ts │ │ │ ├── field │ │ │ │ ├── create.checkbox.field.strategy.ts │ │ │ │ ├── create.date.field.strategy.ts │ │ │ │ ├── create.multiselect.field.strategy.ts │ │ │ │ ├── create.number.field.strategy.ts │ │ │ │ ├── create.select.field.strategy.ts │ │ │ │ ├── create.text.field.strategy.ts │ │ │ │ ├── index.ts │ │ │ │ └── type.ts │ │ │ ├── grid │ │ │ │ ├── builders │ │ │ │ │ ├── boolean.builder.ts │ │ │ │ │ ├── date.builder.ts │ │ │ │ │ ├── number.builder.ts │ │ │ │ │ ├── person.builder.ts │ │ │ │ │ ├── select.builder.ts │ │ │ │ │ └── text.builder.ts │ │ │ │ └── grid.service.ts │ │ │ ├── orgMember │ │ │ │ └── remove.service.ts │ │ │ ├── organizationStorage.service.ts │ │ │ ├── project.ts │ │ │ ├── project │ │ │ │ ├── index.service.ts │ │ │ │ ├── setting.report.service.ts │ │ │ │ └── view.service.ts │ │ │ ├── stats │ │ │ │ ├── done.tasks.service.ts │ │ │ │ ├── index.service.ts │ │ │ │ └── undone.tasks.service.ts │ │ │ ├── status.ts │ │ │ ├── storage.service.ts │ │ │ ├── task │ │ │ │ ├── checklist.service.ts │ │ │ │ ├── create.service.ts │ │ │ │ ├── order.service.ts │ │ │ │ └── update.service.ts │ │ │ ├── todo.counter.ts │ │ │ └── user.ts │ │ └── types.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── webpack.config.js ├── frontend-e2e │ ├── cypress.config.ts │ ├── project.json │ ├── src │ │ ├── e2e │ │ │ └── app.cy.ts │ │ ├── fixtures │ │ │ └── example.json │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── e2e.ts │ └── tsconfig.json └── frontend │ ├── app │ ├── PostHogPageView.tsx │ ├── [orgName] │ │ ├── ProjectSidebar.tsx │ │ ├── error.tsx │ │ ├── favorites │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── meeting │ │ │ ├── MeetingContainer.tsx │ │ │ ├── MeetingRoomList.tsx │ │ │ ├── [roomId] │ │ │ │ ├── MeetingRoom.tsx │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── my-works │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── project │ │ │ ├── [projectId] │ │ │ │ ├── ProjectContentLoading.tsx │ │ │ │ ├── ProjectMemberAdd.tsx │ │ │ │ ├── ProjectMemberDel.tsx │ │ │ │ ├── ProjectMemberManager.tsx │ │ │ │ ├── ProjectMemberRole.tsx │ │ │ │ ├── ProjectName.tsx │ │ │ │ ├── ProjectNav.tsx │ │ │ │ ├── ProjectTabContent.tsx │ │ │ │ ├── TaskCreate.tsx │ │ │ │ ├── TaskForm.tsx │ │ │ │ ├── TaskList.tsx │ │ │ │ ├── TaskSearchAdvanced.tsx │ │ │ │ ├── TaskUpdate2.tsx │ │ │ │ ├── board │ │ │ │ │ ├── BoardActionCreateTask.tsx │ │ │ │ │ ├── BoardColumnDraggable.tsx │ │ │ │ │ ├── BoardContainer.tsx │ │ │ │ │ ├── BoardHeader.tsx │ │ │ │ │ ├── BoardItem.tsx │ │ │ │ │ ├── BoardItemDraggable.tsx │ │ │ │ │ ├── BoardList.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── style.css │ │ │ │ │ ├── useBoardAction.ts │ │ │ │ │ ├── useBoardDndAction.ts │ │ │ │ │ ├── useBoardItemReorder.ts │ │ │ │ │ └── useBoardRealtimeUpdate.ts │ │ │ │ ├── calendar │ │ │ │ │ ├── CalMonthCell.tsx │ │ │ │ │ ├── CalMonthContainer.tsx │ │ │ │ │ ├── CalMonthTask.tsx │ │ │ │ │ ├── CalMonthTaskList.tsx │ │ │ │ │ ├── CalTaskInMonth.tsx │ │ │ │ │ ├── CalTaskInWeek.tsx │ │ │ │ │ ├── CalendarHeader.tsx │ │ │ │ │ ├── CalendarTaskCreate.tsx │ │ │ │ │ ├── context.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── style.css │ │ │ │ │ └── useCalendarAction.ts │ │ │ │ ├── page.tsx │ │ │ │ ├── settings │ │ │ │ │ ├── ProjectPoint.tsx │ │ │ │ │ ├── ProjectTags.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── status │ │ │ │ │ │ ├── ItemStatus.tsx │ │ │ │ │ │ ├── StatusCreate.tsx │ │ │ │ │ │ ├── StatusDnDContainer.tsx │ │ │ │ │ │ ├── StatusItem.tsx │ │ │ │ │ │ ├── StatusItemDnD.tsx │ │ │ │ │ │ ├── StatusType.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── type.ts │ │ │ │ │ └── style.css │ │ │ │ ├── style.css │ │ │ │ └── views │ │ │ │ │ ├── ListCell.tsx │ │ │ │ │ ├── ListCreateTask.tsx │ │ │ │ │ ├── ListMode.tsx │ │ │ │ │ ├── ListRow.tsx │ │ │ │ │ ├── TaskAssignee.css │ │ │ │ │ ├── TaskAssignee.tsx │ │ │ │ │ ├── TaskCheckAll.tsx │ │ │ │ │ ├── TaskDate.tsx │ │ │ │ │ ├── TaskPoint.tsx │ │ │ │ │ ├── TaskPriorityCell.tsx │ │ │ │ │ ├── TaskProgress.tsx │ │ │ │ │ ├── TaskStatus.tsx │ │ │ │ │ ├── TaskTitle.tsx │ │ │ │ │ ├── TaskTypeCell.tsx │ │ │ │ │ └── useTaskUpdate.ts │ │ │ └── page.tsx │ │ ├── report │ │ │ └── page.tsx │ │ ├── setting │ │ │ ├── SettingTab.tsx │ │ │ ├── about │ │ │ │ └── page.tsx │ │ │ ├── apps │ │ │ │ └── page.tsx │ │ │ ├── export-import │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── not-found.tsx │ │ │ ├── page.tsx │ │ │ └── people │ │ │ │ └── page.tsx │ │ └── test │ │ │ └── page.tsx │ ├── _components │ │ ├── AnimateView.tsx │ │ ├── Badge.tsx │ │ ├── Calendar │ │ │ ├── CalendarDay.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── ChartColumn │ │ │ ├── column.css │ │ │ └── index.tsx │ │ ├── DataFetcher │ │ │ ├── context.ts │ │ │ ├── index.tsx │ │ │ ├── useDataFetcher.ts │ │ │ ├── useTaskAdd.ts │ │ │ ├── useTaskFetcher.ts │ │ │ └── useTaskUpdate.ts │ │ ├── Dnd │ │ │ ├── Draggable.tsx │ │ │ ├── Droppable.tsx │ │ │ └── index.tsx │ │ ├── DocViewer │ │ │ └── index.tsx │ │ ├── DropFileZone.tsx │ │ ├── DynamicIcon.tsx │ │ ├── EmojiInput │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── FileKits │ │ │ ├── FileCarousel.tsx │ │ │ ├── FileControl.tsx │ │ │ ├── FileDelete.tsx │ │ │ ├── FileDesc.tsx │ │ │ ├── FileDrop.tsx │ │ │ ├── FileGet.tsx │ │ │ ├── FileItem.tsx │ │ │ ├── FileKitProviderContainer.tsx │ │ │ ├── FileList.tsx │ │ │ ├── FileListWrapper.tsx │ │ │ ├── FilePaste.tsx │ │ │ ├── FileThumb.tsx │ │ │ ├── carousel.css │ │ │ ├── context.ts │ │ │ ├── index.tsx │ │ │ ├── style.css │ │ │ ├── useFileGet.ts │ │ │ ├── useFileUpload.ts │ │ │ └── useSetDefaultCover.ts │ │ ├── GA │ │ │ ├── index.tsx │ │ │ └── utils.ts │ │ ├── HamburgerMenu.tsx │ │ ├── IconSelect │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── ListBox │ │ │ ├── ListBoxBody.tsx │ │ │ ├── ListBoxCreate.tsx │ │ │ ├── ListBoxHeader.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── ListPreset │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── MemberAvatar.tsx │ │ ├── MemberName.tsx │ │ ├── MemberPicker │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── MultiMemberPicker.tsx │ │ ├── OperatorSelect.tsx │ │ ├── PdfViewer │ │ │ └── index.tsx │ │ ├── PointSelect.tsx │ │ ├── PrioritySelect.tsx │ │ ├── PrioritySelectMultiple.tsx │ │ ├── PriorityText.tsx │ │ ├── ProgressBar.tsx │ │ ├── ProjectIconPicker.tsx │ │ ├── ProjectSelectMultiple.tsx │ │ ├── PushNotification │ │ │ └── index.tsx │ │ ├── StatusSelect.tsx │ │ ├── StatusSelectMultiple.tsx │ │ ├── TaskCheckbox.tsx │ │ ├── TaskTypeSelect │ │ │ ├── Icon.tsx │ │ │ ├── icon.css │ │ │ └── index.tsx │ │ └── Time.tsx │ ├── _events │ │ ├── useEventMoveTaskToOtherBoard.ts │ │ ├── useEventSyncProjectMember.ts │ │ ├── useEventSyncProjectStatus.ts │ │ ├── useEventSyncProjectTask.ts │ │ ├── useEventSyncProjectView.ts │ │ ├── useEventTaskComment.ts │ │ ├── useEventTaskReorder.ts │ │ ├── useEventUserProject.ts │ │ └── usePusher.ts │ ├── _features │ │ ├── Activity │ │ │ ├── ActivityCardAttach.tsx │ │ │ ├── ActivityCardComment.tsx │ │ │ ├── ActivityCommentEditor.tsx │ │ │ ├── ActivityContainer.tsx │ │ │ ├── ActivityList.tsx │ │ │ ├── ActivityLog.tsx │ │ │ ├── ActivityLogDesc.tsx │ │ │ ├── ActivitySectionTime.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Automation │ │ │ ├── AutomateCreate.tsx │ │ │ ├── AutomateDesc.tsx │ │ │ ├── AutomateMenu.tsx │ │ │ ├── AutomateRuleList.tsx │ │ │ ├── AutomateThen.tsx │ │ │ ├── AutomateThenValues.tsx │ │ │ ├── AutomateWhen.tsx │ │ │ ├── AutomateWhenValues.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── AutomationScheduler │ │ │ ├── ActionList.tsx │ │ │ ├── AutomateSchedulerCreate.tsx │ │ │ ├── AutomateSchedulerList.tsx │ │ │ ├── TriggerEverySingleDayInWeek.tsx │ │ │ ├── TriggerEveryday.tsx │ │ │ ├── TriggerPresent.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── CustomField │ │ │ ├── CreateField.tsx │ │ │ ├── CreateFieldContainer.tsx │ │ │ ├── CreateFieldDate.tsx │ │ │ ├── CreateFieldFactory.tsx │ │ │ ├── CreateFieldNumber.tsx │ │ │ ├── CreateFieldPerson.tsx │ │ │ ├── CreateFieldSelect.tsx │ │ │ ├── CreateFieldText.tsx │ │ │ ├── CustomFieldModal.tsx │ │ │ ├── DeleteCustomField.tsx │ │ │ ├── EditCustomField.tsx │ │ │ ├── FormSelectType.tsx │ │ │ ├── SubmitCustomFieldConfig.tsx │ │ │ └── store.ts │ │ ├── CustomFieldCheckbox │ │ │ ├── ClearCheckedCheckboxes.tsx │ │ │ ├── CustomFieldCheckboxAll.tsx │ │ │ ├── CustomFieldCheckboxItem.tsx │ │ │ └── useCheckboxStore.ts │ │ ├── CustomFieldDisplay │ │ │ ├── CustomFieldAction.tsx │ │ │ ├── CustomFieldResize.tsx │ │ │ ├── CustomFieldSortableCell.tsx │ │ │ └── index.tsx │ │ ├── CustomFieldInput │ │ │ ├── CustomFieldInpCheckbox.tsx │ │ │ ├── CustomFieldInpCreatedAt.tsx │ │ │ ├── CustomFieldInpCreatedBy.tsx │ │ │ ├── CustomFieldInpDate.tsx │ │ │ ├── CustomFieldInpEmail.tsx │ │ │ ├── CustomFieldInpFile.tsx │ │ │ ├── CustomFieldInpMultiSelect.tsx │ │ │ ├── CustomFieldInpNumber.tsx │ │ │ ├── CustomFieldInpPerson.tsx │ │ │ ├── CustomFieldInpSelect.tsx │ │ │ ├── CustomFieldInpText.tsx │ │ │ ├── CustomFieldInpUrl.tsx │ │ │ ├── CustomFieldInputFactory.tsx │ │ │ ├── CustomFieldInputProvider.tsx │ │ │ ├── context.tsx │ │ │ └── style.css │ │ ├── CustomFieldMultiAction │ │ │ ├── MultiActionInpDisplay.tsx │ │ │ └── index.tsx │ │ ├── Dashboard │ │ │ ├── DasboardComponentCreate.tsx │ │ │ ├── DashboardComponentSetting.tsx │ │ │ ├── DashboardComponentUpdateForm.tsx │ │ │ ├── DboardCompColumnUpdateForm.tsx │ │ │ ├── DboardComponentList.tsx │ │ │ ├── DboardResizeHandle.tsx │ │ │ ├── components │ │ │ │ ├── DbCompBurnChart.tsx │ │ │ │ ├── DbCompColumn.tsx │ │ │ │ ├── DbCompDelete.tsx │ │ │ │ ├── DbCompDragHandler.tsx │ │ │ │ ├── DbCompSummary.tsx │ │ │ │ ├── DbComponent.tsx │ │ │ │ └── dboard-component.css │ │ │ ├── dboard-component-create.css │ │ │ ├── style.css │ │ │ ├── type.ts │ │ │ └── useDboardComponentSubmit.ts │ │ ├── Events │ │ │ └── EventUserProjectUpdate.tsx │ │ ├── Favorites │ │ │ ├── FavoriteAdd.tsx │ │ │ ├── FavoriteAddForm.tsx │ │ │ ├── FavoriteAddModal.tsx │ │ │ ├── FavoriteItem.tsx │ │ │ ├── FavoritePageItem.tsx │ │ │ ├── FavoriteProjectItem.tsx │ │ │ ├── FavoriteRemove.tsx │ │ │ ├── favorite-modal.css │ │ │ └── index.tsx │ │ ├── FilterAdvanced │ │ │ ├── ApplyFilter.tsx │ │ │ ├── ConditionSelect.tsx │ │ │ ├── FieldOperator.tsx │ │ │ ├── FieldSelect.tsx │ │ │ ├── FilterAdvancedModal.tsx │ │ │ ├── FilterAdvancedProvider.tsx │ │ │ ├── FilterAutoApply.tsx │ │ │ ├── FilterButton.tsx │ │ │ ├── FilterSubValue.tsx │ │ │ ├── FilterValue.tsx │ │ │ ├── FilterValueCheckbox.tsx │ │ │ ├── FilterValueDate.tsx │ │ │ ├── FilterValueInput.tsx │ │ │ ├── FilterValueMultiSelect.tsx │ │ │ ├── FilterValuePerson.tsx │ │ │ ├── FilterValueSelect.tsx │ │ │ ├── SaveFilter.tsx │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ ├── style.css │ │ │ ├── type.ts │ │ │ └── useFilterAdvancedStore.ts │ │ ├── GlobalData │ │ │ ├── index.tsx │ │ │ └── useGlobalDataFetch.ts │ │ ├── IntroSection │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── MeetingRoom │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── MyWorks │ │ │ ├── MyOvedueTasks.tsx │ │ │ ├── MyTodayTasks.tsx │ │ │ ├── MyUpcommingTasks.tsx │ │ │ ├── MyUrgentTasks.tsx │ │ │ ├── MyWorkContainer.tsx │ │ │ ├── MyWorkMembers.tsx │ │ │ ├── MyworkCard.tsx │ │ │ ├── MyworkLoading.tsx │ │ │ ├── MyworkProject.tsx │ │ │ ├── MyworkTaskList.tsx │ │ │ ├── MyworkTaskPaginate.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Profile │ │ │ ├── AvatarSelector.tsx │ │ │ ├── AvatarUpdate.tsx │ │ │ ├── PasswordTab.tsx │ │ │ ├── ProfileTab.tsx │ │ │ ├── index.tsx │ │ │ └── useAvatarUpload.ts │ │ ├── Project │ │ │ ├── Add │ │ │ │ ├── FormMembers.tsx │ │ │ │ ├── FormProjectView.tsx │ │ │ │ ├── ProjectAddForm.tsx │ │ │ │ └── ProjectAddModal.tsx │ │ │ ├── GridView │ │ │ │ ├── CreateNewRow.tsx │ │ │ │ ├── GridBtnActions.tsx │ │ │ │ ├── GridContentRow.tsx │ │ │ │ ├── GridDataFilter.tsx │ │ │ │ ├── GridDeleteBtn.tsx │ │ │ │ ├── GridHeadingCheckbox.tsx │ │ │ │ ├── GridHeadingRow.tsx │ │ │ │ ├── GridLoadMore.tsx │ │ │ │ ├── GridRowContainer.tsx │ │ │ │ ├── GridViewContainer.tsx │ │ │ │ ├── grid-style.css │ │ │ │ └── index.tsx │ │ │ ├── Header │ │ │ │ └── index.tsx │ │ │ ├── List │ │ │ │ ├── ProjectArchived.tsx │ │ │ │ ├── ProjectAvailable.tsx │ │ │ │ ├── ProjectItem.tsx │ │ │ │ ├── ProjectItemActions.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── Nav │ │ │ │ ├── List.tsx │ │ │ │ ├── ProjectNavItem.tsx │ │ │ │ └── index.tsx │ │ │ ├── Overview │ │ │ │ ├── OverviewBurnoutChart.tsx │ │ │ │ ├── OverviewContent.tsx │ │ │ │ ├── OverviewDateRange.tsx │ │ │ │ ├── OverviewMemberProgress.tsx │ │ │ │ ├── OverviewTasks.tsx │ │ │ │ ├── OverviewWorkloadByDate.tsx │ │ │ │ ├── OverviewWorkloadByStatus.tsx │ │ │ │ ├── context.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── Pin │ │ │ │ └── index.tsx │ │ │ ├── Team │ │ │ │ ├── TeamMemberInfo.tsx │ │ │ │ ├── TeamMemberInsight.tsx │ │ │ │ ├── TeamMemberProcess.tsx │ │ │ │ ├── TeamMemberStatus.tsx │ │ │ │ ├── TeamMemberStatusTask.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── Vision │ │ │ │ ├── RemoveTaskFromGoal.tsx │ │ │ │ ├── VisionCalendaContainer.tsx │ │ │ │ ├── VisionCalendar.tsx │ │ │ │ ├── VisionContainer.tsx │ │ │ │ ├── VisionCreate.tsx │ │ │ │ ├── VisionDelete.tsx │ │ │ │ ├── VisionItem.tsx │ │ │ │ ├── VisionListHeader.tsx │ │ │ │ ├── VisionListInDate.tsx │ │ │ │ ├── VisionListTask.tsx │ │ │ │ ├── VisionMonthNavigator.tsx │ │ │ │ ├── VisionTaskItem.tsx │ │ │ │ ├── VisionTaskItemDraggable.tsx │ │ │ │ ├── VisionTimeView.tsx │ │ │ │ ├── VisionViewMode.tsx │ │ │ │ ├── context.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ └── VisionTimeline │ │ │ │ ├── TimelineItem.tsx │ │ │ │ ├── TimelineItemDelete.tsx │ │ │ │ ├── TimelineItemDroppable.tsx │ │ │ │ ├── TimelineItem_Backup.tsx │ │ │ │ ├── TimelineTaskFilter.tsx │ │ │ │ ├── TimelineTitle.tsx │ │ │ │ ├── VisionTimelineTrack.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ ├── ProjectAdvanceTabs │ │ │ └── index.tsx │ │ ├── ProjectContainer │ │ │ ├── index.tsx │ │ │ ├── useGetCustomFields.ts │ │ │ ├── useGetMembers.ts │ │ │ ├── useGetProjectPoint.ts │ │ │ ├── useGetProjectStatus.ts │ │ │ ├── useGetProjectViewList.ts │ │ │ ├── useGetTask.ts │ │ │ ├── useGetTaskBackup.ts │ │ │ └── useSetProjectViewCache.ts │ │ ├── ProjectMember │ │ │ └── View.tsx │ │ ├── ProjectSetting │ │ │ ├── Notification │ │ │ │ ├── NotifySettingContainer.tsx │ │ │ │ └── index.tsx │ │ │ └── Report │ │ │ │ ├── ReportSettingContainer.tsx │ │ │ │ └── index.tsx │ │ ├── ProjectView │ │ │ ├── ProjectViewChangeName.tsx │ │ │ ├── ProjectViewCreate.tsx │ │ │ ├── ProjectViewDelete.tsx │ │ │ ├── ProjectViewForMe.tsx │ │ │ ├── ProjectViewIcon.tsx │ │ │ ├── ProjectViewItemDropdown.tsx │ │ │ ├── ProjectViewList.tsx │ │ │ ├── ProjectViewModal.tsx │ │ │ ├── ProjectViewModalForm.tsx │ │ │ ├── ProjectViewSetAsDefault.tsx │ │ │ ├── ProjectViewTitle.tsx │ │ │ ├── ProjectViewTypes.tsx │ │ │ ├── ProjectViewUpdate.tsx │ │ │ ├── context.ts │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ ├── style.css │ │ │ ├── updateContext.ts │ │ │ ├── useDefaultViewTypes.ts │ │ │ ├── useProjectViewAdd.ts │ │ │ ├── useProjectViewList.ts │ │ │ ├── useReRenderView.ts │ │ │ └── useSetViewFilter.ts │ │ ├── ProjectViewFilter │ │ │ ├── BoardFilter.tsx │ │ │ ├── CalendarFilter.tsx │ │ │ ├── DashboardFilter.tsx │ │ │ ├── FilterForm.tsx │ │ │ ├── GoalFilter.tsx │ │ │ ├── GridFilter.tsx │ │ │ ├── ListFilter.tsx │ │ │ └── TeamFilter.tsx │ │ ├── PromptGenerator │ │ │ ├── PromptContainer.tsx │ │ │ ├── PromptContent.tsx │ │ │ ├── PromptTaskAllocation.tsx │ │ │ ├── PromptTaskEvaluation.tsx │ │ │ └── index.tsx │ │ ├── Report │ │ │ ├── ReportByMemberItem.tsx │ │ │ ├── ReportByProjectItem.tsx │ │ │ ├── ReportContent.tsx │ │ │ ├── ReportFilterMember.tsx │ │ │ ├── ReportFilterProject.tsx │ │ │ ├── ReportHeader.tsx │ │ │ ├── ReportMemberStats.tsx │ │ │ ├── ReportProjectStats.tsx │ │ │ ├── ReportSidebar.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ └── style.css │ │ ├── ReportSavedList │ │ │ ├── AddToSavedList.tsx │ │ │ ├── SavedList.tsx │ │ │ ├── SetDefaultConfig.tsx │ │ │ ├── context.tsx │ │ │ └── index.tsx │ │ ├── SettingAbout │ │ │ ├── SettingAbout.tsx │ │ │ ├── SettingStorageConfiguration.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── SettingApps │ │ │ ├── ApplicationList.tsx │ │ │ ├── CreateApplication.tsx │ │ │ └── index.tsx │ │ ├── SettingExport │ │ │ ├── ExportFilter.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── SettingPeople │ │ │ ├── CreateNewMember.tsx │ │ │ ├── SettingPeopleDelete.tsx │ │ │ ├── SettingPeopleInvitation.tsx │ │ │ ├── SettingPeopleMemberList.tsx │ │ │ └── index.tsx │ │ ├── TaskActions │ │ │ ├── TaskDeleteAction.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── TaskChecklist │ │ │ ├── ChecklistAdd.tsx │ │ │ ├── ChecklistInput.tsx │ │ │ ├── ChecklistList.tsx │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ ├── style.css │ │ │ ├── useChecklitCounter.ts │ │ │ └── useGetTaskChecklist.ts │ │ ├── TaskComments │ │ │ ├── TaskComment.tsx │ │ │ ├── TaskCommentContainer.tsx │ │ │ ├── TaskCommentInput.tsx │ │ │ ├── TaskCommentList.tsx │ │ │ ├── TaskCommentListItem.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── TaskDetail │ │ │ ├── TaskCover.tsx │ │ │ ├── TaskDescUpdate.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── TaskFilter │ │ │ ├── CalendarModeFilter.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ ├── style.css │ │ │ ├── useTaskFilterContext.ts │ │ │ ├── useTodoFilter.ts │ │ │ └── useUpdateGroupbyItem.ts │ │ ├── TaskImport │ │ │ ├── DataRemap.tsx │ │ │ ├── TaskImportAction.tsx │ │ │ ├── TaskImportArea.tsx │ │ │ ├── TaskImportCsvFormat.tsx │ │ │ ├── TaskImportPreview.tsx │ │ │ ├── TaskImportStep.tsx │ │ │ ├── context.ts │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── TaskMultipleActions │ │ │ ├── index.tsx │ │ │ ├── useMultipleDelete.ts │ │ │ └── useMultipleUpdate.ts │ │ ├── TimeTracker │ │ │ ├── GlobalTimerDisplay.tsx │ │ │ ├── TimerButton.tsx │ │ │ ├── TimerHistory.tsx │ │ │ ├── index.tsx │ │ │ └── timerStore.ts │ │ ├── UpsaleDialog │ │ │ └── index.tsx │ │ └── UserPermission │ │ │ ├── HasRole.tsx │ │ │ └── useUserRole.ts │ ├── _hooks │ │ ├── status.ts │ │ ├── useCooldown.ts │ │ ├── useDebounce.ts │ │ ├── useEscapeKeyPressed.ts │ │ ├── useGenTaskMappingObject.ts │ │ ├── useGetParams.ts │ │ ├── useOrgIdBySlug.ts │ │ ├── useOutsideClick.ts │ │ ├── useProjectPinUnPin.ts │ │ ├── useServiceAutomation.ts │ │ ├── useServiceFavoriteUpdate.ts │ │ ├── useServiceOrgMember.ts │ │ ├── useServiceProjectArchive.ts │ │ ├── useServiceTaskAdd.ts │ │ ├── useServiceTaskDel.ts │ │ ├── useServiceTaskUpdate.ts │ │ ├── useStatusData.ts │ │ ├── useStatusUtils.ts │ │ ├── useTaskAutomation.ts │ │ ├── useTaskIdChange.ts │ │ ├── useTodoCounter.ts │ │ └── useUrl.ts │ ├── darkmode.css │ ├── email-verification │ │ └── page.tsx │ ├── forgot-password │ │ ├── ForgotPasswordForm.tsx │ │ └── page.tsx │ ├── global.css │ ├── layout.tsx │ ├── organization │ │ ├── OrgList.tsx │ │ ├── create │ │ │ ├── CreateOrganization.tsx │ │ │ ├── page.tsx │ │ │ └── style.css │ │ └── page.tsx │ ├── page.tsx │ ├── profile │ │ └── [userId] │ │ │ └── page.tsx │ ├── providers.tsx │ ├── redirect │ │ ├── UserChecking.tsx │ │ └── page.tsx │ ├── reset-password │ │ ├── [token] │ │ │ ├── ResetPasswordForm.tsx │ │ │ └── page.tsx │ │ └── success │ │ │ └── page.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ ├── SignInactiveUser.tsx │ │ │ ├── SigninForm.tsx │ │ │ ├── page.tsx │ │ │ └── style.css │ ├── sign-out │ │ ├── Signout.tsx │ │ └── page.tsx │ ├── sign-up │ │ └── [[...sign-up]] │ │ │ ├── SignupForm.tsx │ │ │ └── page.tsx │ └── style │ │ └── index.css │ ├── components │ ├── Logo.tsx │ └── RootPage.tsx │ ├── index.d.ts │ ├── jest.config.ts │ ├── layouts │ ├── OrgSection.tsx │ ├── RootLayout.tsx │ ├── ThemeSelection.tsx │ └── UserSection.tsx │ ├── libs │ ├── firebase.ts │ └── pushState.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── postcss.config.js │ ├── project.json │ ├── public │ ├── .gitkeep │ ├── avatars │ │ ├── female-1.png │ │ ├── female-10.png │ │ ├── female-11.png │ │ ├── female-12.png │ │ ├── female-13.png │ │ ├── female-14.png │ │ ├── female-15.png │ │ ├── female-16.png │ │ ├── female-17.png │ │ ├── female-18.png │ │ ├── female-19.png │ │ ├── female-2.png │ │ ├── female-20.png │ │ ├── female-3.png │ │ ├── female-4.png │ │ ├── female-5.png │ │ ├── female-6.png │ │ ├── female-7.png │ │ ├── female-8.png │ │ ├── female-9.png │ │ ├── men-1.png │ │ ├── men-10.png │ │ ├── men-11.png │ │ ├── men-12.png │ │ ├── men-13.png │ │ ├── men-14.png │ │ ├── men-15.png │ │ ├── men-16.png │ │ ├── men-17.png │ │ ├── men-18.png │ │ ├── men-19.png │ │ ├── men-2.png │ │ ├── men-20.png │ │ ├── men-3.png │ │ ├── men-4.png │ │ ├── men-5.png │ │ ├── men-6.png │ │ ├── men-7.png │ │ ├── men-8.png │ │ └── men-9.png │ ├── background.png │ ├── business-report.png │ ├── dialog-background.png │ ├── email.svg │ ├── favicon - Copy.ico │ ├── favicon.ico │ ├── filepacks │ │ ├── ai2.png │ │ ├── avi.png │ │ ├── br.png │ │ ├── css.png │ │ ├── csv.png │ │ ├── doc.png │ │ ├── exe.png │ │ ├── file.png │ │ ├── html.png │ │ ├── jpg.png │ │ ├── js.png │ │ ├── json.png │ │ ├── mp3.png │ │ ├── mp4.png │ │ ├── pdf.png │ │ ├── png.png │ │ ├── ppt.png │ │ ├── ps.png │ │ ├── psd.png │ │ ├── rar.png │ │ ├── svg.png │ │ ├── txt.png │ │ ├── xlsx.png │ │ ├── xml.png │ │ └── zip.png │ ├── google.png │ ├── import-template.xlsx │ ├── logo132x132.svg │ ├── logo32x32.png │ ├── logo44x44.png │ ├── logo71x71.png │ ├── pat-1.webp │ ├── project-view │ │ ├── activity.svg │ │ ├── board.svg │ │ ├── calendar.svg │ │ ├── gantt.svg │ │ ├── list.svg │ │ ├── team.svg │ │ ├── timeline.svg │ │ └── workload.svg │ ├── service-worker.js │ ├── sign-background-1.png │ └── sign-background-cover1.png │ ├── services │ ├── _req.ts │ ├── activity.ts │ ├── apps.ts │ ├── automation.ts │ ├── comment.ts │ ├── dashboard.ts │ ├── favorite.ts │ ├── field.ts │ ├── hooks │ │ └── useServiceProject.ts │ ├── meeting.ts │ ├── member.ts │ ├── organization.ts │ ├── organizationMember.ts │ ├── point.ts │ ├── profile.ts │ ├── project.grid.ts │ ├── project.ts │ ├── projectSettingNotify.ts │ ├── projectSettingReport.ts │ ├── projectView.ts │ ├── report.ts │ ├── scheduler.ts │ ├── settingPeople.ts │ ├── status.ts │ ├── storage.ts │ ├── tag.ts │ ├── task.checklist.ts │ ├── task.ts │ ├── timer.ts │ ├── useServiceProject.ts │ └── vision.ts │ ├── store │ ├── application.ts │ ├── automation.ts │ ├── customFields.ts │ ├── example.ts │ ├── favorite.ts │ ├── global.ts │ ├── member.ts │ ├── menu.ts │ ├── orgMember.ts │ ├── permission.ts │ ├── point.ts │ ├── projecArchived.ts │ ├── project.ts │ ├── projectView.ts │ ├── status.ts │ └── task.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── tsconfig.spec.json ├── docker-compose.services.yml ├── docker-compose.yml ├── docker ├── Dockerfile ├── backend │ ├── Dockerfile │ └── Dockerfile-flyio ├── be-entrypoint.sh ├── fe-entrypoint.sh ├── frontend │ └── Dockerfile ├── setup.md └── test.js ├── fly.sin.toml ├── fly.toml ├── fly.vir.toml ├── jest.config.ts ├── jest.preset.js ├── netlify.toml ├── nx.json ├── package.json ├── packages ├── .gitkeep ├── auth-client │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── GoalierProvider.tsx │ │ ├── index.ts │ │ ├── lib │ │ │ ├── goalie-nextjs.module.css │ │ │ ├── goalie-nextjs.spec.tsx │ │ │ ├── goalie-nextjs.tsx │ │ │ ├── hello-server.tsx │ │ │ └── util.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── _req.ts │ │ │ ├── auth.ts │ │ │ └── organization.ts │ │ ├── types.ts │ │ ├── useGoalieInProtectionMode.ts │ │ └── useUser.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── core │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── client │ │ │ ├── index.ts │ │ │ └── storage │ │ │ │ └── index.ts │ │ ├── common │ │ │ └── index.ts │ │ ├── date │ │ │ ├── converter.ts │ │ │ ├── format.ts │ │ │ └── index.ts │ │ ├── events │ │ │ ├── index.ts │ │ │ └── pusher.ts │ │ ├── index.ts │ │ └── validation │ │ │ ├── _utils.ts │ │ │ ├── email.ts │ │ │ ├── index.ts │ │ │ ├── project.ts │ │ │ ├── task.ts │ │ │ └── user.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── database │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── _prisma.ts │ │ │ ├── activity.ts │ │ │ ├── application.repository.ts │ │ │ ├── comment.repository.ts │ │ │ ├── dashboard.ts │ │ │ ├── favorite.ts │ │ │ ├── field.repository.ts │ │ │ ├── grid.repository.ts │ │ │ ├── index.ts │ │ │ ├── member.ts │ │ │ ├── orgMember.ts │ │ │ ├── organization.ts │ │ │ ├── organizationStorage.ts │ │ │ ├── project.repository.ts │ │ │ ├── project.setting.repository.ts │ │ │ ├── project.ts │ │ │ ├── project.view.repository.ts │ │ │ ├── projectPin.ts │ │ │ ├── projectView.ts │ │ │ ├── scheduler.repository.ts │ │ │ ├── stats.repository.ts │ │ │ ├── storage.ts │ │ │ ├── tag.ts │ │ │ ├── task.checklist.repository.ts │ │ │ ├── task.repository.ts │ │ │ ├── task.ts │ │ │ ├── taskAutomation.ts │ │ │ ├── taskPoint.ts │ │ │ ├── taskStatus.ts │ │ │ ├── timer.repository.ts │ │ │ ├── user.repository.ts │ │ │ ├── user.ts │ │ │ └── vision.ts │ │ ├── prisma │ │ │ ├── dummy.ts │ │ │ ├── schema.prisma │ │ │ ├── seed.ts │ │ │ └── seeder │ │ │ │ ├── customData.ts │ │ │ │ ├── date.builder.ts │ │ │ │ ├── organization.ts │ │ │ │ ├── project.ts │ │ │ │ ├── report.ts │ │ │ │ ├── test.ts │ │ │ │ └── user.ts │ │ └── type.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── event-bus │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── publisher.ts │ │ │ ├── shared-pubsub.spec.ts │ │ │ ├── shared-pubsub.ts │ │ │ └── subscriber.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── task-runner-e2e │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── be-scheduler │ │ │ └── be-scheduler.spec.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ └── tsconfig.spec.json ├── task-runner │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── actions │ │ │ ├── BaseAction.ts │ │ │ └── NotificationAction.ts │ │ ├── assets │ │ │ └── .keep │ │ ├── cronJob.ts │ │ ├── main.ts │ │ └── scheduler.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── webpack.config.js └── ui-components │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── components │ │ ├── Avatar │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Button │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── Card │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Confirmbox │ │ │ ├── ConfirmBox.tsx │ │ │ ├── Icons.tsx │ │ │ ├── index.tsx │ │ │ ├── style.css │ │ │ └── type.ts │ │ ├── Controls │ │ │ ├── CheckboxControl │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ ├── InputControl │ │ │ │ ├── Addon.tsx │ │ │ │ ├── InputIcon.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── ListControl │ │ │ │ ├── ListButton.tsx │ │ │ │ ├── ListIcon.tsx │ │ │ │ ├── ListItem.tsx │ │ │ │ ├── ListOptions.tsx │ │ │ │ ├── ListPortal.tsx │ │ │ │ ├── ListSelectedItem.tsx │ │ │ │ ├── context.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── style.css │ │ │ │ └── type.ts │ │ │ ├── PopoverControl │ │ │ │ └── index.tsx │ │ │ ├── Range │ │ │ │ ├── index.tsx │ │ │ │ └── styles.css │ │ │ ├── RichTextEditorControl │ │ │ │ ├── MentionList.css │ │ │ │ ├── MentionList.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── style.css │ │ │ │ └── suggestionBase.ts │ │ │ ├── TextEditorControl │ │ │ │ ├── BubbleMenuEditor.tsx │ │ │ │ ├── CommonEditorMenu.tsx │ │ │ │ ├── FloatingMenuEditor.tsx │ │ │ │ ├── TextEditor.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.css │ │ │ ├── TextareaControl │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── type.ts │ │ ├── DatePicker │ │ │ ├── Borderless.tsx │ │ │ ├── borderless.css │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Dialog │ │ │ ├── DialogClose.tsx │ │ │ ├── DialogContent.tsx │ │ │ ├── DialogPortal.tsx │ │ │ ├── DialogRoot.tsx │ │ │ ├── DialogTitle.tsx │ │ │ ├── DialogTrigger.tsx │ │ │ ├── context.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Dropdown │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── FormGroup │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── IconColorPicker │ │ │ └── index.tsx │ │ ├── Loading │ │ │ ├── AbsoluteLoading.tsx │ │ │ ├── Icon.tsx │ │ │ ├── LoadingContainer.tsx │ │ │ ├── index.tsx │ │ │ ├── style.css │ │ │ └── type.ts │ │ ├── Message │ │ │ ├── Icons.tsx │ │ │ ├── MessageBox.tsx │ │ │ ├── index.tsx │ │ │ ├── style.css │ │ │ └── type.ts │ │ ├── Modal │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Popover │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Scrollbar │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Switch │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Tab │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Timeline │ │ │ ├── TimelineList.tsx │ │ │ ├── TimelineTrack.tsx │ │ │ ├── index.tsx │ │ │ ├── style.css │ │ │ └── type.ts │ │ ├── Tooltip │ │ │ ├── index.tsx │ │ │ └── style.css │ │ └── utils.ts │ ├── doc.md │ ├── hooks │ │ └── useForm.ts │ ├── index.ts │ └── server.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── render.yaml ├── scripts └── deploy │ └── aws-lightsail.sh ├── test.txt ├── tsconfig.base.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | docker-compose.yml 2 | Dockerfile 3 | node_modules 4 | dist 5 | .github 6 | .vscode 7 | .git 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/flyio.yml: -------------------------------------------------------------------------------- 1 | name: Fly Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy: 8 | name: Deploy to ${{ matrix.region }} 9 | runs-on: ubuntu-latest 10 | concurrency: deploy-group 11 | strategy: 12 | matrix: 13 | include: 14 | - region: Singapore 15 | app: namviek-sin 16 | - region: Virginia 17 | app: namviek-vir 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: superfly/flyctl-actions/setup-flyctl@master 21 | - name: Deploy to ${{ matrix.region }} 22 | run: flyctl deploy --remote-only --app ${{ matrix.app }} 23 | env: 24 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 25 | 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | .circleci/ 5 | .github/ 6 | .husky 7 | build/ 8 | node_modules/ 9 | coverage/ 10 | patches/ 11 | scripts/ 12 | 13 | .bettercodehub.yml 14 | .buckconfig 15 | .gitattributes 16 | .gitignore 17 | .snyk 18 | .watchmanconfig 19 | CONTRIBUTING.md 20 | README.md 21 | SECURITY.md 22 | npm-debug.log 23 | yarn-error.log 24 | 25 | .storybook/ 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "bracketSpacing": true, 4 | "trailingComma": "none", 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "arrowParens": "avoid", 8 | "bracketSameLine": true, 9 | "semi": false 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "firsttris.vscode-jest-runner", 6 | "dbaeumer.vscode-eslint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /apps/backend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'be-gateway', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/apps/backend', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/backend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hudy9x/namviek/65f4f66cc11bdbcb24173f6efacf424408c15b4e/apps/backend/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/backend/src/caches/OrganizationCache.ts: -------------------------------------------------------------------------------- 1 | import { mdOrgGetOne } from "@database" 2 | import { CKEY, getCache, setCache } from "../lib/redis" 3 | 4 | export default class OrganizationCache { 5 | orgId: string 6 | key: string[] 7 | constructor(orgId: string) { 8 | this.orgId = orgId 9 | this.key = [CKEY.ORG_MAX_STORAGE_SIZE, orgId] 10 | } 11 | 12 | async getMaxStorageSize() { 13 | const cached = await getCache(this.key) 14 | if (cached) { 15 | return cached 16 | } 17 | 18 | const { maxStorageSize } = await mdOrgGetOne(this.orgId) 19 | await setCache(this.key, maxStorageSize) 20 | 21 | return maxStorageSize 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /apps/backend/src/caches/TaskCache.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hudy9x/namviek/65f4f66cc11bdbcb24173f6efacf424408c15b4e/apps/backend/src/caches/TaskCache.ts -------------------------------------------------------------------------------- /apps/backend/src/core/Controller.ts: -------------------------------------------------------------------------------- 1 | import { hasMetadata, setMetadata } from './Mapper' 2 | import { MetaKey } from './type' 3 | 4 | export const Controller = (prefix: string): ClassDecorator => { 5 | return (target: any) => { 6 | setMetadata(MetaKey.PREFIX, prefix, target) 7 | 8 | if (!hasMetadata(MetaKey.ROUTES, target)) { 9 | setMetadata(MetaKey.ROUTES, [], target) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/backend/src/core/UseMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { setMetadata } from './Mapper' 2 | import { ExpressMiddlewareFunction, MetaKey } from './type' 3 | 4 | export const UseMiddleware = ( 5 | funcs: ExpressMiddlewareFunction | ExpressMiddlewareFunction[] 6 | ): ClassDecorator => { 7 | return (target: any) => { 8 | setMetadata( 9 | MetaKey.MIDDLEWARES, 10 | Array.isArray(funcs) ? funcs : [funcs], 11 | target 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/backend/src/core/abstracts/Controller.ts: -------------------------------------------------------------------------------- 1 | import { ExpressNext, ExpressRequest, ExpressResponse } from '../type' 2 | 3 | export class BaseController { 4 | req: ExpressRequest 5 | res: ExpressResponse 6 | next: ExpressNext 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/core/decorators/Auth.ts: -------------------------------------------------------------------------------- 1 | import { setMetadata } from '../Mapper' 2 | import { MetaKey } from '../type' 3 | 4 | export const Auth = (roles: string[]): ClassDecorator => { 5 | return (target: any) => { 6 | setMetadata(MetaKey.AUTH, roles, target) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/backend/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Controller' 2 | export * from './Mapper' 3 | export * from './type' 4 | 5 | export * from './UseMiddleware' 6 | 7 | export * from './abstracts/Controller' 8 | 9 | export * from './methods/Method' 10 | export * from './methods/Post' 11 | export * from './methods/Get' 12 | export * from './methods/Put' 13 | export * from './methods/Delete' 14 | 15 | export * from './params/Body' 16 | export * from './params/Req' 17 | export * from './params/Res' 18 | export * from './params/Param' 19 | export * from './params/Next' 20 | export * from './params/Query' 21 | -------------------------------------------------------------------------------- /apps/backend/src/core/methods/Delete.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { HTTPMethod, MetaKey, RouteDefinition } from '../type' 3 | 4 | export const Delete = (path: string): MethodDecorator => { 5 | return (target, propertyKey: string): void => { 6 | if (!hasMetadata(MetaKey.ROUTES, target.constructor)) { 7 | setMetadata(MetaKey.ROUTES, [], target.constructor) 8 | } 9 | 10 | const routes = getMetadata( 11 | MetaKey.ROUTES, 12 | target.constructor 13 | ) as RouteDefinition[] 14 | 15 | routes.push({ 16 | requestMethod: HTTPMethod.DELETE, 17 | path, 18 | methodName: propertyKey 19 | }) 20 | 21 | setMetadata(MetaKey.ROUTES, routes, target.constructor) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/backend/src/core/methods/Get.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { HTTPMethod, MetaKey, RouteDefinition } from '../type' 3 | 4 | export const Get = (path: string): MethodDecorator => { 5 | return (target, propertyKey: string): void => { 6 | if (!hasMetadata(MetaKey.ROUTES, target.constructor)) { 7 | setMetadata(MetaKey.ROUTES, [], target.constructor) 8 | } 9 | 10 | const routes = getMetadata( 11 | MetaKey.ROUTES, 12 | target.constructor 13 | ) as RouteDefinition[] 14 | 15 | routes.push({ 16 | requestMethod: HTTPMethod.GET, 17 | path, 18 | methodName: propertyKey 19 | }) 20 | 21 | setMetadata(MetaKey.ROUTES, routes, target.constructor) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/backend/src/core/methods/Post.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { HTTPMethod, MetaKey, RouteDefinition } from '../type' 3 | 4 | export const Post = (path: string): MethodDecorator => { 5 | return (target, propertyKey: string): void => { 6 | if (!hasMetadata(MetaKey.ROUTES, target.constructor)) { 7 | setMetadata(MetaKey.ROUTES, [], target.constructor) 8 | } 9 | 10 | const routes = getMetadata( 11 | MetaKey.ROUTES, 12 | target.constructor 13 | ) as RouteDefinition[] 14 | 15 | routes.push({ 16 | requestMethod: HTTPMethod.POST, 17 | path, 18 | methodName: propertyKey 19 | }) 20 | 21 | setMetadata(MetaKey.ROUTES, routes, target.constructor) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/backend/src/core/methods/Put.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { HTTPMethod, MetaKey, RouteDefinition } from '../type' 3 | 4 | export const Put = (path: string): MethodDecorator => { 5 | return (target, propertyKey: string): void => { 6 | if (!hasMetadata(MetaKey.ROUTES, target.constructor)) { 7 | setMetadata(MetaKey.ROUTES, [], target.constructor) 8 | } 9 | 10 | const routes = getMetadata( 11 | MetaKey.ROUTES, 12 | target.constructor 13 | ) as RouteDefinition[] 14 | 15 | routes.push({ 16 | requestMethod: HTTPMethod.PUT, 17 | path, 18 | methodName: propertyKey 19 | }) 20 | 21 | setMetadata(MetaKey.ROUTES, routes, target.constructor) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/backend/src/core/params/Body.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { MetaKey, RouterParams } from '../type' 3 | 4 | export const Body = (): ParameterDecorator => { 5 | return (target: any, propertyKey: string, parameterIndex: number) => { 6 | const _target = target.constructor 7 | 8 | if (!hasMetadata(MetaKey.PARAMS, _target, propertyKey)) { 9 | setMetadata(MetaKey.PARAMS, [], _target, propertyKey) 10 | } 11 | 12 | const params = getMetadata(MetaKey.PARAMS, _target, propertyKey) as string[] 13 | 14 | params[parameterIndex] = RouterParams.BODY 15 | 16 | setMetadata(MetaKey.PARAMS, params, _target, propertyKey) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/src/core/params/Next.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { MetaKey, RouterParams } from '../type' 3 | 4 | export const Next = (): ParameterDecorator => { 5 | return (target: any, propertyKey: string, parameterIndex: number) => { 6 | const _target = target.constructor 7 | 8 | if (!hasMetadata(MetaKey.PARAMS, _target, propertyKey)) { 9 | setMetadata(MetaKey.PARAMS, [], _target, propertyKey) 10 | } 11 | 12 | const params = getMetadata(MetaKey.PARAMS, _target, propertyKey) as string[] 13 | 14 | params[parameterIndex] = RouterParams.NEXT 15 | 16 | setMetadata(MetaKey.PARAMS, params, _target, propertyKey) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/src/core/params/Param.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { MetaKey, RouterParams } from '../type' 3 | 4 | export const Param = (): ParameterDecorator => { 5 | return (target: any, propertyKey: string, parameterIndex: number) => { 6 | const _target = target.constructor 7 | 8 | if (!hasMetadata(MetaKey.PARAMS, _target, propertyKey)) { 9 | setMetadata(MetaKey.PARAMS, [], _target, propertyKey) 10 | } 11 | 12 | const params = getMetadata(MetaKey.PARAMS, _target, propertyKey) as string[] 13 | 14 | params[parameterIndex] = RouterParams.PARAM 15 | 16 | setMetadata(MetaKey.PARAMS, params, _target, propertyKey) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/src/core/params/Query.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { MetaKey, RouterParams } from '../type' 3 | 4 | export const Query = (): ParameterDecorator => { 5 | return (target: any, propertyKey: string, parameterIndex: number) => { 6 | const _target = target.constructor 7 | 8 | if (!hasMetadata(MetaKey.PARAMS, _target, propertyKey)) { 9 | setMetadata(MetaKey.PARAMS, [], _target, propertyKey) 10 | } 11 | 12 | const params = getMetadata(MetaKey.PARAMS, _target, propertyKey) as string[] 13 | 14 | params[parameterIndex] = RouterParams.QUERY 15 | 16 | setMetadata(MetaKey.PARAMS, params, _target, propertyKey) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/src/core/params/Req.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { MetaKey, RouterParams } from '../type' 3 | 4 | export const Req = (): ParameterDecorator => { 5 | return (target: any, propertyKey: string, parameterIndex: number) => { 6 | const _target = target.constructor 7 | 8 | if (!hasMetadata(MetaKey.PARAMS, _target, propertyKey)) { 9 | setMetadata(MetaKey.PARAMS, [], _target, propertyKey) 10 | } 11 | 12 | const params = getMetadata(MetaKey.PARAMS, _target, propertyKey) as string[] 13 | 14 | params[parameterIndex] = RouterParams.REQUEST 15 | 16 | setMetadata(MetaKey.PARAMS, params, _target, propertyKey) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/src/core/params/Res.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata, hasMetadata, setMetadata } from '../Mapper' 2 | import { MetaKey, RouterParams } from '../type' 3 | 4 | export const Res = (): ParameterDecorator => { 5 | return (target: any, propertyKey: string, parameterIndex: number) => { 6 | const _target = target.constructor 7 | 8 | if (!hasMetadata(MetaKey.PARAMS, _target, propertyKey)) { 9 | setMetadata(MetaKey.PARAMS, [], _target, propertyKey) 10 | } 11 | 12 | const params = getMetadata(MetaKey.PARAMS, _target, propertyKey) as string[] 13 | 14 | params[parameterIndex] = RouterParams.RESPONSE 15 | 16 | setMetadata(MetaKey.PARAMS, params, _target, propertyKey) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/ApiNotFoundException.ts: -------------------------------------------------------------------------------- 1 | export default class ApiNotFoundException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'API_NOT_FOUND') 5 | this.status = 404 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/BadRequestException.ts: -------------------------------------------------------------------------------- 1 | export default class BadRequestException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'BAD_REQUEST') 5 | this.status = 400 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/CredentialInvalidException.ts: -------------------------------------------------------------------------------- 1 | export default class CredentialInvalidException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'Invalid credential or inactive email') 5 | this.status = 400 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/DataAccessException.ts: -------------------------------------------------------------------------------- 1 | export default class DataAccessException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(`Database Access Error: ${message}` || 'Database access exception') 5 | this.status = 500 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/InactiveAccountException.ts: -------------------------------------------------------------------------------- 1 | export default class InactiveAccountException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'inactive account') 5 | this.status = 403 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/IncorrectConfigurationException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class IncorrectConfigurationException extends Error { 3 | status: number 4 | constructor(message?: string) { 5 | super(message || 'INCORRECT_CONFIGURATION') 6 | this.status = 500 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/InternalErrorException.ts: -------------------------------------------------------------------------------- 1 | export default class InternalErrorException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'INTERNAL_SERVER_ERROR') 5 | this.status = 500 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/InternalServerException.ts: -------------------------------------------------------------------------------- 1 | export default class InternalServerException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'INTERNAL_SERVER_ERROR') 5 | this.status = 500 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/MaxStorageSizeException.ts: -------------------------------------------------------------------------------- 1 | export default class MaxStorageSizeException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'MAX_SIZE_STORAGE') 5 | this.status = 500 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/exceptions/StorageConfigurationNotFoundException.ts: -------------------------------------------------------------------------------- 1 | export default class StorageConfigurationNotFoundException extends Error { 2 | status: number 3 | constructor(message?: string) { 4 | super(message || 'STORAGE_CONFIG_NOT_FOUND') 5 | this.status = 500 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/jobs/status.pusher.job.ts: -------------------------------------------------------------------------------- 1 | import { pusherTrigger } from '../lib/pusher-server' 2 | 3 | export default class StatusPusherJob { 4 | async triggerUpdateEvent({ 5 | projectId, 6 | uid 7 | }: { 8 | projectId: string 9 | uid: string 10 | }) { 11 | pusherTrigger('team-collab', `projectStatus:update-${projectId}`, { 12 | triggerBy: uid 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/backend/src/jobs/task.pusher.job.ts: -------------------------------------------------------------------------------- 1 | import { Task } from '@prisma/client' 2 | import { pusherTrigger } from '../lib/pusher-server' 3 | 4 | export default class TaskPusherJob { 5 | async triggerUpdateEvent({ 6 | projectId, 7 | uid, 8 | type = 'update', 9 | data 10 | }: { 11 | projectId: string 12 | uid: string 13 | type?: 'delete' | 'update' | 'create' | 'update-many' | 'delete-many' 14 | data?: Partial 15 | }) { 16 | console.log('task-pusher-job triggered: ', type) 17 | pusherTrigger('team-collab', `projectTask:update-${projectId}`, { 18 | triggerBy: uid, 19 | type, 20 | data 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/backend/src/lib/firebase-admin.ts: -------------------------------------------------------------------------------- 1 | import { cert, initializeApp, getApps } from 'firebase-admin/app' 2 | 3 | try { 4 | const serviceAccount = { 5 | projectId: process.env.FIREBASE_PROJECT_ID, 6 | clientEmail: process.env.FIREBASE_CLIENT_EMAIL, 7 | privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n') 8 | } 9 | 10 | if (!getApps().length) { 11 | initializeApp({ 12 | credential: cert(serviceAccount) 13 | }) 14 | } 15 | } catch (error) { 16 | console.warn('Firebase admin missing configuration') 17 | } 18 | -------------------------------------------------------------------------------- /apps/backend/src/lib/password.ts: -------------------------------------------------------------------------------- 1 | import { compareSync, genSaltSync, hashSync } from 'bcryptjs' 2 | import { isDevMode } from './utils' 3 | 4 | const salt = genSaltSync(10) 5 | const defaultPwd = process.env.DEFAULT_PWD 6 | 7 | export const hashPassword = (pwd: string) => { 8 | return hashSync(pwd, salt) 9 | } 10 | 11 | export const compareHashPassword = (pwd: string, hashedPwd: string) => { 12 | let valid = false 13 | if (compareSync(pwd, hashedPwd)) { 14 | valid = true 15 | } 16 | 17 | // in development mode, any user can access with their own password and default password 18 | if (!valid && isDevMode() && defaultPwd && pwd === defaultPwd) { 19 | valid = true 20 | } 21 | 22 | return valid 23 | } 24 | -------------------------------------------------------------------------------- /apps/backend/src/lib/url.ts: -------------------------------------------------------------------------------- 1 | const frontendUrl = (process.env.NEXT_PUBLIC_FE_GATEWAY || '').replace(/\/*$/, '') 2 | 3 | const _clean = (url: string) => { 4 | return url.replace(/^\/*/, '') 5 | } 6 | 7 | export const genFrontendUrl = (url: string) => { 8 | return `${frontendUrl}/${_clean(url)}` 9 | } 10 | 11 | export const getLogoUrl = () => { 12 | return genFrontendUrl(`/logo71x71.png`) 13 | } 14 | -------------------------------------------------------------------------------- /apps/backend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | export const isDevMode = () => { 2 | return process.env.DEV_MODE 3 | } 4 | 5 | export const isEmailVerificationEnabled = () => { 6 | const isEnabled = process.env.EMAIL_VERIFICATION_ENABLED 7 | return isEnabled === '1' || isEnabled === 'true' 8 | } 9 | 10 | export const isProdMode = () => { 11 | return !isDevMode() 12 | } 13 | -------------------------------------------------------------------------------- /apps/backend/src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | 3 | export * from './authMiddleware' 4 | export * from './beProjectMemberMiddleware' 5 | 6 | export const testMiddleware = ( 7 | req: Request, 8 | res: Response, 9 | next: NextFunction 10 | ) => { 11 | console.log('next herer') 12 | next() 13 | } 14 | 15 | export const test2Middleware = ( 16 | req: Request, 17 | res: Response, 18 | next: NextFunction 19 | ) => { 20 | console.log('next herer 2') 21 | next() 22 | } 23 | -------------------------------------------------------------------------------- /apps/backend/src/providers/auth/BaseAuthProvider.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseAuthProvider { 2 | protected email: string 3 | protected password: string 4 | protected user: { 5 | id: string 6 | email: string 7 | name: string 8 | photo: string 9 | } 10 | 11 | constructor({ email, password }: { email: string; password: string }) { 12 | this.email = email 13 | this.password = password 14 | } 15 | 16 | abstract verify(): Promise 17 | getUser() { 18 | return this.user 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/backend/src/providers/storage/IStorageProvider.ts: -------------------------------------------------------------------------------- 1 | export interface IStorageProvider { 2 | createPresignedUrlWithClient(name: string, type: string): Promise; 3 | getObjectURL(name: string): Promise; 4 | getObject(name: string): Promise; 5 | deleteObject(name: string): Promise; 6 | randomObjectKeyName(name: string): string; 7 | } 8 | -------------------------------------------------------------------------------- /apps/backend/src/queues/BaseJob.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseJob { 2 | name: string 3 | abstract implement(data: unknown): Promise 4 | } 5 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Common/FieldSortableJob.ts: -------------------------------------------------------------------------------- 1 | import { FieldService } from '../../services/field' 2 | import { BaseJob } from '../BaseJob' 3 | 4 | interface ISortatbleFieldData { 5 | id: string 6 | order: number 7 | } 8 | 9 | export class FieldSortableJob extends BaseJob { 10 | name = 'fieldSortable' 11 | fieldService: FieldService 12 | constructor() { 13 | super() 14 | this.fieldService = new FieldService() 15 | } 16 | async implement(data: ISortatbleFieldData[]) { 17 | console.log('implement field sortable') 18 | await this.fieldService.sortable(data) 19 | console.log('finish implementation') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Common/index.ts: -------------------------------------------------------------------------------- 1 | import { FieldSortableJob } from './FieldSortableJob' 2 | import { BaseQueue } from '../BaseQueue' 3 | 4 | export class CommonQueue extends BaseQueue { 5 | constructor() { 6 | super() 7 | this.queueName = 'Common' 8 | this.jobs = [new FieldSortableJob()] 9 | 10 | this.run() 11 | } 12 | } 13 | 14 | let instance: CommonQueue = null 15 | 16 | export const getCommonQueueInstance = () => { 17 | if (!instance) { 18 | instance = new CommonQueue() 19 | } 20 | 21 | return instance 22 | } 23 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Stats/DoneTasksByMemberJob.ts: -------------------------------------------------------------------------------- 1 | 2 | import StatsDoneTaskService from '../../services/stats/done.tasks.service' 3 | import { BaseJob } from '../BaseJob' 4 | 5 | 6 | export class DoneTasksByMemberJob extends BaseJob { 7 | name = 'doneTasksByMember' 8 | service: StatsDoneTaskService 9 | constructor() { 10 | super() 11 | this.service = new StatsDoneTaskService() 12 | } 13 | async implement(projectId: string) { 14 | await this.service.implement(projectId) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Stats/UnDoneTasksByProjectJob.ts: -------------------------------------------------------------------------------- 1 | 2 | import StatsUnDoneTaskService from '../../services/stats/undone.tasks.service' 3 | import { BaseJob } from '../BaseJob' 4 | 5 | 6 | export class UnDoneTasksByProjectJob extends BaseJob { 7 | name = 'unDoneTasksByProject' 8 | service: StatsUnDoneTaskService 9 | constructor() { 10 | super() 11 | this.service = new StatsUnDoneTaskService() 12 | } 13 | async implement(projectId: string) { 14 | await this.service.implement(projectId) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Stats/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseQueue } from '../BaseQueue' 2 | import { DoneTasksByMemberJob } from './DoneTasksByMemberJob' 3 | import { UnDoneTasksByProjectJob } from './UnDoneTasksByProjectJob' 4 | 5 | 6 | export class StatsQueue extends BaseQueue { 7 | constructor() { 8 | super() 9 | this.queueName = 'Stats' 10 | this.jobs = [new DoneTasksByMemberJob(), new UnDoneTasksByProjectJob()] 11 | 12 | this.run() 13 | } 14 | } 15 | 16 | let instance: StatsQueue = null 17 | 18 | export const getStatsQueueInstance = () => { 19 | if (!instance) { 20 | instance = new StatsQueue() 21 | } 22 | 23 | return instance 24 | } 25 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Task/MoveToAnotherBoardJob.ts: -------------------------------------------------------------------------------- 1 | import { TaskReorderService } from '../../services/task/order.service' 2 | import { BaseJob } from '../BaseJob' 3 | 4 | interface IReorderData { 5 | updatedOrder: [string, string][] 6 | projectId: string 7 | } 8 | 9 | export class MoveToAnotherBoardJob extends BaseJob { 10 | name = 'moveToOtherBoard' 11 | reorderService: TaskReorderService 12 | constructor() { 13 | super() 14 | this.reorderService = new TaskReorderService() 15 | } 16 | async implement(data: IReorderData) { 17 | console.log(1) 18 | this.reorderService.implement(data) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Task/ReorderJob.ts: -------------------------------------------------------------------------------- 1 | import { TaskReorderService } from '../../services/task/order.service' 2 | import { BaseJob } from '../BaseJob' 3 | 4 | interface IReorderData { 5 | updatedOrder: [string, string][] 6 | projectId: string 7 | } 8 | 9 | export class ReorderJob extends BaseJob { 10 | name = 'reorder' 11 | reorderService: TaskReorderService 12 | constructor() { 13 | super() 14 | this.reorderService = new TaskReorderService() 15 | } 16 | async implement(data: IReorderData) { 17 | await this.reorderService.implement(data) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/backend/src/queues/Task/index.ts: -------------------------------------------------------------------------------- 1 | import { ReorderJob } from './ReorderJob' 2 | import { BaseQueue } from '../BaseQueue' 3 | 4 | export class TaskQueue extends BaseQueue { 5 | constructor() { 6 | super() 7 | this.queueName = 'Task' 8 | this.jobs = [new ReorderJob()] 9 | 10 | this.run() 11 | } 12 | } 13 | 14 | let instance: TaskQueue = null 15 | 16 | export const getTaskQueueInstance = () => { 17 | if (!instance) { 18 | instance = new TaskQueue() 19 | } 20 | 21 | return instance 22 | } 23 | -------------------------------------------------------------------------------- /apps/backend/src/queues/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Task' 2 | -------------------------------------------------------------------------------- /apps/backend/src/routes/auth/index.controller.ts: -------------------------------------------------------------------------------- 1 | import { BaseController, Post, Controller } from '../../core' 2 | 3 | @Controller('/auth') 4 | export default class AuthenController extends BaseController { 5 | @Post('/sign-in') 6 | async signIn() { 7 | return 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/backend/src/routes/auth/permission.controller.ts: -------------------------------------------------------------------------------- 1 | import { mdMemberBelongToProject } from "@database"; 2 | import { BaseController, Body, Controller, Get, Param, Query } from "../../core"; 3 | 4 | @Controller('/user-permission') 5 | export default class UserPermission extends BaseController { 6 | @Get('/project') 7 | async getProjectPermission(@Query() query) { 8 | console.log(query) 9 | const uid = '649ab9864792890df8449c68' 10 | const { projectId } = query as { projectId: string } 11 | 12 | const result = await mdMemberBelongToProject(uid, projectId) 13 | 14 | return result 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /apps/backend/src/routes/example/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { authMiddleware } from '../../middlewares'; 3 | import { AuthRequest } from '../../types'; 4 | 5 | const router = Router(); 6 | 7 | router.use([authMiddleware]); 8 | 9 | // It means GET:/api/example 10 | router.get('/example', (req: AuthRequest, res) => { 11 | res.json({ status: 200 }); 12 | }); 13 | 14 | // It means POST:/api/example 15 | router.post('/example', (req: AuthRequest, res) => { 16 | console.log('auth user', req.authen); 17 | res.json({ status: 200 }); 18 | }); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /apps/backend/src/routes/project/tag.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { AuthRequest } from '../../types'; 3 | import { mdTagGetByProjectId } from '@database'; 4 | 5 | const router = Router(); 6 | 7 | router.get('/project/tag/:projectId', async (req: AuthRequest, res) => { 8 | const projectId = req.params.projectId; 9 | 10 | mdTagGetByProjectId(projectId) 11 | .then(result => { 12 | console.log('done tag') 13 | res.json({ status: 200 }); 14 | }) 15 | .catch(err => { 16 | console.log(err); 17 | }); 18 | }); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /apps/backend/src/routes/task/reorder.controller.ts: -------------------------------------------------------------------------------- 1 | import { BaseController, Body, Controller, Post } from '../../core' 2 | import { TaskQueue, getTaskQueueInstance } from '../../queues' 3 | 4 | interface IReorderData { 5 | updatedOrder: [string, string][] 6 | projectId: string 7 | } 8 | 9 | @Controller('/task/reorder') 10 | export default class TaskReorderController extends BaseController { 11 | taskQueue: TaskQueue 12 | constructor() { 13 | super() 14 | 15 | this.taskQueue = getTaskQueueInstance() 16 | } 17 | 18 | @Post('') 19 | async reorder(@Body() body: IReorderData) { 20 | console.log('add reorder job') 21 | await this.taskQueue.addJob('reorder', body) 22 | 23 | return 1 24 | 25 | // return result 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/backend/src/services/field/type.ts: -------------------------------------------------------------------------------- 1 | import { Field } from "@prisma/client" 2 | 3 | export type FieldCreate = Omit 4 | export interface FieldFactoryBase { 5 | create(data: FieldCreate): Promise 6 | } 7 | -------------------------------------------------------------------------------- /apps/backend/src/services/grid/builders/boolean.builder.ts: -------------------------------------------------------------------------------- 1 | export function buildBooleanQuery(path: string, operator: string, value: string) { 2 | switch (operator) { 3 | case 'is': 4 | return { [path]: value.toLowerCase() } 5 | 6 | case 'is empty': 7 | return { 8 | $or: [ 9 | { [path]: null }, 10 | { [path]: '' } 11 | ] 12 | } 13 | 14 | case 'is not empty': 15 | return { 16 | [path]: { 17 | $exists: true, 18 | $nin: ['', null] 19 | } 20 | } 21 | 22 | default: 23 | return { [path]: value.toLowerCase() } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/backend/src/services/project.ts: -------------------------------------------------------------------------------- 1 | import { mdProjectGet } from '@database' 2 | import { CKEY, hgetAll, hset } from '../lib/redis' 3 | 4 | export const serviceGetProjectById = async (id: string) => { 5 | const key = [CKEY.PROJECT, id] 6 | const cached = await hgetAll(key) 7 | 8 | if (cached) return cached 9 | 10 | const result = await mdProjectGet(id) 11 | 12 | await hset(key, result) 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /apps/backend/src/services/project/index.service.ts: -------------------------------------------------------------------------------- 1 | import ProjectRepository from "packages/database/src/lib/project.repository"; 2 | 3 | export default class ProjectService { 4 | projectRepo: ProjectRepository 5 | constructor() { 6 | this.projectRepo = new ProjectRepository() 7 | } 8 | async getAllProjectIds() { 9 | const projectIds = await this.projectRepo.getAvailableProjectIds() 10 | 11 | return projectIds 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /apps/backend/src/services/project/setting.report.service.ts: -------------------------------------------------------------------------------- 1 | import { mdProjectGetReportSetting, mdProjectUpdateReportSetting } from "@database" 2 | 3 | export interface IProjectReportSettingBody { 4 | projectId: string, 5 | countMemberTask: boolean 6 | countProjectTask: boolean 7 | } 8 | 9 | export default class ProjectSettingReportService { 10 | async getReportSetting(projectId: string) { 11 | return await mdProjectGetReportSetting(projectId) 12 | } 13 | 14 | async updateReportSetting({ 15 | projectId, 16 | countMemberTask, 17 | countProjectTask }: IProjectReportSettingBody) { 18 | 19 | return await mdProjectUpdateReportSetting({ 20 | projectId, 21 | countMemberTask, 22 | countProjectTask 23 | }) 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/backend/src/services/status.ts: -------------------------------------------------------------------------------- 1 | import { mdTaskStatusGetById } from '@database' 2 | import { CKEY, hgetAll, hset } from '../lib/redis' 3 | 4 | export const serviceGetStatusById = async (id: string) => { 5 | const key = [CKEY.STATUS, id] 6 | const cached = await hgetAll(key) 7 | 8 | if (cached) return cached 9 | 10 | const result = await mdTaskStatusGetById(id) 11 | 12 | await hset(key, result) 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /apps/backend/src/services/todo.counter.ts: -------------------------------------------------------------------------------- 1 | import { CKEY, delCache, getCache, setCache } from '../lib/redis' 2 | 3 | export const updateTodoCounter = (key: string[], counter: number) => { 4 | setCache([CKEY.TODO_COUNTER, ...key], counter) 5 | } 6 | 7 | export const getTodoCounter = async (key: string[]) => { 8 | return await getCache([CKEY.TODO_COUNTER, ...key]) 9 | } 10 | 11 | export const deleteTodoCounter = async (key: string[]) => { 12 | await delCache([CKEY.TODO_COUNTER, ...key]) 13 | } 14 | -------------------------------------------------------------------------------- /apps/backend/src/types.ts: -------------------------------------------------------------------------------- 1 | import { User } from "@prisma/client"; 2 | import { Request } from "express"; 3 | 4 | export type JWTPayload = Pick 5 | export enum JWTType { 6 | USER = 'USER', 7 | APP = 'APP' 8 | } 9 | 10 | export interface AuthRequest extends Request { 11 | authen: JWTPayload & { 12 | type?: JWTType 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node", "express"] 7 | }, 8 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 9 | "include": ["src/**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /apps/backend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { composePlugins, withNx } = require('@nx/webpack'); 2 | 3 | // Nx plugins for webpack. 4 | module.exports = composePlugins(withNx(), (config) => { 5 | // Update the webpack config as needed here. 6 | // e.g. `config.plugins.push(new MyPlugin())` 7 | return config; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/frontend-e2e/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; 3 | 4 | export default defineConfig({ 5 | e2e: nxE2EPreset(__dirname), 6 | }); 7 | -------------------------------------------------------------------------------- /apps/frontend-e2e/src/e2e/app.cy.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('ui-app', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome ui-app'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/frontend-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/frontend-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/frontend-e2e/src/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/frontend-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | export default function ErrorPage() { 3 | return
Nothing here
4 | } 5 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/favorites/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return
Favorite page
3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/meeting/MeetingContainer.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import '@livekit/components-styles' 3 | import MeetingRoomList from './MeetingRoomList' 4 | 5 | export default function MeetingContainer() { 6 | return
7 | 8 |
9 | } 10 | 11 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/meeting/[roomId]/page.tsx: -------------------------------------------------------------------------------- 1 | import MeetingRoom from "@/features/MeetingRoom"; 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/meeting/page.tsx: -------------------------------------------------------------------------------- 1 | import MeetingContainer from './MeetingContainer' 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/my-works/page.tsx: -------------------------------------------------------------------------------- 1 | import Mywork from '@/features/MyWorks' 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return
Page organization detail
3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/ProjectContentLoading.tsx: -------------------------------------------------------------------------------- 1 | import { Loading } from '@ui-components' 2 | 3 | export default function ProjectContentLoading() { 4 | return ( 5 |
8 | 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/ProjectName.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'next/navigation' 2 | 3 | export default function ProjectName() { 4 | const { orgID } = useParams() 5 | 6 | return
7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/TaskList.tsx: -------------------------------------------------------------------------------- 1 | import ListMode from './views/ListMode' 2 | import TaskFilter from '@/features/TaskFilter' 3 | 4 | export default function TaskList() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/TaskSearchAdvanced.tsx: -------------------------------------------------------------------------------- 1 | export default function TaskSearchAdvanced() { 2 | return
3 | 4 |
; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/board/index.tsx: -------------------------------------------------------------------------------- 1 | import TaskFilter from '@/features/TaskFilter' 2 | import BoardContainer from './BoardContainer' 3 | import TaskMultipleActions from '@/features/TaskMultipleActions' 4 | 5 | export default function BoardRoot() { 6 | return ( 7 |
8 | 9 | 10 | 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/calendar/useCalendarAction.ts: -------------------------------------------------------------------------------- 1 | import { DropResult } from 'react-beautiful-dnd' 2 | import { useTaskUpdate } from '../views/useTaskUpdate' 3 | 4 | export default function useCalendarAction() { 5 | const { updateTaskData } = useTaskUpdate() 6 | const onDragEnd = (result: DropResult) => { 7 | const dropId = result.destination?.droppableId 8 | const taskId = result.draggableId 9 | if (!dropId) return 10 | 11 | const newDate = new Date(dropId) 12 | newDate.setHours(23) 13 | 14 | console.log(dropId) 15 | console.log(newDate) 16 | 17 | updateTaskData({ 18 | id: taskId, 19 | dueDate: newDate 20 | }) 21 | } 22 | 23 | return { 24 | onDragEnd 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { TaskFilterProvider } from '@/features/TaskFilter/context' 2 | import ProjectContainer from '@/features/ProjectContainer' 3 | import './style.css' 4 | 5 | export const revalidate = 1800 // 30 minutes 6 | export default function Project() { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/settings/ProjectTags.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { useParams, useRouter } from "next/navigation" 3 | 4 | 5 | export default function ProjectTags () { 6 | const params = useParams() 7 | console.log(params) 8 | return
Project Status
9 | } -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/settings/status/index.tsx: -------------------------------------------------------------------------------- 1 | import { StatusDnDContainer } from './StatusDnDContainer' 2 | import { DndProvider } from 'react-dnd' 3 | import { HTML5Backend } from 'react-dnd-html5-backend' 4 | import StatusCreate from './StatusCreate' 5 | 6 | export const ProjectStatus = () => { 7 | return ( 8 |
9 |
10 | 11 | 12 | 13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/settings/status/type.ts: -------------------------------------------------------------------------------- 1 | export const colors = [ 2 | '#d9d9d9', // Whitesmoke 3 | '#FF5733', // Red 4 | '#4CAF50', // Green 5 | '#4286f4', // Blue 6 | '#FFD700', // Yellow 7 | '#FFA500', // Orange 8 | '#9B59B6', // Purple 9 | '#FFC0CB', // Pink 10 | '#A52A2A', // Brown 11 | '#808080', // Gray 12 | '#000000', // Black 13 | '#00FFFF', // Cyan 14 | '#FF00FF', // Magenta 15 | '#00FF00', // Lime 16 | '#008080', // Teal 17 | '#808000' // Olive 18 | ] 19 | 20 | export const DEFAULT_COLOR = colors[0] 21 | 22 | export const KEY = 'TASK_STATUS' 23 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/settings/style.css: -------------------------------------------------------------------------------- 1 | .setting-container { 2 | @apply bg-white rounded-lg shadow-lg shadow-indigo-100; 3 | @apply dark:bg-gray-900 dark:shadow-gray-900; 4 | } 5 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/views/ListCell.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export default function ListCell({ 4 | width, 5 | align, 6 | className, 7 | children 8 | }: { 9 | width?: number 10 | align?: 'center' | 'left' | 'right' 11 | className?: string 12 | children: ReactNode 13 | }) { 14 | const classes: string[] = ['list-cell'] 15 | align && classes.push(align) 16 | className && classes.push(className) 17 | return ( 18 |
19 | {children} 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/views/TaskAssignee.css: -------------------------------------------------------------------------------- 1 | .task-assignee.no-name .selected-item-container { 2 | gap: 0 !important; 3 | } 4 | 5 | .task-assignee.no-name .more { 6 | height: 1rem; 7 | width: 1rem; 8 | @apply text-xs; 9 | } 10 | 11 | .task-assignee.no-name .selected-member:not(:first-child) { 12 | margin-left: -4px; 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/views/TaskPoint.tsx: -------------------------------------------------------------------------------- 1 | import PointSelect from '../../../../_components/PointSelect' 2 | import { useTaskUpdate } from './useTaskUpdate' 3 | 4 | export default function TaskPoint({ 5 | taskId, 6 | value, 7 | className 8 | }: { 9 | taskId: string 10 | value: number | null 11 | className?: string 12 | }) { 13 | const { updateTaskData } = useTaskUpdate() 14 | const onUpdate = (point: string) => { 15 | const taskPoint = parseInt(point, 10) 16 | updateTaskData({ 17 | id: taskId, 18 | taskPoint 19 | }) 20 | } 21 | return ( 22 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/[projectId]/views/TaskTypeCell.tsx: -------------------------------------------------------------------------------- 1 | import TaskTypeSelect from '@/components/TaskTypeSelect' 2 | import { TaskType } from '@prisma/client' 3 | import { useTaskUpdate } from './useTaskUpdate' 4 | 5 | export default function TaskTypeCell({ 6 | taskId, 7 | type 8 | }: { 9 | taskId: string 10 | type: TaskType | null 11 | }) { 12 | const { updateTaskData } = useTaskUpdate() 13 | 14 | return ( 15 | { 19 | updateTaskData({ 20 | id: taskId, 21 | type: val 22 | }) 23 | }} 24 | value={type || TaskType.TASK} 25 | /> 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/project/page.tsx: -------------------------------------------------------------------------------- 1 | import ProjectList from '@/features/Project/List' 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/report/page.tsx: -------------------------------------------------------------------------------- 1 | import Report from '@/features/Report' 2 | import { Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Report', 6 | description: 'Where to display report by time' 7 | } 8 | 9 | export default function Page() { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/about/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | import SettingAboutContent from '@/features/SettingAbout' 3 | 4 | export default function SettingAbout() { 5 | return ( 6 |
7 | {'Settings > About'} 8 | 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/apps/page.tsx: -------------------------------------------------------------------------------- 1 | import SettingAppsContainer from "@/features/SettingApps"; 2 | 3 | export default function SettingApp() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/export-import/page.tsx: -------------------------------------------------------------------------------- 1 | import SettingExport from '@/features/SettingExport' 2 | import { SettingFilterProvider } from '@/features/SettingExport/context' 3 | 4 | export default function ExportImportPage() { 5 | return ( 6 | <> 7 | {`Setting > Export`} 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import SettingTabLayout from './SettingTab' 3 | 4 | export default function SettingLayout({ children }: { children: ReactNode }) { 5 | return ( 6 |
7 |
8 |

Settings

9 | 10 | 11 |
12 | 13 |
16 | {children} 17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return
Setting page not found
3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | export default function Page() { 3 | return
hehe
4 | } 5 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/setting/people/page.tsx: -------------------------------------------------------------------------------- 1 | import SettingPeopleContent from '@/features/SettingPeople' 2 | 3 | export default function SettingPeople() { 4 | return ( 5 |
6 | {'Settings > People'} 7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/app/[orgName]/test/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return
3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/AnimateView.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from 'framer-motion' 2 | import { useId } from 'react' 3 | 4 | export default function AnimateView({ 5 | visible, 6 | children 7 | }: { 8 | visible: boolean 9 | children: React.ReactNode 10 | }) { 11 | const id = useId() 12 | return ( 13 | 14 | {visible ? ( 15 | 20 | {children} 21 | 22 | ) : null} 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/ChartColumn/column.css: -------------------------------------------------------------------------------- 1 | .chart-colum-name > div { 2 | @apply relative; 3 | } 4 | 5 | .chart-column-name .selected-member-name { 6 | @apply absolute hidden hover:block top-10 left-1; 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/Dnd/Droppable.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { useDroppable } from '@dnd-kit/core' 3 | 4 | interface IDroppableProps { 5 | className?: string 6 | type?: string 7 | children: ReactNode 8 | droppableId: string 9 | } 10 | export default function Droppable({ 11 | children, 12 | droppableId, 13 | type, 14 | className 15 | }: IDroppableProps) { 16 | const { isOver, setNodeRef } = useDroppable({ 17 | id: droppableId, 18 | data: { type } 19 | }) 20 | 21 | const classes = [className || ''] 22 | isOver && classes.push('is-dragging-over') 23 | 24 | return ( 25 |
26 | {children} 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/Dnd/index.tsx: -------------------------------------------------------------------------------- 1 | import { DndContext, DragOverlay } from '@dnd-kit/core' 2 | import Droppable from './Droppable' 3 | import Draggable from './Draggable' 4 | 5 | export const DragNDropContext = DndContext 6 | export const DropItem = Droppable 7 | export const DragItem = Draggable 8 | export const DragShadow = DragOverlay 9 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/FileKits/FileDesc.tsx: -------------------------------------------------------------------------------- 1 | import { AiOutlineCloudUpload } from 'react-icons/ai' 2 | 3 | export default function FileDesc({ inputId }: { inputId: string }) { 4 | return ( 5 |
6 | 7 |

8 | 13 | , drag n drop Or paste your image here 14 |

15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/FileKits/FileGet.tsx: -------------------------------------------------------------------------------- 1 | import useFileGet from './useFileGet' 2 | 3 | export default function FileGet({ fileIds }: { fileIds: string[] }) { 4 | useFileGet(fileIds) 5 | return <> 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/FileKits/FileListWrapper.tsx: -------------------------------------------------------------------------------- 1 | import FileList from './FileList' 2 | import { useFileKitContext } from './context' 3 | 4 | export default function FileListWrapper() { 5 | const { previewFiles } = useFileKitContext() 6 | 7 | if (!previewFiles.length) return null 8 | 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/FileKits/carousel.css: -------------------------------------------------------------------------------- 1 | .carousel-btn { 2 | @apply w-14 h-14 p-3 rounded-full bg-white/10 cursor-pointer hover:text-gray-400 text-white; 3 | @apply absolute top-1/2; 4 | 5 | &.left { 6 | @apply left-[30px]; 7 | } 8 | 9 | &.right { 10 | @apply right-[30px]; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/IconSelect/style.css: -------------------------------------------------------------------------------- 1 | .icon-select-content { 2 | @apply grid grid-cols-11; 3 | @apply px-2 4 | } 5 | 6 | .icon-select-trigger .btn { 7 | @apply border-none p-0 transition-all; 8 | 9 | &:active{ 10 | @apply scale-95; 11 | } 12 | } 13 | 14 | .icon-select-content .dropdown-item { 15 | min-width: 0px; 16 | 17 | svg { 18 | @apply w-[20px] h-[20px] 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/ListBox/ListBoxBody.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export default function ListBoxBody({ children }: { children: ReactNode }) { 4 | return
{children}
5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/ListBox/ListBoxHeader.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export default function ListBoxHeader({ children }: { children: ReactNode }) { 4 | return ( 5 |
6 | {children} 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/ListBox/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import ListBoxHeader from './ListBoxHeader' 3 | import './style.css' 4 | import ListBoxBody from './ListBoxBody' 5 | 6 | export default function ListBox({ 7 | children, 8 | className 9 | }: { 10 | children: ReactNode 11 | className?: string 12 | }) { 13 | return
{children}
14 | } 15 | 16 | ListBox.Header = ListBoxHeader 17 | ListBox.Body = ListBoxBody 18 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/ListBox/style.css: -------------------------------------------------------------------------------- 1 | .list-box { 2 | @apply bg-white dark:bg-gray-900 rounded-md border dark:border-gray-800 relative shadow-lg shadow-indigo-100 dark:shadow-gray-900; 3 | } 4 | .list-box-actions { 5 | @apply flex items-center gap-1; 6 | } 7 | 8 | .list-box-actions .btn { 9 | @apply px-1 py-1; 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/ListPreset/style.css: -------------------------------------------------------------------------------- 1 | .no-clear-icon .select-clear-icon { 2 | @apply hidden; 3 | } 4 | .no-clear-icon .select-button { 5 | @apply pr-8 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/MemberName.tsx: -------------------------------------------------------------------------------- 1 | import { useMemberStore } from '../../store/member' 2 | import { useMemo } from 'react' 3 | 4 | export default function MemberName({ 5 | uid, 6 | className 7 | }: { 8 | uid: string | null 9 | className?: string 10 | }) { 11 | const { members } = useMemberStore(state => state) 12 | 13 | 14 | const name = useMemo(() => { 15 | if (members.length && uid) { 16 | const matchedUser = members.find(m => m.id === uid) 17 | return matchedUser?.name || '' 18 | } else { 19 | return '' 20 | } 21 | }, [members, uid]) 22 | 23 | 24 | 25 | return ( 26 | {name || 'None'} 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/MemberPicker/style.css: -------------------------------------------------------------------------------- 1 | .member-picker .selected-member-item { 2 | @apply p-0 border-none bg-transparent; 3 | } 4 | 5 | .member-picker .avatar-root { 6 | @apply h-5 w-5; 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/TaskTypeSelect/icon.css: -------------------------------------------------------------------------------- 1 | .task-type-icon { 2 | @apply inline-flex shrink-0 rounded p-[1px] bg-gray-100 dark:bg-gray-800 border dark:border-gray-700; 3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/app/_components/Time.tsx: -------------------------------------------------------------------------------- 1 | import { dateFormat } from '@namviek/core' 2 | import { Tooltip } from '@ui-components' 3 | 4 | export default function Time({ date }: { date: Date }) { 5 | const time = dateFormat(date, 'Pp') 6 | const hour = dateFormat(date, 'HH:mm:ss aa') 7 | 8 | return ( 9 | 10 | {hour} 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/app/_features/Activity/ActivityContainer.tsx: -------------------------------------------------------------------------------- 1 | import { HiOutlineFingerPrint, HiOutlineMap } from 'react-icons/hi2' 2 | import ActivityCommentEditor from './ActivityCommentEditor' 3 | import ActivityList from './ActivityList' 4 | import { useActivityContext } from './context' 5 | import { useEffect } from 'react' 6 | 7 | export default function ActivityContainer({ taskId }: { taskId: string }) { 8 | const { setTaskId } = useActivityContext() 9 | useEffect(() => setTaskId(taskId), [taskId, setTaskId]) 10 | return ( 11 |
12 | 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /apps/frontend/app/_features/Activity/index.tsx: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import { ActivityContextProvider } from './context' 3 | import ActivityContainer from './ActivityContainer' 4 | 5 | export default function Activity({ taskId }: { taskId: string }) { 6 | 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/app/_features/Automation/style.css: -------------------------------------------------------------------------------- 1 | .automation-container { 2 | @apply grid grid-cols-2 divide-x dark:divide-gray-700 relative; 3 | } 4 | 5 | .automation-container .when { 6 | @apply pr-[70px]; 7 | } 8 | .automation-container .then { 9 | @apply pl-[70px]; 10 | } 11 | 12 | .automation-container .when-select .select-button { 13 | @apply p-0 border-none shadow-none text-xs lowercase; 14 | } 15 | 16 | .automation-value { 17 | @apply border rounded-md px-1; 18 | @apply dark:border-gray-700; 19 | } 20 | 21 | .automation-del { 22 | @apply p-1 w-6 h-6 rounded-md border text-gray-500 dark:border-gray-700; 23 | @apply cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800; 24 | } 25 | -------------------------------------------------------------------------------- /apps/frontend/app/_features/AutomationScheduler/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import AutomateSchedulerCreate from './AutomateSchedulerCreate' 3 | import AutomateSchedulerList from './AutomateSchedulerList' 4 | import './style.css' 5 | export default function AutomationScheduler() { 6 | const [visible, setVisible] = useState(false) 7 | const openCreateForm = () => { 8 | setVisible(true) 9 | } 10 | const backToList = () => { 11 | setVisible(false) 12 | } 13 | return
14 | {!visible ? : null} 15 | {visible ? : null} 16 |
17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/app/_features/AutomationScheduler/style.css: -------------------------------------------------------------------------------- 1 | .box-2 { 2 | @apply flex items-center justify-between bg-gray-100 dark:bg-gray-800/30 dark:border-gray-700/50 rounded-md border py-2 px-4; 3 | @apply text-gray-600 dark:text-gray-300; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/app/_features/CustomField/CreateField.tsx: -------------------------------------------------------------------------------- 1 | import Button from "packages/ui-components/src/components/Button"; 2 | import { HiOutlinePlus } from "react-icons/hi2"; 3 | import { useCustomFieldStore } from "./store"; 4 | 5 | export default function CreateField() { 6 | const setVisible = useCustomFieldStore(state => state.setVisible) 7 | const onClick = () => { 8 | setVisible(true) 9 | } 10 | 11 | return <> 12 | 10 | 11 | 12 | 13 |
heheheheheh
14 |
15 |
16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/ui-components/src/components/Popover/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hudy9x/namviek/65f4f66cc11bdbcb24173f6efacf424408c15b4e/packages/ui-components/src/components/Popover/style.css -------------------------------------------------------------------------------- /packages/ui-components/src/components/Tab/style.css: -------------------------------------------------------------------------------- 1 | .tab-root { 2 | @apply flex flex-col; 3 | } 4 | 5 | .tab-list { 6 | @apply flex border-b dark:border-gray-700 items-center mb-2; 7 | } 8 | 9 | .tab-trigger { 10 | @apply flex items-center justify-center select-none; 11 | @apply py-3 px-3 text-sm; 12 | @apply hover:bg-gray-50 dark:hover:bg-gray-800; 13 | } 14 | 15 | .tab-trigger[data-state='active'] { 16 | @apply relative; 17 | } 18 | 19 | .tab-trigger[data-state='active']:after { 20 | height: 1px; 21 | content: ''; 22 | @apply absolute bottom-0 left-0 w-full bg-indigo-400; 23 | } 24 | 25 | .tab-content { 26 | @apply w-full; 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui-components/src/components/Timeline/type.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export interface ITimelineItem { 4 | id: string 5 | title: string 6 | start: Date 7 | end: Date 8 | } 9 | 10 | export interface ITimelineProps { 11 | height?: string 12 | year: number 13 | month: number 14 | children: (data: ITimelineItem) => ReactNode 15 | items: ITimelineItem[] 16 | onChange?: (data: { id: string; start: Date; end: Date }) => void 17 | } 18 | -------------------------------------------------------------------------------- /packages/ui-components/src/components/utils.ts: -------------------------------------------------------------------------------- 1 | export const generatePages = (n: number, start?: number) => { 2 | let startIndex = start; 3 | return new Array(n).fill(1).map((val, id) => { 4 | if (startIndex && startIndex > 0) { 5 | startIndex += 1; 6 | return { 7 | page: startIndex, 8 | }; 9 | } 10 | 11 | return { page: id + 1 }; 12 | }); 13 | }; 14 | 15 | export const randomId = function (length = 6) { 16 | return Math.random() 17 | .toString(36) 18 | .substring(2, length + 2); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ui-components/src/server.ts: -------------------------------------------------------------------------------- 1 | // Use this file to export React server components 2 | export * from './lib/hello-server'; 3 | -------------------------------------------------------------------------------- /packages/ui-components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "allowJs": false, 5 | "esModuleInterop": false, 6 | "allowSyntheticDefaultImports": true, 7 | "strict": true 8 | }, 9 | "files": [], 10 | "include": [], 11 | "references": [ 12 | { 13 | "path": "./tsconfig.lib.json" 14 | }, 15 | { 16 | "path": "./tsconfig.spec.json" 17 | } 18 | ], 19 | "extends": "../../tsconfig.base.json" 20 | } 21 | -------------------------------------------------------------------------------- /packages/ui-components/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../node_modules/@nx/react/typings/cssmodule.d.ts", 9 | "../../node_modules/@nx/next/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "jest.config.ts", 13 | "src/**/*.spec.ts", 14 | "src/**/*.test.ts", 15 | "src/**/*.spec.tsx", 16 | "src/**/*.test.tsx", 17 | "src/**/*.spec.js", 18 | "src/**/*.test.js", 19 | "src/**/*.spec.jsx", 20 | "src/**/*.test.jsx" 21 | ], 22 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/ui-components/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.test.tsx", 13 | "src/**/*.spec.tsx", 14 | "src/**/*.test.js", 15 | "src/**/*.spec.js", 16 | "src/**/*.test.jsx", 17 | "src/**/*.spec.jsx", 18 | "src/**/*.d.ts" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------