Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b162bd6
feat(Organization): require organization types in creation and editing
Paul-AUB Oct 19, 2026
bd0fdff
fix(orga): language for orga
Paul-AUB Oct 19, 2026
cd910a9
feat(orga): support types in most pages
Paul-AUB Oct 19, 2026
3c6ae3b
feat(Entities): unify person and organization type presentation
Paul-AUB Oct 19, 2026
243ecd1
fix(Organization): simplify forms and marker updates
Paul-AUB Oct 19, 2026
0459886
fix(Tooling): exclude bundled tools and local artifacts from lint
Paul-AUB Oct 19, 2026
2e2da6f
feat(orga): add moderator vue to manage organizations for type
Paul-AUB Oct 19, 2026
63c7563
feat(orga): add duplicate review and soft-delete redirects
Paul-AUB Oct 19, 2026
5a45730
feat(orga): custom message and country are no more mandatory
Paul-AUB Oct 20, 2026
768e8cf
fix(EntityTable): align author icons with the first text line
Paul-AUB Oct 20, 2026
78ee800
fix(IconLabel): use an ssr-safe icon selector
Paul-AUB Oct 20, 2026
28a5629
fix(Document): use organization type icons for editors and authors
Paul-AUB Oct 20, 2026
da3667d
improvement(Organization): refresh generic and club icons across ui a…
Paul-AUB Oct 20, 2026
20ecdba
refactor(trad): rename club de spéléo in french to make it quicker
Paul-AUB Oct 20, 2026
9618341
fix(Organization): use type icons in organization cards
Paul-AUB Oct 20, 2026
178c128
fix(Document): disable hidden organization name field
Paul-AUB Oct 20, 2026
7b4fbec
fix(Organization): correct search icons and duplicate scan tests
Paul-AUB Oct 20, 2026
1d15534
feat(Organization): display contribution metadata
Paul-AUB Oct 20, 2026
82fa6ac
improvement(Organization): rename caving club to caving group
Paul-AUB Oct 20, 2026
a5e9800
improvement(Organization): improve duplicate comparison workflow
Paul-AUB Oct 20, 2026
b1c3354
feat(Organization): list soft-deleted organizations
Paul-AUB Oct 20, 2026
ebcd95a
feat(orga): improvements
Paul-AUB Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
544 changes: 544 additions & 0 deletions docs/plans/entity-subtypes-ui-ux.md

Large diffs are not rendered by default.

337 changes: 337 additions & 0 deletions docs/plans/organization-types-1374.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export default [
{
ignores: [
'**/node_modules/**',
'**/.yarn/**',
'**/*.local/**',
'**/*.d.ts',
'**/dist/**',
'**/storybook-static/**',
Expand Down
153 changes: 153 additions & 0 deletions packages/web-app/cypress/e2e/deleted-organizations.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const source = {
id: 42,
name: 'Deleted club with a very long organization name for narrow screens',
type: 'SPELEOLOGY',
isDeleted: true,
redirectTo: 43,
cavers: []
};
const target = {
id: 43,
name: 'Surviving club',
type: 'SPELEOLOGY',
isDeleted: false,
cavers: []
};

const visitDeleted = () => {
cy.loginAs({ groups: ['User', 'Moderator', 'Administrator'] });
cy.visit('/organizations/management?tab=deleted', {
onBeforeLoad: win => win.localStorage.setItem('selectedLanguage', 'en')
});
};

describe('Deleted organizations', () => {
[320, 390, 768, 1280].forEach(width => {
it(`previews deleted organizations and confirms permanent deletion at ${width}px`, () => {
cy.viewport(width, 900);
cy.mockApiCatchAll();
let isPermanentlyDeleted = false;
cy.intercept(
{ method: 'GET', pathname: '/api/v1/organizations' },
req => {
expect(req.query).to.include({
isDeleted: 'true',
limit: '50',
skip: '0'
});
req.reply({
statusCode: isPermanentlyDeleted ? 200 : 206,
headers: isPermanentlyDeleted ? {} : { 'Content-Range': '0-1/1' },
body: { organizations: isPermanentlyDeleted ? [] : [source] }
});
}
).as('deletedOrganizations');
cy.intercept(
{ method: 'GET', pathname: '/api/v1/organizations/42' },
{ body: source }
);
cy.intercept(
{ method: 'GET', pathname: '/api/v1/organizations/43' },
{ body: target }
);
cy.intercept(
{ method: 'DELETE', pathname: '/api/v1/organizations/42' },
req => {
expect(req.query).to.include({ isPermanent: '1', entityId: '43' });
isPermanentlyDeleted = true;
req.reply({ body: source });
}
).as('permanentDelete');
visitDeleted();
cy.wait('@deletedOrganizations', { requestTimeout: 30000 });
cy.get('[data-testid="deleted-organizations-count"]').should(
'contain.text',
'1 result'
);
cy.get('[data-testid="deleted-organization-42"]').within(() => {
cy.contains('p', source.name).should($name => {
const nameBounds = $name[0].getBoundingClientRect();
const row = $name[0].parentElement;
const button = row.querySelector(
'[data-testid="permanent-delete-organization-42"]'
);
const buttonBounds = button.getBoundingClientRect();
expect(row.scrollWidth).to.be.at.most(row.clientWidth);
if (width < 900) {
expect(buttonBounds.top).to.be.at.least(nameBounds.bottom);
expect(buttonBounds.width).to.be.closeTo(row.clientWidth, 1);
} else {
expect(buttonBounds.left).to.be.at.least(nameBounds.right);
}
});
cy.get('[data-testid="open-organization-42"]')
.should('have.attr', 'href', '/ui/organizations/42')
.and('have.attr', 'target', '_blank');
cy.get('a[href$="/edit"]').should('not.exist');
});
cy.document().should(doc => {
expect(doc.documentElement.scrollWidth).to.be.at.most(
doc.documentElement.clientWidth
);
});
cy.screenshot(`deleted-organizations-list-${width}`, {
capture: 'viewport'
});
cy.get('[data-testid="preview-organization-42"]').click();
cy.get('[role="dialog"]:visible').should('contain.text', source.name);
cy.get('[role="dialog"]:visible [data-testid="organization-type"] img')
.invoke('attr', 'src')
.as('sourceIcon', { type: 'static' });
cy.get('[role="dialog"]:visible button[aria-label="close"]').click();
cy.get('[role="dialog"]:visible').should('not.exist');
// Restored preview focus can leave its tooltip over the delete button.
cy.get('[data-testid="preview-organization-42"]').trigger('mouseout');
cy.get('[data-testid="permanent-delete-organization-42"]').focus();
cy.get('[role="tooltip"]').should('not.exist');
cy.get('[data-testid="permanent-delete-organization-42"]').click();
cy.get('[role="dialog"]:visible').within(() => {
cy.get('@sourceIcon').then(iconSrc => {
cy.contains('p', source.name)
.parent()
.find('img')
.should('have.attr', 'src', iconSrc);
});
cy.contains(target.name).should('be.visible');
cy.get('a[href="/ui/organizations/43"]').should('not.exist');
cy.screenshot(`deleted-organization-confirmation-${width}`, {
capture: 'viewport'
});
cy.contains('button', /^Merge and permanently delete$/)
.should('be.enabled')
.click();
});
cy.wait('@permanentDelete');
cy.get('[role="dialog"]:visible').should('not.exist');
cy.get('[data-testid="deleted-organization-42"]').should('not.exist');
cy.get('[data-testid="deleted-organizations-count"]').should(
'contain.text',
'0 results'
);
});
});

it('does not present active organizations returned by an outdated API as deleted', () => {
cy.mockApiCatchAll();
cy.intercept(
{ method: 'GET', pathname: '/api/v1/organizations' },
{ body: { organizations: [target] } }
);
visitDeleted();
cy.get('[data-testid="deleted-organizations-section"]').within(() => {
cy.get('[role="alert"]').should(
'contain.text',
'The list of deleted organizations is currently unavailable.'
);
cy.get('[data-testid="deleted-organizations-count"]').should('not.exist');
cy.get('[data-testid^="deleted-organization-"]').should('not.exist');
cy.get('[data-testid^="permanent-delete-organization-"]').should(
'not.exist'
);
});
});
});
19 changes: 19 additions & 0 deletions packages/web-app/cypress/e2e/deletion-redirect.cy.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
const expectOrganizationIcon = name => {
cy.get('@sourceIcon').then(iconSrc => {
cy.contains('p', name)
.parent()
.find('img')
.should('have.attr', 'src', iconSrc);
});
};

const scenarios = [
{
path: 'organizations',
source: {
name: 'Source organization',
type: 'SPELEOLOGY',
cavers: [],
exploredEntrances: [{ id: 101, name: 'Linked entrance' }],
exploredNetworks: [{ id: 102, name: 'Linked network', nbEntrances: 2 }]
Expand Down Expand Up @@ -81,6 +91,9 @@ describe('Deletion with an existing redirect', () => {
cy.wait('@source');

if (path === 'organizations') {
cy.get('[data-testid="organization-type"] img')
.invoke('attr', 'src')
.as('sourceIcon', { type: 'static' });
// MUI's generated icon test IDs are omitted from production builds.
// Use the application-owned IDs so this also checks the CI bundle.
cy.get('[data-testid="unlink-network-icon"]').should('have.length', 1);
Expand All @@ -89,6 +102,9 @@ describe('Deletion with an existing redirect', () => {

cy.get('button[aria-label="Delete"]').click();
cy.get('[role="dialog"]').within(() => {
if (path === 'organizations') {
expectOrganizationIcon(source.name);
}
cy.get('[role="combobox"]').type('Destination');
});
cy.wait('@search');
Expand All @@ -105,6 +121,9 @@ describe('Deletion with an existing redirect', () => {
cy.contains('button', /^Permanently delete$/).click();
cy.wait('@target');
cy.get('[role="dialog"]').within(() => {
if (path === 'organizations') {
expectOrganizationIcon(source.name);
}
cy.contains(target.title ?? target.name).should('be.visible');
cy.get('[role="combobox"]').should('not.exist');
cy.contains('button', /^Cancel$/).click();
Expand Down
Loading
Loading