├── frontend ├── .husky │ └── pre-commit ├── src │ ├── component │ │ ├── Sidebar │ │ │ └── index.tsx │ │ ├── Clock │ │ │ ├── index.tsx │ │ │ └── Clock.tsx │ │ ├── DropBox │ │ │ ├── index.ts │ │ │ └── DropBox.tsx │ │ ├── Main │ │ │ ├── index.tsx │ │ │ └── Main.tsx │ │ ├── Layout │ │ │ ├── index.tsx │ │ │ └── Layout.tsx │ │ ├── Router │ │ │ └── index.tsx │ │ ├── TodoCard │ │ │ └── index.tsx │ │ ├── UserBox │ │ │ ├── index.tsx │ │ │ ├── schema.ts │ │ │ └── DialogRemoveAvatar.tsx │ │ ├── Column │ │ │ └── index.tsx │ │ ├── SubDrawer │ │ │ ├── index.tsx │ │ │ └── SubDrawer.tsx │ │ ├── UserAvatar │ │ │ ├── index.tsx │ │ │ └── UserAvatar.tsx │ │ ├── GroupUserCard │ │ │ ├── index.tsx │ │ │ └── RemoveGroupDialog.tsx │ │ ├── PasswordField │ │ │ ├── index.tsx │ │ │ └── PasswordField.tsx │ │ ├── ProjectAvatar │ │ │ ├── index.tsx │ │ │ └── ProjectAvatar.tsx │ │ ├── ProjectCard │ │ │ └── index.tsx │ │ ├── ProjectMenu │ │ │ ├── index.tsx │ │ │ └── ProjectMenu.tsx │ │ ├── SearchField │ │ │ ├── index.tsx │ │ │ └── SearchField.tsx │ │ ├── NotificationBox │ │ │ └── index.tsx │ │ ├── ProjectTaskCard │ │ │ └── index.tsx │ │ ├── GroupProjectCard │ │ │ └── index.tsx │ │ ├── GroupProjectMenu │ │ │ ├── index.tsx │ │ │ └── GroupProjectMenu.tsx │ │ ├── ProjectDeleteDialog │ │ │ └── index.tsx │ │ ├── GroupProjectTaskCard │ │ │ └── index.tsx │ │ └── InfiniteScroll │ │ │ └── InfiniteScroll.tsx │ ├── Theme │ │ ├── index.tsx │ │ ├── Animations.ts │ │ └── Color.ts │ ├── View │ │ ├── HomeView │ │ │ ├── index.tsx │ │ │ └── HomeView.tsx │ │ ├── GroupView │ │ │ ├── index.tsx │ │ │ ├── GroupProjectTaskView │ │ │ │ ├── index.tsx │ │ │ │ └── schema.ts │ │ │ ├── GroupProjectView │ │ │ │ ├── UpdateGroup │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── CreateForm.tsx │ │ │ │ ├── schema.ts │ │ │ │ ├── ProjectTab.tsx │ │ │ │ └── UsersTab.tsx │ │ │ └── GroupView.tsx │ │ ├── LoginView │ │ │ ├── index.tsx │ │ │ ├── schema.ts │ │ │ └── LoginForm.tsx │ │ ├── LogoutView │ │ │ ├── index.tsx │ │ │ └── LogoutView.tsx │ │ ├── NotesView │ │ │ ├── index.tsx │ │ │ ├── schema.ts │ │ │ ├── NoNoteView.tsx │ │ │ ├── DrawerLink.tsx │ │ │ ├── NotesView.tsx │ │ │ └── DialogDelete.tsx │ │ ├── CalendarView │ │ │ ├── index.tsx │ │ │ ├── schema.ts │ │ │ ├── MonthView │ │ │ │ └── EventChip.tsx │ │ │ ├── WeekView │ │ │ │ └── WeekEventChip.tsx │ │ │ └── DialogCreate.tsx │ │ ├── DashboardView │ │ │ ├── index.tsx │ │ │ ├── Sections │ │ │ │ └── TodoSection.tsx │ │ │ └── DashboardView.tsx │ │ ├── ProjectTask │ │ │ ├── index.tsx │ │ │ └── schema.ts │ │ ├── RegisterView │ │ │ ├── index.tsx │ │ │ ├── PasswordRenew │ │ │ │ ├── index.tsx │ │ │ │ ├── schema.ts │ │ │ │ └── EmailSection.tsx │ │ │ ├── schema.ts │ │ │ └── RegisterView.tsx │ │ ├── SettingsView │ │ │ ├── index.tsx │ │ │ └── Section │ │ │ │ ├── index.tsx │ │ │ │ ├── LanguageSection.tsx │ │ │ │ ├── RouteSection.tsx │ │ │ │ ├── ColorSection.tsx │ │ │ │ ├── ReminderSection.tsx │ │ │ │ └── ThemeSection.tsx │ │ ├── NoConnectionView │ │ │ ├── index.tsx │ │ │ └── NoConnectionView.tsx │ │ ├── Errors │ │ │ ├── index.tsx │ │ │ ├── NotFoundView.tsx │ │ │ └── ErrorView.tsx │ │ ├── TodoView │ │ │ ├── TodoTask │ │ │ │ ├── schema.ts │ │ │ │ └── CreateForm.tsx │ │ │ ├── schema.ts │ │ │ ├── NoTodoView.tsx │ │ │ ├── TodoPreview.tsx │ │ │ ├── TodoView.tsx │ │ │ ├── DialogDelete.tsx │ │ │ └── TodoLink.tsx │ │ ├── LoadingView │ │ │ └── LoadingView.tsx │ │ └── ProjectView │ │ │ ├── schema.ts │ │ │ └── ProjectView.tsx │ ├── vite-env.d.ts │ ├── assets │ │ └── Logo.png │ ├── config │ │ └── constants.ts │ ├── api │ │ ├── pagination.ts │ │ ├── Image │ │ │ ├── api.ts │ │ │ └── query.ts │ │ ├── Settings │ │ │ ├── api.ts │ │ │ └── query.ts │ │ ├── Todos │ │ │ ├── api.ts │ │ │ └── query.ts │ │ ├── Notifications │ │ │ ├── query.ts │ │ │ └── api.ts │ │ ├── Dashboard │ │ │ └── query.ts │ │ ├── Calendar │ │ │ ├── api.ts │ │ │ └── query.ts │ │ ├── Notes │ │ │ └── api.ts │ │ ├── User │ │ │ └── api.ts │ │ └── ProjectTodos │ │ │ └── api.ts │ ├── utils │ │ ├── dateTime.ts │ │ ├── debounce.ts │ │ ├── deriveBarColor.ts │ │ ├── deriveProjectStatus.ts │ │ ├── useGroupRoles.ts │ │ ├── useCurrentDate.ts │ │ ├── useDebouncedValue.ts │ │ ├── useCurrentWeek.ts │ │ └── userAvatar.ts │ ├── main.tsx │ └── context │ │ ├── PdfContext.tsx │ │ ├── GroupRole.tsx │ │ ├── CalendarContext.tsx │ │ └── AuthContext.tsx ├── src-tauri │ ├── build.rs │ ├── icons │ │ ├── 32x32.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── 128x128.png │ │ ├── zenith.png │ │ ├── 128x128@2x.png │ │ ├── StoreLogo.png │ │ ├── Square30x30Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Square89x89Logo.png │ │ ├── Square107x107Logo.png │ │ ├── Square142x142Logo.png │ │ ├── Square150x150Logo.png │ │ ├── Square284x284Logo.png │ │ └── Square310x310Logo.png │ ├── .gitignore │ ├── src │ │ └── main.rs │ ├── Cargo.toml │ └── tauri.conf.json ├── icon.ico ├── .env.example ├── .vscode │ └── extensions.json ├── .prettierrc ├── .eslint.config.js ├── tsconfig.node.json ├── lingui.config.ts ├── .gitignore ├── README.md ├── index.html ├── tsconfig.json ├── .eslintrc.json ├── vite.config.ts └── public │ └── vite.svg ├── assets ├── Zenith.png ├── notes.png ├── todo.png ├── calendar.png ├── projects.png ├── register.png ├── settings.png └── projecttask.png └── backend ├── backend ├── backend.http ├── Dto │ ├── Groups │ │ ├── LeaveGroupDto.cs │ │ ├── ChangeRoleDto.cs │ │ ├── GroupEditDto.cs │ │ ├── GroupByIdDto.cs │ │ └── GroupUsersDto.cs │ ├── Todos │ │ ├── ToggleTodoDto.cs │ │ ├── AddTodoDto.cs │ │ └── TodoDto.cs │ ├── Users │ │ ├── TokenDto.cs │ │ ├── ForgotPasswordDto.cs │ │ ├── LoginUserDto.cs │ │ ├── ResetPasswordDto.cs │ │ ├── UserDto.cs │ │ ├── UpdateUserDto.cs │ │ └── RegisterUserDto.cs │ ├── Images │ │ └── AddImageDto.cs │ ├── Notes │ │ ├── EditNoteDto.cs │ │ └── AllNotesDto.cs │ ├── CalendarEvents │ │ ├── EventPaginationDto.cs │ │ ├── CalendarEventDto.cs │ │ └── AllCalendarEventsDto.cs │ ├── ProjectTasks │ │ ├── ProjectTaskStatusDto.cs │ │ ├── ProjectTaskDto.cs │ │ ├── AddProjectTaskDto.cs │ │ └── ProjectTaskShortDto.cs │ ├── Token │ │ └── AccessTokenDto.cs │ ├── Dashboard │ │ ├── DashboardNoteDto.cs │ │ ├── DashboardTodoDto.cs │ │ ├── DashboardGroupProjectDto.cs │ │ └── DashboardProjectDto.cs │ ├── Pagination │ │ ├── PaginationRequestDto.cs │ │ └── PaginationResponseDto.cs │ ├── ProjectTodo │ │ ├── AddProjectTodoDto.cs │ │ ├── ProjectTodoDto.cs │ │ └── AllProjectsTodoDto.cs │ ├── Projects │ │ ├── AddProjectDto.cs │ │ ├── EditProjectDto.cs │ │ ├── AllProjectsDto.cs │ │ ├── ProjectDto.cs │ │ └── ProjectByStatusDto.cs │ ├── GroupProjects │ │ ├── AddGroupProjectDto.cs │ │ ├── AllGroupProjectsDto.cs │ │ └── GroupProjectByStatusDto.cs │ ├── GroupProjectTasks │ │ ├── AddGroupProjectTaskDto.cs │ │ ├── GroupProjectTaskDto.cs │ │ └── GroupProjectTaskShortDto.cs │ ├── UserPreferences │ │ └── UserPreferencesDto.cs │ ├── Validators │ │ └── RegisterUserDtoValidator.cs │ └── Notifications │ │ └── NotificationDto.cs ├── Enums │ ├── Roles.cs │ ├── GroupRole.cs │ ├── ProjectStatus.cs │ └── ProjectTaskStatus.cs ├── appsettings.Development.json ├── EmailSettings.cs ├── Interface │ ├── IEmailRepository.cs │ ├── IUserContextRepository.cs │ ├── INotificationRepository.cs │ ├── IUserPreferencesRepository.cs │ ├── IKanbanTaskRepository.cs │ ├── ICalendarEventRepository.cs │ ├── ITodoRepository.cs │ ├── IProjectRepository.cs │ ├── IProjectTaskRepository.cs │ ├── IProjectTodoRepository.cs │ ├── IDashboardRepository.cs │ ├── IGroupProjectTaskRepository.cs │ ├── IGroupProjectRepository.cs │ ├── INoteRepository.cs │ ├── IUserRepository.cs │ └── IGroupRepository.cs ├── Exceptions │ └── NotFoundException.cs ├── appsettings.json ├── AuthSettings.cs ├── Models │ ├── GroupRole.cs │ ├── Todo.cs │ ├── Group.cs │ ├── Note.cs │ ├── ProjectTodo.cs │ ├── KanbanTask.cs │ ├── ProjectTask.cs │ ├── UserPreferences.cs │ ├── CalendarEvent.cs │ ├── GroupProjectTask.cs │ ├── Project.cs │ ├── GroupProject.cs │ ├── Notification.cs │ └── User.cs ├── Store │ └── StringGenerator.cs ├── .env.example ├── Migrations │ ├── 20240616124753_avatar_images.cs │ ├── 20240708220343_refresh-token.cs │ └── 20241106171010_calendar_colors.cs ├── Controllers │ ├── TokenController.cs │ ├── SettingsController.cs │ ├── NotificationController.cs │ ├── ImageController.cs │ ├── TodoController.cs │ ├── CalendarEventController.cs │ ├── ProjectController.cs │ └── ProjectTodoController.cs ├── Repository │ ├── UserContextRepository.cs │ └── EmailRepository.cs ├── Properties │ └── launchSettings.json ├── MapProfile.cs └── backend.csproj ├── docker-compose.yaml ├── backend.sln └── dockerfile /frontend/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm test 2 | -------------------------------------------------------------------------------- /frontend/src/component/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/Theme/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Theme"; 2 | -------------------------------------------------------------------------------- /frontend/src/View/HomeView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './HomeView'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/Clock/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Clock'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/DropBox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DropBox' -------------------------------------------------------------------------------- /frontend/src/component/Main/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Main"; 2 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './GroupView'; 2 | -------------------------------------------------------------------------------- /frontend/src/View/LoginView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./LoginView"; 2 | -------------------------------------------------------------------------------- /frontend/src/View/LogoutView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './LogoutView'; 2 | -------------------------------------------------------------------------------- /frontend/src/View/NotesView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./NotesView"; 2 | -------------------------------------------------------------------------------- /frontend/src/component/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Layout'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/Router/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Router"; 2 | -------------------------------------------------------------------------------- /frontend/src/component/TodoCard/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './TodoCard'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/UserBox/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './UserBox'; 2 | -------------------------------------------------------------------------------- /frontend/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/View/CalendarView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './CalendarView'; 2 | -------------------------------------------------------------------------------- /frontend/src/View/DashboardView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './DashboardView'; 2 | -------------------------------------------------------------------------------- /frontend/src/View/ProjectTask/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ProjectTaskView"; 2 | -------------------------------------------------------------------------------- /frontend/src/View/RegisterView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./RegisterView"; 2 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './SettingsView'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/Column/index.tsx: -------------------------------------------------------------------------------- 1 | export { Column } from './Column'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/SubDrawer/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./SubDrawer"; 2 | -------------------------------------------------------------------------------- /frontend/src/component/UserAvatar/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './UserAvatar'; 2 | -------------------------------------------------------------------------------- /assets/Zenith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/Zenith.png -------------------------------------------------------------------------------- /assets/notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/notes.png -------------------------------------------------------------------------------- /assets/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/todo.png -------------------------------------------------------------------------------- /frontend/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/icon.ico -------------------------------------------------------------------------------- /frontend/src/component/GroupUserCard/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './GroupUserCard'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/PasswordField/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './PasswordField'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectAvatar/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './ProjectAvatar'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectCard/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ProjectCard"; 2 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectMenu/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ProjectMenu"; 2 | -------------------------------------------------------------------------------- /frontend/src/component/SearchField/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './SearchField'; 2 | -------------------------------------------------------------------------------- /assets/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/calendar.png -------------------------------------------------------------------------------- /assets/projects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/projects.png -------------------------------------------------------------------------------- /assets/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/register.png -------------------------------------------------------------------------------- /assets/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/settings.png -------------------------------------------------------------------------------- /frontend/src/View/NoConnectionView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './NoConnectionView'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/NotificationBox/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './NotificationBox'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectTaskCard/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ProjectTaskCard"; 2 | -------------------------------------------------------------------------------- /assets/projecttask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/assets/projecttask.png -------------------------------------------------------------------------------- /frontend/src/component/GroupProjectCard/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './GroupProjectCard'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/GroupProjectMenu/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './GroupProjectMenu'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectDeleteDialog/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './ProjectDelete'; 2 | -------------------------------------------------------------------------------- /frontend/src/component/GroupProjectTaskCard/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './GroupProjectTaskCard'; 2 | -------------------------------------------------------------------------------- /frontend/src/View/Errors/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ErrorView"; 2 | export * from "./NotFoundView"; 3 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectTaskView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './GroupProjectTaskView'; 2 | -------------------------------------------------------------------------------- /frontend/src/assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src/assets/Logo.png -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | #VITE_API_URL="https://localhost:7086" 2 | # DOCKER 3 | VITE_API_URL="http://localhost:5000" -------------------------------------------------------------------------------- /frontend/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /frontend/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /frontend/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /frontend/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /frontend/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/zenith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/zenith.png -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /frontend/src/View/RegisterView/PasswordRenew/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './DialogPassword'; 2 | export * from './PasswordForm'; 3 | -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /frontend/src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dewy01/Zenith/HEAD/frontend/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /backend/backend/backend.http: -------------------------------------------------------------------------------- 1 | @backend_HostAddress = http://localhost:5246 2 | 3 | GET {{backend_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /backend/backend/Dto/Groups/LeaveGroupDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Groups 2 | { 3 | public class LeaveGroupDto 4 | { 5 | public int GroupID { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/Dto/Todos/ToggleTodoDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Todos 2 | { 3 | public class ToggleTodoDto 4 | { 5 | public bool isDone { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/TokenDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class TokenDto 4 | { 5 | public required string Token { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/Enums/Roles.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Enums 2 | { 3 | public enum Roles 4 | { 5 | Unverified, 6 | User, 7 | Admin 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "printWidth": 80 8 | } -------------------------------------------------------------------------------- /backend/backend/Dto/Groups/ChangeRoleDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Groups 2 | { 3 | public class ChangeRoleDto 4 | { 5 | public int UserId { get; set; } 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/backend/Dto/Images/AddImageDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Images 2 | { 3 | public class AddImageDto 4 | { 5 | public IFormFile? Image { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/Enums/GroupRole.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Enums 2 | { 3 | public enum GroupRole 4 | { 5 | User, 6 | Moderator, 7 | Admin 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/backend/Dto/Groups/GroupEditDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Groups 2 | { 3 | public class GroupEditDto 4 | { 5 | public required string GroupName { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/Enums/ProjectStatus.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Enums 2 | { 3 | public enum ProjectStatus 4 | { 5 | OnHold, 6 | InProgress, 7 | Done 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/ForgotPasswordDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class ForgotPasswordDto 4 | { 5 | public required string Email { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/EmailSettings.cs: -------------------------------------------------------------------------------- 1 | namespace backend 2 | { 3 | public class EmailSettings 4 | { 5 | public string? Email { get; set; } 6 | public string? Password { get; set; } 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Interface/IEmailRepository.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Interface 2 | { 3 | public interface IEmailRepository 4 | { 5 | Task SendEmailAsync(string email, string subject, string message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/backend/Enums/ProjectTaskStatus.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Enums 2 | { 3 | public enum ProjectTaskStatus 4 | { 5 | Backlog, 6 | InProgress, 7 | ForReview, 8 | Closed 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/View/NotesView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const noteSchema = z.object({ 4 | title: z.string().min(1), 5 | content: z.string(), 6 | }); 7 | 8 | export type noteModel = z.infer; 9 | -------------------------------------------------------------------------------- /frontend/.eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | 6 | export default tseslint.config( 7 | eslint.configs.recommended, 8 | ...tseslint.configs.recommended, 9 | ); -------------------------------------------------------------------------------- /backend/backend/Dto/Notes/EditNoteDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Notes 2 | { 3 | public class EditNoteDto 4 | { 5 | public required string Title { get; set; } 6 | public required string Content { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/LoginUserDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class LoginUserDto 4 | { 5 | public required string Email { get; set; } 6 | public required string Password { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/backend/Dto/CalendarEvents/EventPaginationDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.CalendarEvents 2 | { 3 | public class EventPaginationDto 4 | { 5 | public string? from { get; set; } 6 | public string? to { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectView/UpdateGroup/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const groupSchema = z 4 | .object({ 5 | groupName: z.string().min(1), 6 | }); 7 | 8 | export type groupModel = z.infer; 9 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/TodoTask/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const todoSchema = z.object({ 4 | title: z.string().min(1), 5 | description: z.string().optional(), 6 | }); 7 | 8 | export type todoModel = z.infer; 9 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTasks/ProjectTaskStatusDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.ProjectTasks 4 | { 5 | public class ProjectTaskStatusDto 6 | { 7 | public ProjectTaskStatus Status { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/Token/AccessTokenDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Token 2 | { 3 | public class AccessTokenDto 4 | { 5 | public required string AccessToken { get; set; } 6 | public required string RefreshToken { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/View/LoginView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const loginFormSchema = z.object({ 4 | email: z.string().min(1).email(), 5 | password: z.string().min(1), 6 | }); 7 | 8 | export type loginFormSchema = z.infer; 9 | -------------------------------------------------------------------------------- /backend/backend/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Exceptions 2 | { 3 | public class NotFoundException : Exception 4 | { 5 | public NotFoundException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/backend/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Microsoft.AspNetCore.DataProtection": "None" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/Section/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './ColorEditSection'; 2 | export * from './ColorSection'; 3 | export * from './LanguageSection'; 4 | export * from './ReminderSection'; 5 | export * from './RouteSection'; 6 | export * from './ThemeSection'; 7 | -------------------------------------------------------------------------------- /backend/backend/AuthSettings.cs: -------------------------------------------------------------------------------- 1 | namespace backend 2 | { 3 | public class AuthSettings 4 | { 5 | public string? JwtKey { get; set; } 6 | public int JwtExpireMinutes { get; set; } 7 | public string? JwtIssuer { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Interface/IUserContextRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace backend.Interface 4 | { 5 | public interface IUserContextRepository 6 | { 7 | int? GetUserId { get; } 8 | ClaimsPrincipal User { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const projectSchema = z.object({ 4 | title: z.string().min(1), 5 | description: z.string().optional(), 6 | color: z.string(), 7 | }); 8 | 9 | export type projectModel = z.infer; 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/Dashboard/DashboardNoteDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Dashboard 2 | { 3 | public class DashboardNoteDto 4 | { 5 | public int NoteID { get; set; } 6 | public string Title { get; set; } 7 | public string ShortDescription { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/Todos/AddTodoDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Todos 2 | { 3 | public class AddTodoDto 4 | { 5 | public int ProjectTodoID { get; set; } 6 | public required string Title { get; set; } 7 | public required string Description { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/config/constants.ts: -------------------------------------------------------------------------------- 1 | 2 | const apiUrl = import.meta.env.VITE_API_URL; 3 | 4 | export const BASE_URL = apiUrl ?? "https://localhost:7086"; 5 | 6 | export const SIDEBAR_WIDTH = 58; 7 | 8 | export const REFRESH_TOKEN = 'Refresh-token'; 9 | 10 | export const AUTH_TOKEN = 'Auth-token'; 11 | 12 | -------------------------------------------------------------------------------- /frontend/lingui.config.ts: -------------------------------------------------------------------------------- 1 | import type { LinguiConfig } from "@lingui/conf"; 2 | 3 | const config: LinguiConfig = { 4 | locales: ["en", "pl"], 5 | catalogs: [ 6 | { 7 | path: "/src/locales/{locale}", 8 | include: ["src"], 9 | }, 10 | ], 11 | }; 12 | 13 | export default config; -------------------------------------------------------------------------------- /backend/backend/Dto/Pagination/PaginationRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Pagination 2 | { 3 | public class PaginationRequestDto 4 | { 5 | public int PageNumber { get; set; } = 1; 6 | public int PageSize { get; set; } = 5; 7 | public string? Filter { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTodo/AddProjectTodoDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.ProjectTodo 2 | { 3 | public class AddProjectTodoDto 4 | { 5 | public required string Title { get; set; } 6 | public required string Description { get; set; } 7 | public required string Color { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/ResetPasswordDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class ResetPasswordDto 4 | { 5 | public required string ResetToken { get; set; } 6 | public required string Password { get; set; } 7 | public required string PasswordConfirm { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/backend/Dto/Todos/TodoDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Todos 2 | { 3 | public class TodoDto 4 | { 5 | public int TodoID { get; set; } 6 | public required string Title { get; set; } 7 | public required string Description { get; set; } 8 | public bool IsDone { get; set; } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/UserDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class UserDto 4 | { 5 | public required string Username { get; set; } 6 | public required string Email { get; set; } 7 | public string? GroupName { get; set; } 8 | public string? Image { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/backend/Dto/Dashboard/DashboardTodoDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Dashboard 2 | { 3 | public class DashboardTodoDto 4 | { 5 | 6 | public int CompletionPercentage { get; set; } 7 | public int TotalTodos { get; set; } 8 | public int CompletedTodos { get; set; } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/backend/Dto/Groups/GroupByIdDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Models; 2 | 3 | namespace backend.Dto.Groups 4 | { 5 | public class GroupByIdDto 6 | { 7 | public int GroupID { get; set; } 8 | public required string GroupName { get; set; } 9 | public virtual List? Users { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/api/pagination.ts: -------------------------------------------------------------------------------- 1 | export interface PaginationRequest { 2 | pageNumber: number; 3 | pageSize: number; 4 | filter? : string; 5 | } 6 | 7 | export interface PaginationResponse { 8 | items: T[]; 9 | totalItems: number; 10 | totalPages: number; 11 | pageNumber: number; 12 | pageSize: number; 13 | } 14 | -------------------------------------------------------------------------------- /backend/backend/Dto/Notes/AllNotesDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Notes 2 | { 3 | public class AllNotesDto 4 | { 5 | 6 | public int NoteID { get; set; } 7 | public required string Title { get; set; } 8 | public required string Content { get; set; } 9 | public DateTime CreatedAt { get; set; } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/UpdateUserDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class UpdateUserDto 4 | { 5 | public required string Username { get; set; } 6 | public required string Email { get; set; } 7 | public string? OldPassword { get; set; } 8 | public string? Password { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/backend/Models/GroupRole.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class GroupRole 4 | { 5 | public int UserId { get; set; } 6 | public User? User { get; set; } 7 | public int GroupId { get; set; } 8 | public Group? Group { get; set; } 9 | public Enums.GroupRole Role { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/backend/Interface/INotificationRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Notifications; 2 | using backend.Models; 3 | 4 | namespace backend.Interface 5 | { 6 | public interface INotificationRepository 7 | { 8 | Task GetNotifications(); 9 | Task UpdateNotifications(); 10 | Task MarkAsRead(int id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/utils/dateTime.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const formatDate = (inputDate: string | number | Date) => { 4 | const date = new Date(inputDate); 5 | const dateFormat = new Intl.DateTimeFormat('pl-PL', { 6 | dateStyle: 'short', 7 | timeStyle: 'short', 8 | timeZone: 'UTC', 9 | }); 10 | 11 | return dateFormat.format(date); 12 | }; -------------------------------------------------------------------------------- /backend/backend/Interface/IUserPreferencesRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Models; 2 | using backend.Dto.UserPreferences; 3 | 4 | namespace backend.Interface 5 | { 6 | public interface IUserPreferencesRepository 7 | { 8 | Task GetSettings(); 9 | Task UpdateUserPreferences(UserPreferencesDto userPreferences); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | export function debounce( 2 | func: (...args: Params) => any, 3 | timeout: number, 4 | ): (...args: Params) => void { 5 | let timer: NodeJS.Timeout; 6 | return (...args: Params) => { 7 | clearTimeout(timer); 8 | timer = setTimeout(() => { 9 | func(...args); 10 | }, timeout); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /backend/backend/Dto/Users/RegisterUserDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Users 2 | { 3 | public class RegisterUserDto 4 | { 5 | public required string Username { get; set; } 6 | public required string Email { get; set; } 7 | public required string Password { get; set; } 8 | public required string PasswordConfirm { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/src/utils/deriveBarColor.ts: -------------------------------------------------------------------------------- 1 | import { ProjectStatus } from "~/api/Projects/api"; 2 | 3 | export const deriveBarColor = (completion: ProjectStatus) => { 4 | switch (completion) { 5 | case ProjectStatus.OnHold: 6 | return 'inherit'; 7 | case ProjectStatus.Done: 8 | return 'success'; 9 | default: 10 | return 'info'; 11 | } 12 | }; -------------------------------------------------------------------------------- /frontend/src/utils/deriveProjectStatus.ts: -------------------------------------------------------------------------------- 1 | import { ProjectStatus } from "~/api/Projects/api"; 2 | 3 | export const deriveProjectStatus = (status: ProjectStatus) => { 4 | switch (status) { 5 | case ProjectStatus.OnHold: 6 | return 'on Hold'; 7 | case ProjectStatus.Done: 8 | return 'Done'; 9 | default: 10 | return 'in Progress'; 11 | } 12 | }; -------------------------------------------------------------------------------- /backend/backend/Dto/CalendarEvents/CalendarEventDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.CalendarEvents 2 | { 3 | public class CalendarEventDto 4 | { 5 | public required string Title { get; set; } 6 | public required string Description { get; set; } 7 | public required string DateTime { get; set; } 8 | public required string EventColor { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/backend/Dto/Projects/AddProjectDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.Projects 4 | { 5 | public class AddProjectDto 6 | { 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public DateTime Deadline { get; set; } 10 | public ProjectStatus Status { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/backend/Dto/Projects/EditProjectDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.Projects 4 | { 5 | public class EditProjectDto 6 | { 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public DateTime Deadline { get; set; } 10 | public ProjectStatus Status { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/View/LogoutView/LogoutView.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { mutateUserLogout } from '~/api/User/query'; 3 | import { LoadingView } from '../LoadingView/LoadingView'; 4 | 5 | export const LogoutView = () => { 6 | const { mutateAsync } = mutateUserLogout(); 7 | 8 | useEffect(() => { 9 | mutateAsync(); 10 | }, []); 11 | 12 | return ; 13 | }; 14 | -------------------------------------------------------------------------------- /backend/backend/Dto/Pagination/PaginationResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Pagination 2 | { 3 | public class PaginationResponseDto 4 | { 5 | public List? Items { get; set; } 6 | public int TotalItems { get; set; } 7 | public int TotalPages { get; set; } 8 | public int PageNumber { get; set; } 9 | public int PageSize { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/View/LoadingView/LoadingView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, CircularProgress } from '@mui/material'; 2 | 3 | export const LoadingView = () => { 4 | return ( 5 | 11 | 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/View/ProjectView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { ProjectStatus } from '~/api/Projects/api'; 3 | 4 | export const projectSchema = z 5 | .object({ 6 | title: z.string().min(1), 7 | description: z.string().optional(), 8 | deadline: z.date(), 9 | status: z.nativeEnum(ProjectStatus), 10 | }); 11 | 12 | export type projectModel = z.infer; 13 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Tauri + React + Typescript 2 | 3 | This template should help get you started developing with Tauri, React and Typescript in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | - [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 8 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTasks/ProjectTaskDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.ProjectTasks 4 | { 5 | public class ProjectTaskDto 6 | { 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public required string Category { get; set; } 10 | public ProjectTaskStatus Status { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTodo/ProjectTodoDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Todos; 2 | 3 | namespace backend.Dto.ProjectTodo 4 | { 5 | public class ProjectTodoDto 6 | { 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public required string Color { get; set; } 10 | public ICollection? Todos { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/api/Image/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | 3 | export const postImage = async (data: FormData) => { 4 | return await axiosInstance.post('/api/images', data, { 5 | headers: { 6 | 'Content-Type': 'multipart/form-data', 7 | }, 8 | }); 9 | }; 10 | 11 | export const deleteImage = async (data: string) => { 12 | return await axiosInstance.delete(`/api/images/${data}`); 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectAvatar/ProjectAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from '@mui/material'; 2 | import { stringAvatar } from '~/utils/userAvatar'; 3 | 4 | type Props = { 5 | name: string; 6 | }; 7 | 8 | export const ProjectAvatar = ({ name }: Props) => { 9 | return ( 10 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/component/DropBox/DropBox.tsx: -------------------------------------------------------------------------------- 1 | import { alpha, Box } from '@mui/material'; 2 | 3 | export const DropBox = () => ( 4 | ({ 6 | width: '100%', 7 | height: '50px', 8 | backgroundColor: theme.palette.action.hover, 9 | borderRadius: 2, 10 | border: '2px dashed', 11 | borderColor: alpha(theme.palette.action.hover, 0.25), 12 | })} 13 | > 14 | ); 15 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Zenith 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /backend/backend/Dto/CalendarEvents/AllCalendarEventsDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.CalendarEvents 2 | { 3 | public class AllCalendarEventsDto 4 | { 5 | public int EventID { get; set; } 6 | public required string Title { get; set; } 7 | public required string Description { get; set; } 8 | public DateTime DateTime { get; set; } 9 | public required string EventColor { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/backend/Dto/Groups/GroupUsersDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Groups 2 | { 3 | public class GroupUsersDto 4 | { 5 | public int UserID { get; set; } 6 | public required string Username { get; set; } 7 | public required string Email { get; set; } 8 | public Enums.GroupRole UserRole { get; set; } 9 | public bool IsMe { get; set; } 10 | public string? Image { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | backend: 4 | container_name: backend 5 | build: . 6 | env_file: "./backend/.env" 7 | ports: 8 | - "5000:8080" 9 | depends_on: 10 | - mssql 11 | mssql: 12 | container_name: mssql 13 | image: mcr.microsoft.com/mssql/server:2022-latest 14 | environment: 15 | - ACCEPT_EULA=Y 16 | - MSSQL_SA_PASSWORD=Password1* 17 | ports: 18 | - "1433:1433" -------------------------------------------------------------------------------- /frontend/src/Theme/Animations.ts: -------------------------------------------------------------------------------- 1 | import { keyframes } from '@mui/material'; 2 | 3 | export const pulseScale = keyframes` 4 | 0% { 5 | transform: scale(1); 6 | } 7 | 50% { 8 | transform: scale(1.05); 9 | } 10 | 100% { 11 | transform: scale(1); 12 | } 13 | `; 14 | 15 | export const pulseOpacity = keyframes` 16 | 0% { 17 | opacity: 0:2; 18 | } 19 | 50% { 20 | opacity: 0.8; 21 | } 22 | 100% { 23 | opacity: 0:2; 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /backend/backend/Models/Todo.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class Todo 4 | { 5 | public int TodoID { get; set; } 6 | public int ProjectTodoID { get; set; } 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | 10 | public bool IsDone { get; set; } 11 | 12 | 13 | public virtual ProjectTodo? ProjectTodo { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/backend/Dto/GroupProjects/AddGroupProjectDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.GroupProjects 4 | { 5 | public class AddGroupProjectDto 6 | { 7 | public required string Title { get; set; } 8 | public int GroupID { get; set; } 9 | public required string Description { get; set; } 10 | public DateTime Deadline { get; set; } 11 | public ProjectStatus Status { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/backend/Interface/IKanbanTaskRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Models; 2 | 3 | namespace backend.Interface 4 | { 5 | public interface IKanbanTaskRepository 6 | { 7 | KanbanTask GetKanbanTaskById(int kanbanTaskId); 8 | List GetAllKanbanTasks(); 9 | void AddKanbanTask(KanbanTask kanbanTask); 10 | void UpdateKanbanTask(KanbanTask kanbanTask); 11 | void DeleteKanbanTask(int kanbanTaskId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTasks/AddProjectTaskDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.ProjectTasks 4 | { 5 | public class AddProjectTaskDto 6 | { 7 | public int ProjectID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public required string Category { get; set; } 11 | public ProjectTaskStatus Status { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { ProjectStatus } from '~/api/Projects/api'; 3 | 4 | export const groupProjectSchema = z 5 | .object({ 6 | title: z.string().min(1), 7 | groupID: z.number().optional(), 8 | description: z.string().optional(), 9 | deadline: z.date(), 10 | status: z.nativeEnum(ProjectStatus), 11 | }); 12 | 13 | export type groupProjectModel = z.infer; 14 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTasks/ProjectTaskShortDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.ProjectTasks 4 | { 5 | public class ProjectTaskShortDto 6 | { 7 | public int ProjectTaskID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public required string Category { get; set; } 11 | public ProjectTaskStatus Status { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/backend/Models/Group.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class Group 4 | { 5 | public int GroupID { get; set; } 6 | public required string GroupName { get; set; } 7 | public string? InviteToken { get; set; } 8 | public DateTime? TokenResetTime { get; set; } 9 | 10 | public required virtual ICollection Users { get; set; } 11 | public virtual ICollection? GroupProjects { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/utils/useGroupRoles.ts: -------------------------------------------------------------------------------- 1 | import { GroupRole } from '~/api/Group/api'; 2 | 3 | export const getGroupRole = (roleId: number) => { 4 | switch (roleId) { 5 | case 1: 6 | return 'Moderator'; 7 | case 2: 8 | return 'Admin'; 9 | default: 10 | return 'User'; 11 | } 12 | }; 13 | 14 | export const isAdmin = (roleId: GroupRole) => { 15 | switch (roleId) { 16 | case 2: 17 | return true; 18 | default: 19 | return false; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/component/Main/Main.tsx: -------------------------------------------------------------------------------- 1 | import { Box, BoxProps } from '@mui/material'; 2 | 3 | export const Main = ({ children, sx, ...rest }: BoxProps) => { 4 | return ( 5 | ({ 7 | flex: 1, 8 | width: '100%', 9 | display: 'flex', 10 | flexDirection: 'column', 11 | padding: theme.spacing(3), 12 | paddingLeft: theme.spacing(34), 13 | })} 14 | {...rest} 15 | > 16 | {children} 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /backend/backend/Interface/ICalendarEventRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.CalendarEvents; 2 | using backend.Models; 3 | 4 | namespace backend.Interface 5 | { 6 | public interface ICalendarEventRepository 7 | { 8 | Task> GetAllEventsBetween(string from, string to, Dictionary colors); 9 | Task AddEvent(CalendarEventDto dto); 10 | Task UpdateEvent(CalendarEventDto dto, int eventId); 11 | Task DeleteEvent(int eventId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/View/ProjectTask/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { ProjectTaskStatus } from '~/api/Projects/api'; 3 | 4 | export const taskSchema = z 5 | .object({ 6 | projectID: z.number(), 7 | title: z.string().min(1), 8 | description: z.string().min(1), 9 | category: z.enum(['Note' , 'Email' , 'Meeting' , 'Research' , 'Design' , 'Development' , 'Maintenance']), 10 | status: z.nativeEnum(ProjectTaskStatus), 11 | }); 12 | 13 | export type taskModel = z.infer; 14 | -------------------------------------------------------------------------------- /backend/backend/Dto/GroupProjectTasks/AddGroupProjectTaskDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.GroupProjectTasks 4 | { 5 | public class AddGroupProjectTaskDto 6 | { 7 | public int ProjectID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public required string Category { get; set; } 11 | public ProjectTaskStatus Status { get; set; } 12 | public int UserId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/backend/Dto/UserPreferences/UserPreferencesDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.UserPreferences 2 | { 3 | public class UserPreferencesDto 4 | { 5 | public required string Theme { get; set; } 6 | public required string Color { get; set; } 7 | public required string Language { get; set; } 8 | public int Reminder { get; set; } 9 | public required Dictionary Routes { get; set; } 10 | public required Dictionary Colors { get; set; } 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/backend/Models/Note.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class Note 4 | { 5 | public int NoteID { get; set; } 6 | public int UserID { get; set; } 7 | public required string Title { get; set; } 8 | public required string Content { get; set; } 9 | public DateTime CreatedAt { get; set; } 10 | public string? ShareToken { get; set; } 11 | public DateTime? TokenResetTime { get; set; } 12 | 13 | public virtual User? User { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/backend/Store/StringGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace backend.Store 5 | { 6 | public static class StringGenerator 7 | { 8 | public static string RandomString(int length) 9 | { 10 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 11 | Random random = new Random(); 12 | return new string(Enumerable.Repeat(chars, length) 13 | .Select(s => s[random.Next(s.Length)]).ToArray()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/utils/useCurrentDate.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | export const useCurrentDate = (passedMonth: number) => { 4 | const month = passedMonth 5 | const year = dayjs().year(); 6 | const firstDay = dayjs(new Date(year, month, 1)).day(); 7 | 8 | let monthCount = 0 - firstDay; 9 | const days = new Array(5).fill([]).map(() => { 10 | return new Array(7).fill(null).map(() => { 11 | monthCount++; 12 | return dayjs(new Date(year, month, monthCount)); 13 | }); 14 | }); 15 | return days; 16 | }; 17 | -------------------------------------------------------------------------------- /backend/backend/Dto/GroupProjectTasks/GroupProjectTaskDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.GroupProjectTasks 4 | { 5 | public class GroupProjectTaskDto 6 | { 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public required string Category { get; set; } 10 | public ProjectTaskStatus Status { get; set; } 11 | public required string User { get; set; } 12 | public string? UserImage { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/backend/Dto/Projects/AllProjectsDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.Projects 4 | { 5 | public class AllProjectsDto 6 | { 7 | public int ProjectID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public DateTime Deadline { get; set; } 11 | public ProjectStatus Status { get; set; } 12 | public float Completion { get; set; } 13 | public bool isOutdated { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/View/RegisterView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const registerFormSchema = z 4 | .object({ 5 | id: z.number(), 6 | username: z.string().min(1), 7 | email: z.string().min(1).email(), 8 | password: z.string().min(8), 9 | passwordConfirm: z.string().min(8), 10 | }) 11 | .refine((item) => item.password === item.passwordConfirm, { 12 | message: "Passwords don't match", 13 | path: ['passwordConfirm'], 14 | }); 15 | 16 | export type registerFormSchema = z.infer; 17 | -------------------------------------------------------------------------------- /backend/backend/Interface/ITodoRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Todos; 2 | using backend.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace backend.Interface 6 | { 7 | public interface ITodoRepository 8 | { 9 | Task> GetAllTodos(int projectId); 10 | Task AddTodo(AddTodoDto todo); 11 | Task UpdateTodo(AddTodoDto todo, int todoId); 12 | Task DeleteTodo(int todoId); 13 | Task ToggleDone(ToggleTodoDto dto, int todoId); 14 | Task ToggleProject(int projectId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/backend/Dto/GroupProjects/AllGroupProjectsDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.GroupProjects 4 | { 5 | public class AllGroupProjectsDto 6 | { 7 | public int GroupProjectID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public DateTime Deadline { get; set; } 11 | public ProjectStatus Status { get; set; } 12 | public float Completion { get; set; } 13 | public bool isOutdated { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectTaskView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { ProjectTaskStatus } from '~/api/Projects/api'; 3 | 4 | export const taskSchema = z 5 | .object({ 6 | projectID: z.number(), 7 | title: z.string().min(1), 8 | description: z.string().min(1), 9 | userId: z.number(), 10 | category: z.enum(['Note' , 'Email' , 'Meeting' , 'Research' , 'Design' , 'Development' , 'Maintenance']), 11 | status: z.nativeEnum(ProjectTaskStatus), 12 | }); 13 | 14 | export type taskModel = z.infer; 15 | -------------------------------------------------------------------------------- /backend/backend/Models/ProjectTodo.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class ProjectTodo 4 | { 5 | public int ProjectTodoID { get; set; } 6 | public int UserID { get; set; } 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public required string Color { get; set; } 10 | public bool IsDone { get; set; } = false; 11 | 12 | public virtual User? User { get; set; } 13 | public virtual ICollection? Todos { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupView.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { NoGroupView } from './NoGroupView'; 3 | import { getIsInGroup } from '~/api/Group/query'; 4 | import { LoadingView } from '../LoadingView/LoadingView'; 5 | import { GroupProjectView } from './GroupProjectView/GroupProjectView'; 6 | 7 | export const GroupView = () => { 8 | const { data: group, isLoading } = getIsInGroup(); 9 | 10 | if (isLoading) { 11 | return ; 12 | } 13 | 14 | return {group ? : }; 15 | }; 16 | -------------------------------------------------------------------------------- /backend/backend/Dto/Projects/ProjectDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.ProjectTasks; 2 | using backend.Enums; 3 | using backend.Models; 4 | 5 | namespace backend.Dto.Projects 6 | { 7 | public class ProjectDto 8 | { 9 | public int ProjectID { get; set; } 10 | public required string Title { get; set; } 11 | public required string Description { get; set; } 12 | public DateTime Deadline { get; set; } 13 | public ProjectStatus Status { get; set; } 14 | public ICollection? ProjectTasks { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import { QueryClientProvider } from '@tanstack/react-query'; 5 | import { AuthProvider } from './context/AuthContext'; 6 | import { queryClient } from './api/api'; 7 | 8 | ReactDOM.createRoot(document.getElementById('root')!).render( 9 | 10 | 11 | 12 | 13 | 14 | 15 | , 16 | ); 17 | -------------------------------------------------------------------------------- /backend/backend/Dto/ProjectTodo/AllProjectsTodoDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.ProjectTodo 2 | { 3 | public class ProjectsTodoDto 4 | { 5 | public List Projects { get; set; } = new List(); 6 | } 7 | public class AllProjectsTodoDto 8 | { 9 | public int ProjectTodoID { get; set; } 10 | public required string Title { get; set; } 11 | public required string Description { get; set; } 12 | public required string Color { get; set; } 13 | public bool IsDone { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/component/UserAvatar/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from '@mui/material'; 2 | import { BASE_URL } from '~/config/constants'; 3 | import { stringAvatar } from '~/utils/userAvatar'; 4 | 5 | type Props = { 6 | image: string | null; 7 | username: string; 8 | }; 9 | 10 | export const UserAvatar = ({ username, image }: Props) => { 11 | return ( 12 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /backend/backend/Interface/IProjectRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Pagination; 2 | using backend.Dto.Projects; 3 | using backend.Models; 4 | 5 | namespace backend.Interface 6 | { 7 | public interface IProjectRepository 8 | { 9 | Task GetProjectById(int projectId); 10 | Task> GetAllProjects(PaginationRequestDto paginationRequest); 11 | Task AddProject(AddProjectDto project); 12 | Task UpdateProject(EditProjectDto project, int projectId); 13 | Task DeleteProject(int projectId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/backend/Models/KanbanTask.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class KanbanTask 4 | { 5 | public int KanbanTaskID { get; set; } 6 | public int UserID { get; set; } 7 | public required string Title { get; set; } 8 | public required string Description { get; set; } 9 | public int Priority { get; set; } 10 | public DateTime Deadline { get; set; } 11 | public required string Category { get; set; } 12 | public required string Status { get; set; } 13 | 14 | public virtual User? User { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/backend/Models/ProjectTask.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Models 4 | { 5 | public class ProjectTask 6 | { 7 | public int ProjectTaskID { get; set; } 8 | public int ProjectID { get; set; } 9 | public required string Title { get; set; } 10 | public required string Description { get; set; } 11 | public required string Category { get; set; } 12 | public ProjectTaskStatus Status { get; set; } 13 | public DateTime EditTime { get; set; } 14 | 15 | public virtual Project? Project { get; set; } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/backend/Models/UserPreferences.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class UserPreferences 4 | { 5 | public int PreferencesID { get; set; } 6 | public int UserID { get; set; } 7 | public required string Theme { get; set; } 8 | public required string Color { get; set; } 9 | public required string Language { get; set; } 10 | public int Reminder { get; set; } 11 | public required string Routes { get; set; } 12 | public string Colors { get; set; } 13 | 14 | 15 | public virtual User? User { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/View/CalendarView/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const eventSchema = z.object({ 4 | title: z.string().min(1), 5 | description: z.string(), 6 | dateTime: z.string(), 7 | eventColor: z.string().min(1), 8 | }); 9 | 10 | export type eventModel = z.infer; 11 | 12 | 13 | export const colorSchema = z.object({ 14 | colors: z.object({ 15 | Purple: z.boolean(), 16 | Red: z.boolean(), 17 | Green: z.boolean(), 18 | Blue: z.boolean(), 19 | Yellow: z.boolean(), 20 | }), 21 | }); 22 | 23 | export type ColorModel = z.infer; 24 | -------------------------------------------------------------------------------- /frontend/src/utils/useDebouncedValue.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { debounce } from 'lodash'; 3 | 4 | type Props = { 5 | value: any; 6 | delay: number; 7 | }; 8 | export const useDebouncedValue = ({ value, delay }: Props) => { 9 | const [debouncedValue, setDebouncedValue] = useState(value); 10 | 11 | useEffect(() => { 12 | const handler = debounce(() => { 13 | setDebouncedValue(value); 14 | }, delay); 15 | 16 | handler(); 17 | 18 | return () => { 19 | handler.cancel(); 20 | }; 21 | }, [value, delay]); 22 | 23 | return debouncedValue; 24 | }; 25 | -------------------------------------------------------------------------------- /backend/backend/Interface/IProjectTaskRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.ProjectTasks; 2 | using backend.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace backend.Interface 6 | { 7 | public interface IProjectTaskRepository 8 | { 9 | Task GetProjectTaskById(int projectTaskId); 10 | Task AddProjectTask(AddProjectTaskDto projectTask); 11 | Task UpdateProjectTask(ProjectTaskDto projectTask, int projectTaskId); 12 | 13 | Task ChangeProjectTaskStatus(ProjectTaskStatusDto status, int projectTaskId); 14 | Task DeleteProjectTask(int projectTaskId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/backend/Interface/IProjectTodoRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Pagination; 2 | using backend.Dto.ProjectTodo; 3 | using backend.Models; 4 | 5 | namespace backend.Interface 6 | { 7 | public interface IProjectTodoRepository 8 | { 9 | Task> GetAllProjects(bool isDone, PaginationRequestDto paginationRequest); 10 | Task AddProject(AddProjectTodoDto project); 11 | Task UpdateProject(AddProjectTodoDto project, int projectId); 12 | Task DeleteProject(int projectId); 13 | Task GetProjectById(int projectId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/api/Settings/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | 3 | export interface Settings { 4 | theme: string; 5 | color: string; 6 | language: string; 7 | reminder: number; 8 | routes: { [routeName: string]: boolean }; 9 | colors: { [color: string]: string }; 10 | } 11 | 12 | export const querySettings = async () => { 13 | const response = await axiosInstance.get('/api/settings/getSettings'); 14 | return response.data as Settings; 15 | }; 16 | 17 | export const editSettings = async (preferences: Settings) => { 18 | return await axiosInstance.patch(`/api/settings/updateSettings`, preferences); 19 | }; 20 | -------------------------------------------------------------------------------- /backend/backend/Dto/GroupProjectTasks/GroupProjectTaskShortDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.GroupProjectTasks 4 | { 5 | public class GroupProjectTaskShortDto 6 | { 7 | public int ProjectTaskID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public required string Category { get; set; } 11 | public ProjectTaskStatus Status { get; set; } 12 | public required string User { get; set; } 13 | public string? UserImage { get; set; } 14 | public bool CanEdit { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/backend/Models/CalendarEvent.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class CalendarEvent 4 | { 5 | public int EventID { get; set; } 6 | public int UserID { get; set; } 7 | public int? NotificationID { get; set; } 8 | public required string Title { get; set; } 9 | public required string Description { get; set; } 10 | public DateTime DateTime { get; set; } 11 | public required string EventColor { get; set; } 12 | 13 | public virtual User? User { get; set; } 14 | public virtual CalendarEventNotification? Notification { get; set; } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/backend/Interface/IDashboardRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.CalendarEvents; 2 | using backend.Dto.Dashboard; 3 | 4 | namespace backend.Interface 5 | { 6 | public interface IDashboardRepository 7 | { 8 | Task GetTodoCompletion(); 9 | Task> GetClosestEvents(int maxEvents = 5); 10 | Task> GetLatestNotes(int maxNotes = 5); 11 | Task GetApproachingDeadlineProjects(int daysThreshold = 7); 12 | Task GetApproachingDeadlineGroupProjects(int daysThreshold = 7); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/backend/Interface/IGroupProjectTaskRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.GroupProjectTasks; 2 | using backend.Dto.ProjectTasks; 3 | using backend.Models; 4 | 5 | namespace backend.Interface 6 | { 7 | public interface IGroupProjectTaskRepository 8 | { 9 | Task GetProjectTaskById(int groupProjectTaskId); 10 | Task AddProjectTask(AddGroupProjectTaskDto dto); 11 | Task UpdateProjectTask(AddGroupProjectTaskDto dto, int groupProjectTaskId); 12 | Task ChangeProjectTaskStatus(ProjectTaskStatusDto status, int groupProjectTaskId); 13 | Task DeleteProjectTask(int groupProjectTaskId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/utils/useCurrentWeek.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | type Props= { 3 | passedMonth: number; 4 | week: number; 5 | } 6 | 7 | export const useCurrentWeek = ({passedMonth, week }: Props) => { 8 | const month = passedMonth 9 | const year = dayjs().year(); 10 | const firstDay = dayjs(new Date(year, month, 1)).day(); 11 | 12 | let monthCount = 0 - firstDay; 13 | const days = 14 | new Array(5).fill([]).map(() => { 15 | return new Array(7).fill(null).map(() => { 16 | monthCount++; 17 | return dayjs(new Date(year, month, monthCount)); 18 | }); 19 | }); 20 | 21 | return days[week]; 22 | }; 23 | -------------------------------------------------------------------------------- /backend/backend/Interface/IGroupProjectRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.GroupProjects; 2 | using backend.Dto.Pagination; 3 | using backend.Dto.Projects; 4 | using backend.Models; 5 | 6 | namespace backend.Interface 7 | { 8 | public interface IGroupProjectRepository 9 | { 10 | Task GetGroupProjectById(int projectId); 11 | Task> GetAllGroupProjects(PaginationRequestDto paginationRequest); 12 | Task AddGroupProject(AddGroupProjectDto dto); 13 | Task UpdateGroupProject(EditProjectDto dto, int projectId); 14 | Task DeleteGroupProject(int projectId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/backend/Interface/INoteRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Notes; 2 | using backend.Dto.Pagination; 3 | using backend.Models; 4 | using Task = System.Threading.Tasks.Task; 5 | 6 | namespace backend.Interface 7 | { 8 | public interface INoteRepository 9 | { 10 | Task GetNoteById(int noteId); 11 | Task> GetAllNotes(PaginationRequestDto pagination); 12 | Task AddNote(); 13 | Task UpdateNote(EditNoteDto dto, int noteId); 14 | Task DeleteNote(int noteId); 15 | 16 | Task GetShareToken(int noteId); 17 | Task GetNoteFromToken(string tokenId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/View/RegisterView/PasswordRenew/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const forgotPasswordSchema = z.object({ 4 | email: z.string().email().min(1), 5 | }); 6 | 7 | export type forgotPasswordModel = z.infer; 8 | 9 | export const resetPasswordSchema = z 10 | .object({ 11 | resetToken: z.string().min(16).max(16), 12 | password: z.string().min(8), 13 | passwordConfirm: z.string().min(8), 14 | }) 15 | .refine((item) => item.password === item.passwordConfirm, { 16 | message: "Passwords don't match", 17 | path: ['passwordConfirm'], 18 | }); 19 | 20 | export type resetPasswordModel = z.infer; 21 | -------------------------------------------------------------------------------- /frontend/src/utils/userAvatar.ts: -------------------------------------------------------------------------------- 1 | export const stringAvatar = (name: string) => { 2 | return { 3 | sx: { 4 | bgcolor: stringToColor(name), 5 | }, 6 | children: `${name.split(' ')[0][0].toLocaleUpperCase()}`, 7 | }; 8 | } 9 | 10 | export const stringToColor = (string: string) => { 11 | let hash = 0; 12 | let i; 13 | 14 | for (i = 0; i < string.length; i += 1) { 15 | hash = string.charCodeAt(i) + ((hash << 5) - hash); 16 | } 17 | 18 | let color = '#'; 19 | 20 | for (i = 0; i < 3; i += 1) { 21 | const value = (hash >> (i * 8)) & 0xff; 22 | color += `00${value.toString(16)}`.slice(-2); 23 | } 24 | 25 | return color; 26 | } 27 | -------------------------------------------------------------------------------- /backend/backend/Models/GroupProjectTask.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Models 4 | { 5 | public class GroupProjectTask 6 | { 7 | public int GroupProjectTaskID { get; set; } 8 | public int GroupProjectID { get; set; } 9 | public int UserID { get; set; } 10 | public required string Title { get; set; } 11 | public required string Description { get; set; } 12 | public required string Category { get; set; } 13 | public ProjectTaskStatus Status { get; set; } 14 | public DateTime EditTime { get; set; } 15 | 16 | public virtual GroupProject? GroupProject { get; set; } 17 | public virtual User? User { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/component/Clock/Clock.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from '@mui/material'; 2 | import { useState } from 'react'; 3 | 4 | export const Clock = () => { 5 | let time = new Date().toLocaleTimeString([], { 6 | hour: '2-digit', 7 | minute: '2-digit', 8 | }); 9 | 10 | const [ctime, setTime] = useState(time); 11 | const UpdateTime = () => { 12 | time = new Date().toLocaleTimeString([], { 13 | hour: '2-digit', 14 | minute: '2-digit', 15 | }); 16 | setTime(time); 17 | }; 18 | setInterval(UpdateTime); 19 | 20 | return ( 21 | 22 | 23 | {ctime} 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /backend/backend/Models/Project.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Models 4 | { 5 | public class Project 6 | { 7 | public int ProjectID { get; set; } 8 | public int UserID { get; set; } 9 | public int? NotificationID { get; set; } 10 | public required string Title { get; set; } 11 | public required string Description { get; set; } 12 | public DateTime Deadline { get; set; } 13 | public ProjectStatus Status { get; set; } 14 | 15 | public virtual User? User { get; set; } 16 | public virtual ICollection? ProjectTasks { get; set; } 17 | public virtual ProjectNotification? Notification { get; set; } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/backend/Dto/Dashboard/DashboardGroupProjectDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.Dashboard 4 | { 5 | public class DashboardGroupProjectDto 6 | { 7 | public int GroupProjectID { get; set; } 8 | public string Title { get; set; } 9 | public DateTime Deadline { get; set; } 10 | public string Description { get; set; } 11 | public ProjectStatus Status { get; set; } 12 | public float CompletionPercentage { get; set; } 13 | } 14 | 15 | public class DashboardGroupProjectSummaryDto 16 | { 17 | public List Projects { get; set; } 18 | public Dictionary ProjectsCountByStatus { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/backend/Interface/IUserRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Token; 2 | using backend.Dto.Users; 3 | using backend.Models; 4 | using Task = System.Threading.Tasks.Task; 5 | 6 | namespace backend.Interface 7 | { 8 | public interface IUserRepository 9 | { 10 | Task GetUserById(); 11 | List GetAllUsers(); 12 | Task AddUser(RegisterUserDto user); 13 | Task UpdateUser(UpdateUserDto user); 14 | Task DeleteUser(); 15 | Task LoginUser(LoginUserDto dto); 16 | Task VerifyEmail(string token); 17 | Task ForgotPassword(ForgotPasswordDto email); 18 | Task ResetPassword(ResetPasswordDto dto); 19 | Task LogoutUser(); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/backend/Interface/IGroupRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Groups; 2 | using backend.Dto.Users; 3 | using backend.Models; 4 | 5 | namespace backend.Interface 6 | { 7 | public interface IGroupRepository 8 | { 9 | Task GetGroup(); 10 | Task AddGroup(GroupEditDto group); 11 | Task UpdateGroup(GroupEditDto dto, int groupId); 12 | Task DeleteGroup(int groupId); 13 | Task GetInviteToken(int groupId); 14 | Task isInGroup(); 15 | Task JoinGroup(TokenDto tokenId); 16 | Task LeaveGroup(LeaveGroupDto dto); 17 | Task ChangeRole(ChangeRoleDto dto); 18 | Task SetAdmin(ChangeRoleDto dto); 19 | Task GetOwnRole(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/backend/Dto/Dashboard/DashboardProjectDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Dto.Dashboard 4 | { 5 | public class DashboardProjectDto 6 | { 7 | public int ProjectID { get; set; } 8 | public string Title { get; set; } 9 | public DateTime Deadline { get; set; } 10 | public string Description { get; set; } 11 | public ProjectStatus Status { get; set; } 12 | public float CompletionPercentage { get; set; } 13 | 14 | } 15 | 16 | public class DashboardProjectSummaryDto 17 | { 18 | public List Projects { get; set; } 19 | public Dictionary ProjectsCountByStatus { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/backend/Models/GroupProject.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | 3 | namespace backend.Models 4 | { 5 | public class GroupProject 6 | { 7 | public int GroupProjectID { get; set; } 8 | public int GroupID { get; set; } 9 | public int? NotificationID { get; set; } 10 | public required string Title { get; set; } 11 | public required string Description { get; set; } 12 | public DateTime Deadline { get; set; } 13 | public ProjectStatus Status { get; set; } 14 | 15 | 16 | public virtual ICollection? GroupProjectTasks { get; set; } 17 | public virtual Group? Group { get; set; } 18 | public virtual GroupProjectNotification? Notification { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | use tauri::Manager; 5 | 6 | #[derive(Clone, serde::Serialize)] 7 | struct Payload { 8 | args: Vec, 9 | cwd: String, 10 | } 11 | 12 | fn main() { 13 | tauri::Builder::default() 14 | .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { 15 | println!("{}, {argv:?}, {cwd}", app.package_info().name); 16 | 17 | app.emit_all("single-instance", Payload { args: argv, cwd }) 18 | .unwrap(); 19 | })) 20 | .run(tauri::generate_context!()) 21 | .expect("error while running tauri application"); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/api/Todos/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | 3 | export interface AddTodo { 4 | projectTodoID: number; 5 | title: string; 6 | description: string; 7 | } 8 | 9 | export interface ToggleTodo { 10 | todoID: number; 11 | isDone: boolean; 12 | } 13 | 14 | export const postAddTodo = async (todo: AddTodo) => { 15 | return await axiosInstance.post('/api/todos/addTodo', todo); 16 | }; 17 | 18 | export const patchToggleTodo = async (todo: ToggleTodo) => { 19 | return await axiosInstance.patch(`/api/todos/toggleStatus/${todo.todoID}`, { 20 | isDone: todo.isDone, 21 | }); 22 | }; 23 | 24 | export const postDeleteTodo = async (todoID: number) => { 25 | return await axiosInstance.delete(`/api/todos/deleteTodo/${todoID}`); 26 | }; 27 | -------------------------------------------------------------------------------- /backend/backend/Dto/Projects/ProjectByStatusDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.ProjectTasks; 2 | using backend.Enums; 3 | 4 | namespace backend.Dto.Projects 5 | { 6 | public class ProjectByStatusDto 7 | { 8 | public int ProjectID { get; set; } 9 | public required string Title { get; set; } 10 | public required string Description { get; set; } 11 | public DateTime Deadline { get; set; } 12 | public ProjectStatus Status { get; set; } 13 | public ICollection? Backlog { get; set; } 14 | public ICollection? inProgress { get; set; } 15 | public ICollection? Review { get; set; } 16 | public ICollection? Closed { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Zenith" 3 | version = "0.0.1" 4 | description = "Zenith" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [build-dependencies] 11 | tauri-build = { version = "1.5", features = [] } 12 | 13 | [dependencies] 14 | tauri = { version = "1.5", features = ["shell-open"] } 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "1.0" 17 | tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } 18 | 19 | [features] 20 | # this feature is used for production builds or when `devPath` points to the filesystem 21 | # DO NOT REMOVE!! 22 | custom-protocol = ["tauri/custom-protocol"] 23 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": ["src/*"], 19 | }, 20 | 21 | /* Linting */ 22 | "strict": true, 23 | "noUnusedLocals": true, 24 | "noUnusedParameters": true, 25 | "noFallthroughCasesInSwitch": true 26 | }, 27 | "include": ["src"], 28 | "references": [{ "path": "./tsconfig.node.json" }] 29 | } 30 | -------------------------------------------------------------------------------- /backend/backend/.env.example: -------------------------------------------------------------------------------- 1 | # local 2 | #CONNECTION_STRING="Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=zenith;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False" 3 | 4 | # docker 5 | CONNECTION_STRING="Server=mssql,1433;Database=zenith;User=sa;Password=Password1*;TrustServerCertificate=True;TrustServerCertificate=True;Trusted_Connection=False; MultipleActiveResultSets=true" 6 | 7 | EMAIL_SENDER="xxxx" 8 | EMAIL_PASSWORD="xxxx" 9 | 10 | JWT_KEY="PRIVATE_KEY_ZENITH_KEY_AUTHORIZATION_KEY" 11 | 12 | # local 13 | #JWT_ISSUER="https://localhost:7086/api" 14 | 15 | #docker 16 | JWT_ISSUER="http://localhost:5000/api" 17 | 18 | JWT_EPIRE_MINUTES="30" 19 | 20 | ASPNETCORE_ENVIRONMENT="Production" 21 | MSSQL_SA_PASSWORD="Password1*" 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/api/Settings/query.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 2 | import { useAuth } from '~/context/AuthContext'; 3 | import { Settings, editSettings, querySettings } from './api'; 4 | 5 | export const getSettings = () => { 6 | const { isAuthenticated } = useAuth(); 7 | return useQuery({ 8 | queryKey: ['allSettings'], 9 | queryFn: querySettings, 10 | enabled: isAuthenticated, 11 | retryDelay: 10 12 | }); 13 | }; 14 | 15 | export const mutateEditSettings = () => { 16 | const queryClient = useQueryClient(); 17 | return useMutation({ 18 | mutationKey: ['editSettings'], 19 | mutationFn: (preferences: Settings) => editSettings(preferences), 20 | onSuccess: () => { 21 | queryClient.invalidateQueries({ queryKey: ['allSettings'] }); 22 | }, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/src/component/SubDrawer/SubDrawer.tsx: -------------------------------------------------------------------------------- 1 | import { Drawer, DrawerProps } from '@mui/material'; 2 | import { SIDEBAR_WIDTH } from '~/config/constants'; 3 | 4 | export const SubDrawer = ({ children, sx, ...rest }: DrawerProps) => { 5 | return ( 6 | 27 | {children} 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/api/Notifications/query.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 2 | import { patchMarkAsRead, queryAllNotifications } from './api'; 3 | import { enqueueSnackbar } from 'notistack'; 4 | import { t } from '@lingui/macro'; 5 | 6 | export const getAllNotifications = () => { 7 | return useQuery({ 8 | queryKey: ['allNotifications'], 9 | queryFn: queryAllNotifications, 10 | }); 11 | }; 12 | 13 | export const mutateMarkAsRead = () => { 14 | const queryClient = useQueryClient(); 15 | return useMutation({ 16 | mutationKey: ['markAsRead'], 17 | mutationFn: (id:number) => 18 | patchMarkAsRead(id), 19 | onSuccess: () => { 20 | enqueueSnackbar(t({message:'Notification dismissed'})); 21 | queryClient.invalidateQueries({ queryKey: ['allNotifications'] }); 22 | }, 23 | }); 24 | }; -------------------------------------------------------------------------------- /backend/backend/Dto/GroupProjects/GroupProjectByStatusDto.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.GroupProjectTasks; 2 | using backend.Enums; 3 | 4 | namespace backend.Dto.GroupProjects 5 | { 6 | public class GroupProjectByStatusDto 7 | { 8 | public int ProjectID { get; set; } 9 | public required string Title { get; set; } 10 | public required string Description { get; set; } 11 | public DateTime Deadline { get; set; } 12 | public ProjectStatus Status { get; set; } 13 | public string? User { get; set; } 14 | public ICollection? Backlog { get; set; } 15 | public ICollection? inProgress { get; set; } 16 | public ICollection? Review { get; set; } 17 | public ICollection? Closed { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/View/NotesView/NoNoteView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from '@mui/material'; 2 | import DescriptionIcon from '@mui/icons-material/Description'; 3 | import { Trans } from '@lingui/macro'; 4 | 5 | export const NoNoteView = () => { 6 | return ( 7 | 19 | 20 | 21 | No Note 22 | 23 | 24 | Create a new note to see preview 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /backend/backend/Migrations/20240616124753_avatar_images.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace backend.Migrations 6 | { 7 | /// 8 | public partial class Avatar_images : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "Image", 15 | table: "Users", 16 | type: "nvarchar(max)", 17 | nullable: true); 18 | } 19 | 20 | /// 21 | protected override void Down(MigrationBuilder migrationBuilder) 22 | { 23 | migrationBuilder.DropColumn( 24 | name: "Image", 25 | table: "Users"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/View/NotesView/DrawerLink.tsx: -------------------------------------------------------------------------------- 1 | import { ListItem, ListItemButton, ListItemText, alpha } from '@mui/material'; 2 | 3 | type DrawerProps = { 4 | isActive: boolean; 5 | children: React.ReactNode; 6 | color?: string; 7 | }; 8 | 9 | export const DrawerLink = ({ isActive, children, color }: DrawerProps) => { 10 | return ( 11 | 12 | ({ 14 | backgroundColor: isActive 15 | ? color 16 | ? alpha(color, theme.palette.action.activatedOpacity) 17 | : theme.palette.action.focus 18 | : '', 19 | display: 'flex', 20 | borderRadius: '10px', 21 | minHeight: '54px', 22 | })} 23 | > 24 | {children} 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:react/recommended" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": 12, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true 18 | } 19 | }, 20 | "plugins": [ 21 | "@typescript-eslint", 22 | "react" 23 | ], 24 | "rules": { 25 | "semi": "off", 26 | "@typescript-eslint/semi": ["warn", "never"], 27 | "react/prop-types": "off", 28 | "react/react-in-jsx-scope": "off" 29 | }, 30 | "settings": { 31 | "react": { 32 | "version": "detect" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/NoTodoView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from '@mui/material'; 2 | import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; 3 | import { Trans } from '@lingui/macro'; 4 | 5 | export const NoTodoView = () => { 6 | return ( 7 | 19 | 20 | 21 | No Todo 22 | 23 | 24 | Create a new todo to see preview 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /backend/backend/Controllers/TokenController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Token; 2 | using backend.Dto.Users; 3 | using backend.Interface; 4 | using backend.Repository; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using System.Data; 8 | 9 | namespace backend.Controllers 10 | { 11 | [Route("api/token")] 12 | [ApiController] 13 | public class TokenController : ControllerBase 14 | { 15 | private readonly ITokenRepository _tokenService; 16 | public TokenController(ITokenRepository tokenService) 17 | { 18 | _tokenService = tokenService; 19 | } 20 | [HttpPost("refresh")] 21 | public async Task> RefreshToken([FromBody] AccessTokenDto dto) 22 | { 23 | var response = await _tokenService.Refresh(dto); 24 | return Ok(response); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/View/Errors/NotFoundView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Typography } from '@mui/material'; 2 | import { NavLink } from 'react-router-dom'; 3 | import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; 4 | import { Trans } from '@lingui/macro'; 5 | 6 | export const NotFoundView = () => { 7 | return ( 8 | 19 | 20 | 21 | View not found 22 | 23 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/src/component/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { Outlet } from 'react-router-dom'; 3 | import { Sidebar } from '../Sidebar/Sidebar'; 4 | import { LoadingView } from '~/View/LoadingView/LoadingView'; 5 | import { Suspense } from 'react'; 6 | 7 | type RouterProps = { 8 | routes: { [routeName: string]: boolean }; 9 | }; 10 | 11 | export const Layout = ({ routes }: RouterProps) => { 12 | return ( 13 | 14 | 15 | 25 | }> 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { lingui } from "@lingui/vite-plugin"; 2 | import react from '@vitejs/plugin-react'; 3 | import { resolve } from 'path'; 4 | import { defineConfig, loadEnv } from 'vite'; 5 | 6 | export default defineConfig(({ mode }) => { 7 | const env = loadEnv(mode, process.cwd()); 8 | 9 | return { 10 | define: { 11 | 'process.env': process.env, 12 | }, 13 | plugins: [ 14 | react({ 15 | babel: { 16 | plugins: ["macros"], 17 | }, 18 | }), 19 | lingui(), 20 | ], 21 | resolve: { 22 | alias: { 23 | '~': resolve(__dirname, 'src'), 24 | }, 25 | }, 26 | clearScreen: false, 27 | server: { 28 | port: 1420, 29 | strictPort: true, 30 | proxy: { 31 | '/api': { 32 | target: env.VITE_API_URL, 33 | changeOrigin: true, 34 | secure: false, 35 | }, 36 | }, 37 | watch: { 38 | ignored: ['**/src-tauri/**'], 39 | }, 40 | }, 41 | }; 42 | }); 43 | -------------------------------------------------------------------------------- /frontend/src/View/NoConnectionView/NoConnectionView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography, Button } from '@mui/material'; 2 | import { NavLink } from 'react-router-dom'; 3 | import CloudOffIcon from '@mui/icons-material/CloudOff'; 4 | import { Trans } from '@lingui/macro'; 5 | 6 | export const NoConnectionView = () => { 7 | return ( 8 | 19 | 20 | 21 | Connection not established 22 | 23 | 26 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /backend/backend/Repository/UserContextRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Interface; 2 | using System.Security.Claims; 3 | 4 | namespace backend.Repository 5 | { 6 | 7 | public class UserContextRepository : IUserContextRepository 8 | { 9 | private readonly IHttpContextAccessor _httpContextAccessor; 10 | public UserContextRepository(IHttpContextAccessor httpContextAccessor) 11 | { 12 | _httpContextAccessor = httpContextAccessor; 13 | } 14 | public ClaimsPrincipal User => 15 | _httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(); 16 | public int? GetUserId 17 | { 18 | get 19 | { 20 | var user = User; 21 | var claim = user.FindFirst(x => x.Type == ClaimTypes.NameIdentifier); 22 | if (claim == null || !int.TryParse(claim.Value, out int userId)) 23 | { 24 | return null; 25 | } 26 | return userId; 27 | } 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/api/Image/query.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@tanstack/react-query'; 2 | import { deleteImage, postImage } from './api'; 3 | import { useSnackbar } from 'notistack'; 4 | import { t } from '@lingui/macro'; 5 | import { queryClient } from '../api'; 6 | 7 | export const mutatePostImage = () => { 8 | const { enqueueSnackbar } = useSnackbar(); 9 | return useMutation({ 10 | mutationKey: ['postImage'], 11 | mutationFn: (data: FormData) => postImage(data), 12 | onSuccess: () => { 13 | enqueueSnackbar(t({ message: 'Avatar updated' })); 14 | queryClient.invalidateQueries({ queryKey: ['getMyAccount'] }); 15 | }, 16 | }); 17 | }; 18 | 19 | export const mutateDeleteImage = () => { 20 | const { enqueueSnackbar } = useSnackbar(); 21 | return useMutation({ 22 | mutationKey: ['deleteImage'], 23 | mutationFn: (data: string) => deleteImage(data), 24 | onSuccess: () => { 25 | enqueueSnackbar(t({ message: 'Avatar deleted' })); 26 | queryClient.invalidateQueries({ queryKey: ['getMyAccount'] }); 27 | }, 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /frontend/src/component/InfiniteScroll/InfiniteScroll.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useCallback, ReactNode } from 'react'; 2 | 3 | interface InfiniteScrollProps { 4 | loadMore: () => void; 5 | hasMore: boolean; 6 | isLoading: boolean; 7 | children: ReactNode; 8 | } 9 | 10 | export const InfiniteScroll: React.FC = ({ 11 | loadMore, 12 | hasMore, 13 | isLoading, 14 | children, 15 | }) => { 16 | const observer = useRef(null); 17 | 18 | const lastElementRef = useCallback( 19 | (node: HTMLElement | null) => { 20 | if (isLoading) return; 21 | if (observer.current) observer.current.disconnect(); 22 | observer.current = new IntersectionObserver((entries) => { 23 | if (entries[0].isIntersecting && hasMore) { 24 | loadMore(); 25 | } 26 | }); 27 | if (node) observer.current.observe(node); 28 | }, 29 | [isLoading, hasMore, loadMore], 30 | ); 31 | 32 | return ( 33 |
34 | {children} 35 |
36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/src/context/PdfContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | useState, 4 | ReactNode, 5 | Dispatch, 6 | SetStateAction, 7 | } from 'react'; 8 | 9 | interface PdfContextProps { 10 | htmlContent: string; 11 | setHtmlContent: Dispatch>; 12 | selectedNote: number | null; 13 | setSelectedNote: Dispatch>; 14 | } 15 | 16 | export const PdfContext = createContext({ 17 | htmlContent: '', 18 | setHtmlContent: () => {}, 19 | selectedNote: null, 20 | setSelectedNote: () => {}, 21 | }); 22 | 23 | interface PdfProviderProps { 24 | children: ReactNode; 25 | } 26 | 27 | export const PdfProvider: React.FC = ({ children }) => { 28 | const [htmlContent, setHtmlContent] = useState(''); 29 | const [selectedNote, setSelectedNote] = useState(null); 30 | 31 | return ( 32 | 35 | {children} 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/src/component/PasswordField/PasswordField.tsx: -------------------------------------------------------------------------------- 1 | import { Visibility, VisibilityOff } from '@mui/icons-material'; 2 | import { 3 | IconButton, 4 | InputAdornment, 5 | TextField, 6 | TextFieldProps, 7 | } from '@mui/material'; 8 | import { useState } from 'react'; 9 | 10 | type PasswordFieldProps = Omit; 11 | 12 | export const PasswordField = (props: PasswordFieldProps) => { 13 | const [showPassword, setShowPassword] = useState(false); 14 | 15 | const handleTogglePasswordVisibility = () => { 16 | setShowPassword((prev) => !prev); 17 | }; 18 | 19 | return ( 20 | 27 | 28 | {showPassword ? : } 29 | 30 | 31 | ), 32 | }} 33 | /> 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /frontend/src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "pnpm dev", 4 | "beforeBuildCommand": "pnpm build", 5 | "devPath": "http://localhost:1420", 6 | "distDir": "../dist" 7 | }, 8 | "package": { 9 | "productName": "Zenith", 10 | "version": "0.0.1" 11 | }, 12 | "tauri": { 13 | "allowlist": { 14 | "all": false, 15 | "shell": { 16 | "all": false, 17 | "open": true 18 | } 19 | }, 20 | "windows": [ 21 | { 22 | "fullscreen": false, 23 | "resizable": true, 24 | "title": "Zenith", 25 | "label": "main", 26 | "width": 1150, 27 | "height": 750, 28 | "fileDropEnabled": false 29 | } 30 | ], 31 | "security": { 32 | "csp": null 33 | }, 34 | "bundle": { 35 | "active": true, 36 | "targets": "all", 37 | "identifier": "Zenith", 38 | "icon": [ 39 | "icons/32x32.png", 40 | "icons/128x128.png", 41 | "icons/128x128@2x.png", 42 | "icons/icon.icns", 43 | "icons/icon.ico" 44 | ] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /backend/backend/Models/Notification.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Models 2 | { 3 | public class Notification 4 | { 5 | public int NotificationID { get; set; } 6 | public int UserID { get; set; } 7 | public required string Message { get; set; } 8 | public DateTime DateTime { get; set; } 9 | public bool isActive { get; set; } = false; 10 | public bool isRead { get; set; } = false; 11 | public string? Discriminator { get; set; } 12 | 13 | public virtual User? User { get; set; } 14 | 15 | } 16 | 17 | public class GroupProjectNotification : Notification 18 | { 19 | public int GroupProjectID { get; set; } 20 | public GroupProject? GroupProject { get; set; } 21 | } 22 | 23 | public class ProjectNotification : Notification 24 | { 25 | public int ProjectID { get; set; } 26 | public Project? Project { get; set; } 27 | } 28 | 29 | public class CalendarEventNotification : Notification 30 | { 31 | public int CalendarEventID { get; set; } 32 | public CalendarEvent? CalendarEvent { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/component/SearchField/SearchField.tsx: -------------------------------------------------------------------------------- 1 | import { Search } from '@mui/icons-material'; 2 | import { 3 | TextField, 4 | alpha, 5 | InputAdornment, 6 | TextFieldProps, 7 | } from '@mui/material'; 8 | import { forwardRef } from 'react'; 9 | 10 | export const SearchField = forwardRef( 11 | ({ InputProps, ...rest }, ref) => { 12 | return ( 13 | ({ 18 | '.MuiInputBase-root': { 19 | color: 'inherit', 20 | backgroundColor: alpha(theme.palette.common.white, 0.15), 21 | boxShadow: 'none', 22 | }, 23 | svg: { 24 | color: theme.palette.action.disabled, 25 | }, 26 | }), 27 | ]} 28 | size="small" 29 | autoComplete="off" 30 | InputProps={{ 31 | ...InputProps, 32 | startAdornment: ( 33 | 34 | 35 | 36 | ), 37 | }} 38 | /> 39 | ); 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /backend/backend.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34408.163 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "backend", "backend\backend.csproj", "{791D8978-4456-4E87-9DCB-E337992D2E0D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {791D8978-4456-4E87-9DCB-E337992D2E0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {791D8978-4456-4E87-9DCB-E337992D2E0D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {791D8978-4456-4E87-9DCB-E337992D2E0D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {791D8978-4456-4E87-9DCB-E337992D2E0D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9FA4EBF3-BCFF-430F-89A0-FE1AFA1F256D} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /backend/backend/Controllers/SettingsController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.UserPreferences; 2 | using backend.Interface; 3 | using backend.Repository; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace backend.Controllers 8 | { 9 | [Route("api/settings")] 10 | [ApiController] 11 | public class SettingsController : ControllerBase 12 | { 13 | private readonly IUserPreferencesRepository _projectService; 14 | public SettingsController(IUserPreferencesRepository projectService) 15 | { 16 | _projectService = projectService; 17 | } 18 | 19 | 20 | [HttpGet("getSettings")] 21 | [Authorize] 22 | public async Task> getSettings() 23 | { 24 | var project = await _projectService.GetSettings(); 25 | return Ok(project); 26 | } 27 | 28 | [HttpPatch("updateSettings")] 29 | [Authorize] 30 | public async Task updateSettings(UserPreferencesDto dto) 31 | { 32 | await _projectService.UpdateUserPreferences(dto); 33 | return Ok(); 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/Section/LanguageSection.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { 3 | Box, 4 | ToggleButton, 5 | ToggleButtonGroup, 6 | Typography, 7 | } from '@mui/material'; 8 | import { Settings } from '~/api/Settings/api'; 9 | import { newSettingsProps } from '../SettingsView'; 10 | 11 | type Props = { 12 | handleClick: (newSettings: newSettingsProps) => void; 13 | settings: Settings | undefined; 14 | }; 15 | 16 | export const LanguageSection = ({ settings, handleClick }: Props) => { 17 | return ( 18 | 19 | 20 | Language: 21 | 22 | 23 | handleClick({ language: 'en' })} 26 | > 27 | English 28 | 29 | handleClick({ language: 'pl' })} 32 | > 33 | Polish 34 | 35 | 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/src/View/Errors/ErrorView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Typography } from '@mui/material'; 2 | import { useRouteError } from 'react-router-dom'; 3 | import BlockIcon from '@mui/icons-material/Block'; 4 | import { Trans } from '@lingui/macro'; 5 | 6 | const handleError = (error: Error | string) => { 7 | if (error instanceof Error) { 8 | return error.message; 9 | } 10 | 11 | return error; 12 | }; 13 | 14 | export const ErrorView = () => { 15 | const error = useRouteError() as string; 16 | 17 | const refresh = () => location.reload(); 18 | 19 | return ( 20 | 31 | 32 | 33 | Unexpected Error: 34 | 35 | {handleError(error)} 36 | 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/src/View/RegisterView/PasswordRenew/EmailSection.tsx: -------------------------------------------------------------------------------- 1 | import { UseFormReturn, useController } from 'react-hook-form'; 2 | import { forgotPasswordModel } from './schema'; 3 | import { Box, TextField } from '@mui/material'; 4 | import { Trans } from '@lingui/macro'; 5 | 6 | type Props = { 7 | onSubmit: (value: forgotPasswordModel) => void; 8 | formContext: UseFormReturn; 9 | }; 10 | 11 | export const EmailSection = ({ onSubmit, formContext }: Props) => { 12 | const { 13 | control, 14 | handleSubmit, 15 | formState: { errors }, 16 | } = formContext; 17 | 18 | const email = useController({ 19 | control: control, 20 | name: 'email', 21 | }); 22 | 23 | return ( 24 | { 28 | onSubmit(data); 29 | })} 30 | > 31 | Email} 33 | ref={email.field.ref} 34 | onChange={email.field.onChange} 35 | onBlur={email.field.onBlur} 36 | name={email.field.name} 37 | error={errors.email !== undefined} 38 | helperText={errors.email?.message} 39 | /> 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /backend/backend/Repository/EmailRepository.cs: -------------------------------------------------------------------------------- 1 | using backend.Interface; 2 | using backend; 3 | using System.Net.Mail; 4 | using System.Net; 5 | 6 | public class EmailRepository : IEmailRepository 7 | { 8 | private readonly EmailSettings _settings; 9 | 10 | public EmailRepository(EmailSettings settings) 11 | { 12 | _settings = settings; 13 | } 14 | 15 | public async Task SendEmailAsync(string email, string subject, string message) 16 | { 17 | if (string.IsNullOrEmpty(_settings.Email)) 18 | { 19 | throw new ArgumentException("Service email not found"); 20 | } 21 | 22 | var mailMessage = new MailMessage(); 23 | mailMessage.From = new MailAddress(_settings.Email); 24 | mailMessage.Subject = subject; 25 | mailMessage.To.Add(new MailAddress(email)); 26 | mailMessage.Body = message; 27 | mailMessage.IsBodyHtml = true; 28 | 29 | var client = new SmtpClient("smtp.gmail.com") 30 | { 31 | Port = 587, 32 | EnableSsl = true, 33 | Credentials = new NetworkCredential(_settings.Email, _settings.Password) 34 | }; 35 | 36 | 37 | 38 | await client.SendMailAsync(mailMessage); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /backend/backend/Migrations/20240708220343_refresh-token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace backend.Migrations 7 | { 8 | /// 9 | public partial class Refreshtoken : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.AddColumn( 15 | name: "RefreshToken", 16 | table: "Users", 17 | type: "nvarchar(max)", 18 | nullable: true); 19 | 20 | migrationBuilder.AddColumn( 21 | name: "RefreshTokenExpiry", 22 | table: "Users", 23 | type: "datetime2", 24 | nullable: true); 25 | } 26 | 27 | /// 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropColumn( 31 | name: "RefreshToken", 32 | table: "Users"); 33 | 34 | migrationBuilder.DropColumn( 35 | name: "RefreshTokenExpiry", 36 | table: "Users"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/backend/Controllers/NotificationController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Notifications; 2 | using backend.Interface; 3 | using backend.Models; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace backend.Controllers 8 | { 9 | [Route("api/notifications")] 10 | [ApiController] 11 | public class NotificationController : ControllerBase 12 | { 13 | private readonly INotificationRepository _notificationService; 14 | public NotificationController(INotificationRepository notificationService) 15 | { 16 | _notificationService = notificationService; 17 | } 18 | 19 | [HttpGet("get")] 20 | [Authorize] 21 | public async Task> GetAllNotes() 22 | { 23 | var notifications = await _notificationService.GetNotifications(); 24 | return Ok(notifications); 25 | } 26 | 27 | [HttpPatch("markAsRead/{notificationId}")] 28 | [Authorize] 29 | public async Task> MarkAsRead([FromRoute] int notificationId) 30 | { 31 | await _notificationService.MarkAsRead(notificationId); 32 | return Ok(); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/component/UserBox/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const userSchema = z 4 | .object({ 5 | username: z.string().min(1, { message: 'Username is required' }), 6 | email: z.string().min(1, { message: 'Email is required' }).email({ message: 'Invalid email address' }), 7 | oldPassword: z.string().min(8, { message: 'Old password must be at least 8 characters' }).optional().or(z.literal('')), 8 | password: z.string().min(8, { message: 'Password must be at least 8 characters' }).optional().or(z.literal('')), 9 | passwordConfirm: z.string().optional(), 10 | }) 11 | .refine((data) => { 12 | if (data.oldPassword && data.oldPassword !== '') { 13 | return data.password && data.passwordConfirm; 14 | } 15 | return true; 16 | }, { 17 | message: 'New password is required', 18 | path: ['password'], 19 | }) 20 | .refine((data) => data.password === data.passwordConfirm, { 21 | message: "Passwords don't match", 22 | path: ['passwordConfirm'], 23 | }); 24 | 25 | export type userModel = z.infer; 26 | 27 | 28 | 29 | export const avatarSchema = z 30 | .object({ 31 | image: z.union([z.instanceof(File), z.null()]), 32 | }) 33 | 34 | export type avatarModel = z.infer; 35 | -------------------------------------------------------------------------------- /backend/backend/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:58289", 8 | "sslPort": 44337 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5246", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7086;http://localhost:5246", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectView/UpdateGroup/CreateForm.tsx: -------------------------------------------------------------------------------- 1 | import { Box, TextField } from '@mui/material'; 2 | import { groupModel } from './schema'; 3 | import { UseFormReturn, useController } from 'react-hook-form'; 4 | import { Trans } from '@lingui/macro'; 5 | 6 | type Props = { 7 | onSubmit: (value: groupModel) => void; 8 | formContext: UseFormReturn; 9 | }; 10 | 11 | export const CreateForm = ({ onSubmit, formContext }: Props) => { 12 | const { 13 | control, 14 | handleSubmit, 15 | formState: { errors }, 16 | } = formContext; 17 | 18 | const groupName = useController({ 19 | control: control, 20 | name: 'groupName', 21 | }); 22 | 23 | return ( 24 | 30 | Group name} 32 | ref={groupName.field.ref} 33 | value={groupName.field.value} 34 | onChange={groupName.field.onChange} 35 | onBlur={groupName.field.onBlur} 36 | name={groupName.field.name} 37 | error={errors.groupName !== undefined} 38 | helperText={errors.groupName?.message} 39 | /> 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /frontend/src/context/GroupRole.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ReactNode, 3 | createContext, 4 | useContext, 5 | useEffect, 6 | useState, 7 | } from 'react'; 8 | import { GroupRole } from '~/api/Group/api'; 9 | import { isAdmin } from '~/utils/useGroupRoles'; 10 | 11 | interface IGroupContext { 12 | userRole: GroupRole; 13 | isGranted: boolean; 14 | isModerator: boolean; 15 | setUserRole: (role: GroupRole) => void; 16 | } 17 | 18 | const GroupContext = createContext({ 19 | userRole: GroupRole.User, 20 | isGranted: false, 21 | isModerator: false, 22 | setUserRole: () => {}, 23 | }); 24 | 25 | export const useGroupContext = () => useContext(GroupContext); 26 | 27 | type Props = { children: ReactNode }; 28 | 29 | export const GroupProvider = ({ children }: Props) => { 30 | const [userRole, setUserRole] = useState(GroupRole.User); 31 | const [isGranted, setIsGranted] = useState(false); 32 | const [isModerator, setIsModerator] = useState(false); 33 | 34 | useEffect(() => { 35 | setIsGranted(isAdmin(userRole)); 36 | setIsModerator(userRole >= 1 ? true : false); 37 | }, [userRole]); 38 | 39 | return ( 40 | 43 | {children} 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/src/api/Dashboard/query.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import { 3 | queryEventsDashboard, 4 | queryGroupProjectsDashboard, 5 | queryNotesDashboard, 6 | queryProjectsDashboard, 7 | queryTodoDashboard, 8 | } from './api'; 9 | 10 | export const getTodoDashboard = () => { 11 | return useQuery({ 12 | queryKey: ['todoDashboard'], 13 | queryFn: queryTodoDashboard, 14 | }); 15 | }; 16 | 17 | export const getEventsDashboard = (maxEvents: number = 5) => { 18 | return useQuery({ 19 | queryKey: ['eventsDashboard', maxEvents], 20 | queryFn: () => queryEventsDashboard(maxEvents), 21 | }); 22 | }; 23 | 24 | export const getNotesDashboard = (maxNotes: number = 5) => { 25 | return useQuery({ 26 | queryKey: ['notesDashboard', maxNotes], 27 | queryFn: () => queryNotesDashboard(maxNotes), 28 | }); 29 | }; 30 | 31 | export const getProjectsDashboard = (daysThreshold: number = 7) => { 32 | return useQuery({ 33 | queryKey: ['projectsDashboard', daysThreshold], 34 | queryFn: () => queryProjectsDashboard(daysThreshold), 35 | }); 36 | }; 37 | 38 | export const getGroupProjectsDashboard = (daysThreshold: number = 7) => { 39 | return useQuery({ 40 | queryKey: ['GroupProjectsDashboard', daysThreshold], 41 | queryFn: () => queryGroupProjectsDashboard(daysThreshold), 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/Section/RouteSection.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { Box, Checkbox, Typography } from '@mui/material'; 3 | import { Settings } from '~/api/Settings/api'; 4 | import { newSettingsProps } from '../SettingsView'; 5 | 6 | type CheckboxProps = { 7 | name: string; 8 | checked: boolean; 9 | onClick: () => void; 10 | }; 11 | 12 | const CheckboxOption = ({ name, checked, onClick }: CheckboxProps) => ( 13 | 14 | 15 | {name} 16 | 17 | ); 18 | 19 | type Props = { 20 | handleClick: (newSettings: newSettingsProps) => void; 21 | settings: Settings | undefined; 22 | }; 23 | 24 | export const RouteSection = ({ settings, handleClick }: Props) => { 25 | return ( 26 | <> 27 | 28 | Routes: 29 | 30 | {Object.entries(settings!.routes).map(([routeName, isChecked]) => ( 31 | 36 | handleClick({ 37 | routes: { ...settings!.routes, [routeName]: !isChecked }, 38 | }) 39 | } 40 | /> 41 | ))} 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /frontend/src/View/HomeView/HomeView.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from '@mui/material'; 2 | import { getMyAccount } from '~/api/User/query'; 3 | import { Clock } from '~/component/Clock'; 4 | import { NotificationBox } from '~/component/NotificationBox'; 5 | import { UserBox } from '~/component/UserBox'; 6 | import { LoadingView } from '../LoadingView/LoadingView'; 7 | 8 | export const HomeView = () => { 9 | const today = new Date().toLocaleDateString(); 10 | const { data: user, isLoading } = getMyAccount(); 11 | 12 | if (isLoading || user === undefined) { 13 | return ; 14 | } 15 | 16 | return ( 17 | 25 | 31 | 32 | 33 | {today} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/src/View/NotesView/NotesView.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { Main } from '~/component/Main'; 3 | import { useContext, useRef, useState } from 'react'; 4 | import { debounce } from 'lodash'; 5 | import { NotePreview } from './NotePreview'; 6 | import { NoNoteView } from './NoNoteView'; 7 | import { NotesDrawer } from './NotesDrawer'; 8 | import { PdfContext } from '~/context/PdfContext'; 9 | 10 | export const NotesView = () => { 11 | const { selectedNote, setSelectedNote } = useContext(PdfContext); 12 | const pageNumber = 1; 13 | const [filter, setFilter] = useState(''); 14 | const pageSize = 5; 15 | 16 | const handleFilter = useRef( 17 | debounce( 18 | (event: React.ChangeEvent) => 19 | setFilter(event.target.value), 20 | 500, 21 | ), 22 | ).current; 23 | 24 | return ( 25 |
26 | 33 | 34 | {selectedNote ? ( 35 | 36 | ) : ( 37 | 38 | )} 39 | 40 |
41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /frontend/src/View/CalendarView/MonthView/EventChip.tsx: -------------------------------------------------------------------------------- 1 | import { Paper, Typography, alpha } from '@mui/material'; 2 | import { DialogEdit } from '../DialogEdit'; 3 | import { useState } from 'react'; 4 | import { CalendarEvent } from '~/api/Calendar/api'; 5 | 6 | type Props = { 7 | event: CalendarEvent; 8 | }; 9 | 10 | export const EventChip = ({ event }: Props) => { 11 | const [openEdit, setOpenEdit] = useState(false); 12 | 13 | const handleClickOpenEdit = () => { 14 | setOpenEdit(true); 15 | }; 16 | 17 | return ( 18 | <> 19 | ({ 21 | backgroundColor: alpha( 22 | event.eventColor, 23 | theme.palette.action.disabledOpacity, 24 | ), 25 | borderLeft: '5px solid', 26 | borderColor: event.eventColor, 27 | padding: 0.5, 28 | cursor: 'pointer', 29 | })} 30 | onClick={handleClickOpenEdit} 31 | > 32 | 39 | {event.title} 40 | 41 | 42 | 48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/Section/ColorSection.tsx: -------------------------------------------------------------------------------- 1 | import CheckIcon from '@mui/icons-material/Check'; 2 | import { Box, ClassNameMap, Paper, Tooltip } from '@mui/material'; 3 | import { Settings } from '~/api/Settings/api'; 4 | import { newSettingsProps } from '../SettingsView'; 5 | 6 | type Props = { 7 | name: string; 8 | value: string; 9 | color: string; 10 | classes: ClassNameMap<'paper' | 'box'>; 11 | handleClick: (newSettings: newSettingsProps) => void; 12 | settings: Settings | undefined; 13 | }; 14 | 15 | export const ColorSection = ({ 16 | name, 17 | value, 18 | color, 19 | classes, 20 | settings, 21 | handleClick, 22 | }: Props) => { 23 | return ( 24 | 25 | 26 | handleClick({ color: value })} 32 | > 33 | {settings?.color === value && ( 34 | ({ 36 | color: theme.palette.getContrastText(color), 37 | stroke: theme.palette.getContrastText(color), 38 | strokeWidth: 2, 39 | })} 40 | /> 41 | )} 42 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /backend/backend/Models/User.cs: -------------------------------------------------------------------------------- 1 | using backend.Enums; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace backend.Models 5 | { 6 | public class User 7 | { 8 | public int UserID { get; set; } 9 | public string? RefreshToken { get; set; } 10 | public DateTime? RefreshTokenExpiry { get; set; } 11 | public string? VerificationToken { get; set; } 12 | public string? PasswordResetToken { get; set; } 13 | public DateTime? PasswordResetTime { get; set; } 14 | public required string Username { get; set; } 15 | public required string Email { get; set; } 16 | public required string Password { get; set; } 17 | public Roles Role { get; set; } 18 | public int? GroupID { get; set; } 19 | public string? Image { get; set; } 20 | 21 | public virtual UserPreferences? Preferences { get; set; } 22 | public virtual Group? Group { get; set; } 23 | public virtual ICollection? Projects { get; set; } 24 | public virtual ICollection? Notes { get; set; } 25 | public virtual ICollection? CalendarEvents { get; set; } 26 | public virtual ICollection? Notifications { get; set; } 27 | public virtual ICollection? KanbanTasks { get; set; } 28 | public virtual ICollection? ProjectTodos { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/View/DashboardView/Sections/TodoSection.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { Box, Paper, Typography } from '@mui/material'; 3 | import { Gauge } from '@mui/x-charts/Gauge'; 4 | import { getTodoDashboard } from '~/api/Dashboard/query'; 5 | import { LoadingView } from '~/View/LoadingView/LoadingView'; 6 | 7 | export const TodoSection = () => { 8 | const { data, isLoading } = getTodoDashboard(); 9 | 10 | if (!data || isLoading) return ; 11 | 12 | return ( 13 | 24 | 25 | Todo projects 26 | 27 | 33 | 39 | 40 | {data.totalTodos} Total toods 41 | 42 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /backend/dockerfile: -------------------------------------------------------------------------------- 1 | # See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | # This stage is used when running from VS in fast mode (Default for Debug configuration) 4 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base 5 | USER app 6 | WORKDIR /app 7 | EXPOSE 8080 8 | EXPOSE 8081 9 | 10 | # Install the necessary library for SQL Server connections 11 | USER root 12 | #RUN apt-get update && apt-get install -y libgssapi-krb5-2 13 | 14 | # This stage is used to build the service project 15 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 16 | ARG BUILD_CONFIGURATION=Release 17 | WORKDIR /src 18 | COPY ["backend/backend.csproj", "backend/"] 19 | RUN dotnet restore "./backend/backend.csproj" 20 | COPY . . 21 | WORKDIR "/src/backend" 22 | RUN dotnet build "./backend.csproj" -c $BUILD_CONFIGURATION -o /app/build 23 | 24 | # This stage is used to publish the service project to be copied to the final stage 25 | FROM build AS publish 26 | ARG BUILD_CONFIGURATION=Release 27 | RUN dotnet publish "./backend.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false 28 | 29 | # This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) 30 | FROM base AS final 31 | WORKDIR /app 32 | COPY --from=publish /app/publish . 33 | ENTRYPOINT ["dotnet", "backend.dll"] -------------------------------------------------------------------------------- /frontend/src/api/Notifications/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | 3 | interface GroupProjectNotification { 4 | notificationID: number; 5 | message: string; 6 | dateTime: Date; 7 | isActive: boolean; 8 | isRead: boolean; 9 | groupProjectID: number; 10 | } 11 | 12 | interface ProjectNotification { 13 | notificationID: number; 14 | message: string; 15 | dateTime: Date; 16 | isActive: boolean; 17 | isRead: boolean; 18 | projectID: number; 19 | } 20 | 21 | interface CalendarEventNotification { 22 | notificationID: number; 23 | message: string; 24 | dateTime: Date; 25 | isActive: boolean; 26 | isRead: boolean; 27 | calendarEventID: number; 28 | } 29 | 30 | export type NotificationRow = { 31 | notificationID: number; 32 | message: string; 33 | dateTime: Date; 34 | isActive: boolean; 35 | isRead: boolean; 36 | } 37 | 38 | interface Notifications { 39 | groupProjectNotifications: GroupProjectNotification[]; 40 | projectNotifications: ProjectNotification[]; 41 | calendarEventNotifications: CalendarEventNotification[]; 42 | } 43 | 44 | 45 | export const queryAllNotifications = async () => { 46 | const response = await axiosInstance.get('/api/notifications/get'); 47 | return response.data as Notifications; 48 | }; 49 | 50 | 51 | export const patchMarkAsRead = async (id: number) => { 52 | return await axiosInstance.patch(`/api/notifications/markAsRead/${id}`); 53 | }; 54 | -------------------------------------------------------------------------------- /backend/backend/Controllers/ImageController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Images; 2 | using backend.Dto.Todos; 3 | using backend.Interface; 4 | using backend.Repository; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace backend.Controllers 9 | { 10 | [Route("api/images")] 11 | [ApiController] 12 | public class ImageController : ControllerBase 13 | { 14 | private readonly IImageRepository _imageService; 15 | public ImageController(IImageRepository imageService) 16 | { 17 | _imageService = imageService; 18 | } 19 | 20 | [HttpGet("{id}")] 21 | public IActionResult GetImage(string id) 22 | { 23 | var image = _imageService.Get(id); 24 | return File(image, "application/octet-stream", id); 25 | } 26 | 27 | [HttpPost] 28 | public async Task PostImage([FromForm] AddImageDto dto) 29 | { 30 | if (dto.Image == null) 31 | { 32 | return BadRequest("Invalid iamge"); 33 | } 34 | 35 | await _imageService.SaveAsync(dto.Image); 36 | return Ok(); 37 | } 38 | [HttpDelete("{id}")] 39 | public async Task DeleteImage(string id) 40 | { 41 | await _imageService.DeleteAvatar(id); 42 | return Ok(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/Section/ReminderSection.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { 3 | Box, 4 | ToggleButton, 5 | ToggleButtonGroup, 6 | Typography, 7 | } from '@mui/material'; 8 | import { Settings } from '~/api/Settings/api'; 9 | import { newSettingsProps } from '../SettingsView'; 10 | 11 | type Props = { 12 | handleClick: (newSettings: newSettingsProps) => void; 13 | settings: Settings | undefined; 14 | }; 15 | 16 | export const ReminderSection = ({ settings, handleClick }: Props) => { 17 | return ( 18 | <> 19 | 20 | 21 | Reminder: 22 | 23 | 24 | handleClick({ reminder: 3 })}> 25 | 3 Days 26 | 27 | handleClick({ reminder: 1 })}> 28 | 1 Day 29 | 30 | handleClick({ reminder: 0 })}> 31 | None 32 | 33 | 34 | 35 | 36 | Applied changes will be visible tomorrow 37 | 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/View/SettingsView/Section/ThemeSection.tsx: -------------------------------------------------------------------------------- 1 | import CheckIcon from '@mui/icons-material/Check'; 2 | import { Box, ClassNameMap, Paper, Tooltip } from '@mui/material'; 3 | import { Settings } from '~/api/Settings/api'; 4 | import { newSettingsProps } from '../SettingsView'; 5 | 6 | type Props = { 7 | name: string; 8 | value: string; 9 | color: string; 10 | classes: ClassNameMap<'paper' | 'box'>; 11 | handleClick: (newSettings: newSettingsProps) => void; 12 | settings: Settings | undefined; 13 | }; 14 | 15 | export const ThemeSection = ({ 16 | name, 17 | value, 18 | color, 19 | classes, 20 | settings, 21 | handleClick, 22 | }: Props) => { 23 | return ( 24 | 25 | 26 | handleClick({ theme: value })} 32 | > 33 | {settings?.theme === value && ( 34 | ({ 36 | color: theme.palette.getContrastText(color), 37 | stroke: theme.palette.getContrastText( 38 | theme.palette.background.default, 39 | ), 40 | strokeWidth: 2, 41 | })} 42 | /> 43 | )} 44 | 45 | 46 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /frontend/src/api/Calendar/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | 3 | export interface mutateEvent { 4 | title: string; 5 | description: string; 6 | dateTime: string; 7 | eventColor: string; 8 | } 9 | 10 | export interface EditEvent { 11 | eventId: number; 12 | event: mutateEvent; 13 | } 14 | 15 | export interface CalendarEvent { 16 | eventID: number; 17 | title: string; 18 | description: string; 19 | dateTime: string; 20 | eventColor: string; 21 | } 22 | 23 | export interface EventPagination { 24 | from: string; 25 | to: string; 26 | colors: string 27 | } 28 | 29 | export const queryEventBetween = async (pagination: EventPagination) => { 30 | const response = await axiosInstance.get( 31 | `/api/calendar/getEventBetween`,{ 32 | params:{ 33 | from: pagination.from, 34 | to: pagination.to, 35 | colors: pagination.colors 36 | } 37 | } 38 | ); 39 | return response.data as CalendarEvent[]; 40 | }; 41 | 42 | export const postAddEvent = async (event: mutateEvent) => { 43 | return await axiosInstance.post('/api/calendar/addEvent', event); 44 | }; 45 | 46 | export const patchEditEvent = async (event: EditEvent) => { 47 | return await axiosInstance.patch( 48 | `/api/calendar/updateEvent/${event.eventId}`, 49 | event.event, 50 | ); 51 | }; 52 | 53 | export const deleteEvent = async (eventId: number) => { 54 | return await axiosInstance.delete(`/api/calendar/deleteEvent/${eventId}`); 55 | }; 56 | -------------------------------------------------------------------------------- /frontend/src/context/CalendarContext.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import { 3 | Dispatch, 4 | ReactNode, 5 | createContext, 6 | useContext, 7 | useState, 8 | } from 'react'; 9 | 10 | interface CalendarContextProps { 11 | monthAsNumber: number; 12 | setMonthAsNumber: Dispatch; 13 | weekAsNumber: number; 14 | setWeekAsNumber: Dispatch; 15 | colors: { [color: string]: string }; 16 | } 17 | 18 | const CalendarContext = createContext( 19 | undefined, 20 | ); 21 | 22 | type Props = { colors: { [color: string]: string }; children: ReactNode }; 23 | 24 | export const CalendarProvider = ({ colors, children }: Props) => { 25 | const [monthAsNumber, setMonthAsNumber] = useState(dayjs().month()); 26 | const [weekAsNumber, setWeekAsNumber] = useState( 27 | //TODO: validate if +1 always returns valid week or temporary fix 28 | dayjs().diff(dayjs().startOf('month'), 'week') + 1, 29 | ); 30 | 31 | return ( 32 | 41 | {children} 42 | 43 | ); 44 | }; 45 | 46 | export const useCalendar = () => { 47 | const context = useContext(CalendarContext); 48 | if (!context) { 49 | throw new Error('useCalendar must be used within its Provider'); 50 | } 51 | return context; 52 | }; 53 | -------------------------------------------------------------------------------- /backend/backend/Dto/Validators/RegisterUserDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using backend.Data; 2 | using backend.Dto.Users; 3 | using FluentValidation; 4 | using System; 5 | 6 | namespace backend.Dto.Validators 7 | { 8 | public class RegisterUserDtoValidator : AbstractValidator 9 | { 10 | 11 | 12 | public RegisterUserDtoValidator(DataContext dbContext) 13 | { 14 | RuleFor(x => x.Email) 15 | .EmailAddress() 16 | .NotEmpty(); 17 | RuleFor(x => x.Password) 18 | .NotEmpty() 19 | .MinimumLength(8); 20 | RuleFor(x => x.Password) 21 | .Equal(y => y.PasswordConfirm); 22 | RuleFor(x => x.Username) 23 | .Custom((value, context) => 24 | { 25 | var usernameInUse = dbContext.Users.Any(x => x.Username == value); 26 | if(usernameInUse) 27 | { 28 | context.AddFailure("Username", "This username is already taken"); 29 | } 30 | }); 31 | RuleFor(x => x.Email) 32 | .Custom((value, context) => 33 | { 34 | var emailInUse = dbContext.Users.Any(x => x.Email == value); 35 | if (emailInUse) 36 | { 37 | context.AddFailure("Email", "This email is taken"); 38 | } 39 | }); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/TodoPreview.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography, alpha } from '@mui/material'; 2 | import { LoadingView } from '../LoadingView/LoadingView'; 3 | import { getProjectTodoById } from '~/api/ProjectTodos/query'; 4 | import { DialogCreate } from './TodoTask/DialogCreate'; 5 | import { TodoCard } from '~/component/TodoCard'; 6 | 7 | type Props = { 8 | projectId: number; 9 | }; 10 | 11 | export const TodoPreview = ({ projectId }: Props) => { 12 | const { data: project, isLoading } = getProjectTodoById(projectId); 13 | 14 | if (isLoading || project === undefined) { 15 | return ; 16 | } 17 | 18 | return ( 19 | 28 | 34 | {project.title} 35 | 36 | 37 | 45 | {project.todos.map((todo) => ( 46 | 47 | ))} 48 | 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /backend/backend/Dto/Notifications/NotificationDto.cs: -------------------------------------------------------------------------------- 1 | namespace backend.Dto.Notifications 2 | { 3 | public class NotificationDto 4 | { 5 | public List? GroupProjectNotifications { get; set; } 6 | public List? ProjectNotifications { get; set; } 7 | public List? CalendarEventNotifications { get; set; } 8 | } 9 | 10 | public class GroupProjectNotificationDto 11 | { 12 | public int NotificationID { get; set; } 13 | public required string Message { get; set; } 14 | public DateTime DateTime { get; set; } 15 | public bool isActive { get; set; } 16 | public bool isRead { get; set; } 17 | public int GroupProjectID { get; set; } 18 | } 19 | 20 | public class ProjectNotificationDto 21 | { 22 | public int NotificationID { get; set; } 23 | public required string Message { get; set; } 24 | public DateTime DateTime { get; set; } 25 | public bool isActive { get; set; } 26 | public bool isRead { get; set; } 27 | public int ProjectID { get; set; } 28 | } 29 | 30 | public class CalendarEventNotificationDto 31 | { 32 | public int NotificationID { get; set; } 33 | public required string Message { get; set; } 34 | public DateTime DateTime { get; set; } 35 | public bool isActive { get; set; } 36 | public bool isRead { get; set; } 37 | public int CalendarEventID { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/context/AuthContext.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, createContext, useContext, useState } from 'react'; 2 | import { queryClient } from '~/api/api'; 3 | import { AUTH_TOKEN, REFRESH_TOKEN } from '~/config/constants'; 4 | 5 | interface AuthContextProps { 6 | isAuthenticated: boolean; 7 | login: (accessToken: string, refreshToken: string) => void; 8 | logout: () => void; 9 | } 10 | 11 | const AuthContext = createContext(undefined); 12 | 13 | type Props = { children: ReactNode }; 14 | 15 | export const AuthProvider = ({ children }: Props) => { 16 | const [isAuthenticated, setIsAuthenticated] = useState(() => { 17 | return ( 18 | localStorage.getItem(AUTH_TOKEN) !== null && 19 | localStorage.getItem(REFRESH_TOKEN) !== null 20 | ); 21 | }); 22 | 23 | const login = (accessToken: string, refreshToken: string) => { 24 | localStorage.setItem(AUTH_TOKEN, accessToken); 25 | localStorage.setItem(REFRESH_TOKEN, refreshToken); 26 | setIsAuthenticated(true); 27 | }; 28 | 29 | const logout = () => { 30 | localStorage.removeItem(AUTH_TOKEN); 31 | localStorage.removeItem(REFRESH_TOKEN); 32 | setIsAuthenticated(false); 33 | queryClient.removeQueries(); 34 | }; 35 | 36 | return ( 37 | 38 | {children} 39 | 40 | ); 41 | }; 42 | 43 | export const useAuth = () => { 44 | const context = useContext(AuthContext); 45 | if (!context) { 46 | throw new Error('useAuth must be used within its Provider'); 47 | } 48 | return context; 49 | }; 50 | -------------------------------------------------------------------------------- /frontend/src/api/Todos/query.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQueryClient } from '@tanstack/react-query'; 2 | import { useSnackbar } from 'notistack'; 3 | import { 4 | AddTodo, 5 | ToggleTodo, 6 | patchToggleTodo, 7 | postAddTodo, 8 | postDeleteTodo, 9 | } from './api'; 10 | import { t } from '@lingui/macro'; 11 | 12 | export const mutateAddTodo = () => { 13 | const queryClient = useQueryClient(); 14 | const { enqueueSnackbar } = useSnackbar(); 15 | return useMutation({ 16 | mutationKey: ['addTodo'], 17 | mutationFn: (todo: AddTodo) => postAddTodo(todo), 18 | onSuccess: () => { 19 | enqueueSnackbar(t({message:'Task added'})); 20 | queryClient.invalidateQueries({ queryKey: ['projectTodoById'] }); 21 | }, 22 | }); 23 | }; 24 | 25 | export const mutateToggleTodo = () => { 26 | const queryClient = useQueryClient(); 27 | return useMutation({ 28 | mutationKey: ['toggleTodo'], 29 | mutationFn: (todo: ToggleTodo) => patchToggleTodo(todo), 30 | onSuccess: () => { 31 | queryClient.invalidateQueries({ queryKey: ['projectTodoById'] }); 32 | queryClient.invalidateQueries({ queryKey: ['projectTodos'] }); 33 | }, 34 | }); 35 | }; 36 | 37 | export const deleteTodo = () => { 38 | const queryClient = useQueryClient(); 39 | const { enqueueSnackbar } = useSnackbar(); 40 | return useMutation({ 41 | mutationKey: ['deleteTodo'], 42 | mutationFn: (todoID: number) => postDeleteTodo(todoID), 43 | onSuccess: () => { 44 | enqueueSnackbar(t({message:'Task deleted'})); 45 | queryClient.invalidateQueries({ queryKey: ['projectTodoById'] }); 46 | }, 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /frontend/src/View/ProjectView/ProjectView.tsx: -------------------------------------------------------------------------------- 1 | import { AppBar, Box, Toolbar, Typography } from '@mui/material'; 2 | import { DialogCreate } from './DialogCreate'; 3 | import { SearchField } from '~/component/SearchField'; 4 | import { Trans, t } from '@lingui/macro'; 5 | import { useRef, useState } from 'react'; 6 | import { debounce } from 'lodash'; 7 | import { ProjectList } from './ProjectList'; 8 | 9 | export const ProjectView = () => { 10 | const [pageNumber, setPageNumber] = useState(1); 11 | const [filter, setFilter] = useState(''); 12 | const pageSize = 5; 13 | 14 | const onPageChange = (_: React.ChangeEvent, value: number) => { 15 | setPageNumber(value); 16 | }; 17 | 18 | const handleFilter = useRef( 19 | debounce((event: React.ChangeEvent) => { 20 | setFilter(event.target.value); 21 | setPageNumber(1); 22 | }, 500), 23 | ).current; 24 | 25 | return ( 26 | 27 | 28 | 29 | 30 | Projects 31 | 32 | 36 | 37 | 38 | 39 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/src/View/CalendarView/WeekView/WeekEventChip.tsx: -------------------------------------------------------------------------------- 1 | import { Paper, Typography, alpha } from '@mui/material'; 2 | import { DialogEdit } from '../DialogEdit'; 3 | import { useState } from 'react'; 4 | import { CalendarEvent } from '~/api/Calendar/api'; 5 | 6 | type Props = { 7 | event: CalendarEvent; 8 | }; 9 | 10 | export const WeekEventChip = ({ event }: Props) => { 11 | const [openEdit, setOpenEdit] = useState(false); 12 | 13 | const handleClickOpenEdit = () => { 14 | setOpenEdit(true); 15 | }; 16 | 17 | return ( 18 | <> 19 | ({ 21 | backgroundColor: alpha( 22 | event.eventColor, 23 | theme.palette.action.disabledOpacity, 24 | ), 25 | borderLeft: '5px solid', 26 | borderColor: event.eventColor, 27 | padding: 0.5, 28 | cursor: 'pointer', 29 | })} 30 | onClick={handleClickOpenEdit} 31 | > 32 | 39 | {event.title} 40 | 41 | 48 | {event.description} 49 | 50 | 51 | 57 | 58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectView/ProjectTab.tsx: -------------------------------------------------------------------------------- 1 | import { Trans, t } from '@lingui/macro'; 2 | import { Box, Typography } from '@mui/material'; 3 | import { debounce } from 'lodash'; 4 | import { useRef, useState } from 'react'; 5 | import { SearchField } from '~/component/SearchField'; 6 | import { ProjectList } from './ProjectList'; 7 | 8 | export const ProjectTab = () => { 9 | const [pageNumber, setPageNumber] = useState(1); 10 | const [filter, setFilter] = useState(''); 11 | const pageSize = 5; 12 | 13 | const onPageChange = (_: React.ChangeEvent, value: number) => { 14 | setPageNumber(value); 15 | }; 16 | 17 | const handleFilter = useRef( 18 | debounce((event: React.ChangeEvent) => { 19 | setFilter(event.target.value); 20 | setPageNumber(1); 21 | }, 500), 22 | ).current; 23 | 24 | return ( 25 | <> 26 | 33 | 34 | Explore projects 35 | 36 | 37 | 41 | 42 | 43 | 48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /backend/backend/Migrations/20241106171010_calendar_colors.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace backend.Migrations 6 | { 7 | /// 8 | public partial class calendar_colors : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AlterColumn( 14 | name: "VerificationToken", 15 | table: "Users", 16 | type: "nvarchar(max)", 17 | nullable: true, 18 | oldClrType: typeof(string), 19 | oldType: "nvarchar(max)"); 20 | 21 | migrationBuilder.AddColumn( 22 | name: "Colors", 23 | table: "UserPreferences", 24 | type: "nvarchar(max)", 25 | nullable: false, 26 | defaultValue: ""); 27 | } 28 | 29 | /// 30 | protected override void Down(MigrationBuilder migrationBuilder) 31 | { 32 | migrationBuilder.DropColumn( 33 | name: "Colors", 34 | table: "UserPreferences"); 35 | 36 | migrationBuilder.AlterColumn( 37 | name: "VerificationToken", 38 | table: "Users", 39 | type: "nvarchar(max)", 40 | nullable: false, 41 | defaultValue: "", 42 | oldClrType: typeof(string), 43 | oldType: "nvarchar(max)", 44 | oldNullable: true); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/Theme/Color.ts: -------------------------------------------------------------------------------- 1 | import { PaletteColorOptions } from '@mui/material'; 2 | 3 | export const blue: PaletteColorOptions = { 4 | '50': '#E3F2FD', 5 | '100': '#BBDEFB', 6 | '200': '#90CAF9', 7 | '300': '#64B5F6', 8 | '400': '#42A5F5', 9 | '500': '#2196F3', 10 | '600': '#1E88E5', 11 | '700': '#1976D2', 12 | '800': '#1565C0', 13 | '900': '#0D47A1', 14 | A100: '#82B1FF', 15 | A200: '#448AFF', 16 | A400: '#2979FF', 17 | A700: '#2962FF', 18 | }; 19 | 20 | export const purple: PaletteColorOptions = { 21 | '50': '#F8EBFA', 22 | '100': '#F1D4F3', 23 | '200': '#E4AAE8', 24 | '300': '#D680DD', 25 | '400': '#C966D2', 26 | '500': '#BC4CC7', 27 | '600': '#B042BA', 28 | '700': '#A238AD', 29 | '800': '#9430A0', 30 | '900': '#832588', 31 | A100: '#F4D6FF', 32 | A200: '#E3A8FF', 33 | A400: '#D27AFF', 34 | A700: '#C24CFF', 35 | }; 36 | 37 | export const green: PaletteColorOptions = { 38 | '50': '#E0F2E9', 39 | '100': '#B3DEC3', 40 | '200': '#80C99D', 41 | '300': '#4DB578', 42 | '400': '#26A458', 43 | '500': '#00963A', 44 | '600': '#008932', 45 | '700': '#00792A', 46 | '800': '#006A22', 47 | '900': '#004F13', 48 | A100: '#A5FFB8', 49 | A200: '#78FF99', 50 | A400: '#4BFF7A', 51 | A700: '#1EFF5B', 52 | }; 53 | 54 | export const red: PaletteColorOptions = { 55 | '50': '#FFEBEE', 56 | '100': '#FFCDD2', 57 | '200': '#EF9A9A', 58 | '300': '#E57373', 59 | '400': '#EF5350', 60 | '500': '#F44336', 61 | '600': '#E53935', 62 | '700': '#D32F2F', 63 | '800': '#C62828', 64 | '900': '#B71C1C', 65 | A100: '#FF8A80', 66 | A200: '#FF5252', 67 | A400: '#FF1744', 68 | A700: '#D50000', 69 | }; 70 | -------------------------------------------------------------------------------- /frontend/src/View/DashboardView/DashboardView.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { Suspense } from 'react'; 3 | import { getIsInGroup } from '~/api/Group/query'; 4 | import { getSettings } from '~/api/Settings/query'; 5 | import { LoadingView } from '../LoadingView/LoadingView'; 6 | import { EventSection } from './Sections/EventSection'; 7 | import { GroupProjectSection } from './Sections/GroupProjectSection'; 8 | import { NoteSection } from './Sections/NoteSection'; 9 | import { ProjectSection } from './Sections/ProjectSection'; 10 | import { TodoSection } from './Sections/TodoSection'; 11 | 12 | export const DashboardView = () => { 13 | const { data: group, isLoading: isLoadingGroup } = getIsInGroup(); 14 | const { data: settings, isLoading } = getSettings(); 15 | 16 | if (!settings || isLoading || isLoadingGroup || group === undefined) 17 | return ; 18 | 19 | const { routes } = settings; 20 | 21 | return ( 22 | 29 | }> 30 | 37 | {routes['Todo'] && } 38 | {routes['Calendar'] && } 39 | {routes['Notes'] && } 40 | {routes['Projects'] && } 41 | {routes['Group Projects'] && group !== false && ( 42 | 43 | )} 44 | 45 | 46 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/TodoView.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { Main } from '~/component/Main'; 3 | import { useRef, useState } from 'react'; 4 | import { TodoPreview } from './TodoPreview'; 5 | import { ProjectTodo } from '~/api/ProjectTodos/api'; 6 | import { debounce } from 'lodash'; 7 | import { NoTodoView } from './NoTodoView'; 8 | import { TodoDrawer } from './TodoDrawer'; 9 | 10 | export enum ViewMode { 11 | unfinished, 12 | completed, 13 | } 14 | 15 | export const TodoView = () => { 16 | const [viewMode, setViewMode] = useState(ViewMode.unfinished); 17 | const [selectedProject, setSelectedProject] = useState< 18 | ProjectTodo | undefined 19 | >(undefined); 20 | 21 | const pageNumber = 1; 22 | const [filter, setFilter] = useState(''); 23 | const pageSize = 5; 24 | 25 | const handleFilter = useRef( 26 | debounce( 27 | (event: React.ChangeEvent) => 28 | setFilter(event.target.value), 29 | 500, 30 | ), 31 | ).current; 32 | 33 | return ( 34 |
35 | 44 | 45 | {selectedProject ? ( 46 | 50 | ) : ( 51 | 52 | )} 53 | 54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /frontend/src/component/ProjectMenu/ProjectMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Menu from '@mui/material/Menu'; 3 | import MenuItem from '@mui/material/MenuItem'; 4 | import { IconButton } from '@mui/material'; 5 | import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; 6 | import CloseIcon from '@mui/icons-material/Close'; 7 | import { ProjectDelete } from '~/component/ProjectDeleteDialog'; 8 | import { DialogEdit } from '~/View/ProjectView/DialogEdit'; 9 | import { Project } from '~/api/Projects/api'; 10 | import { deleteProject } from '~/api/Projects/query'; 11 | 12 | type Props = { 13 | project: Project; 14 | }; 15 | 16 | export const ProjectMenu = ({ project }: Props) => { 17 | const [anchorEl, setAnchorEl] = React.useState(null); 18 | const { mutateAsync } = deleteProject(); 19 | const open = Boolean(anchorEl); 20 | const handleClick = (event: React.MouseEvent) => { 21 | setAnchorEl(event.currentTarget); 22 | }; 23 | const handleClose = () => { 24 | setAnchorEl(null); 25 | }; 26 | 27 | return ( 28 |
29 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /frontend/src/component/GroupProjectMenu/GroupProjectMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Menu from '@mui/material/Menu'; 3 | import MenuItem from '@mui/material/MenuItem'; 4 | import { IconButton } from '@mui/material'; 5 | import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; 6 | import CloseIcon from '@mui/icons-material/Close'; 7 | import { ProjectDelete } from '~/component/ProjectDeleteDialog'; 8 | import { GroupProject } from '~/api/Group/api'; 9 | import { deleteGroupProject } from '~/api/GroupProjects/query'; 10 | import { DialogEdit } from '~/View/GroupView/GroupProjectView/DialogEdit'; 11 | 12 | type Props = { 13 | project: GroupProject; 14 | }; 15 | 16 | export const GroupProjectMenu = ({ project }: Props) => { 17 | const [anchorEl, setAnchorEl] = React.useState(null); 18 | const { mutateAsync } = deleteGroupProject(); 19 | const open = Boolean(anchorEl); 20 | const handleClick = (event: React.MouseEvent) => { 21 | setAnchorEl(event.currentTarget); 22 | }; 23 | const handleClose = () => { 24 | setAnchorEl(null); 25 | }; 26 | 27 | return ( 28 |
29 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /frontend/src/api/Notes/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | import { PaginationRequest, PaginationResponse } from '../pagination'; 3 | 4 | export interface Note { 5 | noteID: number; 6 | title: string; 7 | content: string; 8 | createdAt: string; 9 | } 10 | 11 | type EditNote = { 12 | noteId: number; 13 | note: editProps; 14 | }; 15 | type editProps = { 16 | title: string; 17 | content: string; 18 | }; 19 | 20 | export const queryAllNotes = async (pagination : PaginationRequest) => { 21 | const response = await axiosInstance.get('/api/notes/getAllNotes',{ 22 | params:{ 23 | pageNumber: pagination.pageNumber, 24 | pageSize: pagination.pageSize, 25 | filter: pagination.filter 26 | } 27 | }); 28 | return response.data as PaginationResponse; 29 | }; 30 | 31 | export const postAddNote = async () => { 32 | return await axiosInstance.post('/api/notes/addNote'); 33 | }; 34 | 35 | export const queryNoteByID = async (noteId: number) => { 36 | const response = await axiosInstance.get(`/api/notes/getNoteById/${noteId}`); 37 | return response.data as Note; 38 | }; 39 | 40 | export const editNoteById = async (note: EditNote) => { 41 | return await axiosInstance.patch( 42 | `/api/notes/updateNote/${note.noteId}`, 43 | note.note, 44 | ); 45 | }; 46 | 47 | export const deleteNoteById = async (noteId: number) => { 48 | return await axiosInstance.delete(`/api/notes/deleteNote/${noteId}`); 49 | }; 50 | 51 | export const queryShareToken = async (noteId: number) => { 52 | const response = await axiosInstance.get( 53 | `/api/notes/getShareToken/${noteId}`, 54 | ); 55 | return response.data as string; 56 | }; 57 | 58 | export const queryNoteFromToken = async (token: string) => { 59 | const response = await axiosInstance.get( 60 | `/api/notes/getNoteFromToken/${token}`, 61 | ); 62 | return response.data as Note; 63 | }; 64 | -------------------------------------------------------------------------------- /frontend/src/api/Calendar/query.ts: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 2 | import { useSnackbar } from 'notistack'; 3 | import { 4 | EditEvent, 5 | EventPagination, 6 | deleteEvent, 7 | patchEditEvent, 8 | postAddEvent, 9 | queryEventBetween, 10 | } from './api'; 11 | import { t } from "@lingui/macro" 12 | 13 | export const getEventBetween = (pagination: EventPagination) => { 14 | return useQuery({ 15 | queryKey: ['eventBetween', pagination], 16 | queryFn: () => queryEventBetween(pagination), 17 | }); 18 | }; 19 | 20 | export const mutateAddEvent = () => { 21 | const queryClient = useQueryClient(); 22 | const { enqueueSnackbar } = useSnackbar(); 23 | return useMutation({ 24 | mutationKey: ['addEvent'], 25 | mutationFn: postAddEvent, 26 | onSuccess: () => { 27 | enqueueSnackbar(t({message:'Event added'})); 28 | queryClient.invalidateQueries({ queryKey: ['eventBetween'] }); 29 | }, 30 | }); 31 | }; 32 | 33 | export const mutateEditEvent = () => { 34 | const queryClient = useQueryClient(); 35 | const { enqueueSnackbar } = useSnackbar(); 36 | return useMutation({ 37 | mutationKey: ['editEvent'], 38 | mutationFn: (event: EditEvent) => patchEditEvent(event), 39 | onSuccess: () => { 40 | enqueueSnackbar(t({message:'Event edited'})); 41 | queryClient.invalidateQueries({ queryKey: ['eventBetween'] }); 42 | }, 43 | }); 44 | }; 45 | 46 | export const mutateDeleteEvent = () => { 47 | const queryClient = useQueryClient(); 48 | const { enqueueSnackbar } = useSnackbar(); 49 | return useMutation({ 50 | mutationKey: ['deleteEvent'], 51 | mutationFn: (eventId: number) => deleteEvent(eventId), 52 | onSuccess: () => { 53 | enqueueSnackbar(t({message:'Event deleted'})); 54 | queryClient.invalidateQueries({ queryKey: ['eventBetween'] }); 55 | }, 56 | }); 57 | }; 58 | -------------------------------------------------------------------------------- /backend/backend/MapProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using backend.Dto.CalendarEvents; 3 | using backend.Dto.GroupProjects; 4 | using backend.Dto.GroupProjectTasks; 5 | using backend.Dto.Groups; 6 | using backend.Dto.Notes; 7 | using backend.Dto.Projects; 8 | using backend.Dto.ProjectTasks; 9 | using backend.Dto.ProjectTodo; 10 | using backend.Dto.Todos; 11 | using backend.Models; 12 | 13 | namespace backend 14 | 15 | { 16 | public class MapProfile : Profile 17 | { 18 | public MapProfile() 19 | { 20 | CreateMap(); 21 | CreateMap(); 22 | CreateMap(); 23 | CreateMap() 24 | .ForMember(dest => dest.ProjectTaskID, opt => opt.MapFrom(src => src.GroupProjectTaskID)) 25 | .ForMember(dest => dest.User, opt => opt.MapFrom(src => src.User != null ? src.User.Username : null)) 26 | .ForMember(dest => dest.CanEdit, opt => opt.MapFrom((src, dest, destMember, context) => 27 | { 28 | var currentUserId = context.Items["CurrentUserId"] as int?; 29 | var currentUserRole = context.Items["CurrentUserRole"] as Enums.GroupRole?; 30 | 31 | var isTaskOwner = src.User?.UserID== currentUserId; 32 | var isAdminOrModerator = currentUserRole == Enums.GroupRole.Admin || currentUserRole == Enums.GroupRole.Moderator; 33 | 34 | return isTaskOwner || isAdminOrModerator; 35 | })); 36 | 37 | 38 | CreateMap(); 39 | CreateMap(); 40 | CreateMap(); 41 | CreateMap(); 42 | CreateMap(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/TodoTask/CreateForm.tsx: -------------------------------------------------------------------------------- 1 | import { Box, TextField } from '@mui/material'; 2 | import { todoModel } from './schema'; 3 | import { UseFormReturn, useController } from 'react-hook-form'; 4 | import { Trans } from '@lingui/macro'; 5 | 6 | type Props = { 7 | onSubmit: (value: todoModel) => void; 8 | formContext: UseFormReturn; 9 | }; 10 | 11 | export const CreateForm = ({ onSubmit, formContext }: Props) => { 12 | const { 13 | control, 14 | handleSubmit, 15 | formState: { errors }, 16 | } = formContext; 17 | 18 | const title = useController({ 19 | control: control, 20 | name: 'title', 21 | }); 22 | 23 | const desc = useController({ 24 | control: control, 25 | name: 'description', 26 | }); 27 | 28 | return ( 29 | onSubmit(data))} 40 | > 41 | Title} 44 | inputRef={title.field.ref} 45 | value={title.field.value} 46 | onChange={title.field.onChange} 47 | onBlur={title.field.onBlur} 48 | name={title.field.name} 49 | error={errors.title !== undefined} 50 | helperText={errors.title?.message} 51 | /> 52 | 53 | Description} 56 | inputRef={desc.field.ref} 57 | value={desc.field.value} 58 | onChange={desc.field.onChange} 59 | onBlur={desc.field.onBlur} 60 | name={desc.field.name} 61 | error={errors.description !== undefined} 62 | helperText={errors.description?.message} 63 | /> 64 | 65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /frontend/src/View/GroupView/GroupProjectView/UsersTab.tsx: -------------------------------------------------------------------------------- 1 | import { Trans, t } from '@lingui/macro'; 2 | import { Box, Typography, List, Divider } from '@mui/material'; 3 | import { debounce } from 'lodash'; 4 | import { useRef, useState } from 'react'; 5 | import { GroupUser } from '~/api/Group/api'; 6 | import { GroupUserCard } from '~/component/GroupUserCard'; 7 | import { SearchField } from '~/component/SearchField'; 8 | 9 | type Props = { 10 | users: GroupUser[]; 11 | groupId: number; 12 | }; 13 | 14 | export const UserTab = ({ users, groupId }: Props) => { 15 | const [filter, setFilter] = useState(''); 16 | 17 | const handleFilter = useRef( 18 | debounce( 19 | (event: React.ChangeEvent) => 20 | setFilter(event.target.value), 21 | 500, 22 | ), 23 | ).current; 24 | return ( 25 | <> 26 | 33 | 34 | Explore Users 35 | 36 | 37 | 41 | 42 | 43 | 51 | {users 52 | .filter((a) => 53 | a.username.toLocaleLowerCase().includes(filter.toLocaleLowerCase()), 54 | ) 55 | .map((item) => ( 56 | 57 | 58 | 59 | 60 | ))} 61 | 62 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /backend/backend/Controllers/TodoController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Todos; 2 | using backend.Interface; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace backend.Controllers 7 | { 8 | [Route("api/todos")] 9 | [ApiController] 10 | public class TodoController : ControllerBase 11 | { 12 | private readonly ITodoRepository _projectService; 13 | public TodoController(ITodoRepository projectService) 14 | { 15 | _projectService = projectService; 16 | } 17 | 18 | [HttpGet("getAllTodos/{todoId}")] 19 | [Authorize] 20 | public async Task>> getAllTodos([FromRoute] int todoId) 21 | { 22 | var project = await _projectService.GetAllTodos(todoId); 23 | return Ok(project); 24 | } 25 | 26 | [HttpPost("addTodo")] 27 | [Authorize] 28 | public async Task addTodo(AddTodoDto dto) 29 | { 30 | await _projectService.AddTodo(dto); 31 | return Ok(); 32 | } 33 | 34 | [HttpPatch("updateTodo/{todoId}")] 35 | [Authorize] 36 | public async Task updateTodo(AddTodoDto dto, [FromRoute] int todoId) 37 | { 38 | await _projectService.UpdateTodo(dto, todoId); 39 | return Ok(); 40 | } 41 | 42 | [HttpPatch("toggleStatus/{todoId}")] 43 | [Authorize] 44 | public async Task toggleTodo(ToggleTodoDto dto,[FromRoute] int todoId) 45 | { 46 | await _projectService.ToggleDone(dto,todoId); 47 | return Ok(); 48 | } 49 | 50 | [HttpDelete("deleteTodo/{todoId}")] 51 | [Authorize] 52 | public async Task deleteTodo([FromRoute] int todoId) 53 | { 54 | await _projectService.DeleteTodo(todoId); 55 | return Ok(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /backend/backend/backend.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <_ContentIncludedByDefault Remove="appsettings.json.example" /> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /backend/backend/Controllers/CalendarEventController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.CalendarEvents; 2 | using backend.Interface; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | using System.Text.Json; 6 | 7 | namespace backend.Controllers 8 | { 9 | [Route("api/calendar")] 10 | [ApiController] 11 | public class CalendarEventController : ControllerBase 12 | { 13 | private readonly ICalendarEventRepository _calendarService; 14 | public CalendarEventController(ICalendarEventRepository noteService) 15 | { 16 | _calendarService = noteService; 17 | } 18 | 19 | [HttpGet("getEventBetween")] 20 | [Authorize] 21 | public async Task>> GetAllEvents( 22 | [FromQuery] string from, 23 | [FromQuery] string to, 24 | [FromQuery] string colors) 25 | { 26 | var colorDict = JsonSerializer.Deserialize>(colors); 27 | var events = await _calendarService.GetAllEventsBetween(from, to, colorDict ?? new Dictionary()); 28 | return Ok(events); 29 | } 30 | 31 | [HttpPost("addEvent")] 32 | [Authorize] 33 | public async Task addEvent(CalendarEventDto dto) 34 | { 35 | await _calendarService.AddEvent(dto); 36 | return Ok(); 37 | } 38 | 39 | [HttpPatch("updateEvent/{eventId}")] 40 | [Authorize] 41 | public async Task updateNote(CalendarEventDto dto, [FromRoute] int eventId) 42 | { 43 | await _calendarService.UpdateEvent(dto, eventId); 44 | return Ok(); 45 | } 46 | 47 | [HttpDelete("deleteEvent/{eventId}")] 48 | [Authorize] 49 | public async Task deleteNote([FromRoute] int eventId) 50 | { 51 | await _calendarService.DeleteEvent(eventId); 52 | return Ok(); 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /frontend/src/View/RegisterView/RegisterView.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Box, Button, Paper, Stack, Typography } from '@mui/material'; 2 | import Logo from '~/assets/Logo.png'; 3 | import { FORM_ID, RegisterForm } from './RegisterForm'; 4 | import { registerFormSchema } from './schema'; 5 | import { useForm } from 'react-hook-form'; 6 | import { zodResolver } from '@hookform/resolvers/zod'; 7 | import { NavLink } from 'react-router-dom'; 8 | import { mutateUserRegister } from '~/api/User/query'; 9 | import { Trans } from '@lingui/macro'; 10 | 11 | export const RegisterView = () => { 12 | const form = useForm({ 13 | defaultValues: { 14 | id: -1, 15 | username: '', 16 | email: '', 17 | password: '', 18 | passwordConfirm: '', 19 | }, 20 | resolver: zodResolver(registerFormSchema), 21 | }); 22 | const { mutateAsync } = mutateUserRegister(); 23 | 24 | const handleSubmit = (data: registerFormSchema) => { 25 | mutateAsync(data); 26 | form.reset(); 27 | }; 28 | 29 | return ( 30 | 41 | 42 | 43 | 44 | 45 | 46 | Register 47 | 48 | 49 | 50 | 53 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /frontend/src/View/NotesView/DialogDelete.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Alert, 3 | Button, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | IconButton, 9 | } from '@mui/material'; 10 | import { useState } from 'react'; 11 | import DeleteIcon from '@mui/icons-material/Delete'; 12 | import { deleteNote } from '~/api/Notes/query'; 13 | import { Trans } from '@lingui/macro'; 14 | 15 | type Props = { 16 | noteId: number | null; 17 | }; 18 | 19 | export const DialogDelete = ({ noteId }: Props) => { 20 | const { mutateAsync } = deleteNote(); 21 | const [open, setOpen] = useState(false); 22 | 23 | const handleClickOpen = () => { 24 | setOpen(true); 25 | }; 26 | 27 | const handleClose = () => { 28 | setOpen(false); 29 | }; 30 | 31 | return ( 32 | <> 33 | 34 | 35 | 36 | 46 | 47 | Delete note? 48 | 49 | 50 | 51 | 52 | Deleting this note means that all provided data will be lost. 53 | 54 |
55 | Are you sure? 56 |
57 |
58 | 59 | 62 | 72 | 73 |
74 | 75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /frontend/src/View/CalendarView/DialogCreate.tsx: -------------------------------------------------------------------------------- 1 | import { zodResolver } from '@hookform/resolvers/zod'; 2 | import { Trans } from '@lingui/macro'; 3 | import { 4 | Button, 5 | Dialog, 6 | DialogActions, 7 | DialogContent, 8 | DialogTitle, 9 | useTheme, 10 | } from '@mui/material'; 11 | import dayjs from 'dayjs'; 12 | import { Dispatch } from 'react'; 13 | import { useForm } from 'react-hook-form'; 14 | import { mutateAddEvent } from '~/api/Calendar/query'; 15 | import { CreateForm } from './CreateForm'; 16 | import { eventModel, eventSchema } from './schema'; 17 | 18 | type Props = { 19 | open: boolean; 20 | setOpen: Dispatch; 21 | day: dayjs.Dayjs; 22 | }; 23 | 24 | export const DialogCreate = ({ open, setOpen, day }: Props) => { 25 | const { mutateAsync } = mutateAddEvent(); 26 | 27 | const eventForm = useForm({ 28 | defaultValues: { 29 | title: '', 30 | description: '', 31 | dateTime: day.format('DD.MM.YYYY'), 32 | eventColor: useTheme().palette.secondary.dark, 33 | }, 34 | resolver: zodResolver(eventSchema), 35 | }); 36 | 37 | const handleClose = () => { 38 | eventForm.reset(); 39 | setOpen(false); 40 | }; 41 | 42 | const handleSubmit = (data: eventModel) => { 43 | mutateAsync(data); 44 | handleClose(); 45 | }; 46 | 47 | return ( 48 | 58 | 59 | New Event 60 | 61 | 62 | 63 | 64 | 65 | 68 | 71 | 72 | 73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /frontend/src/api/User/api.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { BASE_URL } from '~/config/constants'; 3 | import { registerFormSchema } from '~/View/RegisterView/schema'; 4 | import { loginFormSchema } from '~/View/LoginView/schema'; 5 | import { userModel } from '~/component/UserBox/schema'; 6 | import { axiosInstance as authInstance }from '../api' 7 | import { 8 | forgotPasswordModel, 9 | resetPasswordModel, 10 | } from '~/View/RegisterView/PasswordRenew/schema'; 11 | import { axiosInstance as loggedInstance } from '../api'; 12 | 13 | export interface MyAccount { 14 | username: string; 15 | email: string; 16 | groupName: string; 17 | image:string; 18 | } 19 | 20 | export interface AccessToken { 21 | accessToken: string; 22 | refreshToken: string; 23 | } 24 | 25 | export const axiosInstance = axios.create({ baseURL: BASE_URL }); 26 | 27 | export const postUserRegister = async (userData: registerFormSchema) => { 28 | return await axiosInstance.post('/api/account/register', userData); 29 | }; 30 | 31 | export const postUserLogin = async (userData: loginFormSchema) => { 32 | const response = await axiosInstance.post('/api/account/login', userData); 33 | return response.data as AccessToken; 34 | }; 35 | 36 | export const postUserLogout = async () => { 37 | return await loggedInstance.post('/api/account/logout'); 38 | }; 39 | 40 | export const postForgotPassword = async (userData: forgotPasswordModel) => { 41 | return await axiosInstance.post('/api/account/forgotPassword', userData); 42 | }; 43 | 44 | export const postResetPassword = async (userData: resetPasswordModel) => { 45 | return await axiosInstance.post('/api/account/resetPassword', userData); 46 | }; 47 | 48 | export const postDeleteAccount = async () => { 49 | return await loggedInstance.delete('/api/account/deleteAccount'); 50 | }; 51 | 52 | export const queryMyAccount = async () => { 53 | const response = await loggedInstance.get('/api/account/getMyAccount'); 54 | return response.data as MyAccount; 55 | }; 56 | 57 | export const updateUser = async (userData: userModel) => { 58 | return await authInstance.patch('/api/account/updateAccount', userData); 59 | }; -------------------------------------------------------------------------------- /backend/backend/Controllers/ProjectController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Pagination; 2 | using backend.Dto.Projects; 3 | using backend.Interface; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace backend.Controllers 8 | { 9 | [Route("api/projects")] 10 | [ApiController] 11 | public class ProjectController : ControllerBase 12 | { 13 | private readonly IProjectRepository _projectService; 14 | public ProjectController(IProjectRepository projectService) 15 | { 16 | _projectService = projectService; 17 | } 18 | 19 | [HttpGet("getAllProjects")] 20 | [Authorize] 21 | public async Task>> getAllProjects([FromQuery] PaginationRequestDto paginationRequest) 22 | { 23 | var projects = await _projectService.GetAllProjects(paginationRequest); 24 | return Ok(projects); 25 | } 26 | 27 | [HttpGet("getProjectById/{projectId}")] 28 | [Authorize] 29 | public async Task> getProjectById([FromRoute] int projectId) 30 | { 31 | var project = await _projectService.GetProjectById(projectId); 32 | return Ok(project); 33 | } 34 | [HttpPost("addProject")] 35 | [Authorize] 36 | public async Task addProject(AddProjectDto dto) 37 | { 38 | await _projectService.AddProject(dto); 39 | return Ok(); 40 | } 41 | 42 | [HttpPatch("updateProject/{projectId}")] 43 | [Authorize] 44 | public async Task updateProject(EditProjectDto dto, [FromRoute] int projectId) 45 | { 46 | await _projectService.UpdateProject(dto,projectId); 47 | return Ok(); 48 | } 49 | 50 | [HttpDelete("deleteProject/{projectId}")] 51 | [Authorize] 52 | public async Task deleteProject([FromRoute] int projectId) 53 | { 54 | await _projectService.DeleteProject(projectId); 55 | return Ok(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/DialogDelete.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Alert, 3 | Button, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | IconButton, 9 | } from '@mui/material'; 10 | import { useState } from 'react'; 11 | import DeleteIcon from '@mui/icons-material/Delete'; 12 | import { deleteProjectTodo } from '~/api/ProjectTodos/query'; 13 | import { Trans } from '@lingui/macro'; 14 | 15 | type Props = { 16 | todoId: number | undefined; 17 | }; 18 | 19 | export const DialogDelete = ({ todoId }: Props) => { 20 | const { mutateAsync } = deleteProjectTodo(); 21 | const [open, setOpen] = useState(false); 22 | 23 | const handleClickOpen = () => { 24 | setOpen(true); 25 | }; 26 | 27 | const handleClose = () => { 28 | setOpen(false); 29 | }; 30 | 31 | return ( 32 | <> 33 | 34 | 35 | 36 | 46 | 47 | Delete Todo? 48 | 49 | 50 | 51 | 52 | Deleting this Todo means that all provided data will be lost. 53 | 54 |
55 | Are you sure? 56 |
57 |
58 | 59 | 62 | 72 | 73 |
74 | 75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /frontend/src/View/LoginView/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { Box, Stack, TextField } from '@mui/material'; 3 | import { UseFormReturn, useController } from 'react-hook-form'; 4 | import { PasswordField } from '~/component/PasswordField'; 5 | import { loginFormSchema } from './schema'; 6 | 7 | type loginFormProps = { 8 | onSubmit: (value: loginFormSchema) => void; 9 | formContext: UseFormReturn; 10 | }; 11 | 12 | export const FORM_ID = 'login-form'; 13 | 14 | export const LoginForm = ({ onSubmit, formContext }: loginFormProps) => { 15 | const { 16 | control, 17 | handleSubmit, 18 | formState: { errors }, 19 | } = formContext; 20 | 21 | const email = useController({ 22 | control: control, 23 | name: 'email', 24 | }); 25 | const password = useController({ 26 | control: control, 27 | name: 'password', 28 | }); 29 | 30 | return ( 31 | { 35 | onSubmit(data); 36 | })} 37 | > 38 | 39 | Email} 43 | type="text" 44 | autoComplete="off" 45 | name={email.field.name} 46 | value={email.field.value} 47 | onChange={email.field.onChange} 48 | onBlur={email.field.onBlur} 49 | inputRef={email.field.ref} 50 | error={errors.email !== undefined} 51 | helperText={errors.email?.message ?? ''} 52 | /> 53 | 54 | Password} 57 | autoComplete="off" 58 | name={password.field.name} 59 | value={password.field.value} 60 | onChange={password.field.onChange} 61 | onBlur={password.field.onBlur} 62 | inputRef={password.field.ref} 63 | error={errors.password !== undefined} 64 | helperText={errors.password?.message ?? ''} 65 | /> 66 | 67 | 68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /frontend/src/api/ProjectTodos/api.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from '../api'; 2 | import { PaginationRequest, PaginationResponse } from '../pagination'; 3 | 4 | export interface ProjectTodo { 5 | projectTodoID: number; 6 | title: string; 7 | description: string; 8 | color: string; 9 | isDone: boolean; 10 | } 11 | 12 | export interface AddProjectTodo { 13 | title: string; 14 | description?: string; 15 | color: string; 16 | } 17 | 18 | export interface EditProjectTodo { 19 | projectId: number; 20 | project: AddProjectTodo; 21 | } 22 | 23 | export interface TodoById { 24 | title: string; 25 | description: string; 26 | color: string; 27 | todos: Todo[]; 28 | } 29 | 30 | export interface Todo { 31 | todoID: number; 32 | title: string; 33 | description: string; 34 | isDone: boolean; 35 | } 36 | 37 | export interface TodoList { 38 | projects: ProjectTodo[]; 39 | } 40 | 41 | export const queryProjectTodo = async (isDone: boolean,pagination : PaginationRequest) => { 42 | const response = await axiosInstance.get(`/api/projectTodos/getAllProjects`, { 43 | params:{ 44 | isDone: isDone, 45 | pageNumber: pagination.pageNumber, 46 | pageSize: pagination.pageSize, 47 | filter: pagination.filter 48 | } 49 | }); 50 | return response.data as PaginationResponse;; 51 | }; 52 | 53 | export const queryProjectTodoById = async (projectId: number) => { 54 | const response = await axiosInstance.get( 55 | `/api/projectTodos/getById/${projectId}`, 56 | ); 57 | return response.data as TodoById; 58 | }; 59 | 60 | export const postAddProjectTodo = async (projectTodo: AddProjectTodo) => { 61 | return await axiosInstance.post('/api/projectTodos/addProject', projectTodo); 62 | }; 63 | 64 | export const postEditProjectTodo = async (projectTodo: EditProjectTodo) => { 65 | return await axiosInstance.patch( 66 | `/api/projectTodos/updateProject/${projectTodo.projectId}`, 67 | projectTodo.project, 68 | ); 69 | }; 70 | 71 | export const postDeleteProjectTodo = async (projectTodoId: number) => { 72 | return await axiosInstance.delete( 73 | `/api/projectTodos/deleteProject/${projectTodoId}`, 74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /frontend/src/component/UserBox/DialogRemoveAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Alert, 3 | Button, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | IconButton, 9 | Tooltip, 10 | } from '@mui/material'; 11 | import { useState } from 'react'; 12 | import { Trans, t } from '@lingui/macro'; 13 | import { mutateDeleteImage } from '~/api/Image/query'; 14 | import DeleteIcon from '@mui/icons-material/Delete'; 15 | 16 | type Props = { 17 | initialImage: string | null; 18 | }; 19 | 20 | export const DialogRemoveAvatar = ({ initialImage }: Props) => { 21 | const [open, setOpen] = useState(false); 22 | const { mutateAsync: deleteImage } = mutateDeleteImage(); 23 | 24 | const handleClickOpen = () => { 25 | setOpen(true); 26 | }; 27 | 28 | const handleClose = () => { 29 | setOpen(false); 30 | }; 31 | 32 | const handleDelete = () => { 33 | deleteImage(initialImage ?? ''); 34 | setOpen(false); 35 | }; 36 | 37 | return ( 38 | <> 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | Remove Avatar 56 | 57 | 58 | 59 | Deleting avatar means that it will be forever lost. 60 |
61 | Are you sure? 62 |
63 |
64 | 65 | 68 | 71 | 72 |
73 | 74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /backend/backend/Controllers/ProjectTodoController.cs: -------------------------------------------------------------------------------- 1 | using backend.Dto.Pagination; 2 | using backend.Dto.ProjectTodo; 3 | using backend.Interface; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace backend.Controllers 8 | { 9 | [Route("api/projectTodos")] 10 | [ApiController] 11 | public class ProjectTodoController : ControllerBase 12 | { 13 | private readonly IProjectTodoRepository _projectService; 14 | public ProjectTodoController(IProjectTodoRepository projectService) 15 | { 16 | _projectService = projectService; 17 | } 18 | 19 | [HttpGet("getAllProjects")] 20 | [Authorize] 21 | public async Task>> getAllProjects([FromQuery] bool isDone,[FromQuery] PaginationRequestDto paginationRequest) 22 | { 23 | var project = await _projectService.GetAllProjects(isDone, paginationRequest); 24 | return Ok(project); 25 | } 26 | 27 | [HttpGet("getById/{projectId}")] 28 | [Authorize] 29 | public async Task> getById([FromRoute] int projectId) 30 | { 31 | var project = await _projectService.GetProjectById(projectId); 32 | return Ok(project); 33 | } 34 | 35 | [HttpPost("addProject")] 36 | [Authorize] 37 | public async Task addProject(AddProjectTodoDto dto) 38 | { 39 | await _projectService.AddProject(dto); 40 | return Ok(); 41 | } 42 | 43 | [HttpPatch("updateProject/{projectId}")] 44 | [Authorize] 45 | public async Task updateProject(AddProjectTodoDto dto, [FromRoute] int projectId) 46 | { 47 | await _projectService.UpdateProject(dto,projectId); 48 | return Ok(); 49 | } 50 | 51 | [HttpDelete("deleteProject/{projectId}")] 52 | [Authorize] 53 | public async Task deleteProject([FromRoute] int projectId) 54 | { 55 | await _projectService.DeleteProject(projectId); 56 | return Ok(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /frontend/src/View/TodoView/TodoLink.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from '@mui/material'; 2 | import { DrawerLink } from '../NotesView/DrawerLink'; 3 | import { DialogEdit } from './DialogEdit'; 4 | import { ProjectTodo } from '~/api/ProjectTodos/api'; 5 | import { Dispatch, SetStateAction } from 'react'; 6 | 7 | type Props = { 8 | project: ProjectTodo; 9 | selectedProject: ProjectTodo | undefined; 10 | setSelectedProject: Dispatch>; 11 | }; 12 | 13 | export const TodoLink = ({ 14 | project, 15 | selectedProject, 16 | setSelectedProject, 17 | }: Props) => { 18 | return ( 19 | setSelectedProject(project)} 22 | display={'flex'} 23 | flexDirection={'column'} 24 | sx={{ 25 | cursor: 'pointer', 26 | }} 27 | > 28 | 33 | 34 | 35 | ({ 37 | textWrap: 'wrap', 38 | wordBreak: 'break-word', 39 | textDecoration: project.isDone ? 'line-through' : '', 40 | color: project.isDone ? theme.palette.text.secondary : '', 41 | })} 42 | > 43 | {project.title} 44 | 45 | ({ 48 | textWrap: 'wrap', 49 | wordBreak: 'break-word', 50 | color: theme.palette.text.secondary, 51 | textDecoration: project.isDone ? 'line-through' : '', 52 | })} 53 | > 54 | {project.description} 55 | 56 | 57 | {project.projectTodoID === selectedProject?.projectTodoID && ( 58 | 59 | )} 60 | 61 | 62 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /frontend/src/component/GroupUserCard/RemoveGroupDialog.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Alert, 3 | Button, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | IconButton, 9 | Tooltip, 10 | } from '@mui/material'; 11 | import { useState } from 'react'; 12 | import DoDisturbIcon from '@mui/icons-material/DoDisturb'; 13 | import { Trans } from '@lingui/macro'; 14 | import { mutateDeleteGroup } from '~/api/Group/query'; 15 | 16 | type Props = { 17 | groupId: number | null; 18 | }; 19 | 20 | export const RemoveGroupDialog = ({ groupId }: Props) => { 21 | const { mutateAsync } = mutateDeleteGroup(); 22 | const [open, setOpen] = useState(false); 23 | 24 | const handleClickOpen = () => { 25 | setOpen(true); 26 | }; 27 | 28 | const handleClose = () => { 29 | setOpen(false); 30 | }; 31 | 32 | return ( 33 | <> 34 | Remove group}> 35 | 36 | 37 | 38 | 39 | 49 | 50 | Delete group? 51 | 52 | 53 | 54 | 55 | Deleting this group means that all provided data will be lost. 56 | 57 |
58 | Are you sure? 59 |
60 |
61 | 62 | 65 | 75 | 76 |
77 | 78 | ); 79 | }; 80 | --------------------------------------------------------------------------------