diff --git a/backend/src/Socialize.Api/DependencyInjection.cs b/backend/src/Socialize.Api/DependencyInjection.cs index 64e723b..698c9ee 100644 --- a/backend/src/Socialize.Api/DependencyInjection.cs +++ b/backend/src/Socialize.Api/DependencyInjection.cs @@ -50,7 +50,7 @@ public static class DependencyInjection { using IServiceScope scope = app.ApplicationServices.CreateScope(); await using AppDbContext context = scope.ServiceProvider.GetRequiredService(); - await context.Database.EnsureCreatedAsync(cancellationToken); + await context.Database.MigrateAsync(cancellationToken); return app; } diff --git a/backend/src/Socialize.Api/Infrastructure/BlobStorage/Contracts/ContainerNames.cs b/backend/src/Socialize.Api/Infrastructure/BlobStorage/Contracts/ContainerNames.cs index b864997..1248878 100644 --- a/backend/src/Socialize.Api/Infrastructure/BlobStorage/Contracts/ContainerNames.cs +++ b/backend/src/Socialize.Api/Infrastructure/BlobStorage/Contracts/ContainerNames.cs @@ -4,5 +4,6 @@ internal static class ContainerNames { public const string Users = "users"; public const string Clients = "clients"; + public const string Workspaces = "workspaces"; public const string Creators = "creators"; } diff --git a/backend/src/Socialize.Api/Migrations/20260430054500_AddWorkspaceLogo.cs b/backend/src/Socialize.Api/Migrations/20260430054500_AddWorkspaceLogo.cs new file mode 100644 index 0000000..944d1af --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260430054500_AddWorkspaceLogo.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Socialize.Api.Data; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + /// + [DbContext(typeof(AppDbContext))] + [Migration("20260430054500_AddWorkspaceLogo")] + public partial class AddWorkspaceLogo : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LogoUrl", + table: "Workspaces", + type: "character varying(2048)", + maxLength: 2048, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LogoUrl", + table: "Workspaces"); + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs index ab8299e..fbc309b 100644 --- a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs +++ b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs @@ -819,6 +819,10 @@ namespace Socialize.Api.Migrations .HasMaxLength(256) .HasColumnType("character varying(256)"); + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + b.Property("OwnerUserId") .HasColumnType("uuid"); diff --git a/backend/src/Socialize.Api/Modules/Workspaces/Data/Workspace.cs b/backend/src/Socialize.Api/Modules/Workspaces/Data/Workspace.cs index ac4a074..113dbd5 100644 --- a/backend/src/Socialize.Api/Modules/Workspaces/Data/Workspace.cs +++ b/backend/src/Socialize.Api/Modules/Workspaces/Data/Workspace.cs @@ -5,6 +5,7 @@ public class Workspace public Guid Id { get; init; } public required string Name { get; set; } public required string Slug { get; set; } + public string? LogoUrl { get; set; } public Guid OwnerUserId { get; set; } public required string TimeZone { get; set; } public DateTimeOffset CreatedAt { get; init; } diff --git a/backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceModelConfiguration.cs b/backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceModelConfiguration.cs index 553186c..1d08706 100644 --- a/backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceModelConfiguration.cs +++ b/backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceModelConfiguration.cs @@ -12,6 +12,7 @@ public static class WorkspaceModelConfiguration workspace.HasKey(x => x.Id); workspace.Property(x => x.Name).HasMaxLength(256).IsRequired(); workspace.Property(x => x.Slug).HasMaxLength(128).IsRequired(); + workspace.Property(x => x.LogoUrl).HasMaxLength(2048); workspace.Property(x => x.TimeZone).HasMaxLength(128).IsRequired(); workspace.Property(x => x.CreatedAt) .ValueGeneratedOnAdd() diff --git a/backend/src/Socialize.Api/Modules/Workspaces/Handlers/ChangeWorkspaceLogo.cs b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/ChangeWorkspaceLogo.cs new file mode 100644 index 0000000..1022355 --- /dev/null +++ b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/ChangeWorkspaceLogo.cs @@ -0,0 +1,68 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using Socialize.Api.Data; +using Socialize.Api.Infrastructure.BlobStorage.Contracts; +using Socialize.Api.Infrastructure.Security; +using Socialize.Api.Modules.Workspaces.Data; + +namespace Socialize.Api.Modules.Workspaces.Handlers; + +public record ChangeWorkspaceLogoRequest( + IFormFile File); + +public record ChangeWorkspaceLogoResponse( + string BlobUrl); + +public sealed class ChangeWorkspaceLogoRequestValidator : Validator +{ + public ChangeWorkspaceLogoRequestValidator() + { + RuleFor(x => x.File) + .NotNull() + .NotEmpty(); + } +} + +public class ChangeWorkspaceLogoHandler( + AppDbContext dbContext, + IBlobStorage blobStorage, + AccessScopeService accessScopeService) + : Endpoint +{ + public override void Configure() + { + Post("/api/workspaces/{id}/logo"); + Options(o => o.WithTags("Workspaces")); + AllowFileUploads(); + } + + public override async Task HandleAsync(ChangeWorkspaceLogoRequest request, CancellationToken ct) + { + Guid id = Route("id"); + + Workspace? workspace = await dbContext.Workspaces.SingleOrDefaultAsync(candidate => candidate.Id == id, ct); + if (workspace is null) + { + await SendNotFoundAsync(ct); + return; + } + + if (!accessScopeService.CanManageWorkspace(User, workspace.Id)) + { + await SendForbiddenAsync(ct); + return; + } + + string blobUrl = await blobStorage.UploadFileAsync( + ContainerNames.Workspaces, + $"{workspace.Id}/{SubDirectoryNames.Profile}/{CommonFileNames.LogoPicture}", + request.File.OpenReadStream(), + request.File.ContentType, + ct); + + workspace.LogoUrl = blobUrl; + await dbContext.SaveChangesAsync(ct); + + await SendOkAsync(new ChangeWorkspaceLogoResponse(blobUrl), ct); + } +} diff --git a/backend/src/Socialize.Api/Modules/Workspaces/Handlers/CreateWorkspace.cs b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/CreateWorkspace.cs index 6bafe95..2bb3c26 100644 --- a/backend/src/Socialize.Api/Modules/Workspaces/Handlers/CreateWorkspace.cs +++ b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/CreateWorkspace.cs @@ -75,6 +75,7 @@ public class CreateWorkspaceHandler( workspace.Id, workspace.Name, workspace.Slug, + workspace.LogoUrl, workspace.TimeZone, workspace.CreatedAt); diff --git a/backend/src/Socialize.Api/Modules/Workspaces/Handlers/GetWorkspaces.cs b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/GetWorkspaces.cs index 107b9d2..609ac7b 100644 --- a/backend/src/Socialize.Api/Modules/Workspaces/Handlers/GetWorkspaces.cs +++ b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/GetWorkspaces.cs @@ -10,6 +10,7 @@ public record WorkspaceDto( Guid Id, string Name, string Slug, + string? LogoUrl, string TimeZone, DateTimeOffset CreatedAt); @@ -40,6 +41,7 @@ public class GetWorkspacesHandler( workspace.Id, workspace.Name, workspace.Slug, + workspace.LogoUrl, workspace.TimeZone, workspace.CreatedAt)) .ToListAsync(ct); diff --git a/backend/src/Socialize.Api/Modules/Workspaces/Handlers/UpdateWorkspace.cs b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/UpdateWorkspace.cs new file mode 100644 index 0000000..c3df33e --- /dev/null +++ b/backend/src/Socialize.Api/Modules/Workspaces/Handlers/UpdateWorkspace.cs @@ -0,0 +1,66 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using Socialize.Api.Data; +using Socialize.Api.Infrastructure.Security; +using Socialize.Api.Modules.Workspaces.Data; + +namespace Socialize.Api.Modules.Workspaces.Handlers; + +public record UpdateWorkspaceRequest( + string Name, + string TimeZone); + +public class UpdateWorkspaceRequestValidator + : Validator +{ + public UpdateWorkspaceRequestValidator() + { + RuleFor(x => x.Name).NotEmpty().MaximumLength(256); + RuleFor(x => x.TimeZone).NotEmpty().MaximumLength(128); + } +} + +public class UpdateWorkspaceHandler( + AppDbContext dbContext, + AccessScopeService accessScopeService) + : Endpoint +{ + public override void Configure() + { + Put("/api/workspaces/{id}"); + Options(o => o.WithTags("Workspaces")); + } + + public override async Task HandleAsync(UpdateWorkspaceRequest request, CancellationToken ct) + { + Guid id = Route("id"); + + Workspace? workspace = await dbContext.Workspaces.SingleOrDefaultAsync(candidate => candidate.Id == id, ct); + if (workspace is null) + { + await SendNotFoundAsync(ct); + return; + } + + if (!accessScopeService.CanManageWorkspace(User, workspace.Id)) + { + await SendForbiddenAsync(ct); + return; + } + + workspace.Name = request.Name.Trim(); + workspace.TimeZone = request.TimeZone.Trim(); + + await dbContext.SaveChangesAsync(ct); + + WorkspaceDto dto = new( + workspace.Id, + workspace.Name, + workspace.Slug, + workspace.LogoUrl, + workspace.TimeZone, + workspace.CreatedAt); + + await SendOkAsync(dto, ct); + } +} diff --git a/docs/TASKS/workspace-review-workflow/002-edit-workspace-settings.md b/docs/TASKS/workspace-review-workflow/002-edit-workspace-settings.md new file mode 100644 index 0000000..d4e5950 --- /dev/null +++ b/docs/TASKS/workspace-review-workflow/002-edit-workspace-settings.md @@ -0,0 +1,24 @@ +# Task: Edit workspace settings + +## Goal + +Allow managers to update the active workspace name and time zone from the workspace settings page. + +## Feature Spec + +- `docs/FEATURES/workspace-review-workflow.md` + +## Scope + +- Add a backend workspace update endpoint for `name` and `timeZone`. +- Add a backend workspace logo upload endpoint. +- Add a frontend workspace store update action. +- Replace the workspace settings general summary with editable details and logo controls. +- Do not display workspace slug or workspace creation date on the workspace settings page. + +## Validation + +```bash +dotnet build backend/Socialize.slnx +cd frontend && npm run build +``` diff --git a/frontend/src/api/schema.d.ts b/frontend/src/api/schema.d.ts index f4bf2c8..b0439a6 100644 --- a/frontend/src/api/schema.d.ts +++ b/frontend/src/api/schema.d.ts @@ -1,3 +1,2714 @@ -// Generated from the backend OpenAPI schema. -// Run ./scripts/update-openapi.sh after backend contract changes. -export interface paths {} +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/workspaces/{id}/logo": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/workspaces": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesWorkspacesHandlersGetWorkspacesHandler"]; + put?: never; + post: operations["SocializeApiModulesWorkspacesHandlersCreateWorkspaceHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/workspaces/{workspaceId}/invites": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesWorkspacesHandlersGetWorkspaceInvitesHandler"]; + put?: never; + post: operations["SocializeApiModulesWorkspacesHandlersCreateWorkspaceInviteHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/workspaces/{workspaceId}/members": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesWorkspacesHandlersGetWorkspaceMembersHandler"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/workspaces/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["SocializeApiModulesWorkspacesHandlersUpdateWorkspaceHandler"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/projects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesProjectsHandlersGetProjectsHandler"]; + put?: never; + post: operations["SocializeApiModulesProjectsHandlersCreateProjectHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/notifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesNotificationsHandlersGetNotificationsHandler"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/notifications/{id}/read": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesNotificationsHandlersMarkNotificationAsReadHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/address": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangeAddressHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/alias": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangeAliasHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/birthdate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangeBirthDateHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/email": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangeEmailHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/fullname": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangeFullnameHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/phone": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangePhoneHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/portrait": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesIdentityHandlersGetCurrentUserPortraitHandler"]; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersChangePortraitHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/forgot-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersForgotPasswordHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesIdentityHandlersGetCurrentUserQueryHandler"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersLoginHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/login-with-facebook": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersLoginWithFacebookHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/login-with-google": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersLoginWithGoogleHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersRefreshTokenHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersRegisterHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/resend-verification": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersResendVerificationHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/reset-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersResetPasswordHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/set-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesIdentityHandlersSetPasswordHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/users/verify-email": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesIdentityHandlersVerifyEmailHandler"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/content-items": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesContentItemsHandlersGetContentItemsHandler"]; + put?: never; + post: operations["SocializeApiModulesContentItemsHandlersCreateContentItemHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/content-items/{id}/revisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesContentItemsHandlersGetContentItemRevisionsHandler"]; + put?: never; + post: operations["SocializeApiModulesContentItemsHandlersCreateContentItemRevisionHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/content-items/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesContentItemsHandlersGetContentItemHandler"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/content-items/{id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesContentItemsHandlersUpdateContentItemStatusHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/comments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesCommentsHandlersGetCommentsHandler"]; + put?: never; + post: operations["SocializeApiModulesCommentsHandlersCreateCommentHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/comments/{id}/resolve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesCommentsHandlersResolveCommentHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/clients/{id}/portrait": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesClientsHandlersChangeClientPortraitHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/clients": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesClientsHandlersGetClientsHandler"]; + put?: never; + post: operations["SocializeApiModulesClientsHandlersCreateClientHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/clients/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["SocializeApiModulesClientsHandlersUpdateClientHandler"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/assets/{id}/revisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesAssetsHandlersCreateAssetRevisionHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/assets/google-drive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesAssetsHandlersCreateGoogleDriveAssetHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/assets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesAssetsHandlersGetAssetsHandler"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/approvals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesApprovalsHandlersGetApprovalsHandler"]; + put?: never; + post: operations["SocializeApiModulesApprovalsHandlersCreateApprovalRequestHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/approvals/{id}/decisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesApprovalsHandlersSubmitApprovalDecisionHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoResponse: { + blobUrl?: string; + }; + SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoRequest: { + /** Format: binary */ + file: string; + }; + FastEndpointsErrorResponse: { + /** + * Format: int32 + * @default 400 + */ + statusCode: number; + /** @default One or more errors occurred! */ + message: string; + errors?: { + [key: string]: string[]; + }; + }; + SocializeApiModulesWorkspacesHandlersWorkspaceDto: { + /** Format: guid */ + id?: string; + name?: string; + slug?: string; + logoUrl?: string | null; + timeZone?: string; + /** Format: date-time */ + createdAt?: string; + }; + SocializeApiModulesWorkspacesHandlersCreateWorkspaceRequest: { + name: string; + slug: string; + timeZone: string; + }; + SocializeApiModulesWorkspacesHandlersWorkspaceInviteDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + email?: string; + role?: string; + status?: string; + /** Format: date-time */ + createdAt?: string; + }; + SocializeApiModulesWorkspacesHandlersCreateWorkspaceInviteRequest: { + /** Format: email */ + email: string; + role: string; + }; + SocializeApiModulesWorkspacesHandlersWorkspaceMemberDto: { + /** Format: guid */ + id?: string; + displayName?: string; + email?: string; + portraitUrl?: string | null; + roles?: string[]; + }; + SocializeApiModulesWorkspacesHandlersUpdateWorkspaceRequest: { + name: string; + timeZone: string; + }; + SocializeApiModulesProjectsHandlersProjectDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + clientId?: string; + name?: string; + description?: string | null; + notes?: string | null; + status?: string; + /** Format: date-time */ + startDate?: string; + /** Format: date-time */ + endDate?: string; + }; + SocializeApiModulesProjectsHandlersCreateProjectRequest: { + /** Format: guid */ + workspaceId: string; + /** Format: guid */ + clientId: string; + name: string; + /** Format: date-time */ + startDate: string; + /** Format: date-time */ + endDate: string; + description?: string | null; + notes?: string | null; + }; + SocializeApiModulesProjectsHandlersGetProjectsRequest: Record; + SocializeApiModulesNotificationsHandlersNotificationEventDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + contentItemId?: string | null; + eventType?: string; + entityType?: string; + /** Format: guid */ + entityId?: string; + message?: string; + /** Format: guid */ + recipientUserId?: string | null; + recipientEmail?: string | null; + metadataJson?: string | null; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + readAt?: string | null; + }; + SocializeApiModulesNotificationsHandlersGetNotificationsRequest: Record; + SocializeApiModulesIdentityHandlersChangeAddressRequest: { + address?: string | null; + }; + SocializeApiModulesIdentityHandlersChangeAliasRequest: { + alias?: string | null; + }; + SocializeApiModulesIdentityHandlersChangeBirthDateRequest: { + /** Format: date-time */ + birthDate?: string; + }; + SocializeApiModulesIdentityHandlersChangeEmailRequest: { + email?: string | null; + }; + SocializeApiModulesIdentityHandlersChangeFullnameRequest: { + firstname?: string | null; + lastname?: string | null; + }; + SocializeApiModulesIdentityHandlersChangePhoneRequest: { + phoneNumber?: string | null; + }; + SocializeApiModulesIdentityHandlersChangePortraitResponse: { + blobUrl?: string; + }; + SocializeApiModulesIdentityHandlersChangePortraitRequest: { + /** Format: binary */ + file: string; + }; + SocializeApiModulesIdentityHandlersForgotPasswordRequest: { + email?: string; + }; + SocializeApiModulesIdentityModelsUserDto: { + /** Format: guid */ + id?: string; + userRoles?: string[]; + persona?: string | null; + authorizedWorkspaceIds?: string[]; + authorizedClientIds?: string[]; + authorizedProjectIds?: string[]; + username?: string; + alias?: string | null; + portraitUrl?: string | null; + firstname?: string | null; + lastname?: string | null; + email?: string | null; + phoneNumber?: string | null; + /** Format: date-time */ + birthDate?: string | null; + address?: string | null; + }; + SystemIOStream: components["schemas"]["SystemMarshalByRefObject"] & { + canTimeout?: boolean; + /** Format: int32 */ + readTimeout?: number; + /** Format: int32 */ + writeTimeout?: number; + }; + SystemMarshalByRefObject: Record; + SocializeApiModulesIdentityHandlersLoginResponse: { + accessToken?: string; + refreshToken?: string; + }; + SocializeApiModulesIdentityHandlersLoginRequest: { + email?: string; + password?: string; + }; + SocializeApiModulesIdentityHandlersLoginWithFacebookResponse: { + accessToken?: string; + refreshToken?: string; + }; + SocializeApiModulesIdentityHandlersLoginWithFacebookRequest: { + token?: string; + }; + SocializeApiModulesIdentityHandlersLoginWithGoogleResponse: { + accessToken?: string; + refreshToken?: string; + }; + SocializeApiModulesIdentityHandlersLoginWithGoogleRequest: { + token?: string; + }; + SocializeApiModulesIdentityHandlersRefreshTokenResponse: { + accessToken?: string; + refreshToken?: string; + }; + SocializeApiModulesIdentityHandlersRefreshTokenRequest: { + refreshToken?: string; + }; + SocializeApiModulesIdentityHandlersRegisterResponse: { + message?: string; + }; + SocializeApiModulesIdentityHandlersRegisterRequest: { + email?: string; + password?: string; + name?: string; + }; + SocializeApiModulesIdentityHandlersResendVerificationResponse: { + message?: string; + }; + SocializeApiModulesIdentityHandlersResendVerificationRequest: { + email?: string; + }; + SocializeApiModulesIdentityHandlersResetPasswordRequest: { + email?: string; + token?: string; + newPassword?: string; + }; + SocializeApiModulesIdentityHandlersSetPasswordRequest: { + newPassword?: string; + }; + SocializeApiModulesIdentityHandlersVerifyEmailResponse: { + message?: string; + }; + SocializeApiModulesIdentityHandlersVerifyEmailRequest: Record; + SocializeApiModulesContentItemsHandlersContentItemDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + clientId?: string; + /** Format: guid */ + projectId?: string; + title?: string; + publicationMessage?: string; + publicationTargets?: string; + hashtags?: string | null; + status?: string; + /** Format: date-time */ + dueDate?: string | null; + currentRevisionLabel?: string; + /** Format: int32 */ + currentRevisionNumber?: number; + }; + SocializeApiModulesContentItemsHandlersCreateContentItemRequest: { + /** Format: guid */ + workspaceId: string; + /** Format: guid */ + clientId: string; + /** Format: guid */ + projectId: string; + title: string; + publicationMessage: string; + publicationTargets: string; + hashtags?: string | null; + /** Format: date-time */ + dueDate?: string | null; + }; + SocializeApiModulesContentItemsHandlersContentItemRevisionDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + contentItemId?: string; + /** Format: int32 */ + revisionNumber?: number; + revisionLabel?: string; + title?: string; + publicationMessage?: string; + publicationTargets?: string; + hashtags?: string | null; + changeSummary?: string | null; + /** Format: guid */ + createdByUserId?: string | null; + /** Format: date-time */ + createdAt?: string; + }; + SocializeApiModulesContentItemsHandlersCreateContentItemRevisionRequest: { + title: string; + publicationMessage: string; + publicationTargets: string; + hashtags?: string | null; + changeSummary?: string | null; + }; + SocializeApiModulesContentItemsHandlersContentItemDetailDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + clientId?: string; + /** Format: guid */ + projectId?: string; + title?: string; + publicationMessage?: string; + publicationTargets?: string; + hashtags?: string | null; + status?: string; + /** Format: date-time */ + dueDate?: string | null; + currentRevisionLabel?: string; + /** Format: int32 */ + currentRevisionNumber?: number; + /** Format: date-time */ + createdAt?: string; + }; + SocializeApiModulesContentItemsHandlersGetContentItemsRequest: Record; + SocializeApiModulesContentItemsHandlersUpdateContentItemStatusRequest: { + status: string; + }; + SocializeApiModulesCommentsHandlersCommentDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + contentItemId?: string; + /** Format: guid */ + parentCommentId?: string | null; + /** Format: guid */ + authorUserId?: string; + authorDisplayName?: string; + authorEmail?: string; + authorPortraitUrl?: string | null; + body?: string; + isResolved?: boolean; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + resolvedAt?: string | null; + }; + SocializeApiModulesCommentsHandlersCreateCommentRequest: { + /** Format: guid */ + workspaceId: string; + /** Format: guid */ + contentItemId: string; + /** Format: guid */ + parentCommentId?: string | null; + body: string; + }; + SocializeApiModulesCommentsHandlersGetCommentsRequest: Record; + SocializeApiModulesClientsHandlersChangeClientPortraitResponse: { + blobUrl?: string; + }; + SocializeApiModulesClientsHandlersChangeClientPortraitRequest: { + /** Format: binary */ + file: string; + }; + SocializeApiModulesClientsHandlersClientDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + name?: string; + status?: string; + portraitUrl?: string | null; + primaryContactName?: string | null; + primaryContactEmail?: string | null; + primaryContactPortraitUrl?: string | null; + }; + SocializeApiModulesClientsHandlersCreateClientRequest: { + /** Format: guid */ + workspaceId: string; + name: string; + portraitUrl?: string | null; + primaryContactName?: string | null; + /** Format: email */ + primaryContactEmail?: string | null; + primaryContactPortraitUrl?: string | null; + }; + SocializeApiModulesClientsHandlersGetClientsRequest: Record; + SocializeApiModulesClientsHandlersUpdateClientRequest: { + name: string; + portraitUrl?: string | null; + status: string; + primaryContactName?: string | null; + /** Format: email */ + primaryContactEmail?: string | null; + primaryContactPortraitUrl?: string | null; + }; + SocializeApiModulesAssetsHandlersAssetRevisionDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + assetId?: string; + /** Format: int32 */ + revisionNumber?: number; + sourceReference?: string; + previewUrl?: string | null; + notes?: string | null; + /** Format: guid */ + createdByUserId?: string | null; + /** Format: date-time */ + createdAt?: string; + }; + SocializeApiModulesAssetsHandlersCreateAssetRevisionRequest: { + sourceReference: string; + previewUrl?: string | null; + notes?: string | null; + }; + SocializeApiModulesAssetsHandlersAssetDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + contentItemId?: string; + assetType?: string; + sourceType?: string; + displayName?: string; + googleDriveFileId?: string | null; + googleDriveLink?: string | null; + previewUrl?: string | null; + /** Format: int32 */ + currentRevisionNumber?: number; + /** Format: date-time */ + createdAt?: string; + revisions?: components["schemas"]["SocializeApiModulesAssetsHandlersAssetRevisionDto"][]; + }; + SocializeApiModulesAssetsHandlersCreateGoogleDriveAssetRequest: { + /** Format: guid */ + workspaceId: string; + /** Format: guid */ + contentItemId: string; + assetType: string; + displayName: string; + googleDriveFileId: string; + googleDriveLink: string; + previewUrl?: string | null; + }; + SocializeApiModulesAssetsHandlersGetAssetsRequest: Record; + SocializeApiModulesApprovalsHandlersApprovalRequestDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + contentItemId?: string; + stage?: string; + reviewerName?: string; + reviewerEmail?: string; + /** Format: guid */ + requestedByUserId?: string; + /** Format: date-time */ + dueAt?: string | null; + state?: string; + accessToken?: string; + /** Format: date-time */ + sentAt?: string; + /** Format: date-time */ + completedAt?: string | null; + decisions?: components["schemas"]["SocializeApiModulesApprovalsHandlersApprovalDecisionDto"][]; + }; + SocializeApiModulesApprovalsHandlersApprovalDecisionDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + approvalRequestId?: string; + decision?: string; + comment?: string | null; + /** Format: guid */ + decidedByUserId?: string | null; + decidedByName?: string; + decidedByEmail?: string; + decidedByPortraitUrl?: string | null; + /** Format: date-time */ + createdAt?: string; + }; + SocializeApiModulesApprovalsHandlersCreateApprovalRequestRequest: { + /** Format: guid */ + workspaceId: string; + /** Format: guid */ + contentItemId: string; + stage: string; + reviewerName: string; + /** Format: email */ + reviewerEmail: string; + /** Format: date-time */ + dueAt?: string | null; + }; + SocializeApiModulesApprovalsHandlersGetApprovalsRequest: Record; + SocializeApiModulesApprovalsHandlersSubmitApprovalDecisionRequest: { + decision: string; + comment?: string | null; + reviewerName?: string | null; + /** Format: email */ + reviewerEmail?: string | null; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesWorkspacesHandlersGetWorkspacesHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesWorkspacesHandlersCreateWorkspaceHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersCreateWorkspaceRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesWorkspacesHandlersGetWorkspaceInvitesHandler: { + parameters: { + query?: never; + header?: never; + path: { + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceInviteDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesWorkspacesHandlersCreateWorkspaceInviteHandler: { + parameters: { + query?: never; + header?: never; + path: { + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersCreateWorkspaceInviteRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceInviteDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesWorkspacesHandlersGetWorkspaceMembersHandler: { + parameters: { + query?: never; + header?: never; + path: { + workspaceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceMemberDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesWorkspacesHandlersUpdateWorkspaceHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersUpdateWorkspaceRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesProjectsHandlersGetProjectsHandler: { + parameters: { + query?: { + workspaceId?: string | null; + clientId?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesProjectsHandlersProjectDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesProjectsHandlersCreateProjectHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesProjectsHandlersCreateProjectRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesProjectsHandlersProjectDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesNotificationsHandlersGetNotificationsHandler: { + parameters: { + query?: { + workspaceId?: string | null; + contentItemId?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesNotificationsHandlersNotificationEventDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesNotificationsHandlersMarkNotificationAsReadHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangeAddressHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangeAddressRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangeAliasHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangeAliasRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangeBirthDateHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangeBirthDateRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangeEmailHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangeEmailRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangeFullnameHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangeFullnameRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangePhoneHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangePhoneRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersGetCurrentUserPortraitHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SystemIOStream"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersChangePortraitHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["SocializeApiModulesIdentityHandlersChangePortraitRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersChangePortraitResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersForgotPasswordHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersForgotPasswordRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersGetCurrentUserQueryHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityModelsUserDto"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersLoginHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersLoginRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersLoginResponse"]; + }; + }; + }; + }; + SocializeApiModulesIdentityHandlersLoginWithFacebookHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersLoginWithFacebookRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersLoginWithFacebookResponse"]; + }; + }; + }; + }; + SocializeApiModulesIdentityHandlersLoginWithGoogleHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersLoginWithGoogleRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersLoginWithGoogleResponse"]; + }; + }; + }; + }; + SocializeApiModulesIdentityHandlersRefreshTokenHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersRefreshTokenRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersRefreshTokenResponse"]; + }; + }; + }; + }; + SocializeApiModulesIdentityHandlersRegisterHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersRegisterRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersRegisterResponse"]; + }; + }; + }; + }; + SocializeApiModulesIdentityHandlersResendVerificationHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersResendVerificationRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersResendVerificationResponse"]; + }; + }; + }; + }; + SocializeApiModulesIdentityHandlersResetPasswordHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersResetPasswordRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersSetPasswordHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersSetPasswordRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesIdentityHandlersVerifyEmailHandler: { + parameters: { + query: { + userId: string; + token: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesIdentityHandlersVerifyEmailResponse"]; + }; + }; + }; + }; + SocializeApiModulesContentItemsHandlersGetContentItemsHandler: { + parameters: { + query?: { + workspaceId?: string | null; + clientId?: string | null; + projectId?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesContentItemsHandlersCreateContentItemHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersCreateContentItemRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesContentItemsHandlersGetContentItemRevisionsHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemRevisionDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesContentItemsHandlersCreateContentItemRevisionHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersCreateContentItemRevisionRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemRevisionDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesContentItemsHandlersGetContentItemHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemDetailDto"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesContentItemsHandlersUpdateContentItemStatusHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersUpdateContentItemStatusRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemDetailDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesCommentsHandlersGetCommentsHandler: { + parameters: { + query: { + contentItemId: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesCommentsHandlersCommentDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesCommentsHandlersCreateCommentHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesCommentsHandlersCreateCommentRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesCommentsHandlersCommentDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesCommentsHandlersResolveCommentHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesCommentsHandlersCommentDto"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesClientsHandlersChangeClientPortraitHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["SocializeApiModulesClientsHandlersChangeClientPortraitRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesClientsHandlersChangeClientPortraitResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesClientsHandlersGetClientsHandler: { + parameters: { + query?: { + workspaceId?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesClientsHandlersClientDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesClientsHandlersCreateClientHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesClientsHandlersCreateClientRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesClientsHandlersClientDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesClientsHandlersUpdateClientHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesClientsHandlersUpdateClientRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesClientsHandlersClientDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesAssetsHandlersCreateAssetRevisionHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesAssetsHandlersCreateAssetRevisionRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesAssetsHandlersAssetRevisionDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesAssetsHandlersCreateGoogleDriveAssetHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesAssetsHandlersCreateGoogleDriveAssetRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesAssetsHandlersAssetDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesAssetsHandlersGetAssetsHandler: { + parameters: { + query: { + contentItemId: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesAssetsHandlersAssetDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesApprovalsHandlersGetApprovalsHandler: { + parameters: { + query: { + contentItemId: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesApprovalsHandlersApprovalRequestDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesApprovalsHandlersCreateApprovalRequestHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesApprovalsHandlersCreateApprovalRequestRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesApprovalsHandlersApprovalRequestDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesApprovalsHandlersSubmitApprovalDecisionHandler: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesApprovalsHandlersSubmitApprovalDecisionRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesApprovalsHandlersApprovalRequestDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + }; + }; +} diff --git a/frontend/src/features/workspaces/components/TimeZoneSelect.vue b/frontend/src/features/workspaces/components/TimeZoneSelect.vue new file mode 100644 index 0000000..3229927 --- /dev/null +++ b/frontend/src/features/workspaces/components/TimeZoneSelect.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/frontend/src/features/workspaces/stores/workspaceStore.js b/frontend/src/features/workspaces/stores/workspaceStore.js index ccf4c4c..7ab9e91 100644 --- a/frontend/src/features/workspaces/stores/workspaceStore.js +++ b/frontend/src/features/workspaces/stores/workspaceStore.js @@ -11,6 +11,8 @@ export const useWorkspaceStore = defineStore('workspace', () => { const activeWorkspaceId = ref(null); const isLoading = ref(false); const isCreating = ref(false); + const isUpdating = ref(false); + const isUploadingLogo = ref(false); const invitesByWorkspace = ref({}); const membersByWorkspace = ref({}); const isInvitesLoading = ref(false); @@ -90,6 +92,74 @@ export const useWorkspaceStore = defineStore('workspace', () => { } } + async function updateWorkspace(workspaceId, payload) { + if (!authStore.isAuthenticated || !workspaceId) { + throw new Error('You must be authenticated to update a workspace.'); + } + + if (isUpdating.value) { + throw new Error('A workspace update request is already in progress.'); + } + + isUpdating.value = true; + error.value = null; + + try { + const response = await client.put(`/api/workspaces/${workspaceId}`, payload); + + if (response.data) { + workspaces.value = workspaces.value + .map(workspace => (workspace.id === workspaceId ? response.data : workspace)) + .sort((left, right) => left.name.localeCompare(right.name)); + } + + return response.data; + } catch (updateError) { + console.error('Failed to update workspace:', updateError); + error.value = 'Failed to update workspace.'; + throw updateError; + } finally { + isUpdating.value = false; + } + } + + async function uploadWorkspaceLogo(workspaceId, file) { + if (!authStore.isAuthenticated || !workspaceId) { + throw new Error('You must be authenticated to upload a workspace logo.'); + } + + if (isUploadingLogo.value) { + throw new Error('A workspace logo upload is already in progress.'); + } + + isUploadingLogo.value = true; + error.value = null; + + try { + const formData = new FormData(); + formData.append('file', file, file.name || 'workspace-logo.png'); + + const response = await client.post(`/api/workspaces/${workspaceId}/logo`, formData); + const blobUrl = response.data?.blobUrl; + + if (blobUrl) { + workspaces.value = workspaces.value.map(workspace => + workspace.id === workspaceId + ? { ...workspace, logoUrl: `${blobUrl}?${Date.now()}` } + : workspace + ); + } + + return response.data; + } catch (uploadError) { + console.error('Failed to upload workspace logo:', uploadError); + error.value = 'Failed to upload workspace logo.'; + throw uploadError; + } finally { + isUploadingLogo.value = false; + } + } + function setActiveWorkspace(workspaceId) { if (workspaces.value.some(workspace => workspace.id === workspaceId)) { activeWorkspaceId.value = workspaceId; @@ -192,6 +262,8 @@ export const useWorkspaceStore = defineStore('workspace', () => { activeWorkspace, isLoading, isCreating, + isUpdating, + isUploadingLogo, invitesByWorkspace, membersByWorkspace, isInvitesLoading, @@ -200,6 +272,8 @@ export const useWorkspaceStore = defineStore('workspace', () => { error, fetchWorkspaces, createWorkspace, + updateWorkspace, + uploadWorkspaceLogo, fetchInvites, fetchMembers, inviteMember, diff --git a/frontend/src/features/workspaces/timeZones.js b/frontend/src/features/workspaces/timeZones.js new file mode 100644 index 0000000..9250183 --- /dev/null +++ b/frontend/src/features/workspaces/timeZones.js @@ -0,0 +1,84 @@ +const FALLBACK_TIME_ZONES = [ + 'UTC', + 'America/Los_Angeles', + 'America/Denver', + 'America/Chicago', + 'America/New_York', + 'America/Toronto', + 'America/Montreal', + 'America/Vancouver', + 'America/Mexico_City', + 'America/Sao_Paulo', + 'Europe/London', + 'Europe/Paris', + 'Europe/Berlin', + 'Europe/Madrid', + 'Europe/Rome', + 'Europe/Amsterdam', + 'Europe/Zurich', + 'Europe/Stockholm', + 'Europe/Warsaw', + 'Africa/Casablanca', + 'Africa/Johannesburg', + 'Asia/Dubai', + 'Asia/Kolkata', + 'Asia/Singapore', + 'Asia/Tokyo', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Australia/Sydney', + 'Pacific/Auckland', +]; + +export function getTimeZoneOptions(selectedTimeZone) { + const supportedTimeZones = getSupportedTimeZones(); + const timeZones = new Set(['UTC', ...supportedTimeZones]); + + if (selectedTimeZone) { + timeZones.add(selectedTimeZone); + } + + return [...timeZones] + .sort((left, right) => left.localeCompare(right)) + .map(timeZone => ({ + value: timeZone, + label: formatTimeZoneLabel(timeZone), + })); +} + +function getSupportedTimeZones() { + if (typeof Intl.supportedValuesOf === 'function') { + return Intl.supportedValuesOf('timeZone'); + } + + return FALLBACK_TIME_ZONES; +} + +function formatTimeZoneLabel(timeZone) { + const offset = formatTimeZoneOffset(timeZone); + + if (!offset) { + return timeZone.replaceAll('_', ' '); + } + + return `${timeZone.replaceAll('_', ' ')} (${offset})`; +} + +function formatTimeZoneOffset(timeZone) { + try { + const parts = new Intl.DateTimeFormat('en-US', { + hour: '2-digit', + timeZone, + timeZoneName: 'shortOffset', + }).formatToParts(new Date()); + const offset = parts.find(part => part.type === 'timeZoneName')?.value; + + if (!offset) { + return null; + } + + return offset.replace('GMT', 'UTC'); + } catch { + return null; + } +} diff --git a/frontend/src/features/workspaces/views/WorkspaceCreateView.vue b/frontend/src/features/workspaces/views/WorkspaceCreateView.vue index 96a9282..ed311f5 100644 --- a/frontend/src/features/workspaces/views/WorkspaceCreateView.vue +++ b/frontend/src/features/workspaces/views/WorkspaceCreateView.vue @@ -2,6 +2,7 @@ import { computed, reactive, ref } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; + import TimeZoneSelect from '@/features/workspaces/components/TimeZoneSelect.vue'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js'; const router = useRouter(); @@ -126,9 +127,8 @@ diff --git a/frontend/src/features/workspaces/views/WorkspaceSettingsView.vue b/frontend/src/features/workspaces/views/WorkspaceSettingsView.vue index f066200..cb6394c 100644 --- a/frontend/src/features/workspaces/views/WorkspaceSettingsView.vue +++ b/frontend/src/features/workspaces/views/WorkspaceSettingsView.vue @@ -1,6 +1,9 @@