Skip to content

Commit

Permalink
Merge branch 'lebihae/betterlydia' into 'main'
Browse files Browse the repository at this point in the history
feat(lydia): improve bank accounts management page, allow deleting lydia accounts

Closes #115 and #1136

See merge request churros/churros!258
  • Loading branch information
ewen-lbh committed Oct 24, 2024
2 parents 2daa50f + 4f87c5e commit a6c0ddd
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 130 deletions.
6 changes: 6 additions & 0 deletions .changeset/small-days-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@churros/api': major
'@churros/app': minor
---

improve groups' Lydia accounts management and fix bugs
1 change: 1 addition & 0 deletions packages/api/src/modules/payments/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export * from './group.lydia-accounts.js';
export * from './mutation.claim-promotion-code.js';
export * from './mutation.create-special-offer-codes.js';
export * from './mutation.delete-lydia-account.js';
export * from './mutation.finish-paypal-registration-payment.js';
export * from './mutation.upsert-lydia-account.js';
export * from './query.lydia-account.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { builder, ensureGlobalId, log, prisma } from '#lib';
import { LocalID } from '#modules/global';
import { canEditLydiaAccounts } from '#modules/groups';
import { LydiaAccountType } from '#modules/payments/types';
import { GraphQLError } from 'graphql';

builder.mutationField('deleteLydiaAccount', (t) =>
t.prismaField({
type: LydiaAccountType,
errors: {},
description:
"Supprimer l'enregistrement d'uncompte Lydia sur un groupe (ne supprime pas le compte Lydia Pro sur Lydia)",
args: {
id: t.arg({ type: LocalID }),
},
async authScopes(_, { id }, { user }) {
const group = await prisma.lydiaAccount
.findUniqueOrThrow({ where: { id: ensureGlobalId(id, 'LydiaAccount') } })
.group({ include: canEditLydiaAccounts.prismaIncludes });

if (!group) throw new GraphQLError("Ce compte Lydia n'est relié à aucun groupe");

return canEditLydiaAccounts(user, group);
},
async resolve(query, _, { id }, { user }) {
const result = await prisma.lydiaAccount.delete({
...query,
where: { id: ensureGlobalId(id, 'LydiaAccount') },
});
await log('lydia-accounts', 'deleted', { account: result }, result.id, user);
return result;
},
}),
);
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { builder, log, prisma } from '#lib';

import { userIsPresidentOf, userIsTreasurerOf } from '#permissions';
import { canEditLydiaAccounts } from '#modules/groups';
import { LydiaAccountType, checkLydiaAccount } from '../index.js';

builder.mutationField('upsertLydiaAccount', (t) =>
t.prismaField({
type: LydiaAccountType,
errors: {},
args: {
id: t.arg.id({ required: false }),
groupUid: t.arg.string(),
name: t.arg.string(),
privateToken: t.arg.string(),
vendorToken: t.arg.string(),
},
authScopes: (_, { groupUid }, { user }) =>
Boolean(
user?.admin || userIsPresidentOf(user, groupUid) || userIsTreasurerOf(user, groupUid),
),
async authScopes(_, { groupUid }, { user }) {
const group = await prisma.group.findUniqueOrThrow({
where: { uid: groupUid },
include: canEditLydiaAccounts.prismaIncludes,
});
return canEditLydiaAccounts(user, group);
},
async resolve(query, _, { id, groupUid, name, privateToken, vendorToken }, { user }) {
await checkLydiaAccount(vendorToken, privateToken);
const data = {
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/modules/payments/types/lydia-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const LydiaAccountType = builder.prismaNode('LydiaAccount', {
studentAssociation: t.relation('studentAssociation', { nullable: true }),
studentAssociationId: t.exposeID('studentAssociationId', { nullable: true }),
events: t.relation('events'),
eventsCount: t.relationCount('events'),
name: t.exposeString('name'),
// tokens are NOT EXPOSED, they should never quit the backend
}),
Expand Down
19 changes: 18 additions & 1 deletion packages/app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,7 @@ A Lydia account
"""
type LydiaAccount implements Node {
events: [Event!]!
eventsCount: Int!
group: Group
groupId: ID
id: ID!
Expand Down Expand Up @@ -2397,6 +2398,10 @@ type Mutation @rateLimit(limit: 1200, duration: 600) {
"""
deleteLink(id: LocalID!): MutationDeleteLinkResult!
"""
Supprimer l'enregistrement d'uncompte Lydia sur un groupe (ne supprime pas le compte Lydia Pro sur Lydia)
"""
deleteLydiaAccount(id: LocalID!): MutationDeleteLydiaAccountResult!
"""
Supprime une page existante. On peut renseigner l'identifiant de la page ou son chemin ainsi que son groupe ou AE d'appartenance.
"""
deletePage(
Expand Down Expand Up @@ -3070,7 +3075,7 @@ type Mutation @rateLimit(limit: 1200, duration: 600) {
name: String!
privateToken: String!
vendorToken: String!
): LydiaAccount!
): MutationUpsertLydiaAccountResult!
upsertManagersOfEvent(eventId: ID!, managers: [ManagerOfEventInput!]!): [EventManager!]!
@deprecated(reason: "Use 'upsertEventManager' and 'removeEventManager' instead")
"""
Expand Down Expand Up @@ -3378,6 +3383,12 @@ type MutationDeleteLinkSuccess {
data: Link!
}

union MutationDeleteLydiaAccountResult = Error | MutationDeleteLydiaAccountSuccess | ZodError

type MutationDeleteLydiaAccountSuccess {
data: LydiaAccount!
}

union MutationDeletePageResult = Error | MutationDeletePageSuccess | ZodError

type MutationDeletePageSuccess {
Expand Down Expand Up @@ -3776,6 +3787,12 @@ type MutationUpsertGroupSuccess {
data: Group!
}

union MutationUpsertLydiaAccountResult = Error | MutationUpsertLydiaAccountSuccess | ZodError

type MutationUpsertLydiaAccountSuccess {
data: LydiaAccount!
}

union MutationUpsertPageResult = Error | MutationUpsertPageSuccess | ZodError

type MutationUpsertPageSuccess {
Expand Down
16 changes: 14 additions & 2 deletions packages/app/src/lib/components/ModalDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export let element: HTMLDialogElement;
export let tall = false;
export let narrow = false;
export let opened = false;
export let maxWidth = 'unset';
export const open = () => {
Expand All @@ -30,6 +31,7 @@

<dialog
class:tall
class:narrow
on:close={(e) => {
if (element.classList.contains('closing')) return;
if (!(e.currentTarget instanceof HTMLDialogElement)) return;
Expand Down Expand Up @@ -73,6 +75,10 @@
height: 80vh;
}
dialog.narrow {
width: 33vw;
}
dialog:global(.closing) {
animation: pop-up 0.5s ease reverse;
}
Expand All @@ -86,19 +92,25 @@
background-color: transparent;
}
dialog:global(.closing)::backdrop {
animation: fade-in 0.25s ease reverse;
}
dialog[open]::backdrop {
overscroll-behavior: contain;
background-color: var(--backdrop);
backdrop-filter: blur(10px);
transition: background-color 0.5s ease;
animation: fade-in 0.75s ease;
}
@keyframes pop-up {
from {
transform: scale(0);
opacity: 0;
transform: scale(0.6);
}
to {
opacity: 1;
transform: scale(1);
}
}
Expand Down
39 changes: 35 additions & 4 deletions packages/app/src/lib/components/ModalOrDrawer.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
<script lang="ts">
import ButtonGhost from '$lib/components/ButtonGhost.svelte';
import ModalDialog from '$lib/components/ModalDialog.svelte';
import ModalDrawer from '$lib/components/ModalDrawer.svelte';
import { isMobile } from '$lib/mobile';
import { createEventDispatcher } from 'svelte';
import IconClose from '~icons/msl/close';
const dispatch = createEventDispatcher<{ close: undefined }>();
/** Signals that the content is quite tall */
export let tall = false;
/** Signals that the content is quite narrow */
export let narrow = false;
/** Set a title. Uses the header slot. Also adds a close button to the right */
export let title = '';
/** Don't render a trigger area */
export let notrigger = false;
Expand Down Expand Up @@ -46,18 +54,37 @@
bind:open={drawerOpen}
>
<slot {close}></slot>
<slot name="header" slot="header" {close}></slot>
<svelte:fragment slot="header">
{#if $$slots.header}
<slot {close} name="header"></slot>
{:else if title}
<header class="using-title">
<h2>{title}</h2>
<ButtonGhost help="Fermer" on:click={close}>
<IconClose />
</ButtonGhost>
</header>
{/if}
</svelte:fragment>
</ModalDrawer>
{:else}
<ModalDialog
on:close-by-outside-click={() => dispatch('close')}
{tall}
{narrow}
bind:element={dialogElement}
bind:open={openDialog}
>
{#if $$slots.header}
<header>
<slot {close} name="header"></slot>
{#if $$slots.header || title}
<header class:using-title={Boolean(title)}>
{#if $$slots.header}
<slot {close} name="header"></slot>
{:else}
<h2>{title}</h2>
<ButtonGhost help="Fermer" on:click={close}>
<IconClose />
</ButtonGhost>
{/if}
</header>
{/if}
<slot {close}></slot>
Expand All @@ -71,4 +98,8 @@
align-items: center;
justify-content: space-between;
}
header.using-title :global(.button-ghost) {
font-size: 1.2em;
}
</style>
29 changes: 29 additions & 0 deletions packages/app/src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,32 @@ export function sentenceJoin(
if (strings.length === 2) return strings.join(` ${join} `);
return `${strings.slice(0, -1).join(', ')} ${join} ${strings.at(-1)}`;
}

/**
* Pluralize a word or noun phrase
* @param singular singular form
* @param count count of objects
* @param plural plural form - default is to add 's' to every word of the singular form
* @returns the singular or plural form, depending on count's value
*/
export function pluralize(singular: string, count: number, plural?: string): string {
// if (LOCALE === 'en') return count === 1 ? singular : plural;
if (LOCALE === 'fr') {
plural ??= singular
.split(' ')
.map((word) => word + 's')
.join(' ');
return count > 1 ? plural : singular;
}
return singular;
}

/**
* Display an amount of things
* @param thing the thing to count
* @param count the amount of things there is
* @returns a sentence part that says how many things there are
*/
export function countThing(thing: string, count: number): string {
return `${count} ${pluralize(thing, count)}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ query PageGroupEditBankAccounts($uid: String!) @loading {
canEditLydiaAccounts(
assert: "Tu n'as pas les droits pour modifier les comptes Lydia de ce groupe"
)
lydiaAccounts {
lydiaAccounts @loading(count: 1) {
id
name
eventsCount
}
}
}
Loading

0 comments on commit a6c0ddd

Please sign in to comment.