Skip to content

Commit

Permalink
(PC-34043)[PRO] feat: Move the url field to the first step of individ…
Browse files Browse the repository at this point in the history
…ual offer creation.
  • Loading branch information
gmeigniez-pass committed Feb 4, 2025
1 parent 98aab50 commit 8f3f305
Show file tree
Hide file tree
Showing 31 changed files with 144 additions and 183 deletions.
5 changes: 4 additions & 1 deletion api/src/pcapi/sandboxes/scripts/getters/pro.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ def create_regular_pro_user_with_virtual_offer() -> dict:
offerers_factories.VenueFactory(name="Mon Lieu", managingOfferer=offerer, isPermanent=True)
virtual_venue = offerers_factories.VirtualVenueFactory(managingOfferer=offerer)
offers_factories.OfferFactory(
name="Mon offre virtuelle", subcategoryId=subcategories.ABO_PLATEFORME_VIDEO.id, venue=virtual_venue
name="Mon offre virtuelle",
subcategoryId=subcategories.ABO_PLATEFORME_VIDEO.id,
venue=virtual_venue,
url="http://www.example.com",
)
return {"user": get_pro_user_helper(pro_user)}

Expand Down
42 changes: 27 additions & 15 deletions pro/cypress/e2e/editDigitalIndividualOffer.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { addDays, format } from 'date-fns'

import { sessionLogInAndGoToPage, logInAndGoToPage } from '../support/helpers.ts'
import {
sessionLogInAndGoToPage,
logInAndGoToPage,
} from '../support/helpers.ts'

describe('Edit digital individual offers', () => {
describe('Display and url modification', () => {
Expand All @@ -17,7 +20,11 @@ describe('Edit digital individual offers', () => {
})

it('An edited offer is displayed with 4 tabs', function () {
sessionLogInAndGoToPage('Session edit digital individual offer', login1, '/offre/individuelle/1/recapitulatif/details')
sessionLogInAndGoToPage(
'Session edit digital individual offer',
login1,
'/offre/individuelle/1/recapitulatif/details'
)

cy.contains('Récapitulatif')

Expand All @@ -33,30 +40,40 @@ describe('Edit digital individual offers', () => {
})

it('I should be able to modify the url of a digital offer', function () {
sessionLogInAndGoToPage('Session edit digital individual offer', login1, '/offres')
sessionLogInAndGoToPage(
'Session edit digital individual offer',
login1,
'/offres'
)

cy.stepLog({ message: 'I open the first offer in the list' })
cy.findAllByTestId('offer-item-row')
.first()
.within(() => {
cy.findByRole('button', { name: 'Voir les actions'}).click()
cy.findByRole('button', { name: 'Voir les actions' }).click()
})
cy.findByRole('menuitem', { name: 'Voir l’offre' }).click()
cy.url().should('contain', '/recapitulatif')

cy.findByRole('link', { name: 'Modifier les détails de l’offre' }).click()

cy.stepLog({ message: 'I update the url link' })
const randomUrl = `http://myrandomurl.fr/`
cy.get('input#url').type('{selectall}{del}' + randomUrl)

cy.stepLog({ message: 'I display Informations pratiques tab' })
cy.findByText('Enregistrer les modifications').click()

cy.findByText('http://myrandomurl.fr/').should('exist')

cy.findByText('Informations pratiques').click()
cy.url().should('contain', '/pratiques')

cy.stepLog({ message: 'I edit the offer displayed' })
cy.get('a[aria-label^="Modifier les détails de l’offre"]').click()
cy.findByRole('link', { name: 'Modifier les détails de l’offre' }).click()
cy.url().should('contain', '/edition/pratiques')

cy.stepLog({ message: 'I update the url link' })
const randomUrl = `http://myrandomurl.fr/`
cy.get('input#url').type('{selectall}{del}' + randomUrl)
cy.findByText('Enregistrer les modifications').click()
cy.findByText('http://myrandomurl.fr/').should('exist')
cy.findByText('Retour à la liste des offres').click()
cy.url().should('contain', '/offres')
cy.findAllByTestId('spinner', { timeout: 30 * 1000 }).should('not.exist')
Expand All @@ -66,19 +83,14 @@ describe('Edit digital individual offers', () => {
cy.findAllByTestId('offer-item-row')
.first()
.within(() => {
cy.findByRole('button', { name: 'Voir les actions'}).click()
cy.findByRole('button', { name: 'Voir les actions' }).click()
})
cy.findByRole('menuitem', { name: 'Voir l’offre' }).click()
cy.url().should('contain', '/recapitulatif')

cy.stepLog({ message: 'I display Informations pratiques tab' })
cy.findByText('Informations pratiques').click()
cy.url().should('contain', '/pratiques')

cy.stepLog({
message: 'the url updated is retrieved in the details of the offer',
})
cy.contains('URL d’accès à l’offre : ' + randomUrl)
})
})

Expand Down
6 changes: 0 additions & 6 deletions pro/src/commons/utils/getOfferConditionalFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ interface GetOfferConditionalFieldsProps {
offerSubCategory?: SubcategoryResponseModel | null
isUserAdmin?: boolean | null
receiveNotificationEmails?: boolean | null
isVenueVirtual?: boolean | null
}

export const getOfferConditionalFields = ({
offerSubCategory = null,
isUserAdmin = null,
receiveNotificationEmails = null,
isVenueVirtual = null,
}: GetOfferConditionalFieldsProps): string[] => {
const offerConditionalFields = []

Expand Down Expand Up @@ -39,10 +37,6 @@ export const getOfferConditionalFields = ({
offerConditionalFields.push('bookingEmail')
}

if (isVenueVirtual) {
offerConditionalFields.push('url')
}

if (offerSubCategory?.canBeWithdrawable) {
offerConditionalFields.push('withdrawalType')
offerConditionalFields.push('withdrawalDelay')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ export function DetailsSummaryScreen({ offer }: DetailsSummaryScreenProps) {
const aboutDescriptions: Description[] = [
{ title: 'Titre de l’offre', text: offerData.name },
{ title: 'Description', text: offerData.description },
]
].concat(
offer.isDigital
? {
title: 'URL d’accès à l’offre',
text: offer.url || ' - ',
}
: []
)
const venueName = offerData.venuePublicName || offerData.venueName
aboutDescriptions.unshift({
title: isOfferAddressEnabled ? 'Structure' : 'Lieu',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,14 @@ export const OfferSection = ({
const aboutDescriptions: Description[] = [
{ title: 'Titre de l’offre', text: offerData.name },
{ title: 'Description', text: offerData.description || '-' },
]
].concat(
offer.isDigital
? {
title: 'URL d’accès à l’offre',
text: offer.url || ' - ',
}
: []
)
const venueName = offerData.venuePublicName || offerData.venueName
aboutDescriptions.unshift({
title: isOfferAddressEnabled ? 'Structure' : 'Lieu',
Expand Down Expand Up @@ -165,14 +172,6 @@ export const OfferSection = ({
offerData.bookingContact || ' - ',
})
}
if (conditionalFields.includes('url')) {
practicalInfoDescriptions.push({
title: 'URL d’accès à l’offre',
text:
/* istanbul ignore next: DEBT, TO FIX */
offerData.url || ' - ',
})
}

const artisticInformationsFields = [
'speaker',
Expand Down Expand Up @@ -229,7 +228,7 @@ export const OfferSection = ({
})}
aria-label="Modifier les informations pratiques de l’offre"
>
{!offerData.isVenueVirtual && isOfferAddressEnabled && (
{!offer.isDigital && isOfferAddressEnabled && (
<SummarySubSection title="Localisation de l’offre">
<SummaryDescriptionList
listDataTestId="localisation-offer-details"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ describe('routes::Summary::serializers', () => {
venueName: 'Le nom du lieu 1',
venuePublicName: 'Mon Lieu',
venueDepartmentCode: '78',
isVenueVirtual: false,
offererName: 'Le nom de la structure 1',
bookingEmail: 'booking@email.com',
bookingContact: 'alfonsoLeBg@exampple.com',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ export const serializeOfferSectionData = (
venueName: offer.venue.name,
venuePublicName: offer.venue.publicName,
venueDepartmentCode: offer.venue.departementCode,
isVenueVirtual: offer.venue.isVirtual,
offererName: offer.venue.managingOfferer.name,
bookingEmail: offer.bookingEmail,
bookingContact: offer.bookingContact,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export const SummaryScreen = () => {
offerSubCategory,
isUserAdmin: false,
receiveNotificationEmails: true,
isVenueVirtual: offer.venue.isVirtual,
})
const subCategoryConditionalFields = offerSubCategory
? offerSubCategory.conditionalFields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ describe('Summary', () => {
description: 'ma description',
subcategoryId: SubcategoryIdEnum.CONCERT,
url: 'https://offer-url.example.com',
isDigital: true,
withdrawalDetails: 'détails de retrait',
bookingEmail: 'booking@example.com',
venue: getOfferVenueFactory({
Expand Down Expand Up @@ -165,18 +166,12 @@ describe('Summary', () => {
expect(
screen.getByText('Notifications des réservations')
).toBeInTheDocument()
expect(
screen.getByText('URL d’accès à l’offre', { exact: false })
).toBeInTheDocument()
expect(screen.getByText('Aperçu dans l’app')).toBeInTheDocument()

expect(screen.getByText(categories[0].proLabel)).toBeInTheDocument()
expect(screen.getByText(subCategories[0].proLabel)).toBeInTheDocument()
expect(screen.getByText('ma venue (nom public)')).toBeInTheDocument()
expect(screen.getByText('détails de retrait')).toBeInTheDocument()
expect(
screen.getByText('https://offer-url.example.com')
).toBeInTheDocument()
expect(screen.getByText('Non accessible')).toBeInTheDocument()
expect(screen.getByText('booking@example.com')).toBeInTheDocument()
expect(screen.getAllByText('mon offre')).toHaveLength(2)
Expand Down Expand Up @@ -218,6 +213,7 @@ describe('Summary', () => {
screen.queryByText('Visualiser dans l’app')
).not.toBeInTheDocument()
})

it('should render component with new sections', async () => {
vi.spyOn(api, 'getOfferer').mockResolvedValue(
defaultGetOffererResponseModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,6 @@ export function UsefulInformationsSummaryScreen({
text: offer.bookingContact || ' - ',
})
}
if (offer.venue.isVirtual) {
practicalInfoDescriptions.push({
title: 'URL d’accès à l’offre',
text: offer.url || ' - ',
})
}
return (
<SummaryLayout>
<SummaryContent>
Expand All @@ -70,7 +64,7 @@ export function UsefulInformationsSummaryScreen({
})}
aria-label="Modifier les détails de l’offre"
>
{!offer.venue.isVirtual && isOfferAddressEnabled && (
{!offer.isDigital && isOfferAddressEnabled && (
<SummarySubSection title="Localisation de l’offre">
<SummaryDescriptionList
descriptions={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ describe('UsefulInformationsSummaryScreen', () => {
withdrawalDelay: 120,
bookingContact: 'robert@exemple.com',
venue: { ...getOfferVenueFactory(), isVirtual: true },
url: 'https://www.example.com',
})

renderUsefulInformationsSummaryScreen(offer)
Expand All @@ -57,7 +56,6 @@ describe('UsefulInformationsSummaryScreen', () => {
screen.getByText('2 minutes avant le début de l’évènement')
).toBeInTheDocument()
expect(screen.getByText('robert@exemple.com')).toBeInTheDocument()
expect(screen.getByText('https://www.example.com')).toBeInTheDocument()
})

it('should render summary with right field with OA FF', async () => {
Expand Down
2 changes: 1 addition & 1 deletion pro/src/components/OfferAppPreview/OfferAppPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const OfferAppPreview = ({
</div>
)}

{!venue.isVirtual && (
{!offer.isDigital && (
<VenueDetails
venue={venue}
address={isOfferAddressEnabled ? offer.address : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ describe('setDefaultInitialValuesFromOffer', () => {
visa: 'USA',
productId: '',
callId: '',
url: undefined,
})
})
})
Expand Down Expand Up @@ -412,6 +413,7 @@ describe('serializeDetailsPostData', () => {
visa: '123456789',
ean: 'any ean',
},
url: undefined,
productId: undefined,
callId: '1',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export const DEFAULT_DETAILS_FORM_VALUES = {
suggestedSubcategory: '',
productId: '',
callId: '',
url: null,
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type DetailsFormValues = {
suggestedSubcategory: string
productId: string
callId?: string
url?: string | null
}

export type Product = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export function setDefaultInitialValuesFromOffer({
DEFAULT_DETAILS_FORM_VALUES.stageDirector,
productId:
offer.productId?.toString() ?? DEFAULT_DETAILS_FORM_VALUES.productId,
url: offer.url,
}
}

Expand Down Expand Up @@ -320,6 +321,7 @@ export function serializeDetailsPostData(
extraData: serializeExtraData(formValues),
productId: formValues.productId ? Number(formValues.productId) : undefined,
callId: formValues.callId,
url: formValues.url,
})
}

Expand All @@ -329,6 +331,7 @@ type PatchPayload = {
extraData?: Record<string, unknown>
name: string
subcategoryId: string
url?: string | null
}

export function serializeDetailsPatchData(
Expand All @@ -340,5 +343,6 @@ export function serializeDetailsPatchData(
description: formValues.description,
durationMinutes: serializeDurationMinutes(formValues.durationMinutes ?? ''),
extraData: serializeExtraData(formValues),
url: formValues.url,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@ const eanValidation = yup
test: (ean) => ean === undefined || ean.length === 13,
})

// TODO: this regex is subject to backtracking which can lead to "catastrophic backtracking", high memory usage and slow performance
// we cannot use the yup url validation because we need to allow {} in the url to interpolate some data
const offerFormUrlRegex = new RegExp(
/*eslint-disable-next-line no-useless-escape*/
/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)(([a-z0-9]+([\-\.\.-\.@_a-z0-9]+)*\.[a-z]{2,5})|((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d))(:[0-9]{1,5})?\S*?$/,
'i'
)

export const getValidationSchema = ({
isOfferAddressEnabled = false,
isDigitalOffer = false,
}: {
isOfferAddressEnabled: boolean
isDigitalOffer: boolean
}) => {
return yup.object().shape({
name: yup.string().trim().max(90).required('Veuillez renseigner un titre'),
Expand Down Expand Up @@ -71,6 +81,20 @@ export const getValidationSchema = ({
? 'Veuillez sélectionner une structure'
: 'Veuillez sélectionner un lieu'
),
url: isDigitalOffer
? yup
.string()
.required(
'Veuillez renseigner une URL valide. Ex : https://exemple.com'
)
.test({
name: 'url',
message:
'Veuillez renseigner une URL valide. Ex : https://exemple.com',
test: (url?: string) =>
url ? url.match(offerFormUrlRegex) !== null : true,
})
: yup.string().nullable(),
})
}

Expand Down
Loading

0 comments on commit 8f3f305

Please sign in to comment.