Releases: sharetribe/web-template
v10.5.0
This minor release is mainly about small improvements. For example:
- Hiding the ActivityFeed messages of a user with "banned" status
- This is done on React app level aka on the browser only.
- Fix for a couple of modals, which had some scrolling issues on some mobile devices.
- MobileMenu (opened with hamburger icon)
- OrderPanel
- EditListingPage.duck.js: fix a bug with availability exceptions fetching.
Translation changes
New translations
+ "TransactionPage.messageSenderBanned": "This message is hidden for safety reasons.",Changed translations
- "TransactionPanel.customerBannedStatus": "The user made the request, but was later banned.",
+ "TransactionPanel.customerBannedStatus": "The user was banned after contacting you.",Changes v10.5.0
- [add] Hide messages from banned users in ActivityFeed
#724 - [fix] This changes the height of modals related to mobile menu and order panel on mobile layout.
#725 - [fix] EditListingPage.duck.js: fix a bug with availability exceptions fetching. A race condition
was introduced when Redux TLK was taken into use.
#722 - [fix] Update the default-negotation transaction processe email templates to use the color from
Branding asset. #721 - [fix] Update the color of buttons in transaction processes email templates.
#718
v10.4.0
This release adds a small cache implementation around SDK instance on the server.
It adds page-assets calls into an in-memory cache. By default, the cache is
using ~10 Mb of memory.
server/index.js:
const sharetribeSDK = sdkUtils.getSdk(req, res);
const sdk = getSDKProxy(sharetribeSDK);JavaScript Proxy is used as an implementation mechanism for both in-memory LRU
(least-recently-used) cache and as a proxy for the SDK instance.
Note: if you have imported a more battle tested cache library to your client app,
you should consider using that instead of this somewhat simple implementation
of a LRU cache.
This is a small nudge towards thinking different caching mechanisms that might help
performance and speed up rendering.
Read more from the PR:
#713
In addition, there are several smaller fixes and improvements.
- SDK is updated to newer version
- Accessibility:
- modals leverage "inert" prop to create focus trap and
- on closing pass the focus back to the related toggle button.
- AuthenticationPage: "Sign up" & "Log in" tabs were overflowing if the words were too long.
Changes v10.4.0
- [add] Add Proxy around SDK instance on server that caches the responses for the page-asset
requests. #713 - [fix] Styleguide examples for booking forms were missing a mandatory prop.
#719 - [fix] PREVENT_DATA_LOADING_IN_SSR environment variable has not been working.
#717 - [add] Add accessibility improvements (Modals, filters, etc.).
#716 - [change] Upgrade Sharetribe SDK to 1.22.0.
#715 - [fix] ListingPageCoverPhoto: fix payoutDetailsWarning message.
#714 - [fix] AuthenticationPage: fix a bug with long words in the title on mobile layout.
#711 - [add] Add currently available translations for DE, ES, FR.
#710
v10.3.0
This release adds support for Console-configured private custom user fields in Console.
In earlier versions, it has been possible to define private and protected custom user fields locally, and all of those fields have been handled on ProfileSettingsPage. With this change, public custom user fields are still handled on ProfileSettingsPage, and protected and private fields are now handled on the recently introduced ManageAccountPage.
In addition, this release includes small fixes to crossorigin default attribute and LocationAutocompleteInputImpl.
Translation changes
New translations
"ManageAccountPage.deleteAccountSubtitle": "Delete your account",
"PrivateDetailsForm.saveChanges": "Save changes",
"PrivateDetailsForm.updateProfileFailed": "Oops, something went wrong. Please try again.",
Changes 2025-11-20
- [add] Add currently available translations for DE, ES, FR.
#708 - [add] Add support for Console-configured private custom user fields
#700 - [change] make crossorigin attributes have an explicit default value.
#703 - [fix] LocationAutocompleteInputImpl: do not select current location automatically if it is the
only prediction. #704 - [add] Add currently available translations for DE, ES, FR.
#706
v10.2.0
This release adds a new account management page that contains a feature for users to delete their marketplace accounts.
Translations changes
- "ModalMissingInformation.fixEmail": "Oops, typo in your email? {fixEmailLink}",
+ "ModalMissingInformation.fixEmail": "Oops, a typo in your email? {fixEmailLink}",
- "PasswordRecoveryPage.fixEmailInfo": "Oops, typo in your email? {fixEmailLink}",
+ "PasswordRecoveryPage.fixEmailInfo": "Oops, a typo in your email? {fixEmailLink}",New translation keys:
"DeleteAccountForm.checkboxLabel": "I want to delete my account",
"DeleteAccountForm.confirmDeleteInfo": "Deleting your {marketplaceName} account removes your personal data, user profile, and listings from the marketplace. This can't be undone.",
"DeleteAccountForm.confirmDeleteTitle": "Confirm account deletion",
"DeleteAccountForm.deleteAccount": "Delete account",
"DeleteAccountForm.deleteAccountInfo": "To delete your account ({email}), please enter your current password.",
"DeleteAccountForm.deleteAccountError": "Oops, something went wrong. Please try again.",
"DeleteAccountForm.ongoingTransactionsError": "Account deletion failed. Please complete any ongoing transactions before deleting your account or contact support.",
"DeleteAccountForm.passwordFailed": "Please double-check your password.",
"DeleteAccountForm.passwordLabel": "Current password",
"DeleteAccountForm.passwordPlaceholder": "Enter your current password…",
"DeleteAccountForm.passwordTooShort": "The password should be at least {minLength} characters.",
"DeleteAccountForm.passwordRequired": "A password is required.",
"DeleteAccountForm.resendPasswordLinkText": "Resend instructions.",
"DeleteAccountForm.resetPasswordInfo": "Forgot your password or don't have one? {resetPasswordLink}",
"DeleteAccountForm.resetPasswordLinkSent": "Instructions to reset your password have been sent to {email}. {resendPasswordLink}",
"DeleteAccountForm.resetPasswordLinkText": "Send reset instructions.",
"DeleteAccountForm.stripeDeletionFailedError": "Account deletion failed. Stripe data could not be deleted. Please contact support.",
"LayoutWrapperAccountSettingsSideNav.manageAccountTabTitle": "Manage account",
"ManageAccountPage.heading": "Manage account",
"ManageAccountPage.title": "Manage account",
"SignupForm.passwordRepeatedOnOtherFields": "Only enter your password in the dedicated field.",Changes 2025-11-19
-
[add] Added ability to delete user account in a new account settings page.
#660 -
[change] SignupForm: check that the password is not copy-pasted to other fields.
#702 -
[fix] ManageListingsPage.duck.js: fix a bug with pagination links not being rendered.
#701 -
[add] Additional accesibility improvements to text and icon colors in search CTA.
#696 -
[add] Improve template accessibility further
- Improved contrast for various colors throughout the Template
- Increased area around clickable elements
- Updated various component roles
v10.1.2
This fixes an issue where price variants on bookable listings (with hour or fixed unit type) didn't update availability calendar correctly when changing from 1 variant to another.
Changes v10.1.2
v10.1.1
v10.1.0
This release takes the regular negotiation flow into use from default-negotiation process.

This means that listing type is changed to have unitType: "offer" (instead of unitType: "request").
=> created listings save the unitType to public data of the listing entity.
In the "offer" mode, the listing is created by the provider. In other words, a user offers their services - e.g. carpenter could create a listing: "Custom cabinets"
Then the listing page shows "Request a quote" forms/submit buttons if the listing has the aforementioned unit type aka "offer". Potential customer can then request a quote for a project they have in their mind.
Note: since this transaction flow is essentially about customer starting a discussion with a provider, there's no separate "Contact" button available on the listing page.
RequestQuotePage
When customer has decided to request a quote (and clicked the button on listing page), he/she will be redirected to a new page (RequestQuotePage) that, currently, only contains a text field where the project specifications can be written.
The page initiates a new transaction entity and the provided specs will be saved to its protected data. Both parties see those specs on the TransactionPage.
Provider has the option to reject the quote-request or make an offer.
Other notable changes
In addition, this release also creates a couple of new shared components:
-
TopbarSimplified: CheckoutPage, MakeOfferPage and the new RequestQuotePage don't use the full Topbar. Instead, there has been a simplified version that only shows logo. We try to avoid distracting user with the many links that normal top bar has.
-
ErrorMessage: CheckoutPageWithInquiryProcess, MakeOfferPage, the new RequestQuotePage, and InquiryForm on listing page.
-
OrderPanel/SubmitFinePrint: unify "This is your own listing" and "you won't be charged yet" fine prints.
- show own listing info for negotiation and inquiry listings
- use shared 'SubmitFinePrint' for most forms (booking* forms & negotiation and inquiry forms)
- ProductOrderForm has more complex fine-print logic. It's handled separately.
-
The previously merged reference email notification template: negotiation-rejected-request contained a bug on the subject line. That email template is fixed on the PR 685.
Translation changes
Deleted translations
- "BookingDatesForm.ownListing": "This is your own listing.",
- "BookingDatesForm.youWontBeChargedInfo": "You won't be charged yet",
- "BookingFixedDurationForm.ownListing": "You can't book your own listing.",
- "BookingFixedDurationForm.youWontBeChargedInfo": "You won't be charged yet",
- "BookingTimeForm.ownListing": "You can't book your own listing.",
- "BookingTimeForm.youWontBeChargedInfo": "You won't be charged yet",
- "CheckoutPage.goToLandingPage": "Go to the homepage",
- "CheckoutPageWithInquiryProcess.initiateInquiryError": "Oops, something went wrong. Please try again. If the problem persists, contact support.",
- "CheckoutPageWithInquiryProcess.initiateInquiryErrorNoProcess": "Oops, no transaction process attached to the listing. Please contact support",
- "InquiryForm.noTransactionRightsError": "Oops, something went wrong. You don't have transaction rights. <NoAccessLink>Read more about transaction rights.</NoAccessLink>",
- "InquiryForm.sendInquiryError": "Oops, something went wrong. Please try again.",
- "InquiryForm.sendInquiryErrorNoProcess": "Oops, no transaction process attached to the listing. Please contact support",
- "InquiryForm.tooManyRequestsError": "Sending inquiry failed. There have been too many requests made in a short amount of time. If the error persists, try refreshing the page or contact support.",
- "InquiryForm.userPendingApprovalError": "Oops, something went wrong. Your account is waiting for approval.",
- "MakeOfferPage.goToLandingPage": "Go to the homepage",
- "MakeOfferPage.listingNotFoundError": "Unfortunately, this listing is no longer available.",
- "MakeOfferPage.makeOfferError": "Oops, something went wrong. Please try again. If the problem persists, contact support.",
- "MakeOfferPage.makeOfferErrorNoProcess": "Oops, no transaction process attached to the listing. Please contact support",New translations
"ErrorMessage.listingNotFoundError": "Unfortunately, this listing is no longer available.",
"ErrorMessage.noProcessAttachedToListing": "Oops, no transaction process attached to the listing. Please contact support",
"ErrorMessage.noTransactionRightsError": "Oops, something went wrong. You don't have transaction rights. <NoAccessLink>Read more about transaction rights.</NoAccessLink>",
"ErrorMessage.tooManyRequestsError": "Action failed. There have been too many requests made in a short amount of time. If the error persists, try refreshing the page or contact support.",
"ErrorMessage.unknownError": "Oops, something went wrong. Please try again. If the problem persists, contact support.",
"ErrorMessage.userPendingApprovalError": "Oops, something went wrong. Your account is waiting for approval.",
"NegotiationRequestQuoteForm.ctaButton": "Request a quote",
"OrderPanel.ctaButtonMessageRequestAQuote": "Request a quote",
"OrderPanel.ownListing": "This is your own listing.",
"OrderPanel.youWontBeChargedInfo": "You won't be charged yet",
"RequestQuotePage.defaultMessageLabel": "Describe your requirements",
"RequestQuotePage.defaultMessagePlaceholder": "What do you need?",
"RequestQuotePage.defaultMessageRequired": "You need to describe your requirements",
"RequestQuotePage.finePrint": "You’ll be notified when {providerName} reacts to your request.",
"RequestQuotePage.heading": "Request a quote",
"RequestQuotePage.listingTitle": "{listingTitle}",
"RequestQuotePage.locationDetailsTitle": "Location",
"RequestQuotePage.submitButtonText": "Request a quote",
"TopbarSimplified.goToLandingPage": "Go to the homepage",
"TransactionPage.ActivityFeed.default-negotiation.quote-requested": "{actor, select, you {You requested a quote.} other {{otherUsersName} requested a quote.}}",
"TransactionPage.ActivityFeed.default-negotiation.request-rejected": "The offer was rejected.",
"TransactionPage.RequestQuote.heading": "Request",
"TransactionPage.RequestQuote.customerDefaultMessageLabel": "Description",
"TransactionPage.default-negotiation.customer.quote-requested.extraInfo": "You’ll be notified when {providerName} reacts to your request.",
"TransactionPage.default-negotiation.customer.quote-requested.title": "Your request has been received!",
"TransactionPage.default-negotiation.customer.request-rejected.title": "The request was rejected.",
"TransactionPage.default-negotiation.customer.transition-withdraw-request.actionButton": "Withdraw request",
"TransactionPage.default-negotiation.customer.transition-withdraw-request.actionError": "Oops, withdraw failed. Please try again.",
"TransactionPage.default-negotiation.provider.quote-requested.extraInfo": "You can now submit an offer.",
"TransactionPage.default-negotiation.provider.quote-requested.title": "{customerName} requested a quote",
"TransactionPage.default-negotiation.provider.request-rejected.title": "The request was rejected.",
"TransactionPage.default-negotiation.provider.transition-make-offer-from-request.actionButton": "Submit an offer",
"TransactionPage.default-negotiation.provider.transition-make-offer-from-request.actionError": "Oops, rejecting failed. Please try again.",
"TransactionPage.default-negotiation.provider.transition-reject-request.actionButton": "Reject request",
"TransactionPage.default-negotiation.provider.transition-reject-request.actionError": "Oops, rejecting failed. Please try again.",## Changes v10.1.0
v10.0.0
This major release introduces Redux Toolkit (RTK) as our new standard for state management.
Redux has deprecated the legacy method of creating a store, and our developer community has also recommended adopting RTK as it provides better tooling ecosystem and support for state management. For example, RTK uses immer.js under the hood, which simplifies immutable state updates by handling immutability automatically.
Learn more:
Why RTK is the recommended Redux approach: https://redux.js.org/introduction/why-rtk-is-redux-today
Immer in RTK: https://redux-toolkit.js.org/usage/immer-reducers
In addition to this migration, this release includes several bug fixes.
There are also a couple of bug fixes included (see the changelog below).
NOTE: the v10.1.2 contains a fix for small bookable units (hour & fixed) with price variants. Availability calendar didn't update correctly when variant was changed.
Notes related to Redux Toolkit change:
The parameter signature of the configureStore has been changed. In addition, we disable serializableCheck at this point - since we have been saving class-based objects like UUIDs, Money, LatLng, LatLngBounds, Dates and Decimals to the store.
This update tries to keep the shape of the state data untouched (to reduce any changes needed outside of those *.duck.js files).
Redux Toolkit tries to remove some of the boilerplates, like action types, action creators, separate reducer, etc.


Instead, there's createSlice function that wraps this kind of setup. E.g. PasswordChangePage.duck.js

RTK way of making asynchronous API calls is to swap plain Redux Thunk calls with createAsyncThunk function.

This PR splits those createAsyncThunk calls to *payloadCreator function (to maintain the code indentation). It also export the vanilla "legacy" thunk functions, so that the UI layer don't need to change code (at least at this point).
I.e. the screenshot above has now an old sendMessage function with old function signature. It internally uses sendMessageThunk function, which is created with createAsyncThunk method. ...and that method has internal function calls sendMessagePayloadCreator, which contains the previous SDK call / promise chain.
One thing, to keep in mind, is that createAsyncThunk wraps the Promise chain internally, which changes the behaviour compared to legacy thunk functions: createAsyncThunk needs to be unwrapped to get access to the underlying Promise chain.
This update tried not to convert loadData functions to use createAsyncThunk. We might do that at some point, but for now it felt better to leave it as it is. Most customizations have probably touched loadData functions - so, this was to reduce Git conflicts a bit. However, the recommendation is to make async calls in a separate thunk functions and only call those from loadData (instead of baking async calls inside loadData function).
For now, it looks like that the *.duck.js files, that follow previous legacy Redux setup, still work. So, it might be that if you have created a "CustomPage" with "CustomPage.duck.js" file, you might not need to make changes to those files when taking update from upstream. However, AI tools can help you to convert your custom *.duck.js files to follow Redux Toolkit format.
Sharetribe templates have always stored some class-based objects to Redux store, which is not recommended. This approach has worked fine because those objects are essentially Dates and API types: Date, UUID, Money, etc. - i.e. they are not supposed to be mutated after they are created. RTK has extra measures to warn against unserializable objects: serializableCheck middleware. For now, due to existing behaviour, we decided to disable the aforementioned middleware, but we might take it into use later on (if/when we have time to address the issue a bit more).
Analytics.js has been changed to use listenerMiddleware. If you have expanded analytics events to something else than locationChanges, you need to update your setup to follow then listenerer pattern.
Changes v10.0.0
-
[change] Start using Redux Toolkit for state management.
- All the *.duck.js files have been converted to Redux Toolkit slices.
- The signature of 'configureStore' function has been changed.
-
[fix] Added missing getAriaLabel to various components used in Styleguide
#687 -
[add] Add noindex metadata to closed listings.
#688 -
[fix] coordinate parsing: "-0" was not parsed correctly to Number.
#686 -
[fix] showPaymentDetailsForUser: handle case when currentUser is null.
#684 -
[add] Add currently available translations for DE, ES, FR.
#683 -
[fix] Only set TransactionPage pageHeading if processName resolves
#681
v9.1.0
Improve the accessibility of multiple different components and fixing a couple of bugs.
This release is marked as minor: it touches quite a big number of files, but mostly this is about adding aria-label attributes to components and updating texts with light grey colors to have enough contrast.
Translation changes
Changed translations
- "EditListingPage.titleCreateListing": "Post a new listing",
+ "EditListingPage.titleCreateListing": "Post a new listing | {panelHeading}",
- "EditListingPage.titleEditListing": "Edit listing",
+ "EditListingPage.titleEditListing": "Edit listing | {panelHeading}",
- "IntegerRangeFilter.screenreader.rangeHandle": "{handle, select, min {Range start {value}} other {Range end {value}}}",
+ "IntegerRangeFilter.screenreader.rangeHandle": "{handle, select, min {Range start {value}} max {Range end {value}} other {Selected value {value}}}",
- "SearchPage.schemaTitle": "Search results for {searchTitle} | {marketplaceName}",
+ "SearchPage.schemaTitle": "Search results for {searchTitle} | {marketplaceName} | {h1}",
- "TransactionPage.schemaTitle": "Sale details: {title}",
+ "TransactionPage.schemaTitle": "Sale details: {title} | {h1}",
New translations
"EditListingStyleForm.screenreader.chooseCardStyle": "Choose a background color for the card: {option, select, white {white} grey {gray} black {black} brand {brand color} other {secondary brand color}}.",
"EditListingWizard.screenreader.tabNavigation": "Listing edit navigation",
"FieldSelectTree.screenreader.optionSelected": "{pathMatch, select, exact {{optionName} is selected.} other {{optionName}. One of the nested options is selected.}}",
"FieldSelectTree.screenreader.option": "Choose {optionName}.",
"FilterForm.screenreader.label": "Filter form. {mode, select, live {Changing filter values will update the search page immediately.} other {}}",
"IconSpinner.screenreader.loading": "Loading...",
"InboxPage.screenreader.sidenav": "Inbox navigation",
"LayoutSideNavigation.screenreader.accountNavigation": "Account settings navigation",
"LocationAutocompleteInput.screenreader.search": "Search",
"ManageListingCard.screenreader.menu": "Listing menu",
"NotFoundPage.screenreader.search": "Search listings",
"ProfilePage.screenreader.reviewsNav": "Reviews navigation",
"SearchMapPriceLabel.screenreader.mapMarkerWithoutPrice": "Listing without price",
"SearchPage.screenreader.openFilterButton": "{label} filter. {status, select, active {Current selection: {values}.} other {Not in use.}}",
"SearchResultsPanel.screenreader.pagination": "Pagination",
"TabNav.screenreader.tabNavigation": "Tab navigation",
"TopbarDesktop.screenreader.profileMenu": "Open profile menu",
"TopbarDesktop.screenreader.search": "Search",
"TopbarDesktop.screenreader.topbarNavigation": "Top bar navigation",
"UserNav.screenreader.userNav": "User profile navigation",Changes v9.1.0
-
[add] Improve template accessibility
- Improve contrast in grey texts
- Add translations for aria-labels
- Unify page title and heading texts
-
[change] Update browserlist-db aka caniuse-lite.
#679 -
[add] Add currently available translations for DE, ES, FR.
#678 -
[fix] BookingDateRangeFilter: don't add focus to the current date on mount.
#677 -
[fix] Fix a bug with coordinate values in the URL.
#676 -
[fix] Fix some Marketplace texts. #675
v9.0.0
This major release introduces a new transaction process, which is named as default-negotiation. This is quite a complex process and it comes with huge number of changes to existing components. (Although, most of the PRs line-counts come from /ext/transaction-processes/default-negotiation/* and test updates.
Note: to modify the new process, you need to have CLI version 1.15.0 or later.
There are also some other changes included - like how PriceFilter and IntegerRangeFilter work.
The default-negotiation process
The transaction has 3 initial transitions:
- inquire
- make-offer
- request-quote (This PR won't handle this - it will be released separately.)
The first 2 transitions are possible for the provider role. This means that the process would be started with reverse negotiation flow: customer has created a "request" type of listing and provider reacts to that.
The request-quote flow would represent the normal forward flow that's used with other processes: provider creates a listing (offer) and customer starts transaction against it.
Note: this is not going to be implemented in this PR and related release.
The default-negotiation process has support for both forward and reverse flows. In practice, this would work so that listing type configuration (set on Console) would define a unit type "request" (or "offer"). Web Template then takes those into use when user creates a listing (unit type is saved to listing's publicData). In addition, the unitType then defines, which initial transitions are possible from the listing page.
There are 2 loops in the process:
- negotiation loop
- Here, customer can create counter-offer to the initial offer made by provider - and provider can make counter offers for customer's counter offer.
- However, most of the planning related discussion should happen through messaging.
- The "Make counter offer" button is hidden when the transaction already contains over 50 transitions.
- NOTE: there are actually 2 negotiation loops: customer-driven (as discussed here) and provider-driven (update-offer loop), which is not yet in use.
- request changes loop
- When provider delivers the "project" or deliverable, customer can request changes.
- The "Request changes" button is hidden when the transaction already contains over 90 transitions or if it has been more than 70 days since
transition/confirm-paymentwas made (to comply with the Stripe's 90 day window for payout).
Template changes
This list contains only some highlights.
Changes to the server setup
The /api/initiate-privileged handles negotiation-specific calls by expecting offerInSubunits info from orderData object and it's also aware of negotiation-related transitions. When negotiation transition is requested, it updates line-items and saves the offerInSubunits into transactions.attributes.metadata.offers.
The /api/transition-privileged works in a same way. It also updates new offers to the aforementioned "offers" array in transaction's metadata. There's a new server/api-util/negotiation.js file that contains helper functions to deal with negotiation process.
Note: there are 2 transitions that rely on "offers" array to be valid aka in sync with past transitions. When the intention is to revoke an offer (
transition/customer-withdraw-counter-offer&transition/provider-reject-counter-offer), the previous offer is used to update line-items and it's also saved as the new entry to the offers array.The validation of offers array is against
transaction.attributes.transitionsarray. Transitions names and actors must match between entries on each array - and if the transitions array is filtered to the relevant offer-related transitions, they should appear in the same order and have the same length.
src/ducks/user.duck.js / fetchCurrentUserNotifications
The request to get transactions that are in a state that requires user's action have been changed to use state filter instead of lastTransition (i.e. take the new API feature into use). In addition, there are now 2 queries: one for provider role and another for customer role. So, each process could now map 2 array of significant states:statesNeedingProviderAttention and statesNeedingCustomerAttention.
EditListingPage
Listings created from listing type that uses this new process would have location tab optional and price tab won't be available at this point (we have a plan to make it optional too).
The wizard has been changed a bit: there's tabsForListingType function that defines different tabs per process.
New page: MakeOfferPage
There's a new route /l/:slug/:id/make-offer, which uses MakeOfferPage component. There provider can make the initial offer for the price negotiation. (Later, it will be possible for provider to update the offer there too.)
On listing page, provider can react to customer's "request" by clicking "Make an offer" button on OrderPanel and that button would redirect user to this new page. Alternatively they can click "contact" button at the bottom of the page.
Note: provider must have Stripe payout details added, otherwise the "Make the offer" button is disabled.
TransactionPage
This is the page where those 2 loops happen. So, there are 2 new modals: MakeCounterOfferModal and RequestChangesModal.
The MakeCounterOfferModal asks a new offer / quote from the user and updates the line-items based on that figure. So, the related transition is privileged one and the transition is made on the client app's server.
TransactionPage.stateDataNegotiation.js file is added and it has a couple of new features. There are limitations for how long some actions buttons are visible and there are potentially 3 actions buttons visible at the same time.
ActionButtonsMaybe.js has been moved from TransactionPanel directory into TransactionPage directory.
Note: if the transaction.attributes.metadata.offers array is corrupted (perhaps wrongly edited through Console) aka it does not match correctly with the past transitions (that have set those offers), then the action buttons for negotiation loop are not shown. Instead, there will be a error message asking user to contact support: "Transaction data is invalid. Please contact support."
InboxPage
Transactions are now queried based on process names instead of last transitions. Now that we have this query option in API, it makes Template more reliable as the showed transactions are only those which the client app is assumed to be able to handle. (The previous filter was subject to fetch transitions that were just named the same as transitions on supported processes.)
Translation changes
New translation keys
"CheckoutPage.default-negotiation.orderBreakdown": "Order breakdown",
"CheckoutPage.default-negotiation.title": "Complete order",
"EditListingWizard.default-negotiation.new.saveDetails": "Next",
"EditListingWizard.default-negotiation.new.saveLocation": "Next",
"EditListingWizard.default-negotiation.new.saveLocationNoPricingTab": "Next",
"EditListingWizard.default-negotiation.new.savePhotos": "Publish listing",
"EditListingWizard.default-negotiation.new.savePricing": "Next",
"EditListingWizard.default-negotiation.new.saveStyle": "Publish listing",
"InboxPage.default-negotiation.canceled.status": "Canceled",
"InboxPage.default-negotiation.changes-requested.status": "Changes requested",
"InboxPage.default-negotiation.completed.status": "Waiting for review",
"InboxPage.default-negotiation.customer-offer-pending.status": "Counter offer",
"InboxPage.default-negotiation.delivered.status": "{transactionRole, select, customer {Accept order} other {Delivered}}",
"InboxPage.default-negotiation.inquiry.status": "Inquiry",
"InboxPage.default-negotiation.offer-accepted.status": "Waiting for delivery",
"InboxPage.default-negotiation.offer-pending.status": "{transactionRole, select, customer {Offer pending} other {Offer received}}",
"InboxPage.default-negotiation.offer-rejected.status": "Rejected",
"InboxPage.default-negotiation.payment-expired.status": "Payment expired",
"InboxPage.default-negotiation.pending-payment.status": "Pending payment",
"InboxPage.default-negotiation.quote-requested.status": "Quote requested",
"InboxPage.default-negotiation.reviewed-by-customer.status": "{transactionRole, select, customer {Waiting for provider review} other {Waiting for your review}}",
"InboxPage.default-negotiation.reviewed-by-provider.status": "{transactionRole, select, customer {Waiting for your review} other {Waiting for customer review}}",
"InboxPage.default-negotiation.reviewed.status": "Completed",
"InboxPage.default-negotiation.update-pending.status": "{transactionRole, select, customer {Offer update pending} other {Offer update received}}",
"IntegerRangeFilter.rangeInputsLabel": "Range:",
"IntegerRangeFilter.screenreader.rangeHandle": "{handle, select, min {Range start {value}} other {Range end {value}}}",
"MakeCounterOfferForm.offerLabel": "Price",
"MakeCounterOfferForm.offerPlaceholder": "{currentOffer}",
"MakeCounterOfferFo...
