work in progress on jamtracks shopping
This commit is contained in:
parent
48335a9d9c
commit
adafcb8569
|
|
@ -23,55 +23,55 @@ describe('JamTracks Page', () => {
|
|||
cy.get('input[type="search"]').should('exist');
|
||||
});
|
||||
|
||||
describe.only('search artists', () => {
|
||||
describe('search artists', () => {
|
||||
beforeEach(() => {
|
||||
//http://www.jamkazam.local:3000/api/jamtracks/autocomplete?match=ac+&limit=5
|
||||
// cy.intercept('GET', /S+\/jamtracks\/autocomplete\?match=ac\S+/, {
|
||||
// body: [{ artists: [{ original_artist: 'AC DC' }], songs: [] }]
|
||||
// }).as('getJamTracksAutoComplete');
|
||||
cy.intercept('GET', /\S+\/jamtracks\?limit=100/, { fixture: 'jamtracks' }).as('getJamTracks');
|
||||
cy.intercept('GET', /\S+\/jamtracks\?per_page=10\&page=1\&\S+/, { fixture: 'jamtracks_page1' }).as('getJamTracks_page1');
|
||||
cy.intercept('GET', /\S+\/jamtracks\?per_page=10\&page=2\&\S+/, { fixture: 'jamtracks_page2' }).as('getJamTracks_page2');
|
||||
cy.intercept('GET', /\S+\/jamtracks\?per_page=10\&page=3\&\S+/, { fixture: 'jamtracks_page3' }).as('getJamTracks_page3');
|
||||
cy.intercept('POST', /\S+\/shopping_carts\/add_jamtrack/, {
|
||||
body: { success: true }
|
||||
}).as('addJamTrackToCart');
|
||||
})
|
||||
|
||||
it('should display the JamTracks', () => {
|
||||
cy.get('input[type="search"]').type('ba{enter}');
|
||||
cy.wait('@getJamTracks_page1');
|
||||
cy.contains('Search Results: JamTracks including "ba"');
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-name-col').should('contain', 'Back in Black by AC DC');
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col .jamtrack-track:visible').should('have.length', 6);
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col').contains('Show all tracks').click();
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col .jamtrack-track:visible').should('have.length', 10);
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col').contains('Show fewer tracks').click();
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr:first .track-tracks-col .jamtrack-track:visible').should('have.length', 6);
|
||||
|
||||
//load more
|
||||
cy.get('button').contains('Load More').click();
|
||||
cy.wait('@getJamTracks_page2');
|
||||
|
||||
//load more
|
||||
cy.get('button').contains('Load More').click();
|
||||
cy.wait('@getJamTracks_page3');
|
||||
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr').should('have.length', 30);
|
||||
|
||||
//no more pages
|
||||
cy.get('[data-testid=moreBtn]').should('not.exist');
|
||||
});
|
||||
|
||||
it('let user to purchase a JamTrack', () => {
|
||||
cy.get('input[type="search"]').type('ba{enter}');
|
||||
cy.wait('@getJamTracks_page1');
|
||||
cy.get('[data-testid=jamtracks-table] tbody tr').eq(2).find('.purchase-button-col button').should('contain', 'Add to Cart').click();
|
||||
cy.wait('@addJamTrackToCart');
|
||||
cy.location('pathname').should('eq', '/shopping-cart')
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should display the JamTracks', () => {
|
||||
cy.get('input[type="search"]').type('ba{enter}');
|
||||
cy.wait('@getJamTracks');
|
||||
cy.contains('Search Results: JamTracks including "ba"');
|
||||
cy.get('[data-testid=jamtracks-table] .track-name-col').should('contain', 'Back in Black by AC DC');
|
||||
cy.get('[data-testid=jamtracks-table] .track-tracks-col .jamtrack-track:visible').should('have.length', 6);
|
||||
cy.get('[data-testid=jamtracks-table] .track-tracks-col').contains('Show all tracks').click();
|
||||
cy.get('[data-testid=jamtracks-table] .track-tracks-col .jamtrack-track:visible').should('have.length', 10);
|
||||
cy.get('[data-testid=jamtracks-table] .track-tracks-col').contains('Show fewer tracks').click();
|
||||
cy.get('[data-testid=jamtracks-table] .track-tracks-col .jamtrack-track:visible').should('have.length', 6);
|
||||
});
|
||||
});
|
||||
|
||||
// it('should display the JamTracks list', () => {
|
||||
// cy.get('table').should('exist');
|
||||
// });
|
||||
|
||||
// it('should display the JamTracks pagination', () => {
|
||||
// cy.get('button').contains('Next').should('exist');
|
||||
// });
|
||||
|
||||
// it('should display the JamTracks preview', () => {
|
||||
// cy.get('table').find('tr').first().find('td').eq(1).find('button').click();
|
||||
// cy.get('table').find('tr').first().find('td').eq(1).find('button').should('contain', 'Preview');
|
||||
// });
|
||||
|
||||
// it('should display the JamTracks purchase button', () => {
|
||||
// cy.get('table').find('tr').first().find('td').eq(2).find('button').click();
|
||||
// cy.get('table').find('tr').first().find('td').eq(2).find('button').should('contain', 'Add to Cart');
|
||||
// });
|
||||
|
||||
// it('should display the JamTracks artist search results', () => {
|
||||
// cy.get('input[type="search"]').type('artist');
|
||||
// cy.get('ul').find('li').first().click();
|
||||
// cy.get('ul').should('not.exist');
|
||||
// });
|
||||
|
||||
// it('should display the JamTracks artist search results', () => {
|
||||
// cy.get('input[type="search"]').type('artist');
|
||||
// cy.get('ul').find('li').first().click();
|
||||
// cy.get('ul').should('not.exist');
|
||||
// });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,191 +0,0 @@
|
|||
{
|
||||
"next": null,
|
||||
"count": 1,
|
||||
"jamtracks": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Back in Black",
|
||||
"description": "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the AC DC song \"Back in Black\".",
|
||||
"recording_type": "Cover",
|
||||
"original_artist": "AC DC",
|
||||
"songwriter": null,
|
||||
"publisher": null,
|
||||
"sales_region": "Worldwide",
|
||||
"price": "1.99",
|
||||
"version": "0",
|
||||
"duration": 221,
|
||||
"year": null,
|
||||
"plan_code": "jamtrack-acdc-backinblack",
|
||||
"allow_free": true,
|
||||
"download_price": "2.99",
|
||||
"upgrade_price": "1.0",
|
||||
"tracks": [
|
||||
{
|
||||
"id": "103dea4d-f2a3-4414-8efe-d2ca378dda60",
|
||||
"part": "Master Mix",
|
||||
"instrument": {
|
||||
"id": "computer",
|
||||
"description": "Computer",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Master",
|
||||
"position": 1000,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Master%20Mix-44100-preview-e9a5a63f34b4d523ee1842fff31f88ce.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Master%20Mix-44100-preview-25fcba7ace7086e3cb6b97d7e33ba72e.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Master%20Mix-44100-preview-9f0b072ed9f4b546e170fcdfb302137e.mp3"
|
||||
},
|
||||
{
|
||||
"id": "2755cbdd-0476-4f3b-9ba1-e2da561ddb4e",
|
||||
"part": "Lead",
|
||||
"instrument": {
|
||||
"id": "voice",
|
||||
"description": "Voice",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 1,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Vocal%20-%20Lead-44100-preview-d35c328fc3936dad9a79fe102dc72950.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Vocal%20-%20Lead-44100-preview-b97b37651eae352fae3b3060918c7bcb.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Vocal%20-%20Lead-44100-preview-d35c328fc3936dad9a79fe102dc72950.aac"
|
||||
},
|
||||
{
|
||||
"id": "0db7c4e1-5e8d-43fe-bd35-98acd8f68b26",
|
||||
"part": "Drums",
|
||||
"instrument": {
|
||||
"id": "drums",
|
||||
"description": "Drums",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 2,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Drums-44100-preview-03aadceb966caf40b96334bdd00234f6.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Drums-44100-preview-854914e3e0d6fdc5f0794325b0ecaead.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Drums-44100-preview-03aadceb966caf40b96334bdd00234f6.aac"
|
||||
},
|
||||
{
|
||||
"id": "2cc79ab6-dab8-4905-85e6-0df5f8e087f1",
|
||||
"part": "Bass",
|
||||
"instrument": {
|
||||
"id": "bass guitar",
|
||||
"description": "Bass Guitar",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 3,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Bass-44100-preview-61c334ac87f811bd010ed3a910764c2e.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Bass-44100-preview-4066dafd7b72e9993b0c0fe1dba2b332.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Bass-44100-preview-61c334ac87f811bd010ed3a910764c2e.aac"
|
||||
},
|
||||
{
|
||||
"id": "ed1d3487-3b32-442f-9c76-8a36fe3bb643",
|
||||
"part": "Solo",
|
||||
"instrument": {
|
||||
"id": "electric guitar",
|
||||
"description": "Electric Guitar",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 4,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Solo-44100-preview-e9fe8572a9ac1022762642cbd92b3c34.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Solo-44100-preview-5fb058042254206cfa9fb4dcb0310b2c.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Solo-44100-preview-e9fe8572a9ac1022762642cbd92b3c34.aac"
|
||||
},
|
||||
{
|
||||
"id": "f4ce7c91-7542-4e03-8fc2-68b31683d33e",
|
||||
"part": "Rhythm 1",
|
||||
"instrument": {
|
||||
"id": "electric guitar",
|
||||
"description": "Electric Guitar",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 5,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%201-44100-preview-6b498479823d4131a01fa535817d5eab.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%201-44100-preview-f4cbb31dbde3e1a3e6012730a7e0e10f.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%201-44100-preview-6b498479823d4131a01fa535817d5eab.aac"
|
||||
},
|
||||
{
|
||||
"id": "2d96c7ec-59f1-4d56-8a7f-7f4c75a0ccef",
|
||||
"part": "Rhythm 2",
|
||||
"instrument": {
|
||||
"id": "electric guitar",
|
||||
"description": "Electric Guitar",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 6,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%202-44100-preview-a626d7c632560f6737e1b6024141289e.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%202-44100-preview-06a0e5af451f001f3465992efcd34ec0.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Rhythm%202-44100-preview-a626d7c632560f6737e1b6024141289e.aac"
|
||||
},
|
||||
{
|
||||
"id": "fce018ca-c897-4137-aa10-ef56a8e1831f",
|
||||
"part": "Intro Scrapes",
|
||||
"instrument": {
|
||||
"id": "electric guitar",
|
||||
"description": "Electric Guitar",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 7,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Intro%20Scrapes-44100-preview-0ddfaa7154e9ba35d05d60477d5dd3e9.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Intro%20Scrapes-44100-preview-f53ce3c5f9dcf81af51560f52635fbb0.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Intro%20Scrapes-44100-preview-0ddfaa7154e9ba35d05d60477d5dd3e9.aac"
|
||||
},
|
||||
{
|
||||
"id": "c9b3e0a8-4db0-4d0f-9769-398a6d56506e",
|
||||
"part": "Main",
|
||||
"instrument": {
|
||||
"id": "electric guitar",
|
||||
"description": "Electric Guitar",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 8,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Main-44100-preview-234a224f75a97d7ff8f55442ece6fcde.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Main-44100-preview-828c9691f5435dea1c90182fa2618c9b.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Electric%20Guitar%20-%20Main-44100-preview-234a224f75a97d7ff8f55442ece6fcde.aac"
|
||||
},
|
||||
{
|
||||
"id": "28c3df07-2a88-45a9-9ae6-3399a5d2eb20",
|
||||
"part": "Sound FX",
|
||||
"instrument": {
|
||||
"id": "computer",
|
||||
"description": "Computer",
|
||||
"created_at": "2021-02-02T23:16:46.168Z",
|
||||
"updated_at": "2021-02-02T23:16:46.168Z",
|
||||
"popularity": 3
|
||||
},
|
||||
"track_type": "Track",
|
||||
"position": 9,
|
||||
"preview_mp3_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Sound%20Effects-44100-preview-6c859c73036cd55bceb65f19f2d2f2f3.mp3",
|
||||
"preview_ogg_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Sound%20Effects-44100-preview-f840d8df4c7388f776477139025ee712.ogg",
|
||||
"preview_aac_url": "https://jamkazam-dev-public.s3.amazonaws.com/jam_track_previews/AC%20DC/Back%20in%20Black/Back%20in%20Black%20Stem%20-%20Sound%20Effects-44100-preview-6c859c73036cd55bceb65f19f2d2f2f3.aac"
|
||||
}
|
||||
],
|
||||
"licensor": null,
|
||||
"genres": ["Rock", "Pop"],
|
||||
"added_cart": true,
|
||||
"can_download": true,
|
||||
"purchased": true
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
const JKShoppingCart = () => {
|
||||
return (
|
||||
<div>ShoppingCart</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default JKShoppingCart
|
||||
|
|
@ -49,8 +49,8 @@ import JKAffiliateEarnings from '../affiliate/JKAffiliateEarnings';
|
|||
import JKAffiliateAgreement from '../affiliate/JKAffiliateAgreement';
|
||||
|
||||
import JKJamTracksFilter from '../jamtracks/JKJamTracksFilter';
|
||||
|
||||
import JKShoppingCart from '../cart/JKShoppingCart';
|
||||
import JKShoppingCart from '../shopping-cart/JKShoppingCart';
|
||||
import JKCheckout from '../shopping-cart/JKCheckout';
|
||||
|
||||
|
||||
//import loadable from '@loadable/component';
|
||||
|
|
@ -287,7 +287,8 @@ function JKDashboardMain() {
|
|||
<PrivateRoute path="/affiliate/earnings" component={JKAffiliateEarnings} />
|
||||
<PrivateRoute path="/affiliate/agreement" component={JKAffiliateAgreement} />
|
||||
<PrivateRoute path="/jamtracks" component={JKJamTracksFilter} />
|
||||
<PrivateRoute path="/cart" component={JKShoppingCart} />
|
||||
<PrivateRoute path="/shopping-cart" component={JKShoppingCart} />
|
||||
<PrivateRoute path="/checkout" component={JKCheckout} />
|
||||
{/*Redirect*/}
|
||||
<Redirect to="/errors/404" />
|
||||
</Switch>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Row, Col } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
|
@ -31,7 +32,7 @@ const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
|||
onClick={() => handleClick(artist)}
|
||||
className={index + 1 > 6 && !expanded ? 'd-none' : null}
|
||||
>
|
||||
<span className='mr-2 pb-1'>
|
||||
<span className='mr-4 pb-1'>
|
||||
{artist.original_artist}
|
||||
</span>
|
||||
</a>
|
||||
|
|
@ -58,4 +59,16 @@ const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
|||
);
|
||||
};
|
||||
|
||||
JKJamTrackArtists.propTypes = {
|
||||
artists: PropTypes.array.isRequired,
|
||||
showArtists: PropTypes.bool,
|
||||
onSelect: PropTypes.func
|
||||
};
|
||||
|
||||
JKJamTrackArtists.defaultProps = {
|
||||
artists: [],
|
||||
showArtists: false,
|
||||
onSelect: () => {}
|
||||
};
|
||||
|
||||
export default JKJamTrackArtists;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { Fragment, useState } from 'react';
|
||||
import { Row, Col, Container } from 'reactstrap';
|
||||
import JKJamTrackTrack from './JKJamTrackTrack';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const JKJamTrackPreview = ({ jamTrack }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
|
@ -34,4 +35,8 @@ const JKJamTrackPreview = ({ jamTrack }) => {
|
|||
);
|
||||
};
|
||||
|
||||
JKJamTrackPreview.propTypes = {
|
||||
jamTrack: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default JKJamTrackPreview;
|
||||
|
|
|
|||
|
|
@ -4,25 +4,26 @@ import PropTypes from 'prop-types';
|
|||
import { addJamtrackToShoppingCart } from '../../helpers/rest';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useShoppingCart } from '../../hooks/useShoppingCart';
|
||||
|
||||
const JKJamTrackPurchaseButton = ({ jamTrack }) => {
|
||||
const history = useHistory();
|
||||
const { currentUser } = useAuth();
|
||||
console.log('currentUser', currentUser);
|
||||
const addToCart = () => {
|
||||
console.log('Add to Cart');
|
||||
const { addCartItem } = useShoppingCart();
|
||||
|
||||
const addToCart = async () => {
|
||||
const options = {
|
||||
id: jamTrack.id,
|
||||
variant: 'full'
|
||||
};
|
||||
addJamtrackToShoppingCart(options)
|
||||
.then(response => {
|
||||
console.log('Add to Cart Response', response);
|
||||
history.push('/cart');
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Add to Cart Error', error);
|
||||
});
|
||||
if (await addCartItem(options)) {
|
||||
toast.success('JamTrack added to cart');
|
||||
history.push('/shopping-cart');
|
||||
} else {
|
||||
console.log('Add to Cart Error');
|
||||
toast.error('Error adding to cart');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -53,4 +54,8 @@ const JKJamTrackPurchaseButton = ({ jamTrack }) => {
|
|||
);
|
||||
};
|
||||
|
||||
JKJamTrackPurchaseButton.propTypes = {
|
||||
jamTrack: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default JKJamTrackPurchaseButton;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import JKInstrumentIcon from '../profile/JKInstrumentIcon';
|
|||
import { Howl } from 'howler';
|
||||
import { useJamTrackPreview } from '../../context/JamTrackPreviewContext';
|
||||
import { Spinner } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const JKJamTrackTrack = ({ track }) => {
|
||||
console.log('debug JKTrackPlayPause track');
|
||||
|
|
@ -129,4 +130,8 @@ const JKJamTrackTrack = ({ track }) => {
|
|||
);
|
||||
};
|
||||
|
||||
JKJamTrackTrack.propTypes = {
|
||||
track: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default JKJamTrackTrack;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ import { Row, Col, FormGroup, Input, InputGroup, InputGroupText, ListGroup, List
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { autocompleteJamTracks } from '../../helpers/rest';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const JKJamTracksAutoComplete = ({ onSelect, onEnter, showDropdown, setShowDropdown, inputValue, setInputValue, inputPlaceholder }) => {
|
||||
const [artists, setArtists] = useState([]);
|
||||
const [songs, setSongs] = useState([]);
|
||||
//const [showDropdown, setShowDropdown] = useState(false);
|
||||
//const [inputValue, setInputValue] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -151,4 +150,24 @@ const JKJamTracksAutoComplete = ({ onSelect, onEnter, showDropdown, setShowDropd
|
|||
);
|
||||
};
|
||||
|
||||
JKJamTracksAutoComplete.propTypes = {
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onEnter: PropTypes.func.isRequired,
|
||||
showDropdown: PropTypes.bool.isRequired,
|
||||
setShowDropdown: PropTypes.func.isRequired,
|
||||
inputValue: PropTypes.string.isRequired,
|
||||
setInputValue: PropTypes.func.isRequired,
|
||||
inputPlaceholder: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
JKJamTracksAutoComplete.defaultProps = {
|
||||
onSelect: () => {},
|
||||
onEnter: () => {},
|
||||
showDropdown: false,
|
||||
setShowDropdown: () => {},
|
||||
inputValue: '',
|
||||
setInputValue: () => {},
|
||||
inputPlaceholder: ''
|
||||
};
|
||||
|
||||
export default React.memo(JKJamTracksAutoComplete);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ const JKJamTracksFilter = () => {
|
|||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [showArtists, setShowArtists] = useState(false);
|
||||
const [jamTracksNextPage, setJamTracksNextPage] = useState(null);
|
||||
const [nextOffset, setNextOffset] = useState(null);
|
||||
const [autoCompleteInputValue, setAutoCompleteInputValue] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const PER_PAGE = 10;
|
||||
|
||||
useEffect(() => {
|
||||
if (selected) {
|
||||
|
|
@ -24,20 +26,10 @@ const JKJamTracksFilter = () => {
|
|||
}
|
||||
}, [selected]);
|
||||
|
||||
const handleOnSelect = selected => {
|
||||
setArtists([]);
|
||||
setJamTracks([]);
|
||||
setSearchTerm('');
|
||||
setShowArtists(false);
|
||||
setSelected(selected);
|
||||
const params = queryOptions(selected);
|
||||
console.log('handleOnSelect _params_', params);
|
||||
fetchJamTracks(params);
|
||||
};
|
||||
|
||||
const queryOptions = selected => {
|
||||
const options = {
|
||||
limit: 100
|
||||
per_page: PER_PAGE,
|
||||
page: page
|
||||
};
|
||||
|
||||
if (typeof selected === 'string') {
|
||||
|
|
@ -51,24 +43,33 @@ const JKJamTracksFilter = () => {
|
|||
options.song = selected.name;
|
||||
}
|
||||
|
||||
if (jamTracksNextPage !== null) {
|
||||
options.next = jamTracksNextPage;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
const handleOnEnter = queryStr => {
|
||||
const handleOnSelect = selected => {
|
||||
setArtists([]);
|
||||
setJamTracks([]);
|
||||
setSelected(null);
|
||||
setSearchTerm('');
|
||||
setShowArtists(false);
|
||||
setSelected(selected);
|
||||
const params = queryOptions(selected);
|
||||
fetchJamTracks(params);
|
||||
};
|
||||
|
||||
const handleOnEnter = queryStr => {
|
||||
setPage(1);
|
||||
setArtists([]);
|
||||
setJamTracks([]);
|
||||
setSelected(x => null);
|
||||
setSearchTerm(queryStr);
|
||||
fetchArtists(queryStr);
|
||||
const params = queryOptions(queryStr);
|
||||
console.log('handleOnEnter _params_', params);
|
||||
console.log('handleOnEnter _params', params, selected);
|
||||
fetchJamTracks(params);
|
||||
};
|
||||
|
||||
const handleOnSelectArtist = artist => {
|
||||
setPage(1);
|
||||
const selectedOpt = {
|
||||
type: 'artist',
|
||||
original_artist: artist.original_artist
|
||||
|
|
@ -78,6 +79,12 @@ const JKJamTracksFilter = () => {
|
|||
handleOnSelect(selectedOpt);
|
||||
};
|
||||
|
||||
const handleOnNextJamTracksPage = () => {
|
||||
const currentQuery = selected ? selected : searchTerm;
|
||||
const params = queryOptions(currentQuery);
|
||||
fetchJamTracks(params);
|
||||
}
|
||||
|
||||
const fetchJamTracks = options => {
|
||||
getJamTracks(options)
|
||||
.then(resp => {
|
||||
|
|
@ -85,8 +92,9 @@ const JKJamTracksFilter = () => {
|
|||
})
|
||||
.then(data => {
|
||||
console.log('jamtracks', data);
|
||||
setJamTracks(data.jamtracks);
|
||||
setJamTracksNextPage(data.next);
|
||||
setJamTracks(prev => [...prev, ...data.jamtracks]);
|
||||
setNextOffset(data.next);
|
||||
setPage(page => page + 1);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('error', error);
|
||||
|
|
@ -113,12 +121,6 @@ const JKJamTracksFilter = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const handleOnNextJamTracksPage = () => {
|
||||
const currentQuery = selected ? selected : searchTerm;
|
||||
const params = queryOptions(currentQuery);
|
||||
fetchJamTracks(params);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FalconCardHeader title={t('search.page_title')} titleClass="font-weight-bold" />
|
||||
|
|
@ -153,8 +155,7 @@ const JKJamTracksFilter = () => {
|
|||
showArtists={showArtists}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<JKJamTracksList selectedType={selected?.type} searchTerm={searchTerm} jamTracks={jamTracks} nextPage={jamTracksNextPage} onNextPage={handleOnNextJamTracksPage} />
|
||||
<JKJamTracksList selectedType={selected?.type} searchTerm={searchTerm} jamTracks={jamTracks} nextOffset={nextOffset} onNextPage={handleOnNextJamTracksPage} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import { Row, Col, Table, Button } from 'reactstrap';
|
||||
import JKJamTrackPreview from './JKJamTrackPreview';
|
||||
import JKJamTrackPurchaseButton from './JKJamTrackPurchaseButton';
|
||||
|
||||
import { JamTrackPreviewProvider } from '../../context/JamTrackPreviewContext';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextPage, onNextPage }) => {
|
||||
const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextOffset, onNextPage }) => {
|
||||
return (
|
||||
<>
|
||||
{selectedType && searchTerm.length && jamTracks.length > 0 ? (
|
||||
|
|
@ -58,10 +58,10 @@ const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextPage, onNext
|
|||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{nextPage && (
|
||||
{nextOffset && (
|
||||
<Row>
|
||||
<Col>
|
||||
<Button color="primary" onClick={onNextPage}>
|
||||
<Button color="primary" onClick={onNextPage} data-testid="moreBtn">
|
||||
Load More
|
||||
</Button>
|
||||
</Col>
|
||||
|
|
@ -71,4 +71,20 @@ const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextPage, onNext
|
|||
);
|
||||
};
|
||||
|
||||
JKJamTracksList.propTypes = {
|
||||
selectedType: PropTypes.string || null,
|
||||
searchTerm: PropTypes.string,
|
||||
jamTracks: PropTypes.array,
|
||||
nextOffset: PropTypes.number,
|
||||
onNextPage: PropTypes.func,
|
||||
};
|
||||
|
||||
JKJamTracksList.defaultProps = {
|
||||
selectedType: null,
|
||||
searchTerm: '',
|
||||
jamTracks: [],
|
||||
nextOffset: null,
|
||||
onNextPage: () => {}
|
||||
};
|
||||
|
||||
export default JKJamTracksList;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,523 @@
|
|||
import React, { Fragment, useState, useContext, useEffect } from 'react';
|
||||
import ContentWithAsideLayout from '../../layouts/ContentWithAsideLayout';
|
||||
import AppContext from '../../context/Context';
|
||||
import CheckoutAside from './checkout/CheckoutAside';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Col,
|
||||
Button,
|
||||
Row,
|
||||
FormGroup,
|
||||
Input,
|
||||
CustomInput,
|
||||
UncontrolledTooltip,
|
||||
Label
|
||||
} from 'reactstrap';
|
||||
import Select from 'react-select';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useForm, Controller, get } from 'react-hook-form';
|
||||
import FalconInput from '../common/FalconInput';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Flex from '../common/Flex';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import iconPaymentMethodsGrid from '../../assets/img/icons/icon-payment-methods-grid.png';
|
||||
import iconPaypalFull from '../../assets/img/icons/icon-paypal-full.png';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import { useShoppingCart } from '../../hooks/useShoppingCart';
|
||||
import { getBillingInfo, getUserDetails, getCountries } from '../../helpers/rest';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
|
||||
const JKCheckout = () => {
|
||||
const { currency } = useContext(AppContext);
|
||||
const { cartTotal: payableTotal, loading: cartLoading } = useShoppingCart();
|
||||
const { greaterThan } = useResponsive();
|
||||
const { currentUser } = useAuth();
|
||||
|
||||
const [paymentMethod, setPaymentMethod] = useState('credit-card');
|
||||
const [reuseExistingCard, setReuseExistingCard] = useState(false);
|
||||
const [billingInfo, setBillingInfo] = useState({});
|
||||
const [countries, setCountries] = useState([]);
|
||||
|
||||
const labelClassName = 'ls text-600 font-weight-semi-bold mb-0';
|
||||
const billingLabelClassName = ' text-right';
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
zip: '',
|
||||
country: 'US',
|
||||
number: '',
|
||||
month: '',
|
||||
year: '',
|
||||
verification_value: ''
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
fetchCountries();
|
||||
populateData();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const fetchCountries = () => {
|
||||
getCountries()
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setCountries(data.countriesx);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
};
|
||||
|
||||
const populateData = async () => {
|
||||
const options = {
|
||||
id: currentUser.id
|
||||
};
|
||||
const userResp = await getUserDetails(options);
|
||||
const userData = await userResp.json();
|
||||
console.log('User Data:', userData);
|
||||
setReuseExistingCard(userData.has_recurly_account && userData.reuse_card);
|
||||
await populateBillingAddress();
|
||||
};
|
||||
|
||||
const populateBillingAddress = async () => {
|
||||
try {
|
||||
const resp = await getBillingInfo();
|
||||
const data = await resp.json();
|
||||
const bi = data.billing_info;
|
||||
|
||||
setValue('first_name', bi.first_name);
|
||||
setValue('last_name', bi.last_name);
|
||||
setValue('address1', bi.address1);
|
||||
setValue('address2', billingInfo.address2);
|
||||
setValue('city', bi.city);
|
||||
setValue('state', bi.state);
|
||||
setValue('zip', bi.zip);
|
||||
setValue('country', bi.country);
|
||||
|
||||
setBillingInfo(bi);
|
||||
} catch (error) {
|
||||
console.error('Failed to get billing info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const clearBillingAddress = () => {
|
||||
setValue('first_name', '');
|
||||
setValue('last_name', '');
|
||||
setValue('address1', '');
|
||||
setValue('address2', '');
|
||||
setValue('city', '');
|
||||
setValue('state', '');
|
||||
setValue('zip', '');
|
||||
setValue('country', '');
|
||||
};
|
||||
|
||||
const onSubmit = data => {
|
||||
if (paymentMethod === 'credit-card') {
|
||||
console.log('Credit Card Data:', data);
|
||||
}
|
||||
if (paymentMethod === 'paypal') {
|
||||
console.log('Paypal Data:', data);
|
||||
handoverToPaypal();
|
||||
}
|
||||
};
|
||||
|
||||
const handoverToPaypal = () => {
|
||||
// Handover to Paypal
|
||||
window.location = `${process.env.REACT_APP_CLIENT_BASE_URL}/paypal/checkout/start`;
|
||||
};
|
||||
|
||||
const handleCountryChange = selectedOption => {
|
||||
setValue('country', selectedOption.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: greaterThan.sm ? '50%' : '100%' }} className="mx-auto">
|
||||
<ContentWithAsideLayout
|
||||
aside={cartLoading ? <div>Cart Loading...</div> : <CheckoutAside />}
|
||||
isStickyAside={false}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Card className="mb-3">
|
||||
<FalconCardHeader title="Billing Address" titleTag="h5" />
|
||||
<CardBody>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="first_name" className={labelClassName.concat(billingLabelClassName)}>
|
||||
First Name
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="first_name"
|
||||
control={control}
|
||||
rules={{ required: 'First Name is required' }}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
{errors.first_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.first_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="last_name" className={labelClassName.concat(billingLabelClassName)}>
|
||||
Last Name
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="last_name"
|
||||
control={control}
|
||||
rules={{ required: 'Last Name is required' }}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
{errors.last_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.last_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="address1" className={labelClassName.concat(billingLabelClassName)}>
|
||||
Address 1
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="address1"
|
||||
control={control}
|
||||
rules={{ required: 'Address line 1 is required' }}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
{errors.address1 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address1.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="address2" className={labelClassName.concat(billingLabelClassName)}>
|
||||
Address 2
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller name="address2" control={control} render={({ field }) => <Input {...field} />} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="city" className={labelClassName.concat(billingLabelClassName)}>
|
||||
City
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="city"
|
||||
control={control}
|
||||
rules={{ required: 'City is required' }}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
{errors.city && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.city.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="state" className={labelClassName.concat(billingLabelClassName)}>
|
||||
State or Region
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="state"
|
||||
control={control}
|
||||
rules={{ required: 'State is required' }}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
{errors.state && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.state.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="zip" className={labelClassName.concat(billingLabelClassName)}>
|
||||
Zip or Postal Code
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="zip"
|
||||
control={control}
|
||||
rules={{ required: 'Zip code is required' }}
|
||||
render={({ field }) => <Input {...field} />}
|
||||
/>
|
||||
{errors.zip && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.zip.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-right">
|
||||
<Label for="country" className={labelClassName.concat(billingLabelClassName)}>
|
||||
Country
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="country"
|
||||
control={control}
|
||||
rules={{ required: 'Country is required' }}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const country = countries.find(country => country.countrycode === value);
|
||||
if (!country) {
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
value={{ value: country.countrycode, label: country.countryname }}
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{errors.country && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.country.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Card className="mb-3">
|
||||
<FalconCardHeader title="Payment Method" titleTag="h5" />
|
||||
<CardBody>
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<>
|
||||
<Flex align="center" className="mb-2">
|
||||
<div className="fs-1">Reuse Existing Card</div>
|
||||
</Flex>
|
||||
<div>Use card ending with {billingInfo.last_four}</div>
|
||||
</>
|
||||
}
|
||||
id="existing-card"
|
||||
value="existing-card"
|
||||
checked={paymentMethod === 'existing-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
<Row form>
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<Flex align="center" className="mb-2 fs-1">
|
||||
Credit Card
|
||||
</Flex>
|
||||
}
|
||||
id="credit-card"
|
||||
value="credit-card"
|
||||
checked={paymentMethod === 'credit-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} className="pl-4">
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<Row form className="align-items-center">
|
||||
<Col>
|
||||
<FormGroup>
|
||||
<Controller
|
||||
name="number"
|
||||
control={control}
|
||||
rules={{
|
||||
required: 'Card number is required',
|
||||
pattern: {
|
||||
value: /^(\d{4} ){3}\d{4}$/i,
|
||||
message: 'Card number must be 16 digits'
|
||||
}
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<FalconInput
|
||||
{...field}
|
||||
label="Card Number"
|
||||
labelclassName={labelClassName.concat(billingLabelClassName)}
|
||||
className="input-spin-none"
|
||||
placeholder="•••• •••• •••• ••••"
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.number && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.number.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row form className="align-items-center">
|
||||
<Col xs={4}>
|
||||
<FormGroup>
|
||||
<Controller
|
||||
name="month"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FalconInput
|
||||
{...field}
|
||||
label="Exp Month"
|
||||
labelclassName={labelClassName.concat(billingLabelClassName)}
|
||||
placeholder="mm"
|
||||
maxLength="2"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FormGroup>
|
||||
<Controller
|
||||
name="year"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FalconInput
|
||||
{...field}
|
||||
label="Exp Year"
|
||||
labelclassName={labelClassName.concat(billingLabelClassName)}
|
||||
placeholder="yy"
|
||||
maxLength="2"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FormGroup>
|
||||
<Controller
|
||||
name="verification_value"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FalconInput
|
||||
{...field}
|
||||
label={
|
||||
<Fragment>
|
||||
CVV
|
||||
<span className="d-inline-block cursor-pointer text-primary" id="CVVTooltip">
|
||||
<FontAwesomeIcon icon="question-circle" className="mx-2" />
|
||||
</span>
|
||||
<UncontrolledTooltip placement="top" target="CVVTooltip">
|
||||
Card verification value
|
||||
</UncontrolledTooltip>
|
||||
</Fragment>
|
||||
}
|
||||
labelclassName={labelClassName.concat(billingLabelClassName)}
|
||||
className="input-spin-none"
|
||||
placeholder="123"
|
||||
maxLength="3"
|
||||
pattern="[0-9]{3}"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<div className="col-4 text-center pt-2 d-none d-sm-block">
|
||||
<div className="rounded p-2 mt-3 bg-100">
|
||||
<div className="text-uppercase fs--2 font-weight-bold">We Accept</div>
|
||||
<img src={iconPaymentMethodsGrid} alt="" width="120" />
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={<img className="pull-right" src={iconPaypalFull} height="20" alt="" />}
|
||||
id="paypal"
|
||||
value="paypal"
|
||||
checked={paymentMethod === 'paypal'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr className="border-dashed my-5" />
|
||||
<Row>
|
||||
<Col className="pl-lg-4 pl-xl-2 pl-xxl-5 text-center">
|
||||
<hr className="border-dashed d-block d-md-none d-xl-block d-xxl-none my-4" />
|
||||
<div className="fs-2 font-weight-semi-bold">
|
||||
All Total:{' '}
|
||||
<span className="text-primary">
|
||||
{currency}
|
||||
{payableTotal}
|
||||
</span>
|
||||
</div>
|
||||
<Button color="primary" className="mt-3 px-5" type="submit" disabled={!payableTotal}>
|
||||
Confirm & Pay
|
||||
</Button>
|
||||
<p className="fs--1 mt-3 mb-0">
|
||||
By clicking <strong>Confirm & Pay </strong>button you agree to JamKazam's{' '}
|
||||
<Link to="#!">Terms of service</Link>, including the <Link to="#!">JamTracks purchase terms</Link>.
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</form>
|
||||
</ContentWithAsideLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKCheckout;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { Button, Card, CardBody } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ButtonIcon from '../common/ButtonIcon';
|
||||
import ShoppingCartFooter from './shopping-cart/ShoppingCartFooter';
|
||||
import ShoppingCartTable from './shopping-cart/ShoppingCartTable';
|
||||
import { isIterableArray } from '../../helpers/utils';
|
||||
import classNames from 'classnames';
|
||||
import { useShoppingCart } from '../../hooks/useShoppingCart';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const JKShoppingCart = () => {
|
||||
const { shoppingCart, loading, removeCartItem } = useShoppingCart();
|
||||
|
||||
const handleRemoveItem = async id => {
|
||||
if (await removeCartItem(id)) {
|
||||
//show toast
|
||||
toast.success('Item removed from cart');
|
||||
}else{
|
||||
toast.error('Error removing item');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FalconCardHeader title={`Shopping Cart (${shoppingCart.length} Items)`} light={false} breakPoint="sm">
|
||||
<ButtonIcon
|
||||
icon="chevron-left"
|
||||
color={classNames({
|
||||
'outline-secondary': isIterableArray(shoppingCart),
|
||||
primary: !isIterableArray(shoppingCart)
|
||||
})}
|
||||
size="sm"
|
||||
className={classNames({ 'border-300': !isIterableArray(shoppingCart) })}
|
||||
tag={Link}
|
||||
to="/jamtracks"
|
||||
>
|
||||
Continue Shopping
|
||||
</ButtonIcon>
|
||||
{isIterableArray(shoppingCart) && (
|
||||
<Button tag={Link} color="primary" size="sm" to="/checkout" className="ml-2">
|
||||
Checkout
|
||||
</Button>
|
||||
)}
|
||||
</FalconCardHeader>
|
||||
<CardBody className="p-0">
|
||||
<ShoppingCartTable shoppingCart={shoppingCart} loading={loading} onRemoveItem={handleRemoveItem} />
|
||||
</CardBody>
|
||||
{isIterableArray(shoppingCart) && <ShoppingCartFooter />}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKShoppingCart;
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import React, { Fragment, useContext } from 'react';
|
||||
//import PropTypes from 'prop-types';
|
||||
import AppContext from '../../../context/Context';
|
||||
import { Alert, Card, CardBody, CardFooter, Media, Table } from 'reactstrap';
|
||||
import FalconCardHeader from '../../common/FalconCardHeader';
|
||||
import {isIterableArray } from '../../../helpers/utils';
|
||||
import Flex from '../../common/Flex';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useShoppingCart } from '../../../hooks/useShoppingCart';
|
||||
|
||||
const CheckoutAside = () => {
|
||||
const { currency } = useContext(AppContext);
|
||||
const { shoppingCart, cartTotal } = useShoppingCart();
|
||||
return (
|
||||
<Card>
|
||||
<FalconCardHeader title="Order Summary" titleTag="h5" light={false}>
|
||||
{/* <ButtonIcon
|
||||
color="link"
|
||||
size="sm"
|
||||
tag={Link}
|
||||
icon="pencil-alt"
|
||||
className="btn-reveal text-600"
|
||||
to="/e-commerce/shopping-cart"
|
||||
/> */}
|
||||
</FalconCardHeader>
|
||||
{isIterableArray(shoppingCart) ? (
|
||||
<Fragment>
|
||||
<CardBody className="pt-0">
|
||||
<Table borderless className="fs--1 mb-0">
|
||||
<tbody>
|
||||
{shoppingCart.map((shoppingCartItem) => {
|
||||
return (
|
||||
<tr className="border-bottom" key={shoppingCartItem.id}>
|
||||
<th className="pl-0">
|
||||
{ shoppingCartItem.product_info.sale_display }
|
||||
</th>
|
||||
<th className="pr-0 text-right">
|
||||
{currency}
|
||||
{shoppingCartItem.product_info.total_price }
|
||||
</th>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{/* <tr className="border-bottom">
|
||||
<th className="pl-0">Subtotal</th>
|
||||
<th className="pr-0 text-right">
|
||||
{currency}
|
||||
{subTotal}
|
||||
</th>
|
||||
</tr> */}
|
||||
|
||||
{/* <tr className="border-bottom">
|
||||
<th className="pl-0">Shipping</th>
|
||||
<th className="pr-0 text-right text-nowrap">
|
||||
+ {currency}
|
||||
{calculatedShippingCost}
|
||||
</th>
|
||||
</tr> */}
|
||||
<tr>
|
||||
<th className="pl-0 pb-0">Total</th>
|
||||
<th className="pr-0 text-right pb-0 text-nowrap">
|
||||
{currency}
|
||||
{cartTotal}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</Table>
|
||||
</CardBody>
|
||||
<CardFooter tag={Flex} justify="between" className="bg-100">
|
||||
<div className="font-weight-semi-bold">Payable Total</div>
|
||||
<div className="font-weight-bold">
|
||||
{currency}
|
||||
{cartTotal}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Fragment>
|
||||
) : (
|
||||
<CardBody className="p-0">
|
||||
<Alert color="warning" className="mb-0 rounded-0 overflow-hidden">
|
||||
<Media className="align-items-center">
|
||||
<FontAwesomeIcon icon={['far', 'dizzy']} className="fs-5" />
|
||||
<Media body className="ml-3">
|
||||
<p className="mb-0">You have no items in your shopping cart. Go ahead and start shopping!</p>
|
||||
</Media>
|
||||
</Media>
|
||||
</Alert>
|
||||
</CardBody>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// CheckoutAside.propTypes = {
|
||||
// };
|
||||
|
||||
export default CheckoutAside;
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
import React, { useState, useContext, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AppContext, { ProductContext } from '../../../context/Context';
|
||||
import { Button, Card, CardBody, Col, CustomInput, FormGroup, Media, Row, UncontrolledTooltip } from 'reactstrap';
|
||||
import FalconCardHeader from '../../common/FalconCardHeader';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import FalconInput from '../../common/FalconInput';
|
||||
import Flex from '../../common/Flex';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import iconPaymentMethodsGrid from '../../../assets/img/icons/icon-payment-methods-grid.png';
|
||||
import iconPaypalFull from '../../../assets/img/icons/icon-paypal-full.png';
|
||||
import shield from '../../../assets/img/icons/shield.png';
|
||||
|
||||
const CheckoutPaymentMethod = ({ payableTotal, paymentMethod, setPaymentMethod }) => {
|
||||
const { currency } = useContext(AppContext);
|
||||
const { shoppingCart, shoppingCartDispatch } = useContext(ProductContext);
|
||||
const [cardNumber, setCardNumber] = useState('');
|
||||
const [expDate, setExpDate] = useState('');
|
||||
const [cvv, setCvv] = useState('');
|
||||
|
||||
const handlePayment = () => {
|
||||
toast(
|
||||
<div className="text-700">
|
||||
<h5 className="text-success fs-0 mb-0">Payment success!</h5>
|
||||
<hr className="my-2" />
|
||||
Total:{' '}
|
||||
<strong>
|
||||
{currency}
|
||||
{payableTotal}
|
||||
</strong>
|
||||
<br />
|
||||
Payment method: <strong className="text-capitalize">{paymentMethod.split('-').join(' ')}</strong>
|
||||
</div>
|
||||
);
|
||||
shoppingCart.map(({ id }) => shoppingCartDispatch({ type: 'REMOVE', id }));
|
||||
};
|
||||
|
||||
const labelClassName = 'ls text-uppercase text-600 font-weight-semi-bold mb-0';
|
||||
return (
|
||||
<Card className="mb-3">
|
||||
<FalconCardHeader title="Payment Method" titleTag="h5" />
|
||||
<CardBody>
|
||||
<Row form>
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<Flex align="center" className="mb-2 fs-1">
|
||||
Credit Card
|
||||
</Flex>
|
||||
}
|
||||
id="credit-card"
|
||||
value="credit-card"
|
||||
checked={paymentMethod === 'credit-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} className="pl-4">
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<Row form className="align-items-center">
|
||||
<Col>
|
||||
<FormGroup>
|
||||
<FalconInput
|
||||
label="Card Number"
|
||||
labelClassName={labelClassName}
|
||||
className="input-spin-none"
|
||||
placeholder="•••• •••• •••• ••••"
|
||||
value={cardNumber}
|
||||
onChange={setCardNumber}
|
||||
type="number"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row form className="align-items-center">
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<FalconInput
|
||||
label="Exp Date"
|
||||
labelClassName={labelClassName}
|
||||
placeholder="mm/yyyy"
|
||||
value={expDate}
|
||||
onChange={setExpDate}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<FalconInput
|
||||
label={
|
||||
<Fragment>
|
||||
CVV
|
||||
<span className="d-inline-block cursor-pointer text-primary" id="CVVTooltip">
|
||||
<FontAwesomeIcon icon="question-circle" className="mx-2" />
|
||||
</span>
|
||||
<UncontrolledTooltip placement="top" target="CVVTooltip">
|
||||
Card verification value
|
||||
</UncontrolledTooltip>
|
||||
</Fragment>
|
||||
}
|
||||
labelClassName={labelClassName}
|
||||
className="input-spin-none"
|
||||
placeholder="123"
|
||||
maxLength="3"
|
||||
pattern="[0-9]{3}"
|
||||
value={cvv}
|
||||
onChange={setCvv}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<div className="col-4 text-center pt-2 d-none d-sm-block">
|
||||
<div className="rounded p-2 mt-3 bg-100">
|
||||
<div className="text-uppercase fs--2 font-weight-bold">We Accept</div>
|
||||
<img src={iconPaymentMethodsGrid} alt="" width="120" />
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={<img className="pull-right" src={iconPaypalFull} height="20" alt="" />}
|
||||
id="paypal"
|
||||
value="paypal"
|
||||
checked={paymentMethod === 'paypal'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr className="border-dashed my-5" />
|
||||
<Row>
|
||||
<Col md={7} xl={12} className="col-xxl-7 vertical-line px-md-3 mb-xxl-0">
|
||||
<Media>
|
||||
<img className="" src={shield} alt="" width="60" />
|
||||
<Media body className="ml-3">
|
||||
<h5 className="mb-2">Buyer Protection</h5>
|
||||
<CustomInput
|
||||
id="protection-option-1"
|
||||
label={
|
||||
<Fragment>
|
||||
<strong>Full Refund </strong>If you don't <br className="d-none d-md-block d-lg-none" />
|
||||
receive your order
|
||||
</Fragment>
|
||||
}
|
||||
type="checkbox"
|
||||
/>
|
||||
<CustomInput
|
||||
id="protection-option-2"
|
||||
label={
|
||||
<Fragment>
|
||||
<strong>Full or Partial Refund, </strong>If the product is not as described in details
|
||||
</Fragment>
|
||||
}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Link className="fs--1 ml-3 pl-2" to="#!">
|
||||
Learn More
|
||||
<FontAwesomeIcon icon="caret-right" transform="down-2" className="ml-1" />
|
||||
</Link>
|
||||
</Media>
|
||||
</Media>
|
||||
</Col>
|
||||
<Col
|
||||
md={5}
|
||||
xl={12}
|
||||
className="col-xxl-5 pl-lg-4 pl-xl-2 pl-xxl-5 text-center text-md-left text-xl-center text-xxl-left"
|
||||
>
|
||||
<hr className="border-dashed d-block d-md-none d-xl-block d-xxl-none my-4" />
|
||||
<div className="fs-2 font-weight-semi-bold">
|
||||
All Total:{' '}
|
||||
<span className="text-primary">
|
||||
{currency}
|
||||
{payableTotal}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
color="success"
|
||||
className="mt-3 px-5"
|
||||
type="submit"
|
||||
disabled={!payableTotal}
|
||||
onClick={handlePayment}
|
||||
>
|
||||
Confirm & Pay
|
||||
</Button>
|
||||
<p className="fs--1 mt-3 mb-0">
|
||||
By clicking <strong>Confirm & Pay </strong>button you agree to the{' '}
|
||||
<Link to="#!">Terms & Conditions</Link>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
CheckoutPaymentMethod.propTypes = {
|
||||
payableTotal: PropTypes.number.isRequired,
|
||||
paymentMethod: PropTypes.string.isRequired,
|
||||
setPaymentMethod: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default CheckoutPaymentMethod;
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Card, CardBody, Col, Input, Label, Row } from 'reactstrap';
|
||||
import FalconCardHeader from '../../common/FalconCardHeader';
|
||||
import ButtonIcon from '../../common/ButtonIcon';
|
||||
|
||||
const CheckoutShippingAddress = ({ shippingAddress, setShippingAddress }) => (
|
||||
<Card className="mb-3">
|
||||
<FalconCardHeader title="Your Shipping Address" titleTag="h5">
|
||||
<ButtonIcon icon="plus" color="falcon-default" size="sm" transform="shrink-2">
|
||||
Add New Address
|
||||
</ButtonIcon>
|
||||
</FalconCardHeader>
|
||||
<CardBody>
|
||||
<Row>
|
||||
<Col md={6} className="mb-3 mb-md-0">
|
||||
<div className="custom-control custom-radio radio-select">
|
||||
<Input
|
||||
className="custom-control-input"
|
||||
id="address-1"
|
||||
type="radio"
|
||||
value="address-1"
|
||||
checked={shippingAddress === 'address-1'}
|
||||
onChange={({ target }) => setShippingAddress(target.value)}
|
||||
/>
|
||||
<Label className="custom-control-label font-weight-bold d-block" htmlFor="address-1">
|
||||
Antony Hopkins
|
||||
<span className="radio-select-content">
|
||||
<span>
|
||||
{' '}
|
||||
2392 Main Avenue,
|
||||
<br />
|
||||
Pensaukee,
|
||||
<br />
|
||||
New Jersey 02139<span className="d-block mb-0 pt-2">+(856) 929-229</span>
|
||||
</span>
|
||||
</span>
|
||||
</Label>
|
||||
<small className="text-primary cursor-pointer">Edit</small>
|
||||
</div>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<div className="position-relative">
|
||||
<div className="custom-control custom-radio radio-select">
|
||||
<Input
|
||||
className="custom-control-input"
|
||||
id="address-2"
|
||||
type="radio"
|
||||
value="address-2"
|
||||
checked={shippingAddress === 'address-2'}
|
||||
onChange={({ target }) => setShippingAddress(target.value)}
|
||||
/>
|
||||
<Label className="custom-control-label font-weight-bold d-block" htmlFor="address-2">
|
||||
Robert Bruce
|
||||
<span className="radio-select-content">
|
||||
<span>
|
||||
3448 Ile De France St #242,
|
||||
<br />
|
||||
Fort Wainwright, <br />
|
||||
Alaska, 99703<span className="d-block mb-0 pt-2">+(901) 637-734</span>
|
||||
</span>
|
||||
</span>
|
||||
</Label>
|
||||
<small className="text-primary cursor-pointer">Edit</small>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
||||
CheckoutShippingAddress.propTypes = {
|
||||
shippingAddress: PropTypes.string.isRequired,
|
||||
setShippingAddress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default CheckoutShippingAddress;
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Col, Media, Modal, ModalBody, ModalFooter, ModalHeader, Row } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { calculateSale } from '../../../helpers/utils';
|
||||
import AppContext from '../../../context/Context';
|
||||
import classNames from 'classnames';
|
||||
import ButtonIcon from '../../common/ButtonIcon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const CartModal = ({ type, id, quantity, title, files, price, sale, modal, setModal }) => {
|
||||
const { currency } = useContext(AppContext);
|
||||
if (!id) return null;
|
||||
return (
|
||||
<Modal isOpen={modal} toggle={() => setModal(!modal)} size="lg">
|
||||
<ModalHeader toggle={() => setModal(!modal)} className="border-200">
|
||||
<Media className="align-items-center">
|
||||
<div
|
||||
className={classNames('icon-item shadow-none', {
|
||||
'bg-soft-danger': type === 'REMOVE',
|
||||
'bg-soft-success': type === 'ADD'
|
||||
})}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={classNames({
|
||||
exclamation: type === 'REMOVE',
|
||||
'cart-plus': type === 'ADD'
|
||||
})}
|
||||
className={classNames({
|
||||
'text-warning': type === 'REMOVE',
|
||||
'text-success': type === 'ADD'
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Media body className="ml-2">
|
||||
You just {(type === 'REMOVE' && 'removed') || (type === 'ADD' && 'added')} {quantity} item
|
||||
{quantity === 1 ? '' : 's'}
|
||||
</Media>
|
||||
</Media>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
className={classNames({
|
||||
'mb-1': type === 'REMOVE'
|
||||
})}
|
||||
>
|
||||
<Row noGutters className="align-items-center">
|
||||
<Col>
|
||||
<Media className="align-items-center">
|
||||
<Link to={`/e-commerce/product-details/${id}`}>
|
||||
<img
|
||||
className="rounded mr-3 d-none d-md-block"
|
||||
src={files[0]['src'] || files[0]['base64']}
|
||||
alt=""
|
||||
width="80"
|
||||
/>
|
||||
</Link>
|
||||
<Media body>
|
||||
<h5 className="fs-0">
|
||||
<Link className="text-900" to={`/e-commerce/product-details/${id}`}>
|
||||
{title}
|
||||
</Link>
|
||||
</h5>
|
||||
</Media>
|
||||
</Media>
|
||||
</Col>
|
||||
<Col sm="auto" className="pl-sm-3 d-none d-sm-block">
|
||||
{currency}
|
||||
{calculateSale(price, sale) * quantity}
|
||||
</Col>
|
||||
</Row>
|
||||
</ModalBody>
|
||||
{type !== 'REMOVE' && (
|
||||
<ModalFooter className="border-200">
|
||||
<Button color="secondary" size="sm" tag={Link} to="/e-commerce/checkout" onClick={() => setModal(!modal)}>
|
||||
Checkout
|
||||
</Button>
|
||||
|
||||
<ButtonIcon
|
||||
tag={Link}
|
||||
to="/e-commerce/shopping-cart"
|
||||
color="primary"
|
||||
size="sm"
|
||||
className="ml-2"
|
||||
icon="chevron-right"
|
||||
iconAlign="right"
|
||||
onClick={() => setModal(!modal)}
|
||||
>
|
||||
Go to Cart
|
||||
</ButtonIcon>
|
||||
</ModalFooter>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
CartModal.propTypes = { value: PropTypes.any };
|
||||
|
||||
CartModal.defaultProps = { value: `CartModal` };
|
||||
|
||||
export default CartModal;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import Flex from '../../common/Flex';
|
||||
import { Button, CardFooter } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const ShoppingCartFooter = () => {
|
||||
|
||||
return (
|
||||
<CardFooter tag={Flex} justify="end" className="bg-light">
|
||||
<Button tag={Link} color="primary" size="sm" to="/checkout" className="ml-2">
|
||||
Checkout
|
||||
</Button>
|
||||
</CardFooter>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShoppingCartFooter;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AppContext from '../../../context/Context';
|
||||
import { Col, Media, Row } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const ShoppingCartItem = ({ shoppingCartItem, onRemoveItem }) => {
|
||||
const { currency } = useContext(AppContext);
|
||||
const { id } = shoppingCartItem;
|
||||
|
||||
const handleRemoveClick = async () => {
|
||||
onRemoveItem(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row noGutters className="align-items-center px-1 border-bottom border-200">
|
||||
<Col xs={8} className="py-3 px-2 px-md-3">
|
||||
<Media className="align-items-center">
|
||||
<Media body>
|
||||
<h5 className="fs-0">
|
||||
<Link className="text-900" to={`/jamtracks`}>
|
||||
{ shoppingCartItem.product_info.sale_display }
|
||||
</Link>
|
||||
</h5>
|
||||
<div
|
||||
className="fs--2 fs-md--1 text-danger cursor-pointer"
|
||||
onClick={handleRemoveClick}
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</Media>
|
||||
</Media>
|
||||
</Col>
|
||||
<Col xs={4} className="p-3">
|
||||
<Row className="align-items-center">
|
||||
<Col md={8} className="d-flex justify-content-end justify-content-md-center px-2 px-md-3 order-1 order-md-0">
|
||||
<div>
|
||||
{ shoppingCartItem.quantity }
|
||||
</div>
|
||||
</Col>
|
||||
<Col md={4} className="text-right pl-0 pr-2 pr-md-3 order-0 order-md-1 mb-2 mb-md-0 text-600">
|
||||
{/* {currency}
|
||||
{calculateSale(price, sale) * quantity} */}
|
||||
{currency}
|
||||
{ shoppingCartItem.product_info.total_price }
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
ShoppingCartItem.propTypes = {
|
||||
shoppingCartItem: PropTypes.object.isRequired,
|
||||
onRemoveItem: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ShoppingCartItem;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import React, { Fragment, useContext } from 'react';
|
||||
import { isIterableArray } from '../../../helpers/utils';
|
||||
import { Col, Row } from 'reactstrap';
|
||||
import ShoppingCartItem from './ShoppingCartItem';
|
||||
import AppContext from '../../../context/Context';
|
||||
|
||||
const ShoppingCartTable = ({ shoppingCart, loading, onRemoveItem }) => {
|
||||
const { currency } = useContext(AppContext);
|
||||
const totalCartPrice = shoppingCart.reduce((acc, item) => acc + parseFloat(item.product_info.total_price), 0);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{loading ? (
|
||||
<Row noGutters className="bg-200 text-900 px-1 fs--1 font-weight-semi-bold">
|
||||
<Col xs={9} md={8} className="p-2 px-md-3">
|
||||
Loading...
|
||||
</Col>
|
||||
</Row>
|
||||
) : isIterableArray(shoppingCart) ? (
|
||||
<Fragment>
|
||||
<Row noGutters className="bg-200 text-900 px-1 fs--1 font-weight-semi-bold">
|
||||
<Col xs={9} md={8} className="p-2 px-md-3">
|
||||
Name
|
||||
</Col>
|
||||
<Col xs={3} md={4} className="px-3">
|
||||
<Row>
|
||||
<Col md={8} className="py-2 d-none d-md-block text-center">
|
||||
Quantity
|
||||
</Col>
|
||||
<Col md={4} className="text-right p-2 px-md-3">
|
||||
Price
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{shoppingCart.map(shoppingCartItem => (
|
||||
<ShoppingCartItem shoppingCartItem={shoppingCartItem} key={shoppingCartItem.id} onRemoveItem={onRemoveItem} />
|
||||
))}
|
||||
<Row noGutters className="font-weight-bold px-1">
|
||||
<Col xs={9} md={8} className="py-2 px-md-3 text-right text-900">
|
||||
Total
|
||||
</Col>
|
||||
<Col className="px-3">
|
||||
<Row>
|
||||
<Col md={8} className="py-2 d-none d-md-block text-center">
|
||||
{shoppingCart.length} (items)
|
||||
</Col>
|
||||
<Col className="col-12 col-md-4 text-right py-2 pr-md-3 pl-0">
|
||||
{currency}
|
||||
{totalCartPrice}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Fragment>
|
||||
) : (
|
||||
<p className="p-card mb-0 bg-light">You have no items in your shopping cart. Go ahead and start shopping!</p>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShoppingCartTable;
|
||||
|
|
@ -5,7 +5,7 @@ const AppContext = createContext(settings);
|
|||
|
||||
export const EmailContext = createContext({ emails: [] });
|
||||
|
||||
export const ProductContext = createContext({ products: [] });
|
||||
export const ProductContext = createContext({ products: [], shoppingCart: [] });
|
||||
|
||||
export const FeedContext = createContext({ feeds: [] });
|
||||
|
||||
|
|
|
|||
|
|
@ -408,11 +408,38 @@ export const getJamTracks = (options = {}) => {
|
|||
|
||||
export const addJamtrackToShoppingCart = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/shopping_carts/add_jamtrack?`, {
|
||||
apiFetch(`/shopping_carts/add_jamtrack`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
export const getShoppingCart = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/shopping_carts`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
export const removeShoppingCart = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/shopping_carts`, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
export const getBillingInfo = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/billing_info`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { getShoppingCart, addJamtrackToShoppingCart, removeShoppingCart } from "../helpers/rest"
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
|
||||
export const useShoppingCart = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [shoppingCart, setShoppingCart] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
getCartItems();
|
||||
}, []);
|
||||
|
||||
const cartTotal = useMemo(() => {
|
||||
//calculate total price
|
||||
const totalPrice = shoppingCart.reduce((acc, item) => acc + parseFloat(item.product_info.total_price), 0.00);
|
||||
return totalPrice;
|
||||
}, [shoppingCart]);
|
||||
|
||||
const getCartItems = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const resp = await getShoppingCart();
|
||||
const data = await resp.json();
|
||||
setShoppingCart(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
setError(error);
|
||||
}finally{
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const addCartItem = async (options) => {
|
||||
try {
|
||||
const resp = await addJamtrackToShoppingCart(options);
|
||||
const data = await resp.json();
|
||||
setShoppingCart([...shoppingCart, data]);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const removeCartItem = async (id) => {
|
||||
try {
|
||||
await removeShoppingCart({id});
|
||||
setShoppingCart(shoppingCart.filter(item => item.id !== id));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return{
|
||||
shoppingCart, error, loading, removeCartItem, addCartItem, cartTotal
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue