获取 selected Card 元素的 ID 并将其传递给 select 元素的模态
Get the ID of a selected Card element and pass it to a modal to select the element
我有一个代码可以在卡片中呈现多个计划。这是代码:
import React, { useEffect, useState } from 'react';
import { I18nText, Card, BaseModal, Button } from '@wtag/react-comp-lib';
import PropTypes from 'prop-types';
import greenCheck from 'affiliateIcons/green-check.svg';
import doneMark from '../../../../assets/images/done-24px.svg';
import routes from '../../../shared/routes';
import httpClient from '../../../shared/libraries/httpClient';
const Plans = ({ affiliateId }) => {
const [plans, setPlans] = useState([]);
const [currentPlan, setCurrentPlan] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const fetchPlans = async () => {
const { data } = await httpClient.get(
routes.billing.changePlan.fetch({ affiliate_id: affiliateId }),
);
setPlans(data.plans);
setCurrentPlan(data.currentPlan);
};
const selectPlan = async plan => {
const { data } = await httpClient.put(
routes.billing.changePlan.update({
id: plan.id,
affiliate_id: affiliateId,
}),
);
if (data.error === null) {
fetchPlans(data.currentPlan);
setModalOpen(false);
}
};
useEffect(() => {
fetchPlans();
}, []);
const data = [
{
id: 0,
identifier: 'free-2020',
name: <I18nText id="pricing.plans.name.free" />,
planTitle: 'free',
price: '00',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '-',
customDomain: '-',
active: true,
},
{
id: 1,
identifier: 'basic-2020',
name: <I18nText id="pricing.plans.name.basic" />,
planTitle: 'basic',
price: '29',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '1',
customDomain: '-',
active: false,
},
{
id: 2,
identifier: 'standard-2020',
name: <I18nText id="pricing.plans.name.standard" />,
planTitle: 'standard',
price: '59',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '1hr/mo',
supplierChannels: '2',
customDomain: '-',
active: false,
},
{
id: 3,
identifier: 'professional-2020',
name: <I18nText id="pricing.plans.name.professional" />,
planTitle: 'professional',
price: '99',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '3hr/mo',
supplierChannels: '5',
customDomain: 'Yes',
active: false,
},
{
id: 4,
identifier: 'custom-2020',
name: <I18nText id="pricing.plans.name.custom" />,
planTitle: 'custom',
price: '-',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: <I18nText id="pricing.plans.features.unlimited" />,
supplierChannels: <I18nText id="pricing.plans.features.unlimited" />,
customDomain: <I18nText id="pricing.plans.features.available" />,
active: false,
},
];
const planData = data.map(d => {
const correspondingPlan = plans.filter(
plan => d.identifier === plan.identifier,
)[0];
if (correspondingPlan) {
return { ...d, ...correspondingPlan };
}
return d;
});
const tableHeader = [
{ id: 0, name: <I18nText id="pricing.table.header.free" /> },
{ id: 1, name: <I18nText id="pricing.table.header.basic" /> },
{ id: 2, name: <I18nText id="pricing.table.header.standard" /> },
{ id: 3, name: <I18nText id="pricing.table.header.professional" /> },
{ id: 4, name: <I18nText id="pricing.table.header.custom" /> },
];
const planOptions = [
{
id: 0,
name: <I18nText id="pricing.table.feature.subscription.search" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 1,
name: <I18nText id="pricing.table.feature.subscription.bookings" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 2,
name: <I18nText id="pricing.table.feature.subscription.traveler" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 3,
name: <I18nText id="pricing.table.feature.subscription.corporate" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 4,
name: <I18nText id="pricing.table.feature.subscription.quotes" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 5,
name: <I18nText id="pricing.table.feature.subscription.ndc" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
];
const getCurrentPlan = currentPlan && currentPlan.name;
const newPlansCard = planData.map(plan => {
const matchPlan = plan.name === getCurrentPlan;
return (
<>
<Card
className="billing-plans__plans-card"
size="normal"
version="v2"
title={plan.name}
headerCenter={true}
key={plan.id}
>
{matchPlan ? (
<div className="billing-plans__current-plan-icon">
<img src={greenCheck} alt="check" />
</div>
) : null}
<div className="billing-plans__plans-card-title">
<sup>US$</sup>
{plan.price}
<sub>
/<I18nText id="pricing.plans.misc.month" />
</sub>
</div>
<div className="billing-plans__plans-card-features-wrapper">
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.searches" />
<span className="billing-plans__features-access">
{plan.freeSearches}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.bookings" />
<span className="billing-plans__features-access">
{plan.freeBookings}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.profiles" />
<span className="billing-plans__features-access">
{plan.travelerProfiles}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.tickets" />
<span className="billing-plans__features-access">
{plan.supportTickets}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.phone" />
<span className="billing-plans__features-access">
{plan.supportByPhone}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.channels" />
<span className="billing-plans__features-access">
{plan.supplierChannels}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.domain" />
<span className="billing-plans__features-access">
{plan.customDomain}
</span>
</div>
</div>
<div className="billing-plans__plans-card-actions">
{!matchPlan ? (
<div>
<BaseModal
linkText={<I18nText id="pricing.plans.actions.select" />}
linkClassName="billing-plans__plans-card-actions-button"
open={modalOpen}
onModalClose={() => setModalOpen(false)}
onModalOpen={() => setModalOpen(true)}
header={
<I18nText id="pricing.plans.actions.confirmationText" />
}
size="small"
showCloseIcon={false}
key={plan.id}
>
<div className="col-12">
<Button
version="v2"
type="accent"
size="normal"
label={I18n.t('shared.action.confirm')}
key={plan.id}
onClick={() => {
selectPlan(plan);
}}
/>
<Button
version="v2"
size="normal"
label={I18n.t('shared.action.cancel')}
onClick={() => setModalOpen(false)}
/>
</div>
</BaseModal>
</div>
) : null}
</div>
</Card>
</>
);
});
const pricingTable = () => {
return (
<div>
<div className="container-full billing-plans__pricing-table">
<Card size="full" version="v2" key={planOptions.id}>
<div className="billing-plans__table-header">
<I18nText id="pricing.plans.sub_title" />
</div>
<table className="billing-plans__table-body">
<thead>
<tr>
<th className="billing-plans__table-sub-header">
<I18nText id="pricing.table.feature.header" />
</th>
{tableHeader.map(({ name, id }) => (
<th className="billing-plans__table-sub-header" key={id}>
{name}
</th>
))}
</tr>
</thead>
<tbody>
{planOptions.map(
({
id,
name,
isFree,
isBasic,
isStandard,
isProfessional,
isCustom,
}) => (
<tr key={id}>
<td className="billing-plans__table-data">{name}</td>
<td className="billing-plans__table-plan">
<img src={isFree} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isBasic} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isStandard} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isProfessional} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isCustom} alt="check" />
</td>
</tr>
),
)}
</tbody>
</table>
</Card>
</div>
</div>
);
};
return (
<div className="billing-plans">
<div>
<div className="grid">
<div className="col-12 offset-xlg-1 offset-lg-1 offset-md-1 offset-sm-1 offset-xs-1 offset-xxs-1">
<div className="billing-plans__plans-header">
<I18nText id="pricing.plans.title" />
</div>
</div>
</div>
<div className="billing-plans__plans-wrapper">
{currentPlan && newPlansCard}
</div>
</div>
{pricingTable()}
</div>
);
};
Plans.propTypes = {
currentPlan: PropTypes.shape({
name: PropTypes.string,
identifier: PropTypes.string,
id: PropTypes.number,
}).isRequired,
affiliateId: PropTypes.number.isRequired,
};
export default Plans;
我想获取 selected 卡片的 ID 并将其传递给模态框。当第一个按钮即:单击卡片模式的确认按钮时,卡片将显示为 selected 并带有刻度线。就像在每张卡片中一样,有 Select 计划按钮,如果用户真的想要 select 计划,它会打开模式对话框以获得确认。如果他 selects 是,则获取特定卡的 ID,然后将 ID 传递给 selectPlan 函数以 Select 特定卡作为计划。在模态的第一个按钮的现有代码库中,我有一个 onClick 函数,它将特定的 selected Plan 发送到 selectPlan 函数,就像这样 selectPlan(plan)
。但是现在,无论我 select 使用相应模式的哪张卡,我都只能得到 ID 为 2 或 6 的卡,尽管有 5 张 ID 为 2、3、4、5、6 的卡。这意味着存在一些问题在当前实现中获取特定的 selected 卡 ID。我想知道我应该在当前代码中更改什么以获取特定的 selected 卡 ID 并使用 onClick 函数将其传递给相应的模式按钮以使用 selected 将计划表示为 selectPlan(plan)
函数。这是计划 UI:
const [plans, setPlans] = useState([]);
const [currentPlan, setCurrentPlan] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [selectedPlan, setSelectedPlan] = useState();
const fetchPlans = async () => {
const { data } = await httpClient.get(
const Plans = ({ affiliateId }) => {
linkClassName="billing-plans__plans-card-actions-button"
open={modalOpen}
onModalClose={() => setModalOpen(false)}
onModalOpen={() => {
setSelectedPlan(plan);
setModalOpen(true);
}}
header={
<I18nText id="pricing.plans.actions.confirmationText" />
}
const Plans = ({ affiliateId }) => {
label={I18n.t('shared.action.confirm')}
key={plan.id}
onClick={() => {
selectPlan(selectedPlan);
}}
/>
<Button
import React, { useEffect, useState } from 'react';
import { I18nText, Card, BaseModal, Button } from '@wtag/react-comp-lib';
import PropTypes from 'prop-types';
import greenCheck from 'affiliateIcons/green-check.svg';
import doneMark from '../../../../assets/images/done-24px.svg';
import routes from '../../../shared/routes';
import httpClient from '../../../shared/libraries/httpClient';
const Plans = ({ affiliateId }) => {
const [plans, setPlans] = useState([]);
const [currentPlan, setCurrentPlan] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [selectedPlan, setSelectedPlan] = useState();
const fetchPlans = async () => {
const { data } = await httpClient.get(
routes.billing.changePlan.fetch({ affiliate_id: affiliateId }),
);
setPlans(data.plans);
setCurrentPlan(data.currentPlan);
};
const selectPlan = async plan => {
const { data } = await httpClient.put(
routes.billing.changePlan.update({
id: plan.id,
affiliate_id: affiliateId,
}),
);
if (data.error === null) {
fetchPlans(data.currentPlan);
setModalOpen(false);
}
};
useEffect(() => {
fetchPlans();
}, []);
const data = [
{
id: 0,
identifier: 'free-2020',
name: <I18nText id="pricing.plans.name.free" />,
planTitle: 'free',
price: '00',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '-',
customDomain: '-',
active: true,
},
{
id: 1,
identifier: 'basic-2020',
name: <I18nText id="pricing.plans.name.basic" />,
planTitle: 'basic',
price: '29',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '1',
customDomain: '-',
active: false,
},
{
id: 2,
identifier: 'standard-2020',
name: <I18nText id="pricing.plans.name.standard" />,
planTitle: 'standard',
price: '59',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '1hr/mo',
supplierChannels: '2',
customDomain: '-',
active: false,
},
{
id: 3,
identifier: 'professional-2020',
name: <I18nText id="pricing.plans.name.professional" />,
planTitle: 'professional',
price: '99',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '3hr/mo',
supplierChannels: '5',
customDomain: 'Yes',
active: false,
},
{
id: 4,
identifier: 'custom-2020',
name: <I18nText id="pricing.plans.name.custom" />,
planTitle: 'custom',
price: '-',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: <I18nText id="pricing.plans.features.unlimited" />,
supplierChannels: <I18nText id="pricing.plans.features.unlimited" />,
customDomain: <I18nText id="pricing.plans.features.available" />,
active: false,
},
];
const planData = data.map(d => {
const correspondingPlan = plans.filter(
plan => d.identifier === plan.identifier,
)[0];
if (correspondingPlan) {
return { ...d, ...correspondingPlan };
}
return d;
});
const tableHeader = [
{ id: 0, name: <I18nText id="pricing.table.header.free" /> },
{ id: 1, name: <I18nText id="pricing.table.header.basic" /> },
{ id: 2, name: <I18nText id="pricing.table.header.standard" /> },
{ id: 3, name: <I18nText id="pricing.table.header.professional" /> },
{ id: 4, name: <I18nText id="pricing.table.header.custom" /> },
];
const planOptions = [
{
id: 0,
name: <I18nText id="pricing.table.feature.subscription.search" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 1,
name: <I18nText id="pricing.table.feature.subscription.bookings" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 2,
name: <I18nText id="pricing.table.feature.subscription.traveler" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 3,
name: <I18nText id="pricing.table.feature.subscription.corporate" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 4,
name: <I18nText id="pricing.table.feature.subscription.quotes" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 5,
name: <I18nText id="pricing.table.feature.subscription.ndc" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
];
const getCurrentPlan = currentPlan && currentPlan.name;
const newPlansCard = planData.map(plan => {
const matchPlan = plan.name === getCurrentPlan;
return (
<>
<Card
className="billing-plans__plans-card"
size="normal"
version="v2"
title={plan.name}
headerCenter={true}
key={plan.id}
>
{matchPlan ? (
<div className="billing-plans__current-plan-icon">
<img src={greenCheck} alt="check" />
</div>
) : null}
<div className="billing-plans__plans-card-title">
<sup>US$</sup>
{plan.price}
<sub>
/<I18nText id="pricing.plans.misc.month" />
</sub>
</div>
<div className="billing-plans__plans-card-features-wrapper">
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.searches" />
<span className="billing-plans__features-access">
{plan.freeSearches}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.bookings" />
<span className="billing-plans__features-access">
{plan.freeBookings}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.profiles" />
<span className="billing-plans__features-access">
{plan.travelerProfiles}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.tickets" />
<span className="billing-plans__features-access">
{plan.supportTickets}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.phone" />
<span className="billing-plans__features-access">
{plan.supportByPhone}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.channels" />
<span className="billing-plans__features-access">
{plan.supplierChannels}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.domain" />
<span className="billing-plans__features-access">
{plan.customDomain}
</span>
</div>
</div>
<div className="billing-plans__plans-card-actions">
{!matchPlan ? (
<div>
<BaseModal
linkText={<I18nText id="pricing.plans.actions.select" />}
linkClassName="billing-plans__plans-card-actions-button"
open={modalOpen}
onModalClose={() => setModalOpen(false)}
onModalOpen={() => {
setSelectedPlan(plan);
setModalOpen(true);
}}
header={
<I18nText id="pricing.plans.actions.confirmationText" />
}
size="small"
showCloseIcon={false}
key={plan.id}
>
<div className="col-12">
<Button
version="v2"
type="accent"
size="normal"
label={I18n.t('shared.action.confirm')}
key={plan.id}
onClick={() => {
selectPlan(selectedPlan);
}}
/>
<Button
version="v2"
size="normal"
label={I18n.t('shared.action.cancel')}
onClick={() => setModalOpen(false)}
/>
</div>
</BaseModal>
</div>
) : null}
</div>
</Card>
</>
);
});
const pricingTable = () => {
return (
<div>
<div className="container-full billing-plans__pricing-table">
<Card size="full" version="v2" key={planOptions.id}>
<div className="billing-plans__table-header">
<I18nText id="pricing.plans.sub_title" />
</div>
<table className="billing-plans__table-body">
<thead>
<tr>
<th className="billing-plans__table-sub-header">
<I18nText id="pricing.table.feature.header" />
</th>
{tableHeader.map(({ name, id }) => (
<th className="billing-plans__table-sub-header" key={id}>
{name}
</th>
))}
</tr>
</thead>
<tbody>
{planOptions.map(
({
id,
name,
isFree,
isBasic,
isStandard,
isProfessional,
isCustom,
}) => (
<tr key={id}>
<td className="billing-plans__table-data">{name}</td>
<td className="billing-plans__table-plan">
<img src={isFree} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isBasic} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isStandard} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isProfessional} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isCustom} alt="check" />
</td>
</tr>
),
)}
</tbody>
</table>
</Card>
</div>
</div>
);
};
return (
<div className="billing-plans">
<div>
<div className="grid">
<div className="col-12 offset-xlg-1 offset-lg-1 offset-md-1 offset-sm-1 offset-xs-1 offset-xxs-1">
<div className="billing-plans__plans-header">
<I18nText id="pricing.plans.title" />
</div>
</div>
</div>
<div className="billing-plans__plans-wrapper">
{currentPlan && newPlansCard}
</div>
</div>
{pricingTable()}
</div>
);
};
Plans.propTypes = {
currentPlan: PropTypes.shape({
name: PropTypes.string,
identifier: PropTypes.string,
id: PropTypes.number,
}).isRequired,
affiliateId: PropTypes.number.isRequired,
};
export default Plans;
我有一个代码可以在卡片中呈现多个计划。这是代码:
import React, { useEffect, useState } from 'react';
import { I18nText, Card, BaseModal, Button } from '@wtag/react-comp-lib';
import PropTypes from 'prop-types';
import greenCheck from 'affiliateIcons/green-check.svg';
import doneMark from '../../../../assets/images/done-24px.svg';
import routes from '../../../shared/routes';
import httpClient from '../../../shared/libraries/httpClient';
const Plans = ({ affiliateId }) => {
const [plans, setPlans] = useState([]);
const [currentPlan, setCurrentPlan] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const fetchPlans = async () => {
const { data } = await httpClient.get(
routes.billing.changePlan.fetch({ affiliate_id: affiliateId }),
);
setPlans(data.plans);
setCurrentPlan(data.currentPlan);
};
const selectPlan = async plan => {
const { data } = await httpClient.put(
routes.billing.changePlan.update({
id: plan.id,
affiliate_id: affiliateId,
}),
);
if (data.error === null) {
fetchPlans(data.currentPlan);
setModalOpen(false);
}
};
useEffect(() => {
fetchPlans();
}, []);
const data = [
{
id: 0,
identifier: 'free-2020',
name: <I18nText id="pricing.plans.name.free" />,
planTitle: 'free',
price: '00',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '-',
customDomain: '-',
active: true,
},
{
id: 1,
identifier: 'basic-2020',
name: <I18nText id="pricing.plans.name.basic" />,
planTitle: 'basic',
price: '29',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '1',
customDomain: '-',
active: false,
},
{
id: 2,
identifier: 'standard-2020',
name: <I18nText id="pricing.plans.name.standard" />,
planTitle: 'standard',
price: '59',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '1hr/mo',
supplierChannels: '2',
customDomain: '-',
active: false,
},
{
id: 3,
identifier: 'professional-2020',
name: <I18nText id="pricing.plans.name.professional" />,
planTitle: 'professional',
price: '99',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '3hr/mo',
supplierChannels: '5',
customDomain: 'Yes',
active: false,
},
{
id: 4,
identifier: 'custom-2020',
name: <I18nText id="pricing.plans.name.custom" />,
planTitle: 'custom',
price: '-',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: <I18nText id="pricing.plans.features.unlimited" />,
supplierChannels: <I18nText id="pricing.plans.features.unlimited" />,
customDomain: <I18nText id="pricing.plans.features.available" />,
active: false,
},
];
const planData = data.map(d => {
const correspondingPlan = plans.filter(
plan => d.identifier === plan.identifier,
)[0];
if (correspondingPlan) {
return { ...d, ...correspondingPlan };
}
return d;
});
const tableHeader = [
{ id: 0, name: <I18nText id="pricing.table.header.free" /> },
{ id: 1, name: <I18nText id="pricing.table.header.basic" /> },
{ id: 2, name: <I18nText id="pricing.table.header.standard" /> },
{ id: 3, name: <I18nText id="pricing.table.header.professional" /> },
{ id: 4, name: <I18nText id="pricing.table.header.custom" /> },
];
const planOptions = [
{
id: 0,
name: <I18nText id="pricing.table.feature.subscription.search" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 1,
name: <I18nText id="pricing.table.feature.subscription.bookings" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 2,
name: <I18nText id="pricing.table.feature.subscription.traveler" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 3,
name: <I18nText id="pricing.table.feature.subscription.corporate" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 4,
name: <I18nText id="pricing.table.feature.subscription.quotes" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 5,
name: <I18nText id="pricing.table.feature.subscription.ndc" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
];
const getCurrentPlan = currentPlan && currentPlan.name;
const newPlansCard = planData.map(plan => {
const matchPlan = plan.name === getCurrentPlan;
return (
<>
<Card
className="billing-plans__plans-card"
size="normal"
version="v2"
title={plan.name}
headerCenter={true}
key={plan.id}
>
{matchPlan ? (
<div className="billing-plans__current-plan-icon">
<img src={greenCheck} alt="check" />
</div>
) : null}
<div className="billing-plans__plans-card-title">
<sup>US$</sup>
{plan.price}
<sub>
/<I18nText id="pricing.plans.misc.month" />
</sub>
</div>
<div className="billing-plans__plans-card-features-wrapper">
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.searches" />
<span className="billing-plans__features-access">
{plan.freeSearches}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.bookings" />
<span className="billing-plans__features-access">
{plan.freeBookings}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.profiles" />
<span className="billing-plans__features-access">
{plan.travelerProfiles}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.tickets" />
<span className="billing-plans__features-access">
{plan.supportTickets}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.phone" />
<span className="billing-plans__features-access">
{plan.supportByPhone}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.channels" />
<span className="billing-plans__features-access">
{plan.supplierChannels}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.domain" />
<span className="billing-plans__features-access">
{plan.customDomain}
</span>
</div>
</div>
<div className="billing-plans__plans-card-actions">
{!matchPlan ? (
<div>
<BaseModal
linkText={<I18nText id="pricing.plans.actions.select" />}
linkClassName="billing-plans__plans-card-actions-button"
open={modalOpen}
onModalClose={() => setModalOpen(false)}
onModalOpen={() => setModalOpen(true)}
header={
<I18nText id="pricing.plans.actions.confirmationText" />
}
size="small"
showCloseIcon={false}
key={plan.id}
>
<div className="col-12">
<Button
version="v2"
type="accent"
size="normal"
label={I18n.t('shared.action.confirm')}
key={plan.id}
onClick={() => {
selectPlan(plan);
}}
/>
<Button
version="v2"
size="normal"
label={I18n.t('shared.action.cancel')}
onClick={() => setModalOpen(false)}
/>
</div>
</BaseModal>
</div>
) : null}
</div>
</Card>
</>
);
});
const pricingTable = () => {
return (
<div>
<div className="container-full billing-plans__pricing-table">
<Card size="full" version="v2" key={planOptions.id}>
<div className="billing-plans__table-header">
<I18nText id="pricing.plans.sub_title" />
</div>
<table className="billing-plans__table-body">
<thead>
<tr>
<th className="billing-plans__table-sub-header">
<I18nText id="pricing.table.feature.header" />
</th>
{tableHeader.map(({ name, id }) => (
<th className="billing-plans__table-sub-header" key={id}>
{name}
</th>
))}
</tr>
</thead>
<tbody>
{planOptions.map(
({
id,
name,
isFree,
isBasic,
isStandard,
isProfessional,
isCustom,
}) => (
<tr key={id}>
<td className="billing-plans__table-data">{name}</td>
<td className="billing-plans__table-plan">
<img src={isFree} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isBasic} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isStandard} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isProfessional} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isCustom} alt="check" />
</td>
</tr>
),
)}
</tbody>
</table>
</Card>
</div>
</div>
);
};
return (
<div className="billing-plans">
<div>
<div className="grid">
<div className="col-12 offset-xlg-1 offset-lg-1 offset-md-1 offset-sm-1 offset-xs-1 offset-xxs-1">
<div className="billing-plans__plans-header">
<I18nText id="pricing.plans.title" />
</div>
</div>
</div>
<div className="billing-plans__plans-wrapper">
{currentPlan && newPlansCard}
</div>
</div>
{pricingTable()}
</div>
);
};
Plans.propTypes = {
currentPlan: PropTypes.shape({
name: PropTypes.string,
identifier: PropTypes.string,
id: PropTypes.number,
}).isRequired,
affiliateId: PropTypes.number.isRequired,
};
export default Plans;
我想获取 selected 卡片的 ID 并将其传递给模态框。当第一个按钮即:单击卡片模式的确认按钮时,卡片将显示为 selected 并带有刻度线。就像在每张卡片中一样,有 Select 计划按钮,如果用户真的想要 select 计划,它会打开模式对话框以获得确认。如果他 selects 是,则获取特定卡的 ID,然后将 ID 传递给 selectPlan 函数以 Select 特定卡作为计划。在模态的第一个按钮的现有代码库中,我有一个 onClick 函数,它将特定的 selected Plan 发送到 selectPlan 函数,就像这样 selectPlan(plan)
。但是现在,无论我 select 使用相应模式的哪张卡,我都只能得到 ID 为 2 或 6 的卡,尽管有 5 张 ID 为 2、3、4、5、6 的卡。这意味着存在一些问题在当前实现中获取特定的 selected 卡 ID。我想知道我应该在当前代码中更改什么以获取特定的 selected 卡 ID 并使用 onClick 函数将其传递给相应的模式按钮以使用 selected 将计划表示为 selectPlan(plan)
函数。这是计划 UI:
const [plans, setPlans] = useState([]);
const [currentPlan, setCurrentPlan] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [selectedPlan, setSelectedPlan] = useState();
const fetchPlans = async () => {
const { data } = await httpClient.get(
const Plans = ({ affiliateId }) => {
linkClassName="billing-plans__plans-card-actions-button"
open={modalOpen}
onModalClose={() => setModalOpen(false)}
onModalOpen={() => {
setSelectedPlan(plan);
setModalOpen(true);
}}
header={
<I18nText id="pricing.plans.actions.confirmationText" />
}
const Plans = ({ affiliateId }) => {
label={I18n.t('shared.action.confirm')}
key={plan.id}
onClick={() => {
selectPlan(selectedPlan);
}}
/>
<Button
import React, { useEffect, useState } from 'react';
import { I18nText, Card, BaseModal, Button } from '@wtag/react-comp-lib';
import PropTypes from 'prop-types';
import greenCheck from 'affiliateIcons/green-check.svg';
import doneMark from '../../../../assets/images/done-24px.svg';
import routes from '../../../shared/routes';
import httpClient from '../../../shared/libraries/httpClient';
const Plans = ({ affiliateId }) => {
const [plans, setPlans] = useState([]);
const [currentPlan, setCurrentPlan] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [selectedPlan, setSelectedPlan] = useState();
const fetchPlans = async () => {
const { data } = await httpClient.get(
routes.billing.changePlan.fetch({ affiliate_id: affiliateId }),
);
setPlans(data.plans);
setCurrentPlan(data.currentPlan);
};
const selectPlan = async plan => {
const { data } = await httpClient.put(
routes.billing.changePlan.update({
id: plan.id,
affiliate_id: affiliateId,
}),
);
if (data.error === null) {
fetchPlans(data.currentPlan);
setModalOpen(false);
}
};
useEffect(() => {
fetchPlans();
}, []);
const data = [
{
id: 0,
identifier: 'free-2020',
name: <I18nText id="pricing.plans.name.free" />,
planTitle: 'free',
price: '00',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '-',
customDomain: '-',
active: true,
},
{
id: 1,
identifier: 'basic-2020',
name: <I18nText id="pricing.plans.name.basic" />,
planTitle: 'basic',
price: '29',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '-',
supplierChannels: '1',
customDomain: '-',
active: false,
},
{
id: 2,
identifier: 'standard-2020',
name: <I18nText id="pricing.plans.name.standard" />,
planTitle: 'standard',
price: '59',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '1hr/mo',
supplierChannels: '2',
customDomain: '-',
active: false,
},
{
id: 3,
identifier: 'professional-2020',
name: <I18nText id="pricing.plans.name.professional" />,
planTitle: 'professional',
price: '99',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: '3hr/mo',
supplierChannels: '5',
customDomain: 'Yes',
active: false,
},
{
id: 4,
identifier: 'custom-2020',
name: <I18nText id="pricing.plans.name.custom" />,
planTitle: 'custom',
price: '-',
freeSearches: <I18nText id="pricing.plans.features.unlimited" />,
freeBookings: <I18nText id="pricing.plans.features.unlimited" />,
travelerProfiles: <I18nText id="pricing.plans.features.unlimited" />,
supportTickets: <I18nText id="pricing.plans.features.available" />,
supportByPhone: <I18nText id="pricing.plans.features.unlimited" />,
supplierChannels: <I18nText id="pricing.plans.features.unlimited" />,
customDomain: <I18nText id="pricing.plans.features.available" />,
active: false,
},
];
const planData = data.map(d => {
const correspondingPlan = plans.filter(
plan => d.identifier === plan.identifier,
)[0];
if (correspondingPlan) {
return { ...d, ...correspondingPlan };
}
return d;
});
const tableHeader = [
{ id: 0, name: <I18nText id="pricing.table.header.free" /> },
{ id: 1, name: <I18nText id="pricing.table.header.basic" /> },
{ id: 2, name: <I18nText id="pricing.table.header.standard" /> },
{ id: 3, name: <I18nText id="pricing.table.header.professional" /> },
{ id: 4, name: <I18nText id="pricing.table.header.custom" /> },
];
const planOptions = [
{
id: 0,
name: <I18nText id="pricing.table.feature.subscription.search" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 1,
name: <I18nText id="pricing.table.feature.subscription.bookings" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 2,
name: <I18nText id="pricing.table.feature.subscription.traveler" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 3,
name: <I18nText id="pricing.table.feature.subscription.corporate" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 4,
name: <I18nText id="pricing.table.feature.subscription.quotes" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
{
id: 5,
name: <I18nText id="pricing.table.feature.subscription.ndc" />,
isFree: doneMark,
isBasic: doneMark,
isStandard: doneMark,
isProfessional: doneMark,
isCustom: doneMark,
},
];
const getCurrentPlan = currentPlan && currentPlan.name;
const newPlansCard = planData.map(plan => {
const matchPlan = plan.name === getCurrentPlan;
return (
<>
<Card
className="billing-plans__plans-card"
size="normal"
version="v2"
title={plan.name}
headerCenter={true}
key={plan.id}
>
{matchPlan ? (
<div className="billing-plans__current-plan-icon">
<img src={greenCheck} alt="check" />
</div>
) : null}
<div className="billing-plans__plans-card-title">
<sup>US$</sup>
{plan.price}
<sub>
/<I18nText id="pricing.plans.misc.month" />
</sub>
</div>
<div className="billing-plans__plans-card-features-wrapper">
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.searches" />
<span className="billing-plans__features-access">
{plan.freeSearches}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.bookings" />
<span className="billing-plans__features-access">
{plan.freeBookings}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.profiles" />
<span className="billing-plans__features-access">
{plan.travelerProfiles}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.tickets" />
<span className="billing-plans__features-access">
{plan.supportTickets}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.phone" />
<span className="billing-plans__features-access">
{plan.supportByPhone}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.channels" />
<span className="billing-plans__features-access">
{plan.supplierChannels}
</span>
</div>
<div className="billing-plans__plans-card-features">
<I18nText id="pricing.plans.features.domain" />
<span className="billing-plans__features-access">
{plan.customDomain}
</span>
</div>
</div>
<div className="billing-plans__plans-card-actions">
{!matchPlan ? (
<div>
<BaseModal
linkText={<I18nText id="pricing.plans.actions.select" />}
linkClassName="billing-plans__plans-card-actions-button"
open={modalOpen}
onModalClose={() => setModalOpen(false)}
onModalOpen={() => {
setSelectedPlan(plan);
setModalOpen(true);
}}
header={
<I18nText id="pricing.plans.actions.confirmationText" />
}
size="small"
showCloseIcon={false}
key={plan.id}
>
<div className="col-12">
<Button
version="v2"
type="accent"
size="normal"
label={I18n.t('shared.action.confirm')}
key={plan.id}
onClick={() => {
selectPlan(selectedPlan);
}}
/>
<Button
version="v2"
size="normal"
label={I18n.t('shared.action.cancel')}
onClick={() => setModalOpen(false)}
/>
</div>
</BaseModal>
</div>
) : null}
</div>
</Card>
</>
);
});
const pricingTable = () => {
return (
<div>
<div className="container-full billing-plans__pricing-table">
<Card size="full" version="v2" key={planOptions.id}>
<div className="billing-plans__table-header">
<I18nText id="pricing.plans.sub_title" />
</div>
<table className="billing-plans__table-body">
<thead>
<tr>
<th className="billing-plans__table-sub-header">
<I18nText id="pricing.table.feature.header" />
</th>
{tableHeader.map(({ name, id }) => (
<th className="billing-plans__table-sub-header" key={id}>
{name}
</th>
))}
</tr>
</thead>
<tbody>
{planOptions.map(
({
id,
name,
isFree,
isBasic,
isStandard,
isProfessional,
isCustom,
}) => (
<tr key={id}>
<td className="billing-plans__table-data">{name}</td>
<td className="billing-plans__table-plan">
<img src={isFree} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isBasic} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isStandard} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isProfessional} alt="check" />
</td>
<td className="billing-plans__table-plan">
<img src={isCustom} alt="check" />
</td>
</tr>
),
)}
</tbody>
</table>
</Card>
</div>
</div>
);
};
return (
<div className="billing-plans">
<div>
<div className="grid">
<div className="col-12 offset-xlg-1 offset-lg-1 offset-md-1 offset-sm-1 offset-xs-1 offset-xxs-1">
<div className="billing-plans__plans-header">
<I18nText id="pricing.plans.title" />
</div>
</div>
</div>
<div className="billing-plans__plans-wrapper">
{currentPlan && newPlansCard}
</div>
</div>
{pricingTable()}
</div>
);
};
Plans.propTypes = {
currentPlan: PropTypes.shape({
name: PropTypes.string,
identifier: PropTypes.string,
id: PropTypes.number,
}).isRequired,
affiliateId: PropTypes.number.isRequired,
};
export default Plans;