Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop
This commit is contained in:
commit
9b6f6f74e9
|
|
@ -21,7 +21,7 @@ describe('Affiliate Earnings', () => {
|
|||
}).as('fetchAllEarnings');
|
||||
cy.visit('/affiliate/earnings');
|
||||
cy.wait('@fetchAllEarnings');
|
||||
cy.contains('No Records!');
|
||||
cy.contains('There is no earnings data yet ');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ describe('Affiliate Earnings', () => {
|
|||
cy.wait('@fetchAllEarnings');
|
||||
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
|
||||
.eq(0)
|
||||
.should('have.text', 'September - 2021');
|
||||
.should('have.text', 'September 2021');
|
||||
cy.get('[data-testid=affiliateEarningsList] tbody tr:first-child td')
|
||||
.eq(1)
|
||||
.should('have.text', 'Silver - 2');
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe('affiliate signups', () => {
|
|||
}).as('fetchAllSignups');
|
||||
cy.visit('/affiliate/signups');
|
||||
cy.wait('@fetchAllSignups');
|
||||
cy.contains('No Records!');
|
||||
cy.contains('There is no signup data yet ');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
///<reference types="cypress" />
|
||||
|
||||
import makeFakeUser from '../../factories/user';
|
||||
|
||||
describe('forgot password', () => {
|
||||
beforeEach(() => {
|
||||
const currentUser = makeFakeUser({
|
||||
email: 'sam@example.com'
|
||||
});
|
||||
cy.clearCookie('remeber_token');
|
||||
});
|
||||
|
||||
it('redirects to forgot password page', () => {
|
||||
cy.visit('/');
|
||||
cy.url().should('include', '/authentication/basic/login');
|
||||
cy.get('a')
|
||||
.contains('Forgot password?')
|
||||
.click();
|
||||
cy.url().should('include', '/authentication/basic/forget-password');
|
||||
cy.get('h5').contains('Forgot Your Password');
|
||||
});
|
||||
|
||||
describe('validate forgot password form', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/authentication/basic/forget-password');
|
||||
cy.get('[data-testid=email]').clear();
|
||||
cy.get('[data-testid=submit]').should('be.disabled');
|
||||
});
|
||||
|
||||
//invalid email format
|
||||
it('invalid email format', () => {
|
||||
cy.get('[data-testid=email]').type('invalid-email-format@example');
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.url().should('not.include', /\/authentication\/basic\/confirm-mail?\S+/);
|
||||
});
|
||||
|
||||
//valid email format but non-existing
|
||||
it('valid email format but non-existing', () => {
|
||||
cy.get('[data-testid=email]').type('valid-email-format@example.com');
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.url().should('not.include', /\/authentication\/basic\/confirm-mail?\S+/);
|
||||
});
|
||||
|
||||
//valid and existing email
|
||||
it('valid and existing email', () => {
|
||||
cy.get('[data-testid=email]').type('nuwan@jamkazam.com');
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.wait(3000);
|
||||
cy.contains('Please check your email!');
|
||||
cy.url().should('match', /\S+authentication\/basic\/confirm-mail?\S+/);
|
||||
cy.contains('An email has been sent to nuwan@jamkazam.com.')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -30,9 +30,9 @@ function submitLogin(){
|
|||
describe('Unauthenticated users redirect to login page', () => {
|
||||
it('redirects to login page', () => {
|
||||
cy.clearCookie('remeber_token')
|
||||
cy.visit('/friends')
|
||||
cy.visit('/')
|
||||
cy.url().should('include', '/authentication/basic/login')
|
||||
cy.contains('Sign in')
|
||||
cy.contains('Sign In')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -81,4 +81,13 @@ describe('Login page', () => {
|
|||
|
||||
})
|
||||
|
||||
describe('Forget password page', () => {
|
||||
it('submit forget password form', () => {
|
||||
cy.visit('/authentication/basic/forget-password')
|
||||
cy.get('[data-testid=email]').type('peter@example.com')
|
||||
cy.get('[data-testid=submit]').click()
|
||||
cy.contains('An email is sent')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -10,3 +10,4 @@
|
|||
@import './custom/form';
|
||||
@import './custom/chips';
|
||||
@import './custom/common';
|
||||
@import './custom/partner_agreement_v1';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,298 @@
|
|||
#partner-agreement-v1 {
|
||||
|
||||
|
||||
* {
|
||||
color:black! important;
|
||||
}
|
||||
|
||||
.c1, .c20 {
|
||||
border-bottom-color: #CCCCCC ! important;
|
||||
border-top-color: #CCCCCC ! important;
|
||||
border-left-color: #CCCCCC ! important;
|
||||
border-right-color: #CCCCCC ! important;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
color: #CCCCCC ! important;
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
li {
|
||||
color: #000000;
|
||||
font-size: 10pt;
|
||||
font-family: "Times New Roman";
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
border-bottom-width: 1pt;
|
||||
border-top-style: solid;
|
||||
width: 193.2pt;
|
||||
border-right-style: solid;
|
||||
padding: 0pt 5.8pt 0pt 5.8pt;
|
||||
border-bottom-color: #000000;
|
||||
border-top-width: 1pt;
|
||||
border-bottom-style: solid;
|
||||
vertical-align: top;
|
||||
border-top-color: #000000;
|
||||
border-left-color: #000000;
|
||||
border-right-color: #000000;
|
||||
border-left-style: solid;
|
||||
border-right-width: 1pt;
|
||||
border-left-width: 1pt
|
||||
}
|
||||
|
||||
.c20 {
|
||||
border-bottom-width: 1pt;
|
||||
border-top-style: solid;
|
||||
width: 238.2pt;
|
||||
border-right-style: solid;
|
||||
padding: 0pt 5.8pt 0pt 5.8pt;
|
||||
border-bottom-color: #000000;
|
||||
border-top-width: 1pt;
|
||||
border-bottom-style: solid;
|
||||
vertical-align: top;
|
||||
border-top-color: #000000;
|
||||
border-left-color: #000000;
|
||||
border-right-color: #000000;
|
||||
border-left-style: solid;
|
||||
border-right-width: 1pt;
|
||||
border-left-width: 1pt
|
||||
}
|
||||
|
||||
.c3 {
|
||||
padding-left: 0pt;
|
||||
line-height: 1.0;
|
||||
padding-top: 0pt;
|
||||
margin-left: 36pt;
|
||||
padding-bottom: 0pt
|
||||
}
|
||||
|
||||
.c12 {
|
||||
vertical-align: baseline;
|
||||
color: #000000;
|
||||
font-style: normal;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.c4 {
|
||||
line-height: 1.0;
|
||||
padding-top: 0pt;
|
||||
text-align: left;
|
||||
padding-bottom: 0pt
|
||||
}
|
||||
|
||||
.c11 {
|
||||
max-width: 432pt;
|
||||
background-color: #ffffff;
|
||||
padding: 72pt 90pt 72pt 90pt
|
||||
}
|
||||
|
||||
.c2 {
|
||||
widows: 2;
|
||||
orphans: 2;
|
||||
direction: ltr
|
||||
}
|
||||
|
||||
.c16 {
|
||||
margin-right: auto;
|
||||
border-collapse: collapse;
|
||||
margin-left: -5.8pt
|
||||
}
|
||||
|
||||
.c9 {
|
||||
line-height: 1.0;
|
||||
padding-top: 36pt;
|
||||
padding-bottom: 0pt
|
||||
}
|
||||
|
||||
.c21 {
|
||||
line-height: 1.0;
|
||||
padding-top: 0pt;
|
||||
padding-bottom: 36pt
|
||||
}
|
||||
|
||||
.c15 {
|
||||
font-size: 14pt;
|
||||
font-family: "Calibri"
|
||||
}
|
||||
|
||||
.c18 {
|
||||
font-size: 12pt;
|
||||
font-family: "Times New Roman"
|
||||
}
|
||||
|
||||
.c6 {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 12pt;
|
||||
font-family: "Calibri"
|
||||
}
|
||||
|
||||
.c22 {
|
||||
font-size: 10pt;
|
||||
font-family: "Times New Roman"
|
||||
}
|
||||
|
||||
.c13 {
|
||||
color: #000000;
|
||||
background-color: #ffff00
|
||||
}
|
||||
|
||||
.c14 {
|
||||
font-size: 9pt;
|
||||
font-family: "Times New Roman"
|
||||
}
|
||||
|
||||
.c10 {
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.c17 {
|
||||
color: #ff0000
|
||||
}
|
||||
|
||||
.c7 {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.c19 {
|
||||
height: 0pt
|
||||
}
|
||||
|
||||
.c8 {
|
||||
font-weight: normal
|
||||
}
|
||||
|
||||
.c5 {
|
||||
height: 10pt
|
||||
}
|
||||
|
||||
.title {
|
||||
widows: 2;
|
||||
padding-top: 0pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
font-size: 14pt;
|
||||
font-family: "Times New Roman";
|
||||
font-weight: bold;
|
||||
padding-bottom: 0pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
widows: 2;
|
||||
padding-top: 18pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: left;
|
||||
color: #666666;
|
||||
font-style: italic;
|
||||
font-size: 24pt;
|
||||
font-family: "Georgia";
|
||||
padding-bottom: 4pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
|
||||
|
||||
p {
|
||||
color: #000000;
|
||||
font-size: 10pt;
|
||||
margin: 0;
|
||||
font-family: "Times New Roman"
|
||||
}
|
||||
|
||||
h1 {
|
||||
widows: 2;
|
||||
padding-top: 0pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
font-size: 12pt;
|
||||
font-family: "Times New Roman";
|
||||
font-weight: bold;
|
||||
padding-bottom: 0pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
h2 {
|
||||
widows: 2;
|
||||
padding-top: 0pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
font-size: 10pt;
|
||||
font-family: "Times New Roman";
|
||||
font-weight: bold;
|
||||
padding-bottom: 0pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
h3 {
|
||||
widows: 2;
|
||||
padding-top: 0pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
font-size: 10pt;
|
||||
text-decoration: underline;
|
||||
font-family: "Times New Roman";
|
||||
padding-bottom: 0pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
h4 {
|
||||
widows: 2;
|
||||
padding-top: 12pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
font-size: 12pt;
|
||||
font-family: "Times New Roman";
|
||||
font-weight: bold;
|
||||
padding-bottom: 2pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
h5 {
|
||||
widows: 2;
|
||||
padding-top: 11pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
font-size: 11pt;
|
||||
font-family: "Times New Roman";
|
||||
font-weight: bold;
|
||||
padding-bottom: 2pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
|
||||
h6 {
|
||||
widows: 2;
|
||||
padding-top: 10pt;
|
||||
line-height: 1.0;
|
||||
orphans: 2;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
font-size: 10pt;
|
||||
font-family: "Times New Roman";
|
||||
font-weight: bold;
|
||||
padding-bottom: 2pt;
|
||||
page-break-after: avoid
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@
|
|||
|
||||
.jamtrack-track {
|
||||
position: relative;
|
||||
background: #ddd;
|
||||
// background: #ddd;
|
||||
}
|
||||
|
||||
.jamtrack-track.jamtrack-track-0 {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const AgreementText = () => {
|
|||
<>
|
||||
<div id="partner-agreement-v1">
|
||||
<p className="c2">
|
||||
<span className="c0">Updated: February 9, 2021.</span>
|
||||
<span className="c0">Updated: November 18, 2024.</span>
|
||||
</p>
|
||||
|
||||
<p className="c2 c5">
|
||||
|
|
@ -37,7 +37,7 @@ const AgreementText = () => {
|
|||
<span className="c0">” means a website. “</span>
|
||||
<span className="c0 c10">JamKazam Site</span>
|
||||
<span className="c0">
|
||||
” means the jamkazam.com website or a JamKazam applicaion or any other site that is owned or operated
|
||||
” means the jamkazam.com website or a JamKazam application or any other site that is owned or operated
|
||||
by or on behalf of us and which is identified as participating in the Program in the{' '}
|
||||
</span>
|
||||
<span className="c0 c10 c17">Program Advertising Fee Schedule</span>
|
||||
|
|
@ -68,7 +68,7 @@ const AgreementText = () => {
|
|||
“
|
||||
</span>
|
||||
<span className="c0 c10">Product</span>
|
||||
<span className="c0">” a product or service sold on the JamKazam Site and listed in the </span>
|
||||
<span className="c0">” is a product or service sold on the JamKazam Site and listed in the </span>
|
||||
<span className="c0 c10 c17">Program Advertising Fee Schedule</span>
|
||||
<span className="c0">
|
||||
in Section 10. In order to facilitate your advertisement of Products, we may make available to you
|
||||
|
|
@ -533,8 +533,7 @@ const AgreementText = () => {
|
|||
<li className="c3 c2">
|
||||
<div className="c0 c8">Product: JamTracks</div>
|
||||
<div className="c2">
|
||||
JamKazam will pay US$0.25 per JamTrack sold as a Qualifying Purchase by Qualifying Customers of these
|
||||
Products.
|
||||
JamKazam will pay advertising fees of 30% of the net revenues from Qualifying Purchases by Qualifying Customers of these Products.
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -979,7 +978,7 @@ const AgreementText = () => {
|
|||
<div>
|
||||
<p className="c2 c21">
|
||||
<span className="c8 c14">JamKazam Confidential </span>
|
||||
<span className="c14 c8"> 02/09/2021</span>
|
||||
<span className="c14 c8"> 11/18/2024</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ const JKAffiliateEarning = ({ payment }) => {
|
|||
{greaterThan.sm ? (
|
||||
<tr>
|
||||
<td>
|
||||
{monthName(payment.month - 1)} - {payment.year}
|
||||
{monthName(payment.month - 1)} {payment.year}
|
||||
</td>
|
||||
<td>
|
||||
<td className='text-center'>
|
||||
{payment.subscriptions.map((subscription, index) => (
|
||||
<div key={index}>
|
||||
{getDisplayName(subscription.plan)} - {subscription.count}
|
||||
</div>
|
||||
))}
|
||||
</td>
|
||||
<td>{payment.jamtracks_sold}</td>
|
||||
<td>${(payment.due_amount_in_cents / 100).toFixed(2)}</td>
|
||||
<td className='text-center'>{payment.jamtracks_sold}</td>
|
||||
<td className='text-center'>${(payment.due_amount_in_cents / 100).toFixed(2)}</td>
|
||||
</tr>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { Card, CardBody, Row, Col, Alert } from 'reactstrap';
|
||||
import { Card, CardBody, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getAffiliatePayments } from '../../helpers/rest';
|
||||
|
|
@ -8,8 +8,8 @@ import { isIterableArray } from '../../helpers/utils';
|
|||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import JKAffiliateEarningsList from './JKAffiliateEarningsList';
|
||||
import JKAffiliateEarningsSwiper from './JKAffiliateEarningsSwiper';
|
||||
|
||||
import Loader from '../common/Loader';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const JKAffiliateEarnings = () => {
|
||||
const { t } = useTranslation('affiliate');
|
||||
|
|
@ -57,9 +57,11 @@ const JKAffiliateEarnings = () => {
|
|||
) : (
|
||||
<Row className="p-card">
|
||||
<Col>
|
||||
<Alert color="info" className="mb-0">
|
||||
{t('no_records', { ns: 'common' })}
|
||||
</Alert>
|
||||
{/* <Alert color="info" className="mb-0"> */}
|
||||
{t('earnings.no_earnings_data')}
|
||||
<Link to="/affiliate/links">{t('earnings.share_affiliate_links')}</Link>
|
||||
{t('earnings.start_to_earn')}
|
||||
{/* </Alert> */}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ const JKAffiliateEarningsList = ({ payments }) => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>{t('earnings.list.header.date')}</th>
|
||||
<th>{t('earnings.list.header.subscriptions')}</th>
|
||||
<th>{t('earnings.list.header.jamtracks')}</th>
|
||||
<th>{t('earnings.list.header.earnings')}</th>
|
||||
<th className='text-center'>{t('earnings.list.header.subscriptions')}</th>
|
||||
<th className='text-center'>{t('earnings.list.header.jamtracks')}</th>
|
||||
<th className='text-center'>{t('earnings.list.header.earnings')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Card, CardBody } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import JKJamTracksAutoComplete from '../jamtracks/JKJamTracksAutoComplete';
|
||||
import { getJamTracks, getAffiliatePartnerData, autocompleteJamTracks } from '../../helpers/rest';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
|
||||
const JKAffiliateLinks = () => {
|
||||
|
|
@ -25,20 +25,19 @@ const JKAffiliateLinks = () => {
|
|||
}, []);
|
||||
|
||||
const fetchAffiliate = async () => {
|
||||
try{
|
||||
try {
|
||||
const response = await getAffiliatePartnerData(currentUser.id);
|
||||
const affiliate = await response.json();
|
||||
if (affiliate) {
|
||||
setAffiliate(affiliate);
|
||||
setAffiliatePartnerId(affiliate.account.partner_id);
|
||||
setAffiliatePartnerId(affiliate.account.id);
|
||||
}
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchJamTracks = async options => {
|
||||
|
||||
try {
|
||||
const resp = await getJamTracks(options);
|
||||
const data = await resp.json();
|
||||
|
|
@ -70,26 +69,14 @@ const JKAffiliateLinks = () => {
|
|||
return options;
|
||||
};
|
||||
|
||||
const homePageLink = `https://www.jamkazam.com?affiliate=${affiliatePartnerId}`;
|
||||
const handleClickOnHomePageLink = () => {
|
||||
if(affiliate){
|
||||
window.open(homePageLink, '_blank');
|
||||
}else{
|
||||
history.push('/affiliate/agreement');
|
||||
}
|
||||
}
|
||||
const homePageLink = useMemo(() => `${process.env.REACT_APP_CLIENT_BASE_URL}?affiliate=${affiliatePartnerId}`, [affiliatePartnerId]);
|
||||
|
||||
const jamTrackLink = jamTrack => {
|
||||
return `https://jamkazam.com/jamtrack/landing/${jamTrack.plan_code}?affiliate=${affiliatePartnerId}`;
|
||||
}
|
||||
|
||||
const handleOnClickJamTrackLink = () => {
|
||||
if(affiliate){
|
||||
window.open(jamTrackLink, '_blank');
|
||||
}else{
|
||||
history.push('/affiliate/agreement');
|
||||
const jamTrackLink = useMemo(() => jamTrack => {
|
||||
if (!jamTrack) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return `${process.env.REACT_APP_CLIENT_BASE_URL}/jamtrack/landing/${jamTrack.plan_code}?affiliate=${affiliatePartnerId}`;
|
||||
}, [affiliatePartnerId]);
|
||||
|
||||
//autocomplete related code
|
||||
const handleOnSelect = selected => {
|
||||
|
|
@ -102,32 +89,40 @@ const JKAffiliateLinks = () => {
|
|||
console.log('onEnter', queryStr);
|
||||
const params = queryOptions(queryStr);
|
||||
fetchJamTracks(params);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card style={{ width: greaterThan.sm ? '75%' : '100%'}} className="mx-auto affiliate-links">
|
||||
<Card style={{ width: greaterThan.sm ? '75%' : '100%' }} className="mx-auto affiliate-links">
|
||||
<FalconCardHeader title={t('links.page_title')} titleClass="font-weight-semi-bold" />
|
||||
<CardBody className="pt-3 pb-5">
|
||||
<p>{t('links.paragraph1')}</p>
|
||||
<div className="affiliate-links__subtitle">{t('links.home_page_subtitle')}</div>
|
||||
<p>
|
||||
{t('links.home_page_paragraph')}:{' '}
|
||||
<a href="#" onClick={handleClickOnHomePageLink}>{homePageLink}</a>
|
||||
{affiliate ? (
|
||||
<a href={homePageLink} target="_blank">
|
||||
{homePageLink}
|
||||
</a>
|
||||
) : (
|
||||
<a href="javascript:void(0);" onClick={() => history.push('/affiliate/agreement')}>
|
||||
{homePageLink}
|
||||
</a>
|
||||
)}
|
||||
</p>
|
||||
<div className="affiliate-links__subtitle">{t('links.jamtracks_pages_subtitle')}</div>
|
||||
<p>{t('links.jamtracks_pages_paragraph')}</p>
|
||||
<div className='mt-4'>
|
||||
<div className="mt-4">
|
||||
<JKJamTracksAutoComplete
|
||||
fetchFunc={autocompleteJamTracks}
|
||||
onSelect={handleOnSelect}
|
||||
onEnter={handleOnEnter}
|
||||
showDropdown={showDropdown}
|
||||
setShowDropdown={setShowDropdown}
|
||||
inputValue={autoCompleteInputValue}
|
||||
setInputValue={setAutoCompleteInputValue}
|
||||
/>
|
||||
fetchFunc={autocompleteJamTracks}
|
||||
onSelect={handleOnSelect}
|
||||
onEnter={handleOnEnter}
|
||||
showDropdown={showDropdown}
|
||||
setShowDropdown={setShowDropdown}
|
||||
inputValue={autoCompleteInputValue}
|
||||
setInputValue={setAutoCompleteInputValue}
|
||||
/>
|
||||
</div>
|
||||
{jamTracks &&
|
||||
{jamTracks &&
|
||||
jamTracks.map(jamTrack => {
|
||||
return (
|
||||
<div key={jamTrack.id}>
|
||||
|
|
@ -135,9 +130,15 @@ const JKAffiliateLinks = () => {
|
|||
{t('links.affiliate_link_for')} {jamTrack.name} {t('links.by')} {jamTrack.original_artist}:
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" onClick={handleOnClickJamTrackLink}>
|
||||
{jamTrackLink(jamTrack)}
|
||||
</a>
|
||||
{affiliate ? (
|
||||
<a href={jamTrackLink(jamTrack)} target="_blank">
|
||||
{jamTrackLink(jamTrack)}
|
||||
</a>
|
||||
) : (
|
||||
<a href="javascript:void(0);" onClick={() => history.push('/affiliate/agreement')}>
|
||||
{jamTrackLink(jamTrack)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Card, CardBody } from 'reactstrap';
|
|||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const JKAffiliateProgram = () => {
|
||||
const { t } = useTranslation('affiliate');
|
||||
|
|
@ -17,9 +18,9 @@ const JKAffiliateProgram = () => {
|
|||
<p>{t('program.paragraph3')}</p>
|
||||
<p>
|
||||
{t('program.paragraph4-1')}
|
||||
<a href=""><strong>{t('program.click-here')}</strong></a>
|
||||
<Link to="/affiliate/agreement"><strong>{t('program.click-here')}</strong></Link>
|
||||
{t('program.paragraph4-2')}
|
||||
<a href=""><strong>{t('program.click-here')}</strong></a>
|
||||
<Link to="/affiliate/links"><strong>{t('program.click-here')}</strong></Link>
|
||||
{t('program.paragraph4-3')}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ const JKAffiliateSignup = ({signup}) => {
|
|||
{greaterThan.sm ? (
|
||||
<tr>
|
||||
<td>{signup.month}</td>
|
||||
<td>{signup.visits}</td>
|
||||
<td>{signup.signups}</td>
|
||||
<td className="text-center">{signup.visits}</td>
|
||||
<td className="text-center">{signup.signups}</td>
|
||||
</tr>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { Card, CardBody, Row, Col, Alert } from 'reactstrap';
|
||||
import { Card, CardBody, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getAffiliateSignups } from '../../helpers/rest';
|
||||
|
|
@ -9,6 +9,7 @@ import Loader from '../common/Loader';
|
|||
import JKAffiliateSignupsList from './JKAffiliateSignupsList';
|
||||
import JKAffiliateSignupsSwiper from './JKAffiliateSignupsSwiper';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import { Link } from 'react-router-dom/cjs/react-router-dom.min';
|
||||
|
||||
const JKAffiliateSignups = () => {
|
||||
const { t } = useTranslation('affiliate');
|
||||
|
|
@ -126,9 +127,11 @@ const JKAffiliateSignups = () => {
|
|||
) : (
|
||||
<Row className="p-card">
|
||||
<Col>
|
||||
<Alert color="info" className="mb-0">
|
||||
{t('no_records', { ns: 'common' })}
|
||||
</Alert>
|
||||
{/* <Alert color="info" className="mb-0"> */}
|
||||
{t('signups.no_signup_data')}
|
||||
<Link to="/affiliate/links">{t('signups.share_affiliate_links')}</Link>
|
||||
{t('signups.with_your_audience')}
|
||||
{/* </Alert> */}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const JKAffiliateSignupsList = ({ signups, nextPage, loading, stepForward }) =>
|
|||
<th width="35%" scope="col">
|
||||
{t('signups.list.header.date')}
|
||||
</th>
|
||||
<th width="35%" scope="col">
|
||||
<th width="35%" scope="col" className="text-center">
|
||||
{t('signups.list.header.visits')}
|
||||
</th>
|
||||
<th scope="col" className="text-center">
|
||||
|
|
|
|||
|
|
@ -4,23 +4,32 @@ import { Button } from 'reactstrap';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import envelope from '../../assets/img/illustrations/envelope.png';
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ConfirmMailContent = ({ email, layout, titleTag: TitleTag }) => (
|
||||
<Fragment>
|
||||
<img className="d-block mx-auto mb-4" src={envelope} alt="sent" width={70} />
|
||||
<TitleTag>Please check your email!</TitleTag>
|
||||
<p>
|
||||
An email has been sent to <strong>{email}</strong>. Please click on the included link to reset your password.
|
||||
</p>
|
||||
<Button tag={Link} color="primary" size="sm" className="mt-3" to={`/authentication/${layout}/login`}>
|
||||
<FontAwesomeIcon icon="chevron-left" transform="shrink-4 down-1" className="mr-1" />
|
||||
Return to login
|
||||
</Button>
|
||||
</Fragment>
|
||||
);
|
||||
const ConfirmMailContent = ({ layout, titleTag: TitleTag }) => {
|
||||
const queryString = useBrowserQuery();
|
||||
const { t } = useTranslation('auth');
|
||||
|
||||
const toWording = queryString && queryString.get('email') ? queryString.get('email') : t('confirmEmailContent.toYou');
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<img className="d-block mx-auto mb-4" src={envelope} alt="sent" width={70} />
|
||||
<TitleTag>{t('confirmEmailContent.title')}</TitleTag>
|
||||
<p>{t('confirmEmailContent.description_1')} {toWording}. {t('confirmEmailContent.description_2')}</p>
|
||||
<p>
|
||||
{t('confirmEmailContent.description_3')}
|
||||
</p>
|
||||
<Button tag={Link} color="primary" size="sm" className="mt-3" to={`/authentication/${layout}/login`}>
|
||||
<FontAwesomeIcon icon="chevron-left" transform="shrink-4 down-1" className="mr-1" />
|
||||
{t('confirmEmailContent.returnToLogin')}
|
||||
</Button>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmMailContent.propTypes = {
|
||||
email: PropTypes.string.isRequired,
|
||||
layout: PropTypes.string,
|
||||
titleTag: PropTypes.string
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,44 +4,60 @@ import { Link } from 'react-router-dom';
|
|||
import { toast } from 'react-toastify';
|
||||
import { Button, Form, FormGroup, Input } from 'reactstrap';
|
||||
import withRedirect from '../../hoc/withRedirect';
|
||||
import { requstResetForgotPassword } from '../../helpers/rest';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ForgetPasswordForm = ({ setRedirect, setRedirectUrl, layout }) => {
|
||||
// State
|
||||
const [email, setEmail] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const { t } = useTranslation('auth');
|
||||
|
||||
// Handler
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
setSubmitting(true);
|
||||
if (email) {
|
||||
toast.success(`An email is sent to ${email} with password reset link`);
|
||||
setRedirect(true);
|
||||
requstResetForgotPassword(email)
|
||||
.then(() => {
|
||||
toast.success(`An email is sent to ${email} with password reset link`);
|
||||
setRedirect(true);
|
||||
})
|
||||
.catch(error => {})
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setRedirectUrl(`/authentication/${layout}/confirm-mail`);
|
||||
}, [setRedirectUrl, layout]);
|
||||
setRedirectUrl(`/authentication/${layout}/confirm-mail?email=${email}`);
|
||||
}, [setRedirectUrl, layout, email]);
|
||||
|
||||
return (
|
||||
<Form className="mt-4" onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
<Input
|
||||
className="form-control"
|
||||
placeholder="Email address"
|
||||
placeholder={t('forgotForm.email')}
|
||||
value={email}
|
||||
onChange={({ target }) => setEmail(target.value)}
|
||||
type="email"
|
||||
required
|
||||
disabled={submitting}
|
||||
data-testid="email"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Button color="primary" block disabled={!email}>
|
||||
Send reset link
|
||||
<Button color="primary" block disabled={!email || submitting} data-testid="submit">
|
||||
{submitting ? t('forgotForm.submitting') : t('forgotForm.submit')}
|
||||
</Button>
|
||||
</FormGroup>
|
||||
<Link className="fs--1 text-600" to="#!">
|
||||
{/* <Link className="fs--1 text-600" to="#!">
|
||||
I can't recover my account using this page
|
||||
<span className="d-inline-block ml-1">→</span>
|
||||
</Link>
|
||||
</Link> */}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import SocialAuthButtons from './SocialAuthButtons';
|
|||
import withRedirect from '../../hoc/withRedirect';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
//import { signin } from '../../services/authService';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const LoginForm = ({ setRedirect, hasLabel, layout }) => {
|
||||
// State
|
||||
|
|
@ -17,6 +18,8 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
|
|||
const [isDisabled, setIsDisabled] = useState(true);
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const { t } = useTranslation('auth');
|
||||
|
||||
const location = useLocation();
|
||||
let { from } = location.state || { from: { pathname: "/" } };
|
||||
|
|
@ -27,8 +30,8 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
|
|||
const handleSubmit = async e => {
|
||||
e.preventDefault();
|
||||
const credentials = {email, password}
|
||||
setIsDisabled(true);
|
||||
const user = await login(credentials)
|
||||
console.log("handleSubmit", user);
|
||||
if(user){
|
||||
setCurrentUser(user)
|
||||
//localStorage.setItem('user', user)
|
||||
|
|
@ -40,7 +43,7 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
|
|||
}else{
|
||||
toast.error("Incorrect email or password");
|
||||
}
|
||||
|
||||
setIsDisabled(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -50,20 +53,20 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
|
|||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
{hasLabel && <Label>Email address</Label>}
|
||||
{hasLabel && <Label>{ t('signinForm.email')}</Label>}
|
||||
<Input
|
||||
data-testid="email"
|
||||
placeholder={!hasLabel ? 'Email address' : ''}
|
||||
placeholder={!hasLabel ? t('signinForm.email') : ''}
|
||||
value={email}
|
||||
onChange={({ target }) => setEmail(target.value)}
|
||||
type="email"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
{hasLabel && <Label>Password</Label>}
|
||||
{hasLabel && <Label>{t('signinForm.password')}</Label>}
|
||||
<Input
|
||||
data-testid="password"
|
||||
placeholder={!hasLabel ? 'Password' : ''}
|
||||
placeholder={!hasLabel ? t('signinForm.password') : ''}
|
||||
value={password}
|
||||
onChange={({ target }) => setPassword(target.value)}
|
||||
type="password"
|
||||
|
|
@ -73,22 +76,22 @@ const LoginForm = ({ setRedirect, hasLabel, layout }) => {
|
|||
<Col xs="auto">
|
||||
<CustomInput
|
||||
id="customCheckRemember"
|
||||
label="Remember me"
|
||||
label={ t('signinForm.remember') }
|
||||
checked={remember}
|
||||
onChange={({ target }) => setRemember(target.checked)}
|
||||
type="checkbox"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs="auto">
|
||||
{/* <Link className="fs--1" to={`/authentication/${layout}/forget-password`}>
|
||||
<Link className="fs--1" to={`/authentication/${layout}/forget-password`}>
|
||||
Forget Password?
|
||||
</Link> */}
|
||||
<a href="https://www.jamkazam.com/request_reset_password" target='_blank'>Forgot Password</a>
|
||||
</Link>
|
||||
{/* <a href="https://www.jamkazam.com/request_reset_password" target='_blank'>Forgot Password</a> */}
|
||||
</Col>
|
||||
</Row>
|
||||
<FormGroup>
|
||||
<Button color="primary" block className="mt-3" data-testid="submit" disabled={isDisabled}>
|
||||
Sign in
|
||||
{ t('signinForm.submit') }
|
||||
</Button>
|
||||
</FormGroup>
|
||||
{/* <Divider className="mt-4">or log in with</Divider>
|
||||
|
|
|
|||
|
|
@ -5,18 +5,42 @@ import { Button, Form, FormGroup, Input } from 'reactstrap';
|
|||
import withRedirect from '../../hoc/withRedirect';
|
||||
import Label from 'reactstrap/es/Label';
|
||||
import classNames from 'classnames';
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
import { resetForgotPassword } from '../../helpers/rest';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const PasswordResetForm = ({ setRedirect, setRedirectUrl, layout, hasLabel }) => {
|
||||
// State
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [isDisabled, setIsDisabled] = useState(true);
|
||||
const queryString = useBrowserQuery();
|
||||
|
||||
const { t } = useTranslation('auth');
|
||||
|
||||
// Handler
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
toast.success('Login with your new password');
|
||||
setRedirect(true);
|
||||
const token = queryString.get('token');
|
||||
const email = queryString.get('email');
|
||||
if (!token || !email) return;
|
||||
|
||||
if (password !== confirmPassword) return;
|
||||
const data = { email, token, password, password_confirmation: confirmPassword}
|
||||
setIsDisabled(true);
|
||||
resetForgotPassword(data)
|
||||
.then(() => {
|
||||
toast.success(t('resetForm.successMessage'));
|
||||
setRedirect(true);
|
||||
})
|
||||
.catch(error => {
|
||||
error.json().then(data => {
|
||||
toast.error(data.message);
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setIsDisabled(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -32,25 +56,25 @@ const PasswordResetForm = ({ setRedirect, setRedirectUrl, layout, hasLabel }) =>
|
|||
return (
|
||||
<Form className={classNames('mt-3', { 'text-left': hasLabel })} onSubmit={handleSubmit}>
|
||||
<FormGroup>
|
||||
{hasLabel && <Label>New Password</Label>}
|
||||
{hasLabel && <Label>{t('resetForm.password')}</Label>}
|
||||
<Input
|
||||
placeholder={!hasLabel ? 'New Password' : ''}
|
||||
placeholder={!hasLabel ? t('resetForm.password') : ''}
|
||||
value={password}
|
||||
onChange={({ target }) => setPassword(target.value)}
|
||||
type="password"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
{hasLabel && <Label>Confirm Password</Label>}
|
||||
{hasLabel && <Label>{t('resetForm.confirmPassword')}</Label>}
|
||||
<Input
|
||||
placeholder={!hasLabel ? 'Confirm Password' : ''}
|
||||
placeholder={!hasLabel ? t('resetForm.confirmPassword') : ''}
|
||||
value={confirmPassword}
|
||||
onChange={({ target }) => setConfirmPassword(target.value)}
|
||||
type="password"
|
||||
/>
|
||||
</FormGroup>
|
||||
<Button color="primary" block className="mt-3" disabled={isDisabled}>
|
||||
Set password
|
||||
{t('resetForm.submit')}
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ConfirmMailContent from '../ConfirmMailContent';
|
|||
|
||||
const ConfirmMail = () => (
|
||||
<div className="text-center">
|
||||
<ConfirmMailContent email="xyz@abc.com" />
|
||||
<ConfirmMailContent />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import ForgetPasswordForm from '../ForgetPasswordForm';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const ForgetPassword = () => {
|
||||
const { t } = useTranslation('auth');
|
||||
return (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0"> Forgot your password?</h5>
|
||||
<small>Enter your email and we'll send you a reset link.</small>
|
||||
<h5 className="mb-0">{ t('forgotForm.title')}</h5>
|
||||
<small>{ t('forgotForm.description')}</small>
|
||||
<ForgetPasswordForm />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,20 +5,23 @@ import Login from './Login';
|
|||
// import Start from './Start';
|
||||
// import Logout from './Logout';
|
||||
// import Registration from './Registration';
|
||||
// import ForgetPassword from './ForgetPassword';
|
||||
// import PasswordReset from './PasswordReset';
|
||||
// import ConfirmMail from './ConfirmMail';
|
||||
import ForgetPassword from './ForgetPassword';
|
||||
import PasswordReset from './PasswordReset';
|
||||
import ConfirmMail from './ConfirmMail';
|
||||
// import LockScreen from './LockScreen';
|
||||
|
||||
const AuthBasicRoutes = ({ match: { url } }) => (
|
||||
<Switch>
|
||||
<Route path={`${url}/login`} exact component={Login} />
|
||||
<Route path={`${url}/forget-password`} exact component={ForgetPassword} />
|
||||
<Route path={`${url}/confirm-mail`} exact component={ConfirmMail} />
|
||||
<Route path={`${url}/reset_password_token`} exact component={PasswordReset} />
|
||||
{/* <Route path={`${url}/start`} exact component={Start} />
|
||||
<Route path={`${url}/logout`} exact component={Logout} />
|
||||
<Route path={`${url}/register`} exact component={Registration} />
|
||||
<Route path={`${url}/forget-password`} exact component={ForgetPassword} />
|
||||
<Route path={`${url}/confirm-mail`} exact component={ConfirmMail} />
|
||||
<Route path={`${url}/password-reset`} exact component={PasswordReset} />
|
||||
|
||||
|
||||
|
||||
<Route path={`${url}/lock-screen`} exact component={LockScreen} /> */}
|
||||
|
||||
{/*Redirect*/}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,27 @@ import React, { Fragment } from 'react';
|
|||
import { Col, Row } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import LoginForm from '../LoginForm';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
//const {t} = useTranslation();
|
||||
const Login = () => {
|
||||
const { t } = useTranslation('auth');
|
||||
|
||||
const Login = () => (
|
||||
|
||||
|
||||
<Fragment>
|
||||
<Row className="text-left justify-content-between">
|
||||
<Col xs="auto">
|
||||
<h5>Sign in</h5>
|
||||
</Col>
|
||||
<Col xs="auto">
|
||||
<p className="fs--1 text-600">
|
||||
or {' '}
|
||||
{/* <Link to="/authentication/basic/register">create an account</Link> */}
|
||||
<a href={`${process.env.REACT_APP_CLIENT_BASE_URL}/signup`}>Sign up</a>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<LoginForm />
|
||||
</Fragment>
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<Row className="text-left justify-content-between">
|
||||
<Col xs="auto">
|
||||
<h5>{t('signin')}</h5>
|
||||
</Col>
|
||||
<Col xs="auto">
|
||||
<p className="fs--1 text-600">
|
||||
or {/* <Link to="/authentication/basic/register">create an account</Link> */}
|
||||
<a href={`${process.env.REACT_APP_CLIENT_BASE_URL}/signup`}>{t('signup')}</a>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<LoginForm />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import React from 'react';
|
||||
import PasswordResetForm from '../PasswordResetForm';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const PasswordReset = () => (
|
||||
<div className="text-center">
|
||||
<h5>Reset new password</h5>
|
||||
<PasswordResetForm />
|
||||
</div>
|
||||
);
|
||||
const PasswordReset = () => {
|
||||
const { t } = useTranslation('auth');
|
||||
return (
|
||||
<div className="text-center">
|
||||
<h5>{ t('resetForm.title')}</h5>
|
||||
<small>{t('resetForm.subTitle')}</small>
|
||||
<PasswordResetForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordReset;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import JKMyFriends from '../page/JKMyFriends';
|
|||
import JKNotifications from '../page/JKNotifications';
|
||||
import JKMessageModal from '../profile/JKMessageModal';
|
||||
import JKUnsubscribe from '../page/JKUnsubscribe';
|
||||
import JKAppLaunch from '../page/JKAppLaunch';
|
||||
|
||||
import JKMusicSessions from '../page/JKMusicSessions';
|
||||
import JKNewMusicSession from '../page/JKNewMusicSession';
|
||||
|
|
@ -305,6 +306,7 @@ function JKDashboardMain() {
|
|||
<PrivateRoute path="/shopping-cart" component={JKShoppingCart} />
|
||||
<PrivateRoute path="/checkout/success" component={JKCheckoutSuccess} />
|
||||
<PrivateRoute path="/checkout" component={JKCheckout} />
|
||||
<PrivateRoute path="/applaunch" component={JKAppLaunch} />
|
||||
{/*Redirect*/}
|
||||
<Redirect to="/errors/404" />
|
||||
</Switch>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { Card, CardBody, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { getUserDetail, postUserEvent, userOpenedJamTrackWebPlayer } from '../../helpers/rest';
|
||||
|
|
@ -74,14 +74,39 @@ const JKJamTrack = () => {
|
|||
<CardBody className="pt-3">{jamTrack && <JKMyJamTrackMixes />}</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col sm={12} md={4} className={ greaterThan.sm ? null : 'mt-4' }>
|
||||
<Col sm={12} md={4}>
|
||||
<Card className="mx-auto">
|
||||
<FalconCardHeader title={t('jamtrack.create_mix.title')} titleClass="font-weight-semi-bold" />
|
||||
<CardBody className="pt-3">{jamTrack && <JKCreateCustomMix />}</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col />
|
||||
<Col sm={12} md={4}>
|
||||
<Card className="mx-auto">
|
||||
<FalconCardHeader title={t('jamtrack.help_resources.title')} titleClass="font-weight-semi-bold" />
|
||||
<CardBody className="pt-3">
|
||||
<div className='mb-3'>
|
||||
<div><a target='_blank' href="https://jamkazam.freshdesk.com/support/solutions/articles/66000501472">{t('jamtrack.help_resources.using_overview.title')}</a></div>
|
||||
{t('jamtrack.help_resources.using_overview.description')}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<div><a target='_blank' href="https://jamkazam.freshdesk.com/support/solutions/articles/66000125792">{t('jamtrack.help_resources.using_mac_windows.title')}</a></div>
|
||||
{t('jamtrack.help_resources.using_mac_windows.description')}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<div>
|
||||
<Link to="/my-jamtracks">{t('jamtrack.help_resources.jamtracks_home.title')}</Link>
|
||||
</div>
|
||||
{t('jamtrack.help_resources.jamtracks_home.description')}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<div>
|
||||
<Link to="/jamtracks">{t('jamtrack.help_resources.see_more.title')}</Link>
|
||||
</div>
|
||||
{t('jamtrack.help_resources.see_more.description')}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Row, Col } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
||||
const { t } = useTranslation('jamtracks');
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const handleClick = artist => {
|
||||
onSelect(artist);
|
||||
|
|
@ -18,7 +20,7 @@ const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
|||
<>
|
||||
<Row>
|
||||
<Col>
|
||||
<strong>Search Results: Artists</strong>
|
||||
<strong>{t('search.search_results.artists')}</strong>
|
||||
</Col>
|
||||
</Row>
|
||||
{artists.length > 0 ? (
|
||||
|
|
@ -43,7 +45,7 @@ const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
|||
<Row className='mt-1 mb-1'>
|
||||
<Col>
|
||||
<a href="#" onClick={toggleMoreArtists}>
|
||||
{expanded ? 'Show fewer artists' : 'Show all artists'}
|
||||
{expanded ? t('search.search_results.show_fewer_artists') : t('search.search_results.show_more_artists')}
|
||||
</a>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -51,7 +53,7 @@ const JKJamTrackArtists = ({ artists, showArtists, onSelect }) => {
|
|||
</>
|
||||
) : (
|
||||
<Row className="mb-2">
|
||||
<Col>No matching artists</Col>
|
||||
<Col>{t('search.search_results.no_matching_artists')}</Col>
|
||||
</Row>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ const JKJamTrackPlayer = () => {
|
|||
const audioUrl =
|
||||
process.env.REACT_APP_API_BASE_URL +
|
||||
`/mixdowns/${activeMix.id}/download.mp3?file_type=mp3&sample_rate=48&mark=${result.visitorId}`;
|
||||
console.log('audioUrl', audioUrl);
|
||||
return audioUrl;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
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';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import JKTooltip from '../common/JKTooltip';
|
||||
|
||||
const JKJamTrackPurchaseButton = ({ jamTrack }) => {
|
||||
const history = useHistory();
|
||||
const { currentUser } = useAuth();
|
||||
const { addCartItem } = useShoppingCart();
|
||||
const { t } = useTranslation('jamtracks');
|
||||
|
||||
|
||||
const addToCart = async () => {
|
||||
const options = {
|
||||
|
|
@ -18,11 +22,11 @@ const JKJamTrackPurchaseButton = ({ jamTrack }) => {
|
|||
variant: 'full'
|
||||
};
|
||||
if (await addCartItem(options)) {
|
||||
toast.success('JamTrack added to cart');
|
||||
toast.success(t('search.list.add_success_alert'));
|
||||
history.push('/shopping-cart');
|
||||
} else {
|
||||
console.log('Add to Cart Error');
|
||||
toast.error('Error adding to cart');
|
||||
toast.error(t('search.list.add_error_alert'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -30,23 +34,22 @@ const JKJamTrackPurchaseButton = ({ jamTrack }) => {
|
|||
<>
|
||||
{jamTrack.purchaed ? (
|
||||
<Button color="light" size="sm" className="mr-1">
|
||||
Purchased
|
||||
{t('search.list.purchased')}
|
||||
</Button>
|
||||
) : jamTrack.is_free && currentUser && currentUser.show_free_jamtrack ? (
|
||||
<Button color="primary" onClick={addToCart} size="sm" className="mr-1">
|
||||
Get It Free!
|
||||
</Button>
|
||||
) : jamTrack.added_cart ? (
|
||||
) : jamTrack.allow_free && currentUser && currentUser.show_free_jamtrack ? (
|
||||
<>
|
||||
<Button color="light" size="sm" className="mr-1">
|
||||
Already in Cart
|
||||
<Button color="primary" onClick={addToCart} size="sm" className="mr-1">
|
||||
{t('search.list.get_it_free')}
|
||||
</Button>
|
||||
<JKTooltip title={t('search.list.get_it_free_help_text')} />
|
||||
</>
|
||||
) : jamTrack.added_cart ? (
|
||||
<Link to="/shopping-cart">{t('search.list.already_in_cart')}</Link>
|
||||
) : (
|
||||
<>
|
||||
<div className="fs-1">$ {jamTrack.download_price}</div>
|
||||
<Button color="primary" size="sm" className="mr-1" onClick={addToCart}>
|
||||
Add to Cart
|
||||
{t('search.list.add_to_cart')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const JKJamTrackTrack = ({ track }) => {
|
|||
</span>
|
||||
{trackInfo && (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
<span className="mr-1 pb-1">
|
||||
<JKInstrumentIcon instrumentId={trackInfo.instrumentId} instrumentName={trackInfo.instrumentDescription} />
|
||||
</span>
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card, CardBody, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
|
@ -6,6 +7,9 @@ import JKJamTracksAutoComplete from './JKJamTracksAutoComplete';
|
|||
import { getJamTracks, getJamTrackArtists, autocompleteJamTracks } from '../../helpers/rest';
|
||||
import JKJamTrackArtists from './JKJamTrackArtists';
|
||||
import JKJamTracksList from './JKJamTracksList';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useShoppingCart } from '../../hooks/useShoppingCart';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
|
||||
const JKJamTracksFilter = () => {
|
||||
const { t } = useTranslation('jamtracks');
|
||||
|
|
@ -19,6 +23,12 @@ const JKJamTracksFilter = () => {
|
|||
const [autoCompleteInputValue, setAutoCompleteInputValue] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const PER_PAGE = 10;
|
||||
const { shoppingCart } = useShoppingCart();
|
||||
const { greaterThan } = useResponsive();
|
||||
|
||||
// useEffect(() => {
|
||||
// setCartItems(shoppingCart);
|
||||
// }, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected) {
|
||||
|
|
@ -46,7 +56,7 @@ const JKJamTracksFilter = () => {
|
|||
return options;
|
||||
};
|
||||
|
||||
const handleOnSelect = async (selected) => {
|
||||
const handleOnSelect = async selected => {
|
||||
setPage(1);
|
||||
setArtists([]);
|
||||
setJamTracks([]);
|
||||
|
|
@ -57,7 +67,7 @@ const JKJamTracksFilter = () => {
|
|||
await fetchJamTracks(params);
|
||||
};
|
||||
|
||||
const handleOnEnter = async(queryStr) => {
|
||||
const handleOnEnter = async queryStr => {
|
||||
setPage(1);
|
||||
setArtists([]);
|
||||
setJamTracks([]);
|
||||
|
|
@ -69,7 +79,7 @@ const JKJamTracksFilter = () => {
|
|||
await fetchJamTracks(params);
|
||||
};
|
||||
|
||||
const handleOnSelectArtist = async(artist) => {
|
||||
const handleOnSelectArtist = async artist => {
|
||||
setPage(1);
|
||||
const selectedOpt = {
|
||||
type: 'artist',
|
||||
|
|
@ -84,7 +94,7 @@ const JKJamTracksFilter = () => {
|
|||
const currentQuery = selected ? selected : searchTerm;
|
||||
const params = queryOptions(currentQuery);
|
||||
await fetchJamTracks(params);
|
||||
}
|
||||
};
|
||||
|
||||
// const fetchJamTracks = options => {
|
||||
// getJamTracks(options)
|
||||
|
|
@ -102,8 +112,7 @@ const JKJamTracksFilter = () => {
|
|||
// });
|
||||
// };
|
||||
|
||||
|
||||
const fetchJamTracks = async(options) => {
|
||||
const fetchJamTracks = async options => {
|
||||
try {
|
||||
console.log('fetchJamTracks', options);
|
||||
const resp = await getJamTracks(options);
|
||||
|
|
@ -111,12 +120,10 @@ const JKJamTracksFilter = () => {
|
|||
console.log('jamtracks', data);
|
||||
setJamTracks(prev => [...prev, ...data.jamtracks]);
|
||||
setNextOffset(data.next);
|
||||
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const fetchArtists = query => {
|
||||
const options = {
|
||||
|
|
@ -142,6 +149,11 @@ const JKJamTracksFilter = () => {
|
|||
<Card>
|
||||
<FalconCardHeader title={t('search.page_title')} titleClass="font-weight-bold" />
|
||||
<CardBody className="pt-3">
|
||||
{!greaterThan.sm && (
|
||||
<Row>
|
||||
<JKJamTrackFilterLinks shoppingCart={shoppingCart} wrapperClassNames='d-flex flex-column' shoppingCartClassNames="col mt-1 mb-2" downloadLinkClassNames='col' />
|
||||
</Row>
|
||||
)}
|
||||
<Row>
|
||||
<Col>
|
||||
<JKJamTracksAutoComplete
|
||||
|
|
@ -155,14 +167,15 @@ const JKJamTracksFilter = () => {
|
|||
inputPlaceholder={t('search.search_input.placeholder')}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="text-right">
|
||||
<span>
|
||||
Download JamTracks catalog as a{' '}
|
||||
<a data-testid="download-pdf" href="https://s3.amazonaws.com/jamkazam-public/public/lists/all-jamkazam-jamtracks.pdf">
|
||||
<strong>PDF file</strong>
|
||||
</a>
|
||||
</span>
|
||||
</Col>
|
||||
{greaterThan.sm && (
|
||||
<Col>
|
||||
<JKJamTrackFilterLinks
|
||||
shoppingCart={shoppingCart}
|
||||
wrapperClassNames="d-flex justify-content-end"
|
||||
shoppingCartClassNames="ml-3 mr-1"
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
<div className="mb-3">
|
||||
|
|
@ -173,10 +186,40 @@ const JKJamTracksFilter = () => {
|
|||
showArtists={showArtists}
|
||||
/>
|
||||
</div>
|
||||
<JKJamTracksList selectedType={selected?.type} searchTerm={searchTerm} jamTracks={jamTracks} nextOffset={nextOffset} onNextPage={handleOnNextJamTracksPage} />
|
||||
<JKJamTracksList
|
||||
selectedType={selected?.type}
|
||||
searchTerm={searchTerm}
|
||||
jamTracks={jamTracks}
|
||||
nextOffset={nextOffset}
|
||||
onNextPage={handleOnNextJamTracksPage}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const JKJamTrackFilterLinks = ({ shoppingCart, wrapperClassNames, shoppingCartClassNames, downloadLinkClassNames }) => {
|
||||
return (
|
||||
<div className={wrapperClassNames}>
|
||||
<div className={downloadLinkClassNames}>
|
||||
Download JamTracks catalog as a{' '}
|
||||
<a
|
||||
data-testid="download-pdf"
|
||||
href="https://s3.amazonaws.com/jamkazam-public/public/lists/all-jamkazam-jamtracks.pdf"
|
||||
>
|
||||
<strong>PDF file</strong>
|
||||
</a>
|
||||
</div>
|
||||
{shoppingCart.length > 0 && (
|
||||
<div className={shoppingCartClassNames}>
|
||||
<Link to="shopping-cart" className="btn btn-primary btn-sm">
|
||||
<FontAwesomeIcon icon="shopping-cart" className="mr-1" />
|
||||
View Cart ({shoppingCart.length})
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKJamTracksFilter;
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@ import JKJamTrackPreview from './JKJamTrackPreview';
|
|||
import JKJamTrackPurchaseButton from './JKJamTrackPurchaseButton';
|
||||
import { JamTrackPreviewProvider } from '../../context/JamTrackPreviewContext';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextOffset, onNextPage }) => {
|
||||
const { t } = useTranslation('jamtracks');
|
||||
return (
|
||||
<>
|
||||
{selectedType && searchTerm.length && jamTracks.length > 0 ? (
|
||||
<Row className="mb-2">
|
||||
<Col>
|
||||
<strong>
|
||||
Search Results: JamTracks for {selectedType} "{searchTerm}"
|
||||
{t('search.search_results.for_selection')} {selectedType} "{searchTerm}"
|
||||
</strong>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -21,7 +23,7 @@ const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextOffset, onNe
|
|||
jamTracks.length > 0 && (
|
||||
<Row className="mb-2">
|
||||
<Col>
|
||||
<strong>Search Results: JamTracks including "{searchTerm}"</strong>
|
||||
<strong>{t('search.search_results.for_search_term')} "{searchTerm}"</strong>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
|
|
@ -32,22 +34,22 @@ const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextOffset, onNe
|
|||
<Table striped bordered className="fs--1" data-testid="jamtracks-table">
|
||||
<thead className="bg-200 text-900">
|
||||
<tr>
|
||||
<th width="30%">Song</th>
|
||||
<th width="55%">Tracks</th>
|
||||
<th>Shop</th>
|
||||
<th width="30%">{t('search.list.song')}</th>
|
||||
<th width="55%">{t('search.list.tracks')}</th>
|
||||
<th>{t('search.list.shop')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<JamTrackPreviewProvider>
|
||||
{jamTracks.map((jamTrack, index) => (
|
||||
<tr key={`jamtrck-preview-row-${jamTrack.id}`}>
|
||||
<td className='track-name-col'>
|
||||
<td className="track-name-col">
|
||||
{jamTrack.name} by {jamTrack.original_artist}
|
||||
</td>
|
||||
<td className='track-tracks-col'>
|
||||
<td className="track-tracks-col">
|
||||
<JKJamTrackPreview jamTrack={jamTrack} />
|
||||
</td>
|
||||
<td className='purchase-button-col'>
|
||||
<td className="purchase-button-col">
|
||||
<JKJamTrackPurchaseButton jamTrack={jamTrack} />
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -62,7 +64,7 @@ const JKJamTracksList = ({ selectedType, searchTerm, jamTracks, nextOffset, onNe
|
|||
<Row>
|
||||
<Col>
|
||||
<Button color="primary" onClick={onNextPage} data-testid="moreBtn">
|
||||
Load More
|
||||
{t('search.list.load_more')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -76,7 +78,7 @@ JKJamTracksList.propTypes = {
|
|||
searchTerm: PropTypes.string,
|
||||
jamTracks: PropTypes.array,
|
||||
nextOffset: PropTypes.number,
|
||||
onNextPage: PropTypes.func,
|
||||
onNextPage: PropTypes.func
|
||||
};
|
||||
|
||||
JKJamTracksList.defaultProps = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import jkCustomUrlScheme from '../../helpers/jkCustomUrlScheme';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardBody, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
|
||||
const JKCustomUrlSchemaHandle = () => {
|
||||
const [urlScheme, setUrlScheme] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const queryStr = window.location.search;
|
||||
if (!queryStr) return;
|
||||
const urlParams = new URLSearchParams(queryStr);
|
||||
const action = urlParams.get('act');
|
||||
const params = urlParams.get('p');
|
||||
const appUrl = jkCustomUrlScheme(action, params);
|
||||
setUrlScheme(appUrl);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (urlScheme) {
|
||||
console.log('opening custom url', urlScheme);
|
||||
window.open(urlScheme, '_self');
|
||||
}
|
||||
}, [urlScheme]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card>
|
||||
<FalconCardHeader title={t('new.page_title', { ns: 'sessions' })} titleClass="font-weight-bold" />
|
||||
<CardBody className="pt-0">
|
||||
<Row>
|
||||
<Col>
|
||||
<div className='pt-5 text-center pb-5'>
|
||||
{urlScheme && (
|
||||
<p>
|
||||
If JamKazam app doesn't open automatically, <a href={urlScheme}>click here</a>.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKCustomUrlSchemaHandle;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useRef, useState, useEffect } from 'react';
|
||||
//import {useHistory} from 'react-router-dom';
|
||||
import {useHistory} from 'react-router-dom';
|
||||
import { Form, FormGroup, Input, Label, Card, CardBody, Button, Row, Col } from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import JKTooltip from '../common/JKTooltip';
|
||||
|
|
@ -8,18 +8,11 @@ import { useAuth } from '../../context/UserAuth';
|
|||
import JKFriendsAutoComplete from '../people/JKFriendsAutoComplete';
|
||||
import JKSessionInviteesChips from '../people/JKSessionInviteesChips';
|
||||
import { getFriends } from '../../helpers/rest';
|
||||
import jkCustomUrlScheme from '../../helpers/jkCustomUrlScheme';
|
||||
import JKModalDialog from '../common/JKModalDialog';
|
||||
import useNativeAppCheck from '../../hooks/useNativeAppCheck';
|
||||
import { useNativeApp } from '../../context/NativeAppContext';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import { sessionPrivacyMap } from '../../config';
|
||||
// const privacyMap = {
|
||||
// public: 1,
|
||||
// private_invite: 2,
|
||||
// private_approve: 3
|
||||
// };
|
||||
|
||||
|
||||
const JKNewMusicSession = () => {
|
||||
const { currentUser } = useAuth();
|
||||
|
|
@ -31,7 +24,7 @@ const JKNewMusicSession = () => {
|
|||
const [privacy, setPrivacy] = useState('1');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [showAppUnavailable, setShowAppUnavailable] = useState(false);
|
||||
//const history = useHistory();
|
||||
const history = useHistory();
|
||||
const formRef = useRef();
|
||||
const isNativeAppAvailable = useNativeAppCheck();
|
||||
const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp();
|
||||
|
|
@ -91,15 +84,11 @@ const JKNewMusicSession = () => {
|
|||
} catch (error) {
|
||||
console.error('localStorage is not available', error);
|
||||
}
|
||||
//check if jamkazam app is installed
|
||||
try {
|
||||
//check if jamkazam app is installed
|
||||
await isNativeAppAvailable();
|
||||
//window.open jamkazam app url using custom URL scheme
|
||||
//an example URL would be: jamkazam://url=https://www.jamkazam.com/client#/createSession/privacy~2|description~hello|inviteeIds~1,2,3,4
|
||||
const q = `privacy~${payload.privacy}|description~${payload.description}|inviteeIds~${payload.inviteeIds}`;
|
||||
const urlScheme = jkCustomUrlScheme('createSession', q);
|
||||
window.location.href = urlScheme;
|
||||
//history.push('/sessions');
|
||||
history.push(`/applaunch?act=createSession&p=${q}`);
|
||||
} catch (error) {
|
||||
toggleAppUnavilableModel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,13 @@ import { useTranslation } from 'react-i18next';
|
|||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col, Button, UncontrolledTooltip } from 'reactstrap';
|
||||
|
||||
import jkCustomUrlScheme from '../../helpers/jkCustomUrlScheme';
|
||||
|
||||
import JKUserLatencyBadge from '../profile/JKUserLatencyBadge';
|
||||
import JKSessionUser from './JKSessionUser';
|
||||
import useNativeAppCheck from '../../hooks/useNativeAppCheck';
|
||||
import { useNativeApp } from '../../context/NativeAppContext';
|
||||
import EnterIcon from '../../icons/enter.svg';
|
||||
import JKInstrumentIcon from '../profile/JKInstrumentIcon';
|
||||
|
||||
import {useHistory} from 'react-router-dom';
|
||||
import useSessionHelper from './JKUseSessionHelper';
|
||||
|
||||
function JKSession({ session }) {
|
||||
|
|
@ -161,6 +158,7 @@ function JoinSessionButton({ session }) {
|
|||
const isNativeAppAvailable = useNativeAppCheck();
|
||||
const { setNativeAppUnavailable } = useNativeApp();
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
async function joinSession(e) {
|
||||
e.preventDefault();
|
||||
|
|
@ -169,9 +167,8 @@ function JoinSessionButton({ session }) {
|
|||
} else {
|
||||
try {
|
||||
await isNativeAppAvailable();
|
||||
const q = `joinSessionId~${session.id}`;
|
||||
const urlScheme = jkCustomUrlScheme('findSession', q);
|
||||
window.document.href = urlScheme;
|
||||
const q = `sessionId~${session.id}`;
|
||||
history.push(`/applaunch?act=joinSession&p=${q}`);
|
||||
return;
|
||||
} catch (error) {
|
||||
setNativeAppUnavailable(true);
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import JKLatencyBadge from '../profile/JKLatencyBadge';
|
|||
import JKProfileSidePanel from '../profile/JKProfileSidePanel';
|
||||
import JKProfileAvatar from '../profile/JKProfileAvatar';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
|
||||
function JKSessionUser({ user }) {
|
||||
const dispatch = useDispatch();
|
||||
const { currentUser } = useAuth();
|
||||
|
||||
const latencyData = useSelector(state => {
|
||||
const userLatency = state.latency.latencies.find(l => l.user_id === user.id);
|
||||
return {
|
||||
|
|
@ -62,7 +65,8 @@ function JKSessionUser({ user }) {
|
|||
</a>
|
||||
</div>
|
||||
<div className="ml-2 ms-2" style={{ marginRight: 'auto' }}>
|
||||
<JKLatencyBadge latencyData={latencyData} showBadgeOnly={true} />
|
||||
{ currentUser && currentUser.id !== user.id && <JKLatencyBadge latencyData={latencyData} showBadgeOnly={true} /> }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import JKUserLatencyBadge from '../profile/JKUserLatencyBadge';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { fetchUserLatencies } from '../../store/features/latencySlice';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const JKSessionsHistoryItem = ({ session_id, sessionGroup }) => {
|
||||
const { greaterThan } = useResponsive();
|
||||
|
|
@ -15,6 +16,7 @@ const JKSessionsHistoryItem = ({ session_id, sessionGroup }) => {
|
|||
const [participants, setParticipants] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
const { currentUser } = useAuth();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('sessionGroup', sessionGroup);
|
||||
|
|
@ -30,8 +32,8 @@ const JKSessionsHistoryItem = ({ session_id, sessionGroup }) => {
|
|||
name: `${history.first_name} ${history.last_name}`
|
||||
}
|
||||
};
|
||||
if (history.instrments) {
|
||||
participant.tracks = history.instrments.split('|').map((instrument, index) => ({
|
||||
if (history.instruments) {
|
||||
participant.tracks = history.instruments.split('|').map((instrument, index) => ({
|
||||
id: index,
|
||||
instrument_id: instrument,
|
||||
instrument: instrument
|
||||
|
|
@ -43,14 +45,11 @@ const JKSessionsHistoryItem = ({ session_id, sessionGroup }) => {
|
|||
);
|
||||
}, [sessionGroup]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (participants.length > 0 && currentUser) {
|
||||
const currentUserId = currentUser.id;
|
||||
const otherUserIds = participants.filter(p => p.id !== currentUserId).map(p => p.id);
|
||||
console.log('X_DEBUG_ JKSessionsHistoryItem', currentUserId, otherUserIds);
|
||||
fetchUserLatencies({ currentUserId, otherUserIds});
|
||||
dispatch(fetchUserLatencies({ currentUserId, otherUserIds }));
|
||||
}
|
||||
}, [participants]);
|
||||
|
||||
|
|
@ -94,10 +93,14 @@ const JKSessionsHistoryItem = ({ session_id, sessionGroup }) => {
|
|||
))}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{participants.filter(p => p.user.id !== currentUser.id).map(participant => (
|
||||
{participants.map(participant => (
|
||||
<Row key={participant.id} style={musicianRowStyle}>
|
||||
<Col>
|
||||
<JKUserLatencyBadge key={participant.id} user={participant.user} showBadgeOnly={true} />
|
||||
{participant.id !== currentUser.id ? (
|
||||
<JKUserLatencyBadge key={participant.id} user={participant.user} showBadgeOnly={true} />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
|
|
@ -106,22 +109,23 @@ const JKSessionsHistoryItem = ({ session_id, sessionGroup }) => {
|
|||
{participants.map(participant => (
|
||||
<Row style={musicianRowStyle} key={participant.id} data-testid={`Participant${participant.id}Tracks`}>
|
||||
<Col>
|
||||
{participant.tracks && participant.tracks.map(track => (
|
||||
<span key={track.id} className="mr-1 mb-1" title={track.instrment}>
|
||||
<a
|
||||
id={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
data-testid={`Track${track.id}Instrument`}
|
||||
>
|
||||
<JKInstrumentIcon instrumentId={track.instrument_id} instrumentName={track.instrument} />
|
||||
</a>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
>
|
||||
{track.instrument}
|
||||
</UncontrolledTooltip>
|
||||
</span>
|
||||
))}
|
||||
{participant.tracks &&
|
||||
participant.tracks.map(track => (
|
||||
<span key={track.id} className="mr-1 mb-1" title={track.instrment}>
|
||||
<a
|
||||
id={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
data-testid={`Track${track.id}Instrument`}
|
||||
>
|
||||
<JKInstrumentIcon instrumentId={track.instrument_id} instrumentName={track.instrument} />
|
||||
</a>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
>
|
||||
{track.instrument}
|
||||
</UncontrolledTooltip>
|
||||
</span>
|
||||
))}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import moment from 'moment';
|
||||
|
||||
const useSessionHelper = (session) => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -16,19 +17,9 @@ const useSessionHelper = (session) => {
|
|||
};
|
||||
|
||||
const sessionDateTime = session => {
|
||||
if(!session.created_at) return '';
|
||||
const date = new Date(session.created_at);
|
||||
const d = new Date(date);
|
||||
// return d.toLocaleDateString('en-us', {
|
||||
// weekday: 'long',
|
||||
// year: 'numeric',
|
||||
// month: 'short',
|
||||
// day: 'numeric',
|
||||
// timeZoneName: 'short'
|
||||
// });
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
dateStyle: 'full',
|
||||
timeStyle: 'long',
|
||||
}).format(date);
|
||||
return moment(date).format('dddd, MMMM DD, YYYY, h:mmA z');
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
export default (section, queryStr) => {
|
||||
const url = encodeURI(`${process.env.REACT_APP_CLIENT_BASE_URL}/signin?redirect-to=/client#/${section}/custom~yes|${queryStr}`);
|
||||
const urlScheme = `jamkazam://url=${url}`;
|
||||
//remove the leading '?' from queryStr
|
||||
if(queryStr.startsWith('?')) {
|
||||
queryStr = queryStr.substring(1);
|
||||
}
|
||||
const url = encodeURI(`${process.env.REACT_APP_CLIENT_BASE_URL}/client#/${section}/custom~yes|${queryStr}`);
|
||||
const urlScheme = `jamkazam://${url}`;
|
||||
return urlScheme;
|
||||
};
|
||||
|
|
@ -298,6 +298,39 @@ export const requestPasswordReset = userId => {
|
|||
});
|
||||
};
|
||||
|
||||
export const resetPassword = email => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/reset_password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email })
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const requstResetForgotPassword = email => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/request_reset_forgot_password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email })
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const resetForgotPassword = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/reset_forgot_password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const postUserAppInteraction = (userId, options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${userId}/app_interactions`, {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@
|
|||
},
|
||||
"signups": {
|
||||
"page_title": "Signups",
|
||||
"no_data": "No signups yet",
|
||||
"no_signup_data": "There is no signup data yet for your affiliate account. To start to generate affiliate signups, please ",
|
||||
"share_affiliate_links": "share affiliate links",
|
||||
"with_your_audience": " with your audience.",
|
||||
"load_more": "Load More",
|
||||
"list": {
|
||||
"header": {
|
||||
|
|
@ -80,7 +82,9 @@
|
|||
},
|
||||
"earnings": {
|
||||
"page_title": "Earnings",
|
||||
"no_data": "No earnings yet",
|
||||
"no_earnings_data": "There is no earnings data yet for your affiliate account. To start to generate affiliate signups, please ",
|
||||
"share_affiliate_links": "share affiliate links",
|
||||
"start_to_earn": " with your audience. Once you start to get signups, there is typically some lag time until new users convert to premium subscribers, generating affiliate income (e.g. new users get a free 30-day gold plan at signup).",
|
||||
"load_more": "Load More",
|
||||
"list": {
|
||||
"header": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,36 @@
|
|||
{
|
||||
"signup": "Sign Up",
|
||||
"signin": "Sign In",
|
||||
"signout": "Sign Out"
|
||||
"signout": "Sign Out",
|
||||
"signinForm": {
|
||||
"email": "Email Address",
|
||||
"password": "Password",
|
||||
"remember": "Remember me",
|
||||
"forgot": "Forgot password?",
|
||||
"submit": "Sign In"
|
||||
},
|
||||
"forgotForm": {
|
||||
"title": "Forgot Your Password",
|
||||
"description": "Enter your email address and we'll send you a link to reset your password.",
|
||||
"email": "Email Address",
|
||||
"submit": "Send Reset Link",
|
||||
"submitting": "Sending Reset Link..."
|
||||
},
|
||||
"confirmEmailContent": {
|
||||
"title": "Please check your email!",
|
||||
"description_1": "An email has been sent to ",
|
||||
"toYou": "to you",
|
||||
"description_2": "Please click on the included link to reset your password.",
|
||||
"description_3": "If you don't see the email, check other places it might be, like your junk, spam, social, or other folders. If you still can't find it, you may not be entering the email address that you used when signing up with the service.",
|
||||
"returnToLogin": "Return to Sign In"
|
||||
},
|
||||
"resetForm": {
|
||||
"title": "Reset Your Password",
|
||||
"subTitle": "Enter your new password below.",
|
||||
"password": "New Password",
|
||||
"confirmPassword": "Confirm New Password",
|
||||
"submit": "Reset Password",
|
||||
"submitting": "Resetting Password...",
|
||||
"successMessage": "Your password has been reset. Please sign in with your new password."
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,27 @@
|
|||
"search_input": {
|
||||
"title": "Search",
|
||||
"placeholder": "Search by artist, song, style, or keyword"
|
||||
},
|
||||
"search_results": {
|
||||
"artists": "Search Results: Artists",
|
||||
"show_fewer_artists": "Show Fewer Artists",
|
||||
"show_more_artists": "Show More Artists",
|
||||
"no_matching_artists": "No matching artists found.",
|
||||
"for_selection": "Search Results: JamTracks for",
|
||||
"for_search_term": "Search Results: JamTracks including"
|
||||
},
|
||||
"list": {
|
||||
"song": "Song",
|
||||
"tracks": "Tracks",
|
||||
"shop": "Shop",
|
||||
"load_more": "Load More",
|
||||
"get_it_free_help_text": "Click this button to get your first JamTrack free.",
|
||||
"purchased": "Purchased",
|
||||
"get_it_free": "Get it Free!",
|
||||
"add_to_cart": "Add to Cart",
|
||||
"already_in_cart": "Already in Cart",
|
||||
"add_success_alert": "JamTrack was added to cart successfully.",
|
||||
"add_error_alert": "There was an error adding the JamTrack to the cart."
|
||||
}
|
||||
},
|
||||
"my": {
|
||||
|
|
@ -39,6 +60,26 @@
|
|||
"pitch": "Pitch",
|
||||
"mix_name": "Mix Name",
|
||||
"create": "Create Mix"
|
||||
},
|
||||
"help_resources": {
|
||||
"title": "Helpful Resources",
|
||||
"using_overview": {
|
||||
"title": "Using JamTracks - Overview ",
|
||||
"description": "that explains how to use JamTracks in the browser."
|
||||
},
|
||||
"using_mac_windows": {
|
||||
"title": "Using JamTracks in the Mac or Windows App",
|
||||
"description": "that explains how to use JamTracks in the JamKazam app."
|
||||
},
|
||||
"jamtracks_home": {
|
||||
"title": "Go to the JamTracks Home Page",
|
||||
"description": "where you can access all of your JamTracks and search for more."
|
||||
},
|
||||
"see_more": {
|
||||
"title": "see more JamTracks by this artist",
|
||||
"description": "to checkout more songs you might like."
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,19 @@ import Logo from '../components/navbar/Logo';
|
|||
import Section from '../components/common/Section';
|
||||
import AuthBasicRoutes from '../components/auth/basic/JKAuthBasicRoutes';
|
||||
import UserAuth from '../context/UserAuth';
|
||||
import { BrowserQueryProvider } from '../context/BrowserQuery';
|
||||
|
||||
const AuthBasicLayout = ({location}) => (
|
||||
const AuthBasicLayout = ({ location }) => (
|
||||
<Section className="py-0">
|
||||
<Row className="flex-center min-vh-100 py-6">
|
||||
<Col sm={10} md={8} lg={6} xl={5} className="col-xxl-4">
|
||||
<Logo width={200} />
|
||||
<Card>
|
||||
<CardBody className="fs--1 font-weight-normal p-5">
|
||||
<UserAuth path={location.pathname}>
|
||||
<AuthBasicRoutes />
|
||||
<UserAuth path={location.pathname}>
|
||||
<BrowserQueryProvider>
|
||||
<AuthBasicRoutes />
|
||||
</BrowserQueryProvider>
|
||||
</UserAuth>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -69,11 +69,11 @@ export const affiliateRoutes = {
|
|||
icon: 'dollar-sign',
|
||||
children: [
|
||||
{ to: '/affiliate/program', name: 'Program'},
|
||||
{ to: '/affiliate/payee', name: 'Payee'},
|
||||
{ to: '/affiliate/agreement', name: 'Agreement'},
|
||||
{ to: '/affiliate/links', name: 'Links'},
|
||||
{ to: '/affiliate/payee', name: 'Payee'},
|
||||
{ to: '/affiliate/signups', name: 'Signups'},
|
||||
{ to: '/affiliate/earnings', name: 'Earnings'},
|
||||
{ to: '/affiliate/agreement', name: 'Agreement'}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,6 @@ module MailerHelper
|
|||
latency_scores[:unknown][:label]
|
||||
end
|
||||
|
||||
lbl
|
||||
lbl.titleize
|
||||
end
|
||||
end
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<div style="padding: 2em; margin-top: 2em; background-color: #f0f0f0;">
|
||||
<div
|
||||
style="
|
||||
margin: 3em auto 3em auto;
|
||||
margin: 3em auto 1.5em auto;
|
||||
padding: 2em;
|
||||
background-color: #ffffff;
|
||||
max-width: 700px;
|
||||
|
|
@ -25,39 +25,46 @@
|
|||
|
||||
</div>
|
||||
<footer style="text-align: center; margin: 0px auto; padding: 0px auto;">
|
||||
<% if @user && @user.unsubscribe_token -%>
|
||||
<p>
|
||||
<%= I18n.t "mailer_layout.footer.paragraph1" -%> <a href="https://www.jamkazam.com" target="_blank">JamKazam</a>. <a
|
||||
href="https://www.jamkazam.com/unsubscribe/<%= @user.unsubscribe_token %>"
|
||||
style="
|
||||
color: #2c7be5;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid #2c7be5;
|
||||
"
|
||||
><%= I18n.t "mailer_layout.footer.unsubscribe" -%></a>
|
||||
</p>
|
||||
<% end -%>
|
||||
<div style="text-align: center; margin: 1em 0;">
|
||||
<a href="https://www.facebook.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/fb-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="Facebook" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
|
||||
</a>
|
||||
<a href="https://www.instagram.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/instagram-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="Instagram" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
<a href="https://www.tiktok.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/tiktok-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="TikTok" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
<a href="https://www.youtube.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/youtube-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="YouTube" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
<a href="https://www.x.com" target="_blank" style="text-decoration: none;">
|
||||
<img src=<%= asset_url("/email/twitter-x-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="X.com" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<%= I18n.t "mailer_layout.footer.copyright" -%>
|
||||
</p>
|
||||
<small>
|
||||
<% if @user && @user.unsubscribe_token -%>
|
||||
<p>
|
||||
<%= I18n.t "mailer_layout.footer.paragraph1" -%> <a href="https://www.jamkazam.com" target="_blank">JamKazam</a>. <br /> <%= I18n.t "mailer_layout.footer.you_can" -%> <a
|
||||
|
||||
href="https://www.jamkazam.com/unsubscribe/<%= @user.unsubscribe_token %>"
|
||||
style="
|
||||
color: #2c7be5;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid #2c7be5;
|
||||
"
|
||||
><%= I18n.t "mailer_layout.footer.unsubscribe" -%></a>
|
||||
<%= I18n.t "mailer_layout.footer.to_stop_receiving_emails" -%>
|
||||
</p>
|
||||
<% end -%>
|
||||
<div style="text-align: center; margin: 1em 0;">
|
||||
<span style="margin-right: 1em;">
|
||||
<%= I18n.t "mailer_layout.footer.connect_with_us" -%>
|
||||
</span>
|
||||
<a href="https://www.facebook.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/fb-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="Facebook" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
|
||||
</a>
|
||||
<a href="https://www.instagram.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/instagram-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="Instagram" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
<a href="https://www.tiktok.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/tiktok-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="TikTok" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
<a href="https://www.youtube.com" target="_blank" style="text-decoration: none;">
|
||||
<img src="<%= asset_url("/email/youtube-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="YouTube" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
<a href="https://www.x.com" target="_blank" style="text-decoration: none;">
|
||||
<img src=<%= asset_url("/email/twitter-x-icon.svg", host: APP_CONFIG.action_mailer.asset_host ) -%>" alt="X.com" style="width: 24px; height: 24px; margin: 0 5px;">
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<%= I18n.t "mailer_layout.footer.copyright" -%>
|
||||
</p>
|
||||
</small>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -263,7 +263,6 @@
|
|||
|
||||
// called from sidebar when messages come in
|
||||
function messageReceived(payload) {
|
||||
alert("messageReceived");
|
||||
if(showing && otherId == payload.sender_id) {
|
||||
if(fullyInitialized) {
|
||||
renderMessage(payload.msg, payload.sender_id, payload.sender_name, payload.created_at, true);
|
||||
|
|
|
|||
|
|
@ -17,4 +17,26 @@ class ApiSessionsController < ApiController
|
|||
render :json => {}, :status => :ok
|
||||
end
|
||||
end
|
||||
|
||||
#update password token. inteanded for the react app (spa)
|
||||
def request_reset_password
|
||||
begin
|
||||
url = APP_CONFIG.spa_origin + '/authentication/basic'
|
||||
User.reset_password(params[:email], url)
|
||||
render :json => {}, :status => 204
|
||||
rescue JamRuby::JamArgumentError
|
||||
render :json => {:message => ValidationMessages::EMAIL_NOT_FOUND}, :status => 403
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def reset_forgot_password
|
||||
begin
|
||||
User.set_password_from_token(params[:email], params[:token], params[:password], params[:password_confirmation])
|
||||
render :json => {}, :status => 204
|
||||
rescue JamRuby::JamArgumentError => e
|
||||
render :json => {:message => e.field_message}, :status => 403
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ if !current_user
|
|||
}
|
||||
else
|
||||
|
||||
attributes :id, :name, :description, :musician_access, :approval_required, :friends_can_join, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id, :jam_track_id, :music_session_id_int, :use_video_conferencing_server
|
||||
attributes :id, :name, :description, :musician_access, :approval_required, :friends_can_join, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id, :jam_track_id, :music_session_id_int, :use_video_conferencing_server, :created_at
|
||||
|
||||
if @on_join
|
||||
node :subscription do |session|
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
object @cart
|
||||
extends "api_shopping_carts/show"
|
||||
|
||||
node :show_free_jamtrack do
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
object @cart
|
||||
|
||||
extends "api_shopping_carts/show"
|
||||
|
||||
node :show_free_jamtrack do
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ en:
|
|||
mailer_layout:
|
||||
footer:
|
||||
paragraph1: "This email was sent to you because you have an account at"
|
||||
unsubscribe: "Unsubscribe"
|
||||
you_can: "You can"
|
||||
unsubscribe: "unsubscribe"
|
||||
to_stop_receiving_emails: "to stop receiving emails from us."
|
||||
copyright: "Copyright © 2024 JamKazam, Inc. All rights reserved."
|
||||
connect_with_us: "Connect with us:"
|
||||
user_mailer:
|
||||
confirm_email:
|
||||
subject: "Please confirm your JamKazam email address"
|
||||
|
|
@ -30,7 +33,7 @@ en:
|
|||
view_profile: "View Profile"
|
||||
send_message: "Send Message"
|
||||
send_friend_request: "Send Friend Request"
|
||||
paragraph2: "To find great musical matches across the entire JamKazam commiunity and make new connections, use the button below to access our musician search feature. This let you filter JamKazammers by latency, instruments, skill level, genre interests, last active day and more."
|
||||
paragraph2: "To find great musical matches across the entire JamKazam community and make new connections, use the button below to access our musician search feature. This lets you filter JamKazammers by latency, instruments, skill level, genre interests, last active date and more."
|
||||
search_musicians: "Search JamKazam Musicians"
|
||||
|
||||
welcome_message:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ en:
|
|||
mailer_layout:
|
||||
footer:
|
||||
paragraph1: "This email was sent to you because you have an account at"
|
||||
unsubscribe: "Unsubscribe"
|
||||
unsubscribe: "unsubscribe"
|
||||
copyright: "Copyright © 2024 JamKazam, Inc. All rights reserved."
|
||||
user_mailer:
|
||||
confirm_email:
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ Rails.application.routes.draw do
|
|||
root to: 'users#home'
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
# signup, and signup completed, related pages
|
||||
get '/signup', to: 'users#new'
|
||||
post '/signup', to: 'users#create'
|
||||
|
|
@ -32,6 +29,8 @@ Rails.application.routes.draw do
|
|||
delete '/signout', to: 'sessions#destroy'
|
||||
get '/passthrough', to: 'sessions#passthrough'
|
||||
|
||||
|
||||
|
||||
match '/redeem_giftcard', to: 'landings#redeem_giftcard', via: :get
|
||||
match '/account/activate/code_old', to: 'landings#account_activate', via: :get
|
||||
match '/account/activate/code', to: 'landings#amazon_lessons_promo_1', via: :get
|
||||
|
|
@ -407,8 +406,13 @@ Rails.application.routes.draw do
|
|||
match '/users/authorizations/google' => 'api_users#google_auth', :via => :get
|
||||
match '/users/:id/set_password' => 'api_users#set_password', :via => :post
|
||||
match '/users/:id/request_reset_password' => 'api_users#request_reset_password', :via => :post
|
||||
match '/users/:id/reset_password' => 'api_users#reset_password', :via => :post
|
||||
match '/users/:id/app_interactions' => 'api_users#post_app_interactions', :via => :post
|
||||
|
||||
#reset forgot password (not logged in)
|
||||
post '/request_reset_forgot_password', to: 'api_sessions#request_reset_password'
|
||||
post '/reset_forgot_password', to: 'api_sessions#reset_forgot_password'
|
||||
|
||||
match '/reviews' => 'api_reviews#index', :via => :get
|
||||
match '/reviews' => 'api_reviews#create', :via => :post
|
||||
match '/reviews/:id' => 'api_reviews#update', :via => :post
|
||||
|
|
|
|||
Loading…
Reference in New Issue