Compare commits
4 Commits
work-in-pr
...
1722d65d22
| Author | SHA1 | Date | |
|---|---|---|---|
| 1722d65d22 | |||
| 14023e65d5 | |||
| 237b1a4242 | |||
| ace0279bd0 |
@@ -2,10 +2,10 @@
|
||||
|
||||
public static class KnownRoles
|
||||
{
|
||||
public const string Administrator = nameof(Administrator);
|
||||
public const string Manager = nameof(Manager);
|
||||
public const string Client = nameof(Client);
|
||||
public const string Provider = nameof(Provider);
|
||||
public const string WorkspaceMember = nameof(WorkspaceMember);
|
||||
public const string Developer = nameof(Developer);
|
||||
public const string Administrator = "administrator";
|
||||
public const string Manager = "manager";
|
||||
public const string Client = "client";
|
||||
public const string Provider = "provider";
|
||||
public const string WorkspaceMember = "workspace-member";
|
||||
public const string Developer = "developer";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
public static class WorkspaceInviteStatuses
|
||||
{
|
||||
public const string Pending = "Pending";
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class CreateWorkspaceInviteRequestValidator
|
||||
public CreateWorkspaceInviteRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Email).NotEmpty().MaximumLength(256).EmailAddress();
|
||||
RuleFor(x => x.Role).NotEmpty().Must(role => AllowedRoles.Contains(role));
|
||||
RuleFor(x => x.Role).NotEmpty().Must(role => AllowedRoles.Contains(role)).WithMessage("A valid role should be specified");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class CreateWorkspaceInviteHandler(
|
||||
bool duplicateInvite = await dbContext.WorkspaceInvites.AnyAsync(
|
||||
invite => invite.WorkspaceId == workspaceId &&
|
||||
invite.Email == normalizedEmail &&
|
||||
invite.Status == "Pending",
|
||||
invite.Status == WorkspaceInviteStatuses.Pending,
|
||||
ct);
|
||||
|
||||
if (duplicateInvite)
|
||||
@@ -81,7 +81,7 @@ public class CreateWorkspaceInviteHandler(
|
||||
WorkspaceId = workspaceId,
|
||||
Email = normalizedEmail,
|
||||
Role = normalizedRole,
|
||||
Status = "Pending",
|
||||
Status = WorkspaceInviteStatuses.Pending,
|
||||
InvitedByUserId = User.GetUserId(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Feature: Agentic Platform Scaffold
|
||||
|
||||
## Status
|
||||
|
||||
In Progress
|
||||
|
||||
## Goal
|
||||
|
||||
Align Socialize with the structure generated by `bootstrap-vdp-agentic.sh` while preserving the current product implementation.
|
||||
|
||||
## Backend
|
||||
|
||||
The backend is located at:
|
||||
|
||||
```txt
|
||||
backend/src/Socialize.Api
|
||||
```
|
||||
|
||||
The solution is:
|
||||
|
||||
```txt
|
||||
backend/Socialize.slnx
|
||||
```
|
||||
|
||||
The test project is:
|
||||
|
||||
```txt
|
||||
backend/tests/Socialize.Tests
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
The frontend remains the existing Vue 3 app. Feature-owned route views and stores live under `frontend/src/features/<feature>`, while shared app shell code stays under `frontend/src/layouts`, `frontend/src/components`, `frontend/src/plugins`, and `frontend/src/router`.
|
||||
|
||||
## API Contract
|
||||
|
||||
OpenAPI workflow:
|
||||
|
||||
```bash
|
||||
./scripts/update-openapi.sh
|
||||
```
|
||||
|
||||
Writes:
|
||||
|
||||
```txt
|
||||
shared/openapi/openapi.json
|
||||
frontend/src/api/schema.d.ts
|
||||
```
|
||||
|
||||
## Done When
|
||||
|
||||
- [x] Backend code lives under `backend/src/Socialize.Api`
|
||||
- [x] Backend solution exists at `backend/Socialize.slnx`
|
||||
- [x] Test project exists under `backend/tests/Socialize.Tests`
|
||||
- [x] Root scripts exist
|
||||
- [x] Docker Compose and Caddy files exist
|
||||
- [x] Agentic docs, specs, tasks, and prompts exist
|
||||
- [ ] OpenAPI generation verified against a running backend
|
||||
- [x] Backend build passes
|
||||
- [x] Frontend build passes
|
||||
66
docs/FEATURES/workspace-invites.md
Normal file
66
docs/FEATURES/workspace-invites.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Feature: Workspace Invites
|
||||
|
||||
## Status
|
||||
|
||||
Draft
|
||||
|
||||
## Goal
|
||||
|
||||
Allow workspace managers to invite teammates, clients, and providers into a workspace and allow invited people to accept access with the correct role and workspace scope.
|
||||
|
||||
## User Stories
|
||||
|
||||
- As a workspace manager, I want to invite a person by email and role so that they can access the right workspace.
|
||||
- As an invited person, I want to accept an invite from a link so that I can join the workspace without administrator help.
|
||||
- As an invited person without an account, I want to create my account as part of accepting the invite.
|
||||
- As an invited person with an account, I want the accepted workspace to appear after sign-in.
|
||||
- As a workspace manager, I want to see pending, accepted, cancelled, and expired invites so that I understand who has access or still needs follow-up.
|
||||
|
||||
## Domain Rules
|
||||
|
||||
- Workspace invites belong to exactly one workspace.
|
||||
- Invite email matching should use normalized email addresses.
|
||||
- Pending invite tokens must be single-use and should expire.
|
||||
- Accepted invites must grant the invited role and a workspace scope claim for the invite workspace.
|
||||
- Signed-in users may accept invites only when their account email matches the invite email.
|
||||
- New users may create an account during invite acceptance, then receive the invited role and workspace scope.
|
||||
- Accepted, cancelled, and expired invites must not be accepted again.
|
||||
- Managers can create, list, cancel, and resend invites only for workspaces they can manage.
|
||||
- Managers must not be able to create duplicate pending invites for the same normalized email in the same workspace.
|
||||
- Invite acceptance must be auditable through stored status and timestamp changes.
|
||||
|
||||
## Proposed Statuses
|
||||
|
||||
- `Pending`
|
||||
- `Accepted`
|
||||
- `Cancelled`
|
||||
- `Expired`
|
||||
|
||||
## Backend Surface
|
||||
|
||||
- `POST /api/workspaces/{workspaceId:guid}/invites`
|
||||
- `GET /api/workspaces/{workspaceId:guid}/invites`
|
||||
- `POST /api/workspace-invites/{inviteId:guid}/resend`
|
||||
- `POST /api/workspace-invites/{inviteId:guid}/cancel`
|
||||
- `GET /api/workspace-invites/accept/{token}`
|
||||
- `POST /api/workspace-invites/accept`
|
||||
|
||||
## Frontend Surface
|
||||
|
||||
- Workspace settings members tab for invite creation and invite management.
|
||||
- Public invite acceptance route.
|
||||
- Authenticated invite acceptance route for signed-in users.
|
||||
- Registration/sign-in handoff for invited users without a usable session.
|
||||
|
||||
## Done When
|
||||
|
||||
- [ ] Invite creation sends an email with an acceptance link.
|
||||
- [ ] Acceptance link validates a pending, unexpired, single-use token.
|
||||
- [ ] Signed-in users can accept matching-email invites.
|
||||
- [ ] New users can register through the invite path.
|
||||
- [ ] Accepted invites grant role and workspace scope.
|
||||
- [ ] Accepted users see the workspace after token refresh or sign-in.
|
||||
- [ ] Managers can cancel and resend pending invites.
|
||||
- [ ] Invite statuses are represented without magic strings.
|
||||
- [ ] Backend tests cover create, duplicate, accept, expired, cancelled, and email mismatch cases.
|
||||
- [ ] OpenAPI and frontend API usage are updated after contract changes.
|
||||
@@ -1,64 +0,0 @@
|
||||
# Task: Align repository with bootstrap scaffold
|
||||
|
||||
## Feature
|
||||
|
||||
`docs/FEATURES/platform-scaffold.md`
|
||||
|
||||
## Goal
|
||||
|
||||
Move the current Socialize repository into the structure that `bootstrap-vdp-agentic.sh` would have generated, without replacing the existing product implementation.
|
||||
|
||||
## Context
|
||||
|
||||
The script generates a simple .NET + Vue monorepo with:
|
||||
|
||||
- backend under `backend/src/<App>.Api`
|
||||
- tests under `backend/tests/<App>.Tests`
|
||||
- root scripts under `scripts/`
|
||||
- Docker Compose and Caddy deployment files
|
||||
- OpenAPI sync into `shared/openapi`
|
||||
- agentic docs under `docs/FEATURES`, `docs/TASKS`, `docs/PROMPTS`, and `docs/DECISIONS`
|
||||
|
||||
Socialize already has a larger FastEndpoints backend and Vue app. Preserve that implementation while adopting the scaffold.
|
||||
|
||||
## Files Likely To Change
|
||||
|
||||
- `backend/Socialize.slnx`
|
||||
- `backend/src/Socialize.Api/**`
|
||||
- `backend/tests/Socialize.Tests/**`
|
||||
- `scripts/**`
|
||||
- `deploy/caddy/Caddyfile`
|
||||
- `docker-compose.yml`
|
||||
- `docs/**`
|
||||
- `README.md`
|
||||
- `AGENTS.md`
|
||||
- `.github/workflows/backend-ci.yml`
|
||||
- `frontend/package.json`
|
||||
- `frontend/scripts/fetch-openapi.mjs`
|
||||
- `frontend/src/api/schema.d.ts`
|
||||
|
||||
## Constraints
|
||||
|
||||
- Preserve existing product code.
|
||||
- Do not convert the frontend to TypeScript in this task.
|
||||
- Do not rewrite backend modules into minimal API folders in this task.
|
||||
- Do not introduce new secrets.
|
||||
|
||||
## Done When
|
||||
|
||||
- [x] Backend implementation moved under `backend/src/Socialize.Api`
|
||||
- [x] Backend solution points at the new project path
|
||||
- [x] Test project scaffold exists
|
||||
- [x] Root scripts exist
|
||||
- [x] OpenAPI sync command exists
|
||||
- [x] Agentic docs/specs/tasks/prompts exist
|
||||
- [x] Backend build passes
|
||||
- [x] Frontend build passes
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
dotnet build backend/Socialize.slnx
|
||||
dotnet test backend/Socialize.slnx
|
||||
cd frontend && npm run build
|
||||
```
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task: Contain backend feature mapping
|
||||
|
||||
## Feature
|
||||
|
||||
`docs/FEATURES/platform-scaffold.md`
|
||||
|
||||
## Goal
|
||||
|
||||
Move backend feature-specific persistence mapping out of the shared `AppDbContext` body and into the owning `Modules/<Feature>/Data` folders.
|
||||
|
||||
## Context
|
||||
|
||||
Architecture docs state that current backend feature code stays under `Modules/<Feature>`. `AppDbContext` remains the shared EF Core composition point, but feature-owned model configuration should live with the feature entities.
|
||||
|
||||
## Files Likely To Change
|
||||
|
||||
- `backend/src/Socialize.Api/Data/AppDbContext.cs`
|
||||
- `backend/src/Socialize.Api/Modules/*/Data/*`
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not change API contracts.
|
||||
- Do not change table names, indexes, or column constraints.
|
||||
- Do not introduce a broader persistence refactor.
|
||||
|
||||
## Done When
|
||||
|
||||
- [x] Feature entity mappings live under the owning module folders.
|
||||
- [x] `AppDbContext` delegates feature configuration to modules.
|
||||
- [x] Backend build passes.
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
dotnet build backend/Socialize.slnx
|
||||
dotnet test backend/Socialize.slnx
|
||||
```
|
||||
@@ -1,40 +0,0 @@
|
||||
# Task: Use local blob storage
|
||||
|
||||
## Feature
|
||||
|
||||
`docs/FEATURES/platform-scaffold.md`
|
||||
|
||||
## Goal
|
||||
|
||||
Store uploaded portraits and logos on the API server filesystem instead of Azure Blob Storage.
|
||||
|
||||
## Context
|
||||
|
||||
User, client, and workspace portrait uploads already flow through `IBlobStorage`. The implementation can change without altering endpoint contracts or frontend behavior.
|
||||
|
||||
## Files Likely To Change
|
||||
|
||||
- `backend/src/Socialize.Api/Infrastructure/DependencyInjection.cs`
|
||||
- `backend/src/Socialize.Api/Infrastructure/BlobStorage/Services/*`
|
||||
- `backend/src/Socialize.Api/Infrastructure/BlobStorage/Configuration/*`
|
||||
- `backend/src/Socialize.Api/Program.cs`
|
||||
- `backend/src/Socialize.Api/appsettings.Development.json`
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not change API request or response contracts.
|
||||
- Keep upload validation behavior consistent with the existing blob storage implementation.
|
||||
- Serve returned blob URLs from the API host so the existing frontend can keep using `portraitUrl` and `logoUrl`.
|
||||
|
||||
## Done When
|
||||
|
||||
- [x] `IBlobStorage` resolves to local filesystem storage by default.
|
||||
- [x] Uploaded files are served back from the API host.
|
||||
- [x] Backend build passes.
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
dotnet build backend/Socialize.slnx
|
||||
dotnet test backend/Socialize.slnx
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
# Task: Improve UI Surface Contrast
|
||||
|
||||
## Goal
|
||||
|
||||
Increase contrast between the app background, panels, and form controls so inputs are easier to identify against white or near-white surfaces.
|
||||
|
||||
## Feature Spec
|
||||
|
||||
`docs/FEATURES/platform-scaffold.md`
|
||||
|
||||
## Scope
|
||||
|
||||
- Update the shared frontend color tokens.
|
||||
- Configure Vuetify to use the Socialize light theme colors.
|
||||
- Add shared form control and surface defaults for native and Vuetify controls.
|
||||
- Avoid feature-specific behavior changes.
|
||||
|
||||
## Likely Files
|
||||
|
||||
- `frontend/src/assets/main.css`
|
||||
- `frontend/src/main.js`
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
# Task: Backend Workspace Invite Foundation
|
||||
|
||||
## Goal
|
||||
|
||||
Implement the backend data model and endpoints needed to create, list, and accept workspace invites.
|
||||
|
||||
## Feature Spec
|
||||
|
||||
- `docs/FEATURES/workspace-invites.md`
|
||||
|
||||
## Scope
|
||||
|
||||
- Add a `WorkspaceInvite` persistence model with workspace id, normalized email, role, status, inviter, token data, timestamps, and expiration.
|
||||
- Add invite statuses for `Pending`, `Accepted`, `Cancelled`, and `Expired` without magic strings.
|
||||
- Add a manager-only endpoint to create workspace invites.
|
||||
- Add a manager-only endpoint to list workspace invites.
|
||||
- Prevent duplicate pending invites for the same normalized email in the same workspace.
|
||||
- Add a public endpoint to resolve an invite token for display-safe invite details.
|
||||
- Add an accept endpoint that validates token, status, expiration, and email match.
|
||||
- On acceptance, grant the invited role and `KnownClaims.WorkspaceScope` claim to the user.
|
||||
- Mark the invite as accepted in the same transaction as access grants.
|
||||
- Add backend tests for create, list, pending, accepted, expired, cancelled, duplicate, and email mismatch paths.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Keep backend code under `backend/src/Socialize.Api`.
|
||||
- Keep workspace feature code under `Modules/Workspaces`.
|
||||
- Do not expose raw token values in manager invite lists.
|
||||
- Frontend invite screens are covered by task 003 and task 004.
|
||||
|
||||
## Likely Files
|
||||
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceInvite.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceInviteStatuses.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Data/WorkspaceModelConfiguration.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/CreateWorkspaceInvite.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/GetWorkspaceInvites.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/*Accept*Invite*.cs`
|
||||
- `backend/tests/Socialize.Tests/`
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
dotnet build backend/Socialize.slnx
|
||||
dotnet test backend/Socialize.slnx
|
||||
```
|
||||
37
docs/TASKS/workspace-invites/002-invite-email-delivery.md
Normal file
37
docs/TASKS/workspace-invites/002-invite-email-delivery.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Task: Invite Email Delivery
|
||||
|
||||
## Goal
|
||||
|
||||
Send invited users an acceptance link when a workspace invite is created or resent.
|
||||
|
||||
## Feature Spec
|
||||
|
||||
- `docs/FEATURES/workspace-invites.md`
|
||||
|
||||
## Scope
|
||||
|
||||
- Generate acceptance URLs from configured website options.
|
||||
- Send an invite email after successful invite creation.
|
||||
- Add a manager-only resend endpoint for pending, unexpired invites.
|
||||
- Avoid sending email if invite creation fails.
|
||||
- Do not include sensitive token values in logs.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Use the repository email infrastructure.
|
||||
- Do not introduce a new email provider.
|
||||
- Keep email copy concise and product-specific.
|
||||
|
||||
## Likely Files
|
||||
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/CreateWorkspaceInvite.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/ResendWorkspaceInvite.cs`
|
||||
- `backend/src/Socialize.Api/Infrastructure/Emailer/`
|
||||
- `backend/src/Socialize.Api/Infrastructure/Configuration/WebsiteOptions.cs`
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
dotnet build backend/Socialize.slnx
|
||||
dotnet test backend/Socialize.slnx
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
# Task: Frontend Invite Acceptance
|
||||
|
||||
## Goal
|
||||
|
||||
Build the invite acceptance route and connect it to registration or sign-in.
|
||||
|
||||
## Feature Spec
|
||||
|
||||
- `docs/FEATURES/workspace-invites.md`
|
||||
|
||||
## Scope
|
||||
|
||||
- Add a public route for invite acceptance links.
|
||||
- Load display-safe invite details from the token.
|
||||
- If the user is signed in with the invited email, allow direct acceptance.
|
||||
- If the user is signed in with a different email, show a clear mismatch state.
|
||||
- If the user is signed out, route them to sign in or register and resume acceptance afterward.
|
||||
- Refresh the current user profile after acceptance so the new workspace appears.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Frontend runtime config must flow through `frontend/src/config.js`.
|
||||
- Feature-owned code belongs under `frontend/src/features/workspaces`.
|
||||
- Do not add a marketing-style landing page for invite acceptance.
|
||||
|
||||
## Likely Files
|
||||
|
||||
- `frontend/src/router/router.js`
|
||||
- `frontend/src/features/workspaces/`
|
||||
- `frontend/src/features/workspaces/stores/workspaceStore.js`
|
||||
- `frontend/src/locales/en.json`
|
||||
- `frontend/src/locales/fr.json`
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
42
docs/TASKS/workspace-invites/004-invite-management-polish.md
Normal file
42
docs/TASKS/workspace-invites/004-invite-management-polish.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Task: Invite Management Polish
|
||||
|
||||
## Goal
|
||||
|
||||
Make workspace invite management complete for managers after acceptance exists.
|
||||
|
||||
## Feature Spec
|
||||
|
||||
- `docs/FEATURES/workspace-invites.md`
|
||||
|
||||
## Scope
|
||||
|
||||
- Show invite statuses in workspace settings.
|
||||
- Add manager actions to cancel and resend pending invites.
|
||||
- Hide or disable actions for accepted, cancelled, and expired invites.
|
||||
- Decide whether the default list shows all invites or only active pending invites.
|
||||
- Ensure accepted users appear in the active members list after acceptance.
|
||||
- Update OpenAPI and frontend API usage after backend contract changes.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Keep workspace settings within repository layout conventions.
|
||||
- Avoid broad member-management refactors.
|
||||
|
||||
## Likely Files
|
||||
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/GetWorkspaceInvites.cs`
|
||||
- `backend/src/Socialize.Api/Modules/Workspaces/Handlers/CancelWorkspaceInvite.cs`
|
||||
- `frontend/src/features/workspaces/views/WorkspaceSettingsView.vue`
|
||||
- `frontend/src/features/workspaces/stores/workspaceStore.js`
|
||||
- `shared/openapi/openapi.json`
|
||||
- `frontend/src/api/schema.d.ts`
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
dotnet build backend/Socialize.slnx
|
||||
dotnet test backend/Socialize.slnx
|
||||
./scripts/update-openapi.sh
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
const inviteForm = reactive({
|
||||
email: '',
|
||||
role: 'workspaceMember',
|
||||
role: 'workspace-member',
|
||||
});
|
||||
|
||||
const pendingInvites = computed(() =>
|
||||
@@ -161,7 +161,7 @@
|
||||
});
|
||||
|
||||
inviteForm.email = '';
|
||||
inviteForm.role = 'workspaceMember';
|
||||
inviteForm.role = 'workspace-member';
|
||||
} catch (error) {
|
||||
console.error('Failed to invite workspace member:', error);
|
||||
}
|
||||
@@ -330,7 +330,7 @@
|
||||
<label class="field">
|
||||
<span>{{ t('workspaceSettings.fields.memberRole') }}</span>
|
||||
<select v-model="inviteForm.role">
|
||||
<option value="workspaceMember">{{ t('workspaceSettings.roles.workspaceMember') }}</option>
|
||||
<option value="workspace-member">{{ t('workspaceSettings.roles.workspace-member') }}</option>
|
||||
<option value="client">{{ t('workspaceSettings.roles.client') }}</option>
|
||||
<option value="provider">{{ t('workspaceSettings.roles.provider') }}</option>
|
||||
</select>
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
"manager": "Manager",
|
||||
"client": "Client reviewer",
|
||||
"provider": "Subcontractor",
|
||||
"workspaceMember": "Workspace member"
|
||||
"workspace-member": "Workspace member"
|
||||
},
|
||||
"summary": {
|
||||
"name": "Name",
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
"manager": "Gestionnaire",
|
||||
"client": "Réviseur client",
|
||||
"provider": "Sous-traitant",
|
||||
"workspaceMember": "Membre de l'espace"
|
||||
"workspace-member": "Membre de l'espace"
|
||||
},
|
||||
"summary": {
|
||||
"name": "Nom",
|
||||
|
||||
@@ -27,211 +27,211 @@ const DeveloperFeedbackListView = () => import('@/features/feedback/views/Develo
|
||||
const DeveloperFeedbackDetailView = () => import('@/features/feedback/views/DeveloperFeedbackDetailView.vue');
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'landing',
|
||||
component: Landing,
|
||||
},
|
||||
{
|
||||
path: '/app',
|
||||
redirect: { name: 'dashboard' },
|
||||
},
|
||||
{
|
||||
path: '/app/dashboard',
|
||||
name: 'dashboard',
|
||||
component: OverviewView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/workspace',
|
||||
name: 'workspace-dashboard',
|
||||
component: DashboardView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/channels',
|
||||
name: 'channels',
|
||||
component: ChannelsView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/media-library',
|
||||
name: 'media-library',
|
||||
component: MediaLibraryView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/campaigns',
|
||||
name: 'campaigns',
|
||||
component: CampaignsView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/campaigns/:projectId',
|
||||
name: 'campaign-detail',
|
||||
component: CampaignDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/reviews',
|
||||
name: 'review-queue',
|
||||
component: ReviewQueueView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/my-feedback',
|
||||
name: 'my-feedback',
|
||||
component: MyFeedbackListView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/my-feedback/:id',
|
||||
name: 'my-feedback-detail',
|
||||
component: MyFeedbackDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/feedback',
|
||||
name: 'developer-feedback',
|
||||
component: DeveloperFeedbackListView,
|
||||
meta: { requiresAuth: true, roles: ['Developer'] },
|
||||
},
|
||||
{
|
||||
path: '/app/feedback/:id',
|
||||
name: 'developer-feedback-detail',
|
||||
component: DeveloperFeedbackDetailView,
|
||||
meta: { requiresAuth: true, roles: ['Developer'] },
|
||||
},
|
||||
{
|
||||
path: '/app/workspace-settings',
|
||||
name: 'workspace-settings',
|
||||
{
|
||||
path: '/',
|
||||
name: 'landing',
|
||||
component: Landing,
|
||||
},
|
||||
{
|
||||
path: '/app',
|
||||
redirect: { name: 'dashboard' },
|
||||
},
|
||||
{
|
||||
path: '/app/dashboard',
|
||||
name: 'dashboard',
|
||||
component: OverviewView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/workspace',
|
||||
name: 'workspace-dashboard',
|
||||
component: DashboardView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/channels',
|
||||
name: 'channels',
|
||||
component: ChannelsView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/media-library',
|
||||
name: 'media-library',
|
||||
component: MediaLibraryView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/campaigns',
|
||||
name: 'campaigns',
|
||||
component: CampaignsView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/campaigns/:projectId',
|
||||
name: 'campaign-detail',
|
||||
component: CampaignDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/reviews',
|
||||
name: 'review-queue',
|
||||
component: ReviewQueueView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/my-feedback',
|
||||
name: 'my-feedback',
|
||||
component: MyFeedbackListView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/my-feedback/:id',
|
||||
name: 'my-feedback-detail',
|
||||
component: MyFeedbackDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/feedback',
|
||||
name: 'developer-feedback',
|
||||
component: DeveloperFeedbackListView,
|
||||
meta: { requiresAuth: true, roles: ['developer'] },
|
||||
},
|
||||
{
|
||||
path: '/app/feedback/:id',
|
||||
name: 'developer-feedback-detail',
|
||||
component: DeveloperFeedbackDetailView,
|
||||
meta: { requiresAuth: true, roles: ['developer'] },
|
||||
},
|
||||
{
|
||||
path: '/app/workspace-settings',
|
||||
name: 'workspace-settings',
|
||||
component: WorkspaceSettingsView,
|
||||
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||
},
|
||||
{
|
||||
path: '/app/workspaces/new',
|
||||
name: 'workspace-create',
|
||||
component: WorkspaceCreateView,
|
||||
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||
},
|
||||
{
|
||||
path: '/app/settings',
|
||||
component: SettingsLayoutView,
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: { name: 'settings-user-information' },
|
||||
},
|
||||
{
|
||||
path: 'user-information',
|
||||
name: 'settings-user-information',
|
||||
component: UserSettingsView,
|
||||
},
|
||||
{
|
||||
path: 'workspaces',
|
||||
name: 'settings-workspaces',
|
||||
component: WorkspaceSettingsView,
|
||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
||||
},
|
||||
{
|
||||
path: '/app/workspaces/new',
|
||||
name: 'workspace-create',
|
||||
component: WorkspaceCreateView,
|
||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
||||
},
|
||||
{
|
||||
path: '/app/settings',
|
||||
component: SettingsLayoutView,
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: { name: 'settings-user-information' },
|
||||
},
|
||||
{
|
||||
path: 'user-information',
|
||||
name: 'settings-user-information',
|
||||
component: UserSettingsView,
|
||||
},
|
||||
{
|
||||
path: 'workspaces',
|
||||
name: 'settings-workspaces',
|
||||
component: WorkspaceSettingsView,
|
||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
||||
},
|
||||
{
|
||||
path: 'integrations',
|
||||
name: 'settings-integrations',
|
||||
component: IntegrationsSettingsView,
|
||||
meta: { requiresAuth: true, roles: ['Administrator', 'Manager'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/app/content',
|
||||
name: 'content-items',
|
||||
component: ContentItemsView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/content/new',
|
||||
name: 'content-item-create',
|
||||
component: ContentItemDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/content/:id',
|
||||
name: 'content-item-detail',
|
||||
component: ContentItemDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: LoginView,
|
||||
meta: { notAuthenticated: true },
|
||||
props: route => ({ returnUrl: route.query.returnUrl || '/app/dashboard' }),
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
redirect: { name: 'dashboard' },
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: RegisterView,
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: '/forgot-password',
|
||||
name: 'forgot-password',
|
||||
component: ForgotPasswordView,
|
||||
meta: { notAuthenticated: true },
|
||||
},
|
||||
{
|
||||
path: '/reset-password',
|
||||
name: 'reset-password',
|
||||
component: ResetPasswordView,
|
||||
meta: { notAuthenticated: true },
|
||||
props: route => ({ email: route.query.email, token: route.query.token }),
|
||||
},
|
||||
{
|
||||
path: '/verify-email',
|
||||
name: 'verify-email',
|
||||
component: VerifyEmailView,
|
||||
meta: { notAuthenticated: true },
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: { name: 'landing' },
|
||||
},
|
||||
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||
},
|
||||
{
|
||||
path: 'integrations',
|
||||
name: 'settings-integrations',
|
||||
component: IntegrationsSettingsView,
|
||||
meta: { requiresAuth: true, roles: ['administrator', 'manager'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/app/content',
|
||||
name: 'content-items',
|
||||
component: ContentItemsView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/content/new',
|
||||
name: 'content-item-create',
|
||||
component: ContentItemDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/app/content/:id',
|
||||
name: 'content-item-detail',
|
||||
component: ContentItemDetailView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: LoginView,
|
||||
meta: { notAuthenticated: true },
|
||||
props: route => ({ returnUrl: route.query.returnUrl || '/app/dashboard' }),
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
redirect: { name: 'dashboard' },
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: RegisterView,
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: '/forgot-password',
|
||||
name: 'forgot-password',
|
||||
component: ForgotPasswordView,
|
||||
meta: { notAuthenticated: true },
|
||||
},
|
||||
{
|
||||
path: '/reset-password',
|
||||
name: 'reset-password',
|
||||
component: ResetPasswordView,
|
||||
meta: { notAuthenticated: true },
|
||||
props: route => ({ email: route.query.email, token: route.query.token }),
|
||||
},
|
||||
{
|
||||
path: '/verify-email',
|
||||
name: 'verify-email',
|
||||
component: VerifyEmailView,
|
||||
meta: { notAuthenticated: true },
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: { name: 'landing' },
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
// Navigation guards
|
||||
router.beforeEach((to, from, next) => {
|
||||
const authStore = useAuthStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
next({
|
||||
name: 'login',
|
||||
query: { returnUrl: to.fullPath },
|
||||
});
|
||||
} else {
|
||||
const requiredRoles = to.matched.flatMap(record => record.meta.roles ?? []);
|
||||
if (requiredRoles.length > 0 && !authStore.hasAnyRole(requiredRoles)) {
|
||||
next({ name: 'dashboard' });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
} else if (to.matched.some(record => record.meta.notAuthenticated)) {
|
||||
if (authStore.isAuthenticated) next({ name: 'dashboard' });
|
||||
else next();
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
next({
|
||||
name: 'login',
|
||||
query: { returnUrl: to.fullPath },
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
const requiredRoles = to.matched.flatMap(record => record.meta.roles ?? []);
|
||||
if (requiredRoles.length > 0 && !authStore.hasAnyRole(requiredRoles)) {
|
||||
next({ name: 'dashboard' });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
} else if (to.matched.some(record => record.meta.notAuthenticated)) {
|
||||
if (authStore.isAuthenticated) next({ name: 'dashboard' });
|
||||
else next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user