Skip to content

Commit

Permalink
(PC-34043)[PRO] feat: Let users create a digital offer on a physical …
Browse files Browse the repository at this point in the history
…venue.
  • Loading branch information
gmeigniez-pass committed Jan 31, 2025
1 parent 7edaaaa commit 954a71b
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 289 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ import {
buildShowSubTypeOptions,
completeSubcategoryConditionalFields,
buildSubcategoryOptions,
buildVenueOptions,
formatVenuesOptions,
deSerializeDurationMinutes,
serializeExtraData,
setDefaultInitialValues,
setDefaultInitialValuesFromOffer,
setFormReadOnlyFields,
serializeDetailsPostData,
Expand Down Expand Up @@ -150,69 +149,46 @@ describe('buildSubcategoryFields', () => {
})
})

describe('buildVenueOptions', () => {
it('should build venues options', () => {
expect(
buildVenueOptions(
[venueListItemFactory({}), venueListItemFactory({})],
false,
false
)
).toStrictEqual([
{
label: 'Sélectionner le partenaire',
value: '',
},
{
label: 'Le nom du lieu 1',
value: '1',
},
{
label: 'Le nom du lieu 2',
value: '2',
},
])
describe('formatVenuesOptions', () => {
it('should format venues as options', () => {
const formattedVenuesOptions = formatVenuesOptions(
[
venueListItemFactory({ isVirtual: false, id: 10 }),
venueListItemFactory({ isVirtual: false, id: 3 }),
],
false
)
expect(formattedVenuesOptions).toEqual(
expect.arrayContaining([expect.objectContaining({ value: '10' })])
)

expect(formattedVenuesOptions).toEqual(
expect.arrayContaining([expect.objectContaining({ value: '3' })])
)
})
})

describe('setDefaultInitialValues', () => {
it('should set default initial values', () => {
expect(
setDefaultInitialValues({
filteredVenues: [venueListItemFactory({}), venueListItemFactory({})],
areSuggestedSubcategoriesUsed: false,
})
).toStrictEqual({
author: '',
categoryId: '',
description: '',
durationMinutes: '',
ean: '',
gtl_id: '',
name: '',
performer: '',
showSubType: '',
showType: '',
speaker: '',
stageDirector: '',
subcategoryConditionalFields: [],
suggestedSubcategory: '',
subcategoryId: '',
venueId: '',
visa: '',
productId: '',
callId: '',
})
it('should exclude digital venues if a physcal venue is available', () => {
const formattedVenuesOptions = formatVenuesOptions(
[
venueListItemFactory({ isVirtual: true, id: 10 }),
venueListItemFactory({ isVirtual: false, id: 3 }),
],
false
)
expect(formattedVenuesOptions).not.toEqual(
expect.arrayContaining([expect.objectContaining({ value: '10' })])
)

expect(
setDefaultInitialValues({
filteredVenues: [venueListItemFactory({ id: 666 })],
areSuggestedSubcategoriesUsed: false,
})
).toStrictEqual(
expect.objectContaining({
venueId: '666',
})
expect(formattedVenuesOptions).toEqual(
expect.arrayContaining([expect.objectContaining({ value: '3' })])
)

const formattedDigitalVenuesOptions = formatVenuesOptions(
[venueListItemFactory({ isVirtual: true, id: 10 })],
true
)
expect(formattedDigitalVenuesOptions).toEqual(
expect.arrayContaining([expect.objectContaining({ value: '10' })])
)
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,58 +179,22 @@ export const onSubcategoryChange = async ({
})
}

export const buildVenueOptions = (
export const formatVenuesOptions = (
venues: VenueListItemResponseModel[],
areSuggestedCategoriesEnabled: boolean,
isOfferAddressEnabled: boolean
isOnline: boolean
) => {
let venueOptions = venues
.filter((venue) => !areSuggestedCategoriesEnabled || !venue.isVirtual)
// We want to display the virtual venues only if there are no physical venues available
// We also want to prevent selecting a virtual venue for a physical offer form
const hasAtLeastOnePhysicalVenue = venues.some((v) => !v.isVirtual)
return venues
.filter((venue) =>
hasAtLeastOnePhysicalVenue || !isOnline ? !venue.isVirtual : true
)
.map((venue) => ({
value: venue.id.toString(),
label: computeVenueDisplayName(venue),
}))
.sort((a, b) => a.label.localeCompare(b.label, 'fr'))
if (venueOptions.length > 1) {
venueOptions = [
{
value: '',
label: isOfferAddressEnabled
? 'Sélectionner la structure'
: 'Sélectionner le partenaire',
},
...venueOptions,
]
}

return venueOptions
}

type SetDefaultInitialValuesProps = {
filteredVenues: VenueListItemResponseModel[]
areSuggestedSubcategoriesUsed: boolean
}

export function setDefaultInitialValues({
filteredVenues,
areSuggestedSubcategoriesUsed,
}: SetDefaultInitialValuesProps): DetailsFormValues {
let venueId = ''

const venues = areSuggestedSubcategoriesUsed
? filteredVenues.filter((v) => !v.isVirtual)
: filteredVenues

if (venues.length === 1) {
venueId = String(venues[0].id)
} else if (venues.length === 0 && filteredVenues.length > 0) {
venueId = String(filteredVenues[0].id)
}

return {
...DEFAULT_DETAILS_FORM_VALUES,
venueId,
}
}

type SetDefaultInitialValuesFromOfferProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
CategoryResponseModel,
SubcategoryResponseModel,
SuggestedSubcategoriesResponseModel,
VenueListItemResponseModel,
} from 'apiClient/v1'
import { useIndividualOfferContext } from 'commons/context/IndividualOfferContext/IndividualOfferContext'
import { CATEGORY_STATUS } from 'commons/core/Offers/constants'
Expand All @@ -19,10 +18,7 @@ import { OnImageUploadArgs } from 'components/ImageUploader/components/ButtonIma
import fullMoreIcon from 'icons/full-more.svg'
import { DEFAULT_DETAILS_FORM_VALUES } from 'pages/IndividualOffer/IndividualOfferDetails/commons/constants'
import { DetailsFormValues } from 'pages/IndividualOffer/IndividualOfferDetails/commons/types'
import {
buildVenueOptions,
isSubCategoryCD,
} from 'pages/IndividualOffer/IndividualOfferDetails/commons/utils'
import { isSubCategoryCD } from 'pages/IndividualOffer/IndividualOfferDetails/commons/utils'
import { Callout } from 'ui-kit/Callout/Callout'
import { CalloutVariant } from 'ui-kit/Callout/types'
import { Select } from 'ui-kit/form/Select/Select'
Expand All @@ -38,7 +34,7 @@ const DEBOUNCE_TIME_BEFORE_REQUEST = 400
type DetailsFormProps = {
isEanSearchDisplayed: boolean
isProductBased: boolean
filteredVenues: VenueListItemResponseModel[]
venuesOptions: { label: string; value: string }[]
filteredCategories: CategoryResponseModel[]
filteredSubcategories: SubcategoryResponseModel[]
readonlyFields: string[]
Expand All @@ -50,7 +46,7 @@ type DetailsFormProps = {
export const DetailsForm = ({
isEanSearchDisplayed,
isProductBased,
filteredVenues,
venuesOptions,
filteredCategories,
filteredSubcategories,
readonlyFields: readOnlyFields,
Expand All @@ -71,12 +67,6 @@ export const DetailsForm = ({
const { offer, subCategories } = useIndividualOfferContext()
const offerAddressEnabled = useActiveFeature('WIP_ENABLE_OFFER_ADDRESS')

const venueOptions = buildVenueOptions(
filteredVenues,
areSuggestedSubcategoriesUsed,
offerAddressEnabled
)

async function getSuggestedSubcategories() {
if (!areSuggestedSubcategoriesUsed && !offer) {
return
Expand Down Expand Up @@ -131,14 +121,14 @@ export const DetailsForm = ({
subcategoryId !== DEFAULT_DETAILS_FORM_VALUES.subcategoryId

const showAddVenueBanner =
!areSuggestedSubcategoriesUsed && venueOptions.length === 0
!areSuggestedSubcategoriesUsed && venuesOptions.length === 0

const isSuggestedSubcategoryDisplayed =
areSuggestedSubcategoriesUsed && !offer && !isProductBased

const showOtherAddVenueBanner =
areSuggestedSubcategoriesUsed &&
venueOptions.length === 0 &&
venuesOptions.length === 0 &&
subcategory?.onlineOfflinePlatform === CATEGORY_STATUS.OFFLINE

return (
Expand Down Expand Up @@ -166,12 +156,18 @@ export const DetailsForm = ({
)}
{!showAddVenueBanner && (
<>
{venueOptions.length > 1 && (
{venuesOptions.length > 1 && (
<FormLayout.Row>
<Select
label={offerAddressEnabled ? 'Qui propose l’offre ?' : 'Lieu'}
name="venueId"
options={venueOptions}
options={venuesOptions}
defaultOption={{
value: '',
label: offerAddressEnabled
? 'Sélectionner la structure'
: 'Sélectionner le partenaire',
}}
onChange={async (ev) => {
if (isProductBased) {
return
Expand All @@ -181,7 +177,7 @@ export const DetailsForm = ({
}}
disabled={
readOnlyFields.includes('venueId') ||
venueOptions.length === 1
venuesOptions.length === 1
}
{...(isSuggestedSubcategoryDisplayed && {
'aria-controls': 'suggested-subcategories',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1038,14 +1038,14 @@ describe('IndividualOfferDetails', () => {
})
})

it('should not render venue field when there is just one venue', () => {
it('should not render venue field when there is just one virtual venue', () => {
vi.spyOn(useAnalytics, 'useRemoteConfigParams').mockReturnValue({
SUGGESTED_CATEGORIES: 'true',
})

renderDetailsScreen({
props: {
venues: [venueListItemFactory({ id: 189 })],
venues: [venueListItemFactory({ id: 189, isVirtual: true })],
},
contextValue,
// There is no world where WIP_SUGGESTED_SUBCATEGORIES is enabled without WIP_SPLIT_OFFER
Expand All @@ -1055,6 +1055,39 @@ describe('IndividualOfferDetails', () => {
expect(screen.queryByText(/Lieu/)).not.toBeInTheDocument()
})

it('should not render venue field when there is just one physical venue', () => {
vi.spyOn(useAnalytics, 'useRemoteConfigParams').mockReturnValue({
SUGGESTED_CATEGORIES: 'true',
})

renderDetailsScreen({
props: {
venues: [venueListItemFactory({ id: 189, isVirtual: false })],
},
contextValue,
})

expect(screen.queryByText(/Lieu/)).not.toBeInTheDocument()
})

it('should not render venue field when there is one physical venue and one virtual venue', () => {
vi.spyOn(useAnalytics, 'useRemoteConfigParams').mockReturnValue({
SUGGESTED_CATEGORIES: 'true',
})

renderDetailsScreen({
props: {
venues: [
venueListItemFactory({ id: 189, isVirtual: false }),
venueListItemFactory({ id: 190, isVirtual: true }),
],
},
contextValue,
})

expect(screen.queryByText(/Lieu/)).not.toBeInTheDocument()
})

describe('onboarding', () => {
beforeEach(() => {
vi.spyOn(router, 'useNavigate').mockReturnValue(mockNavigate)
Expand Down
Loading

0 comments on commit 954a71b

Please sign in to comment.