Add social sharing buttons and QR code for events

Public event page:
- Share buttons: X (Twitter), Facebook, Instagram (copy link), TikTok (copy link), copy link
- Buttons use url_encode for share URLs with event title + URL
- Instagram/TikTok copy to clipboard (no direct share URL support)
- Consistent brutal design with aria-labels

Organizer dashboard:
- Share X, Facebook, copy link buttons per event in events list
- QR code download button per event
- Route /mon-compte/evenement/{id}/qrcode: generates 400px PNG QR code via Endroid
- QR code points to public event URL, downloaded as qrcode-{slug}.png

JS module:
- assets/modules/share.js: initShare() handles data-share-copy buttons
- Copies URL to clipboard, shows checkmark for 1.5s then restores icon
- 4 tests (no buttons, copy, checkmark restore, multiple buttons)

Social icons already displayed via _social_icons.html.twig component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-23 15:11:53 +01:00
parent 1af242c307
commit 6b009a4511
7 changed files with 171 additions and 1 deletions

75
tests/js/share.test.js Normal file
View File

@@ -0,0 +1,75 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { initShare } from '../../assets/modules/share.js'
describe('initShare', () => {
beforeEach(() => {
document.body.innerHTML = ''
})
it('does nothing without share buttons', () => {
expect(() => initShare()).not.toThrow()
})
it('copies URL to clipboard on click', async () => {
document.body.innerHTML = `
<button data-share-copy="https://example.com/event/1">
<svg class="icon"></svg>
</button>
`
const writeText = vi.fn().mockResolvedValue(undefined)
globalThis.navigator = { clipboard: { writeText } }
initShare()
document.querySelector('[data-share-copy]').click()
await new Promise(r => setTimeout(r, 10))
expect(writeText).toHaveBeenCalledWith('https://example.com/event/1')
})
it('shows checkmark after copy then restores', async () => {
vi.useFakeTimers()
document.body.innerHTML = `
<button data-share-copy="https://example.com">
<svg class="original"></svg>
</button>
`
globalThis.navigator = { clipboard: { writeText: vi.fn().mockResolvedValue(undefined) } }
initShare()
const btn = document.querySelector('[data-share-copy]')
const originalHtml = btn.innerHTML
btn.click()
await vi.advanceTimersByTimeAsync(100)
expect(btn.innerHTML).toContain('M5 13l4 4L19 7')
vi.advanceTimersByTime(1500)
expect(btn.innerHTML).toBe(originalHtml)
vi.useRealTimers()
})
it('handles multiple share buttons', async () => {
document.body.innerHTML = `
<button data-share-copy="https://a.com"><svg></svg></button>
<button data-share-copy="https://b.com"><svg></svg></button>
`
const writeText = vi.fn().mockResolvedValue(undefined)
globalThis.navigator = { clipboard: { writeText } }
initShare()
document.querySelectorAll('[data-share-copy]')[1].click()
await new Promise(r => setTimeout(r, 10))
expect(writeText).toHaveBeenCalledWith('https://b.com')
})
})