Merge remote-tracking branch 'remotes/dartandrevinsky/private-event-sourcing-examples-38' into private-event-sourcing-examples-38
This commit is contained in:
@@ -18,7 +18,6 @@ public class AccountInfo {
|
||||
private long balance;
|
||||
private List<AccountChangeInfo> changes;
|
||||
private Map<String, AccountTransactionInfo> transactions;
|
||||
private Map<String, TransferState> transferStates;
|
||||
private String version;
|
||||
private Date date;
|
||||
|
||||
@@ -77,12 +76,4 @@ public class AccountInfo {
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public Map<String, TransferState> getTransferStates() {
|
||||
return transferStates;
|
||||
}
|
||||
|
||||
public void setTransferStates(Map<String, TransferState> transferStates) {
|
||||
this.transferStates = transferStates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,12 @@ public class AccountInfoUpdateService {
|
||||
|
||||
|
||||
public void addTransaction(String accountId, AccountTransactionInfo ti) {
|
||||
System.out.println("Start addTransaction for: "+ti.toString());
|
||||
mongoTemplate.upsert(new Query(where("id").is(accountId)),
|
||||
new Update().
|
||||
set("transactions." + ti.getTransactionId(), ti),
|
||||
AccountInfo.class);
|
||||
System.out.println("End addTransaction for: "+ti.toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -76,9 +78,11 @@ public class AccountInfoUpdateService {
|
||||
}
|
||||
|
||||
public void updateTransactionStatus(String accountId, String transactionId, TransferState status) {
|
||||
System.out.println("Start updateTransactionStatus "+accountId +" "+transactionId+" "+status);
|
||||
mongoTemplate.upsert(new Query(where("id").is(accountId)),
|
||||
new Update().
|
||||
set("transferStates." + transactionId, status),
|
||||
set("transactions." + transactionId + ".status", status),
|
||||
AccountInfo.class);
|
||||
System.out.println("End updateTransactionStatus "+accountId +" "+transactionId+" "+status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ public class AccountQueryService {
|
||||
if (account == null)
|
||||
throw new AccountNotFoundException(accountId);
|
||||
else
|
||||
if(account.getTransferStates()!=null)
|
||||
account.getTransactions().stream().forEach(ati -> ati.setStatus(account.getTransferStates().get(ati.getTransactionId())));
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class AuthController {
|
||||
|
||||
@RequestMapping(value = "/login", method = POST)
|
||||
public ResponseEntity<QuerySideCustomer> doAuth(@RequestBody @Valid AuthRequest request) throws IOException {
|
||||
QuerySideCustomer customer = customerAuthService.findByEmail(request.getEmail());
|
||||
QuerySideCustomer customer = customerAuthService.findByEmailAndPassword(request.getEmail(), request.getPassword());
|
||||
|
||||
Token token = tokenService.allocateToken(objectMapper.writeValueAsString(new User(request.getEmail())));
|
||||
return ResponseEntity.status(HttpStatus.OK).header("access-token", token.getKey())
|
||||
|
||||
@@ -12,11 +12,15 @@ public class AuthRequest {
|
||||
@Email
|
||||
private String email;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
|
||||
public AuthRequest() {
|
||||
}
|
||||
|
||||
public AuthRequest(String email) {
|
||||
public AuthRequest(String email, String password) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
@@ -26,4 +30,12 @@ public class AuthRequest {
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@@ -18,6 +19,7 @@ import org.springframework.security.core.token.KeyBasedPersistenceTokenService;
|
||||
import org.springframework.security.core.token.TokenService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
@@ -50,15 +52,13 @@ public class AuthConfiguration extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
public UserDetailsService userDetailsServiceBean() {
|
||||
return email -> {
|
||||
/* QuerySideCustomer customer = customerAuthService.findByEmail(email);
|
||||
if (customer != null) {
|
||||
return new User(email);
|
||||
} else {
|
||||
throw new UsernameNotFoundException(String.format("could not find the customer '%s'", email));
|
||||
}*/
|
||||
//authorize everyone with basic authentication
|
||||
return new User(email, "", true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("USER"));
|
||||
QuerySideCustomer customer = customerAuthService.findByEmail(email);
|
||||
if (customer != null) {
|
||||
return new User(email, customer.getPassword(), true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("USER"));
|
||||
} else {
|
||||
throw new UsernameNotFoundException(String.format("could not find the customer '%s'", email));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,4 +8,6 @@ import java.util.List;
|
||||
interface CustomerAuthRepository extends MongoRepository<QuerySideCustomer, String> {
|
||||
|
||||
List<QuerySideCustomer> findByEmail(String email);
|
||||
|
||||
List<QuerySideCustomer> findByEmailAndPassword(String email, String password);
|
||||
}
|
||||
@@ -19,9 +19,14 @@ public class CustomerAuthService {
|
||||
List<QuerySideCustomer> customers = customerAuthRepository.findByEmail(email);
|
||||
if (customers.isEmpty())
|
||||
throw new EmptyResultDataAccessException(1);
|
||||
//TODO: add unique email constraint
|
||||
/* else if(customers.size()>1)
|
||||
throw new IncorrectResultSizeDataAccessException(1, customers.size());*/
|
||||
else
|
||||
return customers.get(0);
|
||||
}
|
||||
|
||||
public QuerySideCustomer findByEmailAndPassword(String email, String password) {
|
||||
List<QuerySideCustomer> customers = customerAuthRepository.findByEmailAndPassword(email, password);
|
||||
if (customers.isEmpty())
|
||||
throw new EmptyResultDataAccessException(1);
|
||||
else
|
||||
return customers.get(0);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ apply plugin: 'java'
|
||||
dependencies {
|
||||
compile "commons-lang:commons-lang:2.6"
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
|
||||
|
||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public class CustomerInfo {
|
||||
@NotNull
|
||||
protected String email;
|
||||
@NotNull
|
||||
protected String password;
|
||||
@NotNull
|
||||
protected String ssn;
|
||||
@NotNull
|
||||
protected String phoneNumber;
|
||||
@@ -21,9 +23,10 @@ public class CustomerInfo {
|
||||
public CustomerInfo() {
|
||||
}
|
||||
|
||||
public CustomerInfo(Name name, String email, String ssn, String phoneNumber, Address address) {
|
||||
public CustomerInfo(Name name, String email, String password, String ssn, String phoneNumber, Address address) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.ssn = ssn;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.address = address;
|
||||
@@ -37,6 +40,10 @@ public class CustomerInfo {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getSsn() {
|
||||
return ssn;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
|
||||
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Main on 05.02.2016.
|
||||
*/
|
||||
public class QuerySideCustomer {
|
||||
|
||||
private String id;
|
||||
private Name name;
|
||||
@Indexed(unique=true)
|
||||
private String email;
|
||||
private String password;
|
||||
private String ssn;
|
||||
private String phoneNumber;
|
||||
private Address address;
|
||||
@@ -17,10 +22,11 @@ public class QuerySideCustomer {
|
||||
public QuerySideCustomer() {
|
||||
}
|
||||
|
||||
public QuerySideCustomer(String id, Name name, String email, String ssn, String phoneNumber, Address address, Map<String, ToAccountInfo> toAccounts) {
|
||||
public QuerySideCustomer(String id, Name name, String email, String password, String ssn, String phoneNumber, Address address, Map<String, ToAccountInfo> toAccounts) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.ssn = ssn;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.address = address;
|
||||
@@ -51,6 +57,14 @@ public class QuerySideCustomer {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getSsn() {
|
||||
return ssn;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ public class CustomerInfoUpdateService {
|
||||
querySideCustomerRepository.save(new QuerySideCustomer(id,
|
||||
customerInfo.getName(),
|
||||
customerInfo.getEmail(),
|
||||
customerInfo.getPassword(),
|
||||
customerInfo.getSsn(),
|
||||
customerInfo.getPhoneNumber(),
|
||||
customerInfo.getAddress(),
|
||||
|
||||
@@ -14,6 +14,7 @@ dependencies {
|
||||
testCompile project(":testutil")
|
||||
testCompile project(":customers-command-side-service")
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -48,8 +48,10 @@ public class CustomersQuerySideServiceIntegrationTest {
|
||||
|
||||
final CustomerResponse customerResponse = restTemplate.postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
|
||||
final String customerId = customerResponse.getId();
|
||||
final String email = customerResponse.getCustomerInfo().getEmail();
|
||||
final String password = customerResponse.getCustomerInfo().getPassword();
|
||||
|
||||
customersTestUtils.assertCustomerResponse(customerId, customerInfo);
|
||||
customersTestUtils.assertCustomerResponse(customerId, email, password, customerInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web;
|
||||
|
||||
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers.CustomersCommandSideWebConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.CustomersQuerySideWebConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -14,7 +17,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@Import({CustomersCommandSideServiceConfiguration.class, CustomersQuerySideServiceConfiguration.class, AuthConfiguration.class})
|
||||
@Import({CustomersCommandSideWebConfiguration.class, CustomersQuerySideWebConfiguration.class, EventuateJdbcEventStoreConfiguration.class, AuthConfiguration.class})
|
||||
@EnableAutoConfiguration
|
||||
public class CustomersQuerySideServiceTestConfiguration {
|
||||
|
||||
|
||||
@@ -53,19 +53,14 @@ public class BankingAuthTest {
|
||||
|
||||
final CustomerResponse customerResponse = restTemplate.postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
|
||||
final String customerId = customerResponse.getId();
|
||||
final String password = customerResponse.getCustomerInfo().getPassword();
|
||||
|
||||
Assert.assertNotNull(customerId);
|
||||
Assert.assertEquals(customerInfo, customerResponse.getCustomerInfo());
|
||||
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
customersTestUtils.assertCustomerResponse(customerId, email, password, customerInfo);
|
||||
|
||||
customersTestUtils.assertCustomerResponse(customerId, customerInfo);
|
||||
|
||||
AuthRequest authRequest = new AuthRequest(email);
|
||||
AuthRequest authRequest = new AuthRequest(email, password);
|
||||
|
||||
final QuerySideCustomer loginQuerySideCustomer = restTemplate.postForEntity(baseUrl("/login"), authRequest, QuerySideCustomer.class).getBody();
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ public abstract class AbstractRestAPITest {
|
||||
|
||||
//@Test
|
||||
public void shouldCreateAccountsAndTransferMoney() {
|
||||
CustomerInfo customerInfo = generateCustomerInfo();
|
||||
|
||||
final CustomerResponse customerResponse = getRestTemplate().postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
|
||||
final String customerId = customerResponse.getId();
|
||||
final String email = customerResponse.getCustomerInfo().getEmail();
|
||||
final String password = customerResponse.getCustomerInfo().getPassword();
|
||||
|
||||
BigDecimal initialFromAccountBalance = new BigDecimal(500);
|
||||
BigDecimal initialToAccountBalance = new BigDecimal(100);
|
||||
BigDecimal amountToTransfer = new BigDecimal(150);
|
||||
@@ -35,36 +42,36 @@ public abstract class AbstractRestAPITest {
|
||||
BigDecimal finalToAccountBalance = initialToAccountBalance.add(amountToTransfer);
|
||||
|
||||
final CreateAccountResponse fromAccount = getAuthenticatedRestTemplate().postForEntity(baseUrl("/accounts"),
|
||||
new CreateAccountRequest("00000000-00000000", "My 1 Account", "", initialFromAccountBalance),
|
||||
CreateAccountResponse.class);
|
||||
new CreateAccountRequest(customerId, "My 1 Account", "", initialFromAccountBalance),
|
||||
CreateAccountResponse.class, email, password);
|
||||
|
||||
final String fromAccountId = fromAccount.getAccountId();
|
||||
|
||||
CreateAccountResponse toAccount = getAuthenticatedRestTemplate().postForEntity(baseUrl("/accounts"),
|
||||
new CreateAccountRequest("00000000-00000000", "My 2 Account", "", initialToAccountBalance),
|
||||
CreateAccountResponse.class);
|
||||
new CreateAccountRequest(customerId, "My 2 Account", "", initialToAccountBalance),
|
||||
CreateAccountResponse.class, email, password);
|
||||
|
||||
String toAccountId = toAccount.getAccountId();
|
||||
|
||||
Assert.assertNotNull(fromAccountId);
|
||||
Assert.assertNotNull(toAccountId);
|
||||
|
||||
assertAccountBalance(fromAccountId, initialFromAccountBalance);
|
||||
assertAccountBalance(toAccountId, initialToAccountBalance);
|
||||
assertAccountBalance(email, password, fromAccountId, initialFromAccountBalance);
|
||||
assertAccountBalance(email, password, toAccountId, initialToAccountBalance);
|
||||
|
||||
final CreateMoneyTransferResponse moneyTransfer = getAuthenticatedRestTemplate().postForEntity(baseUrl("/transfers"),
|
||||
new CreateMoneyTransferRequest(fromAccountId, toAccountId, amountToTransfer, ""),
|
||||
CreateMoneyTransferResponse.class);
|
||||
CreateMoneyTransferResponse.class, email, password);
|
||||
|
||||
assertAccountBalance(fromAccountId, finalFromAccountBalance);
|
||||
assertAccountBalance(toAccountId, finalToAccountBalance);
|
||||
assertAccountBalance(email, password, fromAccountId, finalFromAccountBalance);
|
||||
assertAccountBalance(email, password, toAccountId, finalToAccountBalance);
|
||||
|
||||
eventually(
|
||||
new Producer<AccountHistoryResponse>() {
|
||||
@Override
|
||||
public CompletableFuture<AccountHistoryResponse> produce() {
|
||||
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/accounts/" + fromAccountId + "/history"),
|
||||
AccountHistoryResponse.class));
|
||||
AccountHistoryResponse.class, email, password));
|
||||
}
|
||||
},
|
||||
new Verifier<AccountHistoryResponse>() {
|
||||
@@ -92,28 +99,30 @@ public abstract class AbstractRestAPITest {
|
||||
|
||||
final CustomerResponse customerResponse = getRestTemplate().postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
|
||||
final String customerId = customerResponse.getId();
|
||||
final String email = customerResponse.getCustomerInfo().getEmail();
|
||||
final String password = customerResponse.getCustomerInfo().getPassword();
|
||||
|
||||
Assert.assertNotNull(customerId);
|
||||
assertEquals(customerInfo, customerResponse.getCustomerInfo());
|
||||
|
||||
getCustomersTestUtils().assertCustomerResponse(customerId, customerInfo);
|
||||
getCustomersTestUtils().assertCustomerResponse(customerId, email, password, customerInfo);
|
||||
|
||||
final CreateAccountResponse account = getAuthenticatedRestTemplate().postForEntity(baseUrl("/accounts"),
|
||||
new CreateAccountRequest(customerId, "My 1 Account", "", initialFromAccountBalance),
|
||||
CreateAccountResponse.class);
|
||||
CreateAccountResponse.class, email, password);
|
||||
|
||||
final String accountId = account.getAccountId();
|
||||
|
||||
Assert.assertNotNull(accountId);
|
||||
|
||||
assertAccountBalance(accountId, initialFromAccountBalance);
|
||||
assertAccountBalance(email, password, accountId, initialFromAccountBalance);
|
||||
|
||||
eventually(
|
||||
new Producer<GetAccountsResponse>() {
|
||||
@Override
|
||||
public CompletableFuture<GetAccountsResponse> produce() {
|
||||
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/customers/"+customerId+"/accounts"),
|
||||
GetAccountsResponse.class));
|
||||
GetAccountsResponse.class, email, password));
|
||||
}
|
||||
},
|
||||
new Verifier<GetAccountsResponse>() {
|
||||
@@ -148,33 +157,35 @@ public abstract class AbstractRestAPITest {
|
||||
|
||||
final CustomerResponse customerResponse = getRestTemplate().postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
|
||||
final String customerId = customerResponse.getId();
|
||||
final String email = customerResponse.getCustomerInfo().getEmail();
|
||||
final String password = customerResponse.getCustomerInfo().getPassword();
|
||||
|
||||
Assert.assertNotNull(customerId);
|
||||
assertEquals(customerInfo, customerResponse.getCustomerInfo());
|
||||
|
||||
getCustomersTestUtils().assertCustomerResponse(customerId, customerInfo);
|
||||
getCustomersTestUtils().assertCustomerResponse(customerId, email, password, customerInfo);
|
||||
|
||||
ToAccountInfo toAccountInfo = generateToAccountInfo();
|
||||
|
||||
getAuthenticatedRestTemplate().postForEntity(baseUrl("/customers/" + customerId + "/toaccounts"),
|
||||
toAccountInfo,
|
||||
null);
|
||||
null, email, password);
|
||||
|
||||
assertToAccountsContains(customerId, toAccountInfo);
|
||||
assertToAccountsContains(customerId, email, password, toAccountInfo);
|
||||
}
|
||||
|
||||
private BigDecimal toCents(BigDecimal dollarAmount) {
|
||||
return dollarAmount.multiply(new BigDecimal(100));
|
||||
}
|
||||
|
||||
private void assertAccountBalance(final String fromAccountId, final BigDecimal expectedBalanceInDollars) {
|
||||
private void assertAccountBalance(final String email, final String password, final String fromAccountId, final BigDecimal expectedBalanceInDollars) {
|
||||
final BigDecimal inCents = toCents(expectedBalanceInDollars);
|
||||
eventually(
|
||||
new Producer<GetAccountResponse>() {
|
||||
@Override
|
||||
public CompletableFuture<GetAccountResponse> produce() {
|
||||
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/accounts/" + fromAccountId),
|
||||
GetAccountResponse.class));
|
||||
GetAccountResponse.class, email, password));
|
||||
}
|
||||
},
|
||||
new Verifier<GetAccountResponse>() {
|
||||
@@ -186,13 +197,13 @@ public abstract class AbstractRestAPITest {
|
||||
});
|
||||
}
|
||||
|
||||
private void assertToAccountsContains(final String customerId, final ToAccountInfo toAccountInfo) {
|
||||
private void assertToAccountsContains(final String customerId, final String email, final String password, final ToAccountInfo toAccountInfo) {
|
||||
eventually(
|
||||
new Producer<QuerySideCustomer>() {
|
||||
@Override
|
||||
public CompletableFuture<QuerySideCustomer> produce() {
|
||||
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/customers/" + customerId),
|
||||
QuerySideCustomer.class));
|
||||
QuerySideCustomer.class, email, password));
|
||||
}
|
||||
},
|
||||
new Verifier<QuerySideCustomer>() {
|
||||
|
||||
@@ -11,19 +11,23 @@ public class AuthenticatedRestTemplate {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public <T> T getForEntity(String url, Class<T> clazz) {
|
||||
public <T> T getForEntity(String url, Class<T> clazz, String email, String password) {
|
||||
return BasicAuthUtils.doBasicAuthenticatedRequest(restTemplate,
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
clazz);
|
||||
clazz,
|
||||
email,
|
||||
password);
|
||||
}
|
||||
|
||||
public <T> T postForEntity(String url, Object requestObject, Class<T> clazz) {
|
||||
public <T> T postForEntity(String url, Object requestObject, Class<T> clazz, String email, String password) {
|
||||
return BasicAuthUtils.doBasicAuthenticatedRequest(restTemplate,
|
||||
url,
|
||||
HttpMethod.POST,
|
||||
clazz,
|
||||
requestObject
|
||||
requestObject,
|
||||
email,
|
||||
password
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import java.nio.charset.Charset;
|
||||
*/
|
||||
public class BasicAuthUtils {
|
||||
|
||||
public static HttpHeaders basicAuthHeaders(String username) {
|
||||
public static HttpHeaders basicAuthHeaders(String username, String password) {
|
||||
return new HttpHeaders() {
|
||||
{
|
||||
String auth = username + ":";
|
||||
String auth = username + ":" + password;
|
||||
byte[] encodedAuth = Base64.encodeBase64(
|
||||
auth.getBytes(Charset.forName("US-ASCII")));
|
||||
String authHeader = "Basic " + new String(encodedAuth);
|
||||
@@ -24,16 +24,16 @@ public class BasicAuthUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType) {
|
||||
return doBasicAuthenticatedRequest(restTemplate, url, httpMethod, responseType, null);
|
||||
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, String email, String password) {
|
||||
return doBasicAuthenticatedRequest(restTemplate, url, httpMethod, responseType, null, email, password);
|
||||
}
|
||||
|
||||
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, Object requestObject) {
|
||||
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, Object requestObject, String email, String password) {
|
||||
HttpEntity httpEntity;
|
||||
if (requestObject != null) {
|
||||
httpEntity = new HttpEntity<>(requestObject, BasicAuthUtils.basicAuthHeaders("test_user@mail.com"));
|
||||
httpEntity = new HttpEntity<>(requestObject, BasicAuthUtils.basicAuthHeaders(email, password));
|
||||
} else {
|
||||
httpEntity = new HttpEntity(BasicAuthUtils.basicAuthHeaders("test_user@mail.com"));
|
||||
httpEntity = new HttpEntity(BasicAuthUtils.basicAuthHeaders(email, password));
|
||||
}
|
||||
|
||||
ResponseEntity<T> responseEntity = restTemplate.exchange(url,
|
||||
|
||||
@@ -21,13 +21,13 @@ public class CustomersTestUtils {
|
||||
this.customersBaseUrl = customersBaseUrl;
|
||||
}
|
||||
|
||||
public void assertCustomerResponse(final String customerId, final CustomerInfo customerInfo) {
|
||||
public void assertCustomerResponse(final String customerId, final String email, final String password, final CustomerInfo customerInfo) {
|
||||
AuthenticatedRestTemplate art = new AuthenticatedRestTemplate(restTemplate);
|
||||
eventually(
|
||||
new Producer<QuerySideCustomer>() {
|
||||
@Override
|
||||
public CompletableFuture<QuerySideCustomer> produce() {
|
||||
return CompletableFuture.completedFuture(art.getForEntity(customersBaseUrl + customerId, QuerySideCustomer.class));
|
||||
return CompletableFuture.completedFuture(art.getForEntity(customersBaseUrl + customerId, QuerySideCustomer.class, email, password));
|
||||
}
|
||||
},
|
||||
new Verifier<QuerySideCustomer>() {
|
||||
@@ -55,6 +55,7 @@ public class CustomersTestUtils {
|
||||
return new CustomerInfo(
|
||||
new Name("John", "Doe"),
|
||||
email,
|
||||
"simple_password",
|
||||
"000-00-0000",
|
||||
"1-111-111-1111",
|
||||
new Address("street 1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1
js-frontend/build/app.0f8c47485a49f44c2404.js.map
Normal file
1
js-frontend/build/app.0f8c47485a49f44c2404.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -12,8 +12,8 @@
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap-theme.min.css"><link href="/style.6d7a32b1405ea1bb2bdf.css" rel="stylesheet"></head>
|
||||
<body><div id="root"></div><script src="/manifest.087a5454fa0c34daf3c9.js"></script><script src="/vendor.c882d66445aebc52c21b.js"></script><script src="/style.6d7a32b1405ea1bb2bdf.js"></script><script src="/app.fcbedf54f0345474ccc1.js"></script><script>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap-theme.min.css"><link href="/style.6734b6ac5d23d61b8e5f.css" rel="stylesheet"></head>
|
||||
<body><div id="root"></div><script src="/manifest.445ede0f13a048c8cbfa.js"></script><script src="/vendor.d3d74fb754fa5fff76bc.js"></script><script src="/style.6734b6ac5d23d61b8e5f.js"></script><script src="/app.0f8c47485a49f44c2404.js"></script><script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
@@ -27,5 +27,5 @@
|
||||
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script><!--{"files":{"publicPath":"/","chunks":{"manifest":{"size":0,"entry":"/manifest.087a5454fa0c34daf3c9.js","hash":"087a5454fa0c34daf3c9","css":[]},"vendor":{"size":1670874,"entry":"/vendor.c882d66445aebc52c21b.js","hash":"c882d66445aebc52c21b","css":[]},"style":{"size":122,"entry":"/style.6d7a32b1405ea1bb2bdf.js","hash":"6d7a32b1405ea1bb2bdf","css":["/style.6d7a32b1405ea1bb2bdf.css"]},"app":{"size":352314,"entry":"/app.fcbedf54f0345474ccc1.js","hash":"fcbedf54f0345474ccc1","css":[]}},"js":["/manifest.087a5454fa0c34daf3c9.js","/vendor.c882d66445aebc52c21b.js","/style.6d7a32b1405ea1bb2bdf.js","/app.fcbedf54f0345474ccc1.js"],"css":["/style.6d7a32b1405ea1bb2bdf.css"]},"options":{"template":"/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/node_modules/html-webpack-plugin/lib/loader.js!/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/public/index.ejs","filename":"index.html","hash":false,"inject":false,"compile":true,"favicon":false,"minify":false,"cache":true,"showErrors":true,"chunks":"all","excludeChunks":[],"title":"Money Transfer App","xhtml":false,"description":"ES Money Transfer App","appMountId":"root","googleAnalytics":{"trackingId":"UA-XXXX-XX","pageViewOnLoad":true},"mobile":true}}--></body>
|
||||
</script><!--{"files":{"publicPath":"/","chunks":{"manifest":{"size":0,"entry":"/manifest.445ede0f13a048c8cbfa.js","hash":"445ede0f13a048c8cbfa","css":[]},"vendor":{"size":1670874,"entry":"/vendor.d3d74fb754fa5fff76bc.js","hash":"d3d74fb754fa5fff76bc","css":[]},"style":{"size":122,"entry":"/style.6734b6ac5d23d61b8e5f.js","hash":"6734b6ac5d23d61b8e5f","css":["/style.6734b6ac5d23d61b8e5f.css"]},"app":{"size":351839,"entry":"/app.0f8c47485a49f44c2404.js","hash":"0f8c47485a49f44c2404","css":[]}},"js":["/manifest.445ede0f13a048c8cbfa.js","/vendor.d3d74fb754fa5fff76bc.js","/style.6734b6ac5d23d61b8e5f.js","/app.0f8c47485a49f44c2404.js"],"css":["/style.6734b6ac5d23d61b8e5f.css"]},"options":{"template":"/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/node_modules/html-webpack-plugin/lib/loader.js!/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/public/index.ejs","filename":"index.html","hash":false,"inject":false,"compile":true,"favicon":false,"minify":false,"cache":true,"showErrors":true,"chunks":"all","excludeChunks":[],"title":"Money Transfer App","xhtml":false,"description":"ES Money Transfer App","appMountId":"root","googleAnalytics":{"trackingId":"UA-XXXX-XX","pageViewOnLoad":true},"mobile":true}}--></body>
|
||||
</html>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
/******/ script.charset = 'utf-8';
|
||||
/******/ script.async = true;
|
||||
/******/
|
||||
/******/ script.src = __webpack_require__.p + "" + {"0":"fcbedf54f0345474ccc1","1":"6d7a32b1405ea1bb2bdf","2":"c882d66445aebc52c21b"}[chunkId] + ".js";
|
||||
/******/ script.src = __webpack_require__.p + "" + {"0":"0f8c47485a49f44c2404","1":"6734b6ac5d23d61b8e5f","2":"d3d74fb754fa5fff76bc"}[chunkId] + ".js";
|
||||
/******/ head.appendChild(script);
|
||||
/******/ }
|
||||
/******/ };
|
||||
@@ -92,4 +92,4 @@
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([]);
|
||||
//# sourceMappingURL=manifest.087a5454fa0c34daf3c9.js.map
|
||||
//# sourceMappingURL=manifest.445ede0f13a048c8cbfa.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["webpack:///webpack/bootstrap edd3ecd6d3192330eb69?"],"names":[],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAQ,oBAAoB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,YAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uDAA+C,iFAAiF;AAChI;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA","file":"manifest.087a5454fa0c34daf3c9.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t3:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"\" + {\"0\":\"fcbedf54f0345474ccc1\",\"1\":\"6d7a32b1405ea1bb2bdf\",\"2\":\"c882d66445aebc52c21b\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap edd3ecd6d3192330eb69\n **/"],"sourceRoot":""}
|
||||
{"version":3,"sources":["webpack:///webpack/bootstrap 876686e379ba43dfdb45?"],"names":[],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAQ,oBAAoB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,YAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uDAA+C,iFAAiF;AAChI;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA","file":"manifest.445ede0f13a048c8cbfa.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t3:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"\" + {\"0\":\"0f8c47485a49f44c2404\",\"1\":\"6734b6ac5d23d61b8e5f\",\"2\":\"d3d74fb754fa5fff76bc\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap 876686e379ba43dfdb45\n **/"],"sourceRoot":""}
|
||||
@@ -438,4 +438,4 @@ body {
|
||||
h1 {
|
||||
margin-top: .5em;
|
||||
}
|
||||
/*# sourceMappingURL=style.6d7a32b1405ea1bb2bdf.css.map*/
|
||||
/*# sourceMappingURL=style.6734b6ac5d23d61b8e5f.css.map*/
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"style.6d7a32b1405ea1bb2bdf.css","sourceRoot":""}
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"style.6734b6ac5d23d61b8e5f.css","sourceRoot":""}
|
||||
@@ -3,20 +3,20 @@ webpackJsonp([1,3],{
|
||||
/***/ 0:
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
__webpack_require__(614);
|
||||
module.exports = __webpack_require__(618);
|
||||
__webpack_require__(611);
|
||||
module.exports = __webpack_require__(615);
|
||||
|
||||
|
||||
/***/ },
|
||||
|
||||
/***/ 614:
|
||||
/***/ 611:
|
||||
/***/ function(module, exports) {
|
||||
|
||||
// removed by extract-text-webpack-plugin
|
||||
|
||||
/***/ },
|
||||
|
||||
/***/ 618:
|
||||
/***/ 615:
|
||||
/***/ function(module, exports) {
|
||||
|
||||
// removed by extract-text-webpack-plugin
|
||||
@@ -24,4 +24,4 @@ webpackJsonp([1,3],{
|
||||
/***/ }
|
||||
|
||||
});
|
||||
//# sourceMappingURL=style.6d7a32b1405ea1bb2bdf.js.map
|
||||
//# sourceMappingURL=style.6734b6ac5d23d61b8e5f.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["webpack:///./~/react-select/dist/react-select.css?","webpack:///./src/main.less?"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,0C;;;;;;;ACAA,0C","file":"style.6d7a32b1405ea1bb2bdf.js","sourcesContent":["// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./~/react-select/dist/react-select.css\n ** module id = 614\n ** module chunks = 1\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/main.less\n ** module id = 618\n ** module chunks = 1\n **/"],"sourceRoot":""}
|
||||
{"version":3,"sources":["webpack:///./~/react-select/dist/react-select.css?","webpack:///./src/main.less?"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,0C;;;;;;;ACAA,0C","file":"style.6734b6ac5d23d61b8e5f.js","sourcesContent":["// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./~/react-select/dist/react-select.css\n ** module id = 611\n ** module chunks = 1\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/main.less\n ** module id = 615\n ** module chunks = 1\n **/"],"sourceRoot":""}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ import { Provider, connect} from "react-redux";
|
||||
import thunk from "redux-thunk";
|
||||
import createLogger from 'redux-logger';
|
||||
import { Route, IndexRoute, Link, IndexLink } from "react-router";
|
||||
import { RouterContext } from 'react-router';
|
||||
import { ReduxRouter} from "redux-router";
|
||||
import { createHistory, createHashHistory, createMemoryHistory } from "history";
|
||||
import { pushState, routerStateReducer, reduxReactRouter as clientRouter} from "redux-router";
|
||||
@@ -16,6 +17,7 @@ import { reduxReactRouter as serverRouter } from "redux-router/server";
|
||||
import mainReducer from './reducers';
|
||||
|
||||
import { configure as endpointsConfig } from './actions/configure';
|
||||
import { visitLocation } from './actions/navigate';
|
||||
import { requireAuthentication } from './components/AuthComponent';
|
||||
import Container from "./components/partials/Container";
|
||||
import MyAccounts from "./views/MyAccounts";
|
||||
@@ -25,7 +27,8 @@ import SignUp from "./views/SignUp";
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (<Container>
|
||||
return (
|
||||
<Container>
|
||||
{this.props.children}
|
||||
</Container>);
|
||||
}
|
||||
@@ -38,12 +41,19 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
|
||||
router: routerStateReducer
|
||||
});
|
||||
|
||||
let dispatch = null;
|
||||
|
||||
const onEnter = (nextState) => {
|
||||
const { location } = nextState;
|
||||
dispatch && dispatch(visitLocation(location));
|
||||
};
|
||||
|
||||
const routes = (
|
||||
<Route path="/" component={App}>
|
||||
<IndexRoute component={requireAuthentication(MyAccounts)} />
|
||||
<Route path="signin" component={SignIn} />
|
||||
<Route path="register" component={SignUp} />
|
||||
<Route path="account/:accountId" component={requireAuthentication(Account)} />
|
||||
<Route path="/" component={ App }>
|
||||
<IndexRoute component={ requireAuthentication(MyAccounts) } />
|
||||
<Route path="signin" component={ SignIn } onEnter={ onEnter } />
|
||||
<Route path="register" component={ SignUp } onEnter={ onEnter } />
|
||||
<Route path="account/:accountId" component={ requireAuthentication(Account) } />
|
||||
</Route>
|
||||
);
|
||||
|
||||
@@ -60,6 +70,7 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
|
||||
})
|
||||
)(createStore)(reducer);
|
||||
|
||||
dispatch = store.dispatch;
|
||||
|
||||
/**
|
||||
* The React Router 1.0 routes for both the server and the client.
|
||||
@@ -85,13 +96,11 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
|
||||
},
|
||||
handleLoginResponse: function(resp) {
|
||||
debugger;
|
||||
|
||||
return resp.data;
|
||||
},
|
||||
|
||||
handleAccountUpdateResponse: function(resp) {
|
||||
debugger;
|
||||
|
||||
return resp.data;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Created by andrew on 25/02/16.
|
||||
*/
|
||||
|
||||
export function configureStart({...props} = {}) {
|
||||
return {
|
||||
...props,
|
||||
type: T.AUTH.CONFIGURE_START
|
||||
};
|
||||
}
|
||||
export function configureComplete({config, ...props} = {}) {
|
||||
return {
|
||||
...props,
|
||||
type: T.AUTH.CONFIGURE_COMPLETE,
|
||||
config
|
||||
};
|
||||
}
|
||||
export function configureError({errors, ...props} = {}) {
|
||||
return {
|
||||
...props,
|
||||
type: T.AUTH.CONFIGURE_ERROR,
|
||||
error: errors
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
/**
|
||||
* Created by andrew on 26/02/16.
|
||||
*/
|
||||
import * as C from "../utils/constants";
|
||||
import {
|
||||
authenticate,
|
||||
authenticateStart,
|
||||
authenticateComplete,
|
||||
authenticateError
|
||||
} from "./authenticate";
|
||||
|
||||
import {
|
||||
retrieveData,
|
||||
} from "../utils/sessionStorage";
|
||||
|
||||
|
||||
import {applyConfig} from "../utils/clientSettings";
|
||||
|
||||
//import {
|
||||
// showFirstTimeLoginSuccessModal,
|
||||
// showFirstTimeLoginErrorModal,
|
||||
// showPasswordResetSuccessModal,
|
||||
// showPasswordResetErrorModal
|
||||
//} from "./ui";
|
||||
|
||||
import getRedirectInfo from "../utils/parseUrl";
|
||||
import { pushState } from "redux-router";
|
||||
import root from '../utils/root';
|
||||
|
||||
export const SET_ENDPOINT_KEYS = "SET_ENDPOINT_KEYS";
|
||||
export const STORE_CURRENT_ENDPOINT_KEY = "STORE_CURRENT_ENDPOINT_KEY";
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ export const accountComplete = makeActionCreator(T.ACCOUNT.SINGLE_COMPLETE, 'pay
|
||||
export const accountError = makeActionCreator(T.ACCOUNT.SINGLE_ERROR, 'error');
|
||||
|
||||
|
||||
export function accountsList(userId) {
|
||||
export function accountsList(customerId) {
|
||||
return dispatch => {
|
||||
dispatch(accountsListRequested());
|
||||
return api.apiRetrieveAccounts(userId)
|
||||
.then(list => {
|
||||
dispatch(accountsListReceived(list));
|
||||
return api.apiRetrieveAccounts(customerId)
|
||||
.then(({ accounts = []}) => {
|
||||
dispatch(accountsListReceived(accounts));
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(accountsListError(err));
|
||||
@@ -148,11 +148,10 @@ export const deleteAccountError = makeActionCreator(T.ACCOUNT.DELETE_ERROR);
|
||||
export function deleteAccount(customerId, accountId) {
|
||||
return dispatch => {
|
||||
dispatch(deleteAccountRequested());
|
||||
return api.apiDeleteAccount(accountId)
|
||||
return api.apiDeleteAccount(customerId, accountId)
|
||||
.then(data => {
|
||||
//debugger;
|
||||
dispatch(deleteAccountComplete());
|
||||
return Promise.resolve('ok');
|
||||
dispatch(deleteAccountComplete(data));
|
||||
return Promise.resolve(data);
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(deleteAccountError());
|
||||
@@ -208,7 +207,7 @@ export const createRefOwnerLookup = lookup => {
|
||||
|
||||
export const createRefAccountLookup = customerId => {
|
||||
return dispatch => {
|
||||
dispatch(createRefAccountLookupStart());
|
||||
dispatch(createRefAccountLookupStart(customerId));
|
||||
return api.apiRetrieveAccounts(customerId)
|
||||
.then(({ accounts }) => {
|
||||
const arr = accounts.map(({ accountId, title }) => ({
|
||||
|
||||
7
js-frontend/src/actions/navigate.js
Normal file
7
js-frontend/src/actions/navigate.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Created by andrew on 26/02/16.
|
||||
*/
|
||||
import T from '../constants/ACTION_TYPES';
|
||||
import { makeActionCreator } from '../utils/actions';
|
||||
|
||||
export const visitLocation = makeActionCreator(T.LOCATION.ENTER, 'location');
|
||||
@@ -1,29 +0,0 @@
|
||||
import T from '../constants/ACTION_TYPES'
|
||||
import ROUTES from '../constants/ROUTES'
|
||||
|
||||
|
||||
// `navigate` is used to facilitate changing routes within another action
|
||||
// without rendering any other changes first
|
||||
export function start(name, options) {
|
||||
return dispatch => {
|
||||
const currentURI = window.location.hash.substr(1)
|
||||
const newURI = ROUTES.generate(name, options)
|
||||
|
||||
if (currentURI != newURI) {
|
||||
dispatch({
|
||||
type: T.NAVIGATION.START,
|
||||
})
|
||||
|
||||
window.location.replace(
|
||||
window.location.pathname + window.location.search + '#' + newURI
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function complete() {
|
||||
return {
|
||||
type: T.NAVIGATION.COMPLETE,
|
||||
location: ROUTES.lookup(window.location.hash.substr(1)),
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,11 @@
|
||||
/**
|
||||
* Created by andrew on 26/02/16.
|
||||
*/
|
||||
import {
|
||||
setCurrentEndpointKey,
|
||||
getCurrentEndpointKey,
|
||||
persistUserData
|
||||
} from "../utils/sessionStorage";
|
||||
|
||||
import { entityReceived } from './entities';
|
||||
import { storeCurrentEndpointKey } from "./configure";
|
||||
//import { parseResponse } from "../utils/handleFetchResponse";
|
||||
//import fetch from "../utils/fetch";
|
||||
|
||||
import { apiSignIn } from '../utils/api';
|
||||
import { makeActionCreator } from '../utils/actions';
|
||||
|
||||
import T from '../constants/ACTION_TYPES';
|
||||
|
||||
//import root from '../utils/root';
|
||||
import { makeActionCreator } from '../utils/actions';
|
||||
import { persistUserData } from "../utils/sessionStorage";
|
||||
import { entityReceived } from './entities';
|
||||
import { apiSignIn } from '../utils/api';
|
||||
|
||||
export const emailSignInFormUpdate = makeActionCreator(T.AUTH.SIGN_IN_FORM_UPDATE, 'key', 'value');
|
||||
export const emailSignInStart = makeActionCreator(T.AUTH.SIGN_IN_START);
|
||||
|
||||
@@ -1,48 +1,34 @@
|
||||
/**
|
||||
* Created by andrew on 11/03/16.
|
||||
*/
|
||||
import {
|
||||
getEmailSignUpUrl
|
||||
} from "../utils/sessionStorage";
|
||||
|
||||
|
||||
import { entityReceived } from './entities';
|
||||
import { storeCurrentEndpointKey } from "./configure";
|
||||
//import { parseResponse } from "../utils/handleFetchResponse";
|
||||
import { push } from 'redux-router';
|
||||
import T from '../constants/ACTION_TYPES';
|
||||
import { makeActionCreator } from '../utils/actions';
|
||||
import { apiSignUp } from "../utils/api";
|
||||
import { emailSignInFormUpdate } from './signIn';
|
||||
import { push } from 'redux-router';
|
||||
|
||||
import T from '../constants/ACTION_TYPES';
|
||||
export const emailSignUpFormUpdate = makeActionCreator(T.AUTH.SIGN_UP_FORM_UPDATE, 'key', 'value');
|
||||
export const emailSignUpStart = makeActionCreator(T.AUTH.SIGN_UP_START);
|
||||
export const emailSignUpComplete = makeActionCreator(T.AUTH.SIGN_UP_COMPLETE, 'user');
|
||||
export const emailSignUpError = makeActionCreator(T.AUTH.SIGN_UP_ERROR, 'error');
|
||||
|
||||
export function emailSignUpFormUpdate(key, value) {
|
||||
return { type: T.AUTH.SIGN_UP_FORM_UPDATE, key, value };
|
||||
}
|
||||
|
||||
export function emailSignUpStart() {
|
||||
return { type: T.AUTH.SIGN_UP_START };
|
||||
}
|
||||
|
||||
export function emailSignUpComplete(user) {
|
||||
return { type: T.AUTH.SIGN_UP_COMPLETE, user };
|
||||
}
|
||||
|
||||
export function emailSignUpError(errors) {
|
||||
return { type: T.AUTH.SIGN_UP_ERROR, errors };
|
||||
}
|
||||
|
||||
export function emailSignUp(body) {
|
||||
return dispatch => {
|
||||
dispatch(emailSignUpStart());
|
||||
|
||||
return apiSignUp(body)
|
||||
.then(({data}) => {
|
||||
.then(({ data }) => {
|
||||
dispatch(emailSignUpComplete(data));
|
||||
const { email } = body;
|
||||
dispatch(emailSignInFormUpdate('email', email));
|
||||
dispatch(push('/signin'));
|
||||
})
|
||||
.catch(({errors}) => dispatch(emailSignUpError(errors)));
|
||||
.catch(({ errors }) => {
|
||||
dispatch(emailSignUpError({
|
||||
errors
|
||||
}))
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import redirector from './redirector'
|
||||
import renderer from './renderer'
|
||||
|
||||
export default [
|
||||
redirector,
|
||||
renderer,
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as navigation from '../actions/navigation'
|
||||
import ROUTES from '../constants/ROUTES'
|
||||
|
||||
|
||||
export default function redirector(state, dispatch) {
|
||||
const {name, options} = state.navigation.location || {}
|
||||
const currentURI = window.location.hash.substr(1)
|
||||
const canonicalURI = name && ROUTES.generate(name, options)
|
||||
|
||||
if (canonicalURI && canonicalURI !== currentURI) {
|
||||
// If the URL entered includes extra `/` characters, or otherwise
|
||||
// differs from the canonical URL, navigate the user to the
|
||||
// canonical URL (which will result in `complete` being called again)
|
||||
dispatch(navigation.start(name, options))
|
||||
}
|
||||
else if (name == 'root') {
|
||||
// If we've hit the root location, redirect the user to the main page
|
||||
dispatch(navigation.start('documentList'))
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import Application from '../App'
|
||||
|
||||
|
||||
// Store a reference to our application's root DOM node to prevent repeating
|
||||
// this on every state update
|
||||
const APP_NODE = document.getElementById('react-app')
|
||||
|
||||
export default function renderer(state, dispatch) {
|
||||
// Don't re-render if we're in the process of navigating to a new page
|
||||
if (!state.navigation.transitioning) {
|
||||
ReactDOM.render(
|
||||
<Application state={state} dispatch={dispatch} />,
|
||||
APP_NODE
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class TransfersTable extends React.Component {
|
||||
}, v) => {
|
||||
if (v.entryType == 'account') {
|
||||
balance = v.initialBalance;
|
||||
} else if (v.entryType == 'transaction') {
|
||||
} else if (v.entryType == 'transaction' && (v.status !== 'FAILED_DUE_TO_INSUFFICIENT_FUNDS')) {
|
||||
const isOriginating = v.fromAccountId == currentAccountId;
|
||||
balance += (isOriginating ? -1 : 1) * v.amount;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { TODO_DEFINE, defineActionType } from '../utils/defineActionTypes'
|
||||
|
||||
export default defineActionType({
|
||||
|
||||
LOCATION: {
|
||||
ENTER: TODO_DEFINE
|
||||
},
|
||||
/*
|
||||
* View model
|
||||
*/
|
||||
|
||||
@@ -10,47 +10,48 @@ import Input from "./Input";
|
||||
import ButtonLoader from "./ButtonLoader";
|
||||
import AuxErrorLabel from './AuxErrorLabel';
|
||||
|
||||
import { emailSignInFormUpdate, emailSignIn } from "../../actions/signIn";
|
||||
import * as AS from "../../actions/signIn";
|
||||
|
||||
/*
|
||||
<Input type="password"
|
||||
label="Password"
|
||||
className="email-sign-in-password"
|
||||
placeholder="Password"
|
||||
disabled={disabled}
|
||||
value={this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "form", "password"])}
|
||||
errors={this.props.auth.getIn(["emailSignIn", this.getEndpoint(), "errors", "password"])}
|
||||
onChange={this.handleInput.bind(this, "password")}
|
||||
{...this.props.inputProps.password} />
|
||||
*/
|
||||
const formValidation = (payload) => [
|
||||
'email',
|
||||
'password'
|
||||
].reduce((memo, prop) => {
|
||||
let result = [];
|
||||
const value = (payload[prop] || '').replace(/(^\s+)|(\s+$)/g, '');
|
||||
|
||||
class EmailSignInForm extends React.Component {
|
||||
switch (prop) {
|
||||
case 'email':
|
||||
case 'password':
|
||||
if (/^$/.test(value)) {
|
||||
result.push('required');
|
||||
}
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
endpoint: PropTypes.string,
|
||||
inputProps: PropTypes.shape({
|
||||
email: PropTypes.object,
|
||||
password: PropTypes.object,
|
||||
submit: PropTypes.object
|
||||
})
|
||||
};
|
||||
if (result.length) {
|
||||
memo[prop] = result;
|
||||
memo.hasErrors = true;
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
static defaultProps = {
|
||||
inputProps: {
|
||||
email: {},
|
||||
password: {},
|
||||
submit: {}
|
||||
}
|
||||
};
|
||||
export class EmailSignInForm extends React.Component {
|
||||
|
||||
handleInput (key, val) {
|
||||
this.props.dispatch(emailSignInFormUpdate(key, val));
|
||||
this.props.dispatch(AS.emailSignInFormUpdate(key, val));
|
||||
}
|
||||
|
||||
handleSubmit (event) {
|
||||
event.preventDefault();
|
||||
let formData = { ...this.props.auth.signIn.form };
|
||||
this.props.dispatch(emailSignIn(formData));
|
||||
|
||||
const formData = read(this.props.auth, 'signIn.form');
|
||||
|
||||
const validationErrors = formValidation(formData);
|
||||
if (validationErrors.hasErrors) {
|
||||
this.props.dispatch(AS.emailSignInError(validationErrors));
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(AS.emailSignIn(formData));
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -62,10 +63,8 @@ class EmailSignInForm extends React.Component {
|
||||
);
|
||||
|
||||
//const error = read(this.props.auth, 'signIn.errors.email', null);
|
||||
//debugger;
|
||||
const formErrors = read(this.props.auth, 'signIn.errors.errors', '');
|
||||
|
||||
|
||||
return (
|
||||
<form className='redux-auth email-sign-in-form clearfix'
|
||||
onSubmit={this.handleSubmit.bind(this)}>
|
||||
@@ -75,7 +74,7 @@ class EmailSignInForm extends React.Component {
|
||||
}}>
|
||||
<AuxErrorLabel
|
||||
label="Form:"
|
||||
errors={formErrors.length ? [formErrors] : [] }
|
||||
errors={ formErrors.length ? [ formErrors ] : [] }
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -90,6 +89,17 @@ class EmailSignInForm extends React.Component {
|
||||
onChange={this.handleInput.bind(this, "email")}
|
||||
{...this.props.inputProps.email} />
|
||||
|
||||
<Input type="password"
|
||||
className="password-sign-in-email"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
disabled={disabled}
|
||||
value={read(this.props.auth, 'signIn.form.password', '')}
|
||||
errors={read(this.props.auth, 'signIn.errors.password', [])}
|
||||
onChange={this.handleInput.bind(this, "password")}
|
||||
{...this.props.inputProps.password} />
|
||||
|
||||
<ButtonLoader loading={read(this.props.auth, 'signIn.loading', false)}
|
||||
type="submit"
|
||||
icon={<BS.Glyphicon glyph="log-in" />}
|
||||
@@ -108,4 +118,21 @@ class EmailSignInForm extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({app}) => ({auth: app.auth}))(EmailSignInForm);
|
||||
EmailSignInForm.propTypes = {
|
||||
endpoint: PropTypes.string,
|
||||
inputProps: PropTypes.shape({
|
||||
email: PropTypes.object,
|
||||
password: PropTypes.object,
|
||||
submit: PropTypes.object
|
||||
})
|
||||
};
|
||||
|
||||
EmailSignInForm.defaultProps = {
|
||||
inputProps: {
|
||||
email: {},
|
||||
password: {},
|
||||
submit: {}
|
||||
}
|
||||
};
|
||||
|
||||
// export default connect(({app}) => ({auth: app.auth}))(EmailSignInForm);
|
||||
@@ -2,40 +2,77 @@
|
||||
* Created by andrew on 15/02/16.
|
||||
*/
|
||||
import React, {PropTypes} from "react";
|
||||
//import auth from "redux-auth";
|
||||
import { connect } from "react-redux";
|
||||
import { Glyphicon } from "react-bootstrap";
|
||||
import Input from "./Input";
|
||||
import ButtonLoader from "./ButtonLoader";
|
||||
//import { emailSignUpFormUpdate, emailSignUp } from "redux-auth";
|
||||
import IndexPanel from "./../../components/partials/IndexPanel";
|
||||
import AuxErrorLabel from './AuxErrorLabel';
|
||||
import { customerInfoMap } from '../../entities/formToPayloadMappers';
|
||||
|
||||
import read from '../../utils/readProp';
|
||||
import * as AS from '../../actions/signUp';
|
||||
|
||||
import { Glyphicon } from "react-bootstrap";
|
||||
import { connect } from "react-redux";
|
||||
const formValidation = (payload) => [
|
||||
'fname',
|
||||
'lname',
|
||||
'email',
|
||||
'password',
|
||||
'passwordConfirm',
|
||||
'ssn',
|
||||
'phoneNumber',
|
||||
'address1',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'zip'
|
||||
].reduce((memo, prop) => {
|
||||
let result = [];
|
||||
const value = (payload[prop] || '').replace(/(^\s+)|(\s+$)/g, '');
|
||||
|
||||
import {emailSignUpFormUpdate, emailSignUp} from '../../actions/signUp';
|
||||
switch (prop) {
|
||||
case 'fname':
|
||||
case 'lname':
|
||||
case 'email':
|
||||
case 'ssn':
|
||||
case 'password':
|
||||
case 'passwordConfirm':
|
||||
if (/^$/.test(value)) {
|
||||
result.push('required');
|
||||
}
|
||||
}
|
||||
|
||||
switch (prop) {
|
||||
case 'passwordConfirm':
|
||||
if (value != payload['password']) {
|
||||
result.push('need to be equal to password');
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length) {
|
||||
memo[prop] = result;
|
||||
memo.hasErrors = true;
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
|
||||
class EmailSignUpForm extends React.Component {
|
||||
|
||||
getEndpoint () {
|
||||
return (
|
||||
this.props.endpoint ||
|
||||
this.props.auth.getIn(["configure", "currentEndpointKey"]) ||
|
||||
this.props.auth.getIn(["configure", "defaultEndpointKey"])
|
||||
);
|
||||
}
|
||||
|
||||
handleInput (key, val) {
|
||||
this.props.dispatch(emailSignUpFormUpdate(key, val));
|
||||
this.props.dispatch(AS.emailSignUpFormUpdate(key, val));
|
||||
}
|
||||
|
||||
handleSubmit (event) {
|
||||
event.preventDefault();
|
||||
|
||||
let formData = { ...this.props.auth.signUp.form };
|
||||
this.props.dispatch(emailSignUp(customerInfoMap(formData)));
|
||||
const formData = read(this.props.auth, 'signUp.form');
|
||||
const validationErrors = formValidation(formData);
|
||||
if (validationErrors.hasErrors) {
|
||||
this.props.dispatch(AS.emailSignUpError(validationErrors));
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(AS.emailSignUp(customerInfoMap(formData)));
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -45,10 +82,21 @@ class EmailSignUpForm extends React.Component {
|
||||
this.props.auth.signUp.loading
|
||||
);
|
||||
|
||||
const formErrors = read(this.props.auth, 'signUp.errors.errors', '');
|
||||
|
||||
return (
|
||||
<form className='redux-auth email-sign-up-form clearfix'
|
||||
onSubmit={this.handleSubmit.bind(this)}>
|
||||
|
||||
<div className="form-group" style={{
|
||||
display: formErrors ? 'block' : 'none'
|
||||
}}>
|
||||
<AuxErrorLabel
|
||||
label="Form:"
|
||||
errors={ formErrors.length ? [ formErrors ] : [] }
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IndexPanel header="basic">
|
||||
|
||||
<Input type="text"
|
||||
@@ -81,6 +129,28 @@ class EmailSignUpForm extends React.Component {
|
||||
onChange={this.handleInput.bind(this, "email")}
|
||||
/>
|
||||
|
||||
<Input type="password"
|
||||
className="password-sign-in-email"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
disabled={disabled}
|
||||
value={read(this.props.auth, 'signUp.form.password', '')}
|
||||
errors={read(this.props.auth, 'signUp.errors.password', [])}
|
||||
onChange={this.handleInput.bind(this, "password")}
|
||||
/>
|
||||
|
||||
<Input type="password"
|
||||
className="password-sign-in-email"
|
||||
label="Confirm password"
|
||||
placeholder="Confirm password"
|
||||
name="password-confirm"
|
||||
disabled={disabled}
|
||||
value={read(this.props.auth, 'signUp.form.passwordConfirm', '')}
|
||||
errors={read(this.props.auth, 'signUp.errors.passwordConfirm', [])}
|
||||
onChange={this.handleInput.bind(this, "passwordConfirm")}
|
||||
/>
|
||||
|
||||
|
||||
</IndexPanel>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
export const customerInfoMap = ({
|
||||
ssn,
|
||||
password,
|
||||
address1,
|
||||
address2,
|
||||
city, //: "Moscow"
|
||||
@@ -17,6 +18,7 @@ export const customerInfoMap = ({
|
||||
"firstName": fname,
|
||||
"lastName": lname
|
||||
},
|
||||
password,
|
||||
email,
|
||||
ssn,
|
||||
"phoneNumber": phoneNumber,
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { createStore, applyMiddleware } from 'redux'
|
||||
import reduxThunk from 'redux-thunk'
|
||||
import reduxMulti from 'redux-multi'
|
||||
import { batchedSubscribe } from 'redux-batched-subscribe'
|
||||
|
||||
import * as navigation from './actions/navigation'
|
||||
import actors from './actors'
|
||||
import rootReducer from './reducers';
|
||||
import { blocked } from './utils/blockedExecution';
|
||||
|
||||
|
||||
// Add middleware to allow our action creators to return functions and arrays
|
||||
const createStoreWithMiddleware = applyMiddleware(
|
||||
reduxThunk,
|
||||
reduxMulti
|
||||
)(createStore);
|
||||
|
||||
// Ensure our listeners are only called once, even when one of the above
|
||||
// middleware call the underlying store's `dispatch` multiple times
|
||||
const createStoreWithBatching = batchedSubscribe(
|
||||
fn => fn()
|
||||
)(createStoreWithMiddleware);
|
||||
|
||||
// Create a store with our application reducer
|
||||
const store = createStoreWithBatching(rootReducer)
|
||||
|
||||
// Handle changes to our store with a list of actor functions, but ensure
|
||||
// that the actor sequence cannot be started by a dispatch from an actor
|
||||
store.subscribe(blocked(() => {
|
||||
for (let actor of actors) {
|
||||
actor(store.getState(), store.dispatch)
|
||||
}
|
||||
}));
|
||||
|
||||
// Dispatch navigation events when the URL's hash changes, and when the
|
||||
// application loads
|
||||
function onHashChange() {
|
||||
store.dispatch(navigation.complete())
|
||||
}
|
||||
window.addEventListener('hashchange', onHashChange, false);
|
||||
onHashChange();
|
||||
@@ -4,9 +4,29 @@
|
||||
import T from '../../constants/ACTION_TYPES';
|
||||
import createFormReducer from '../createFormReducer';
|
||||
|
||||
export const signInReducer = createFormReducer([
|
||||
const internalSignInReducer = createFormReducer([
|
||||
T.AUTH.SIGN_IN_START,
|
||||
T.AUTH.SIGN_IN_COMPLETE,
|
||||
T.AUTH.SIGN_IN_ERROR,
|
||||
T.AUTH.SIGN_IN_FORM_UPDATE
|
||||
]);
|
||||
|
||||
export const signInReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case T.LOCATION.ENTER: {
|
||||
const { location } = action;
|
||||
const { pathname } = location;
|
||||
if (pathname == '/signin') {
|
||||
return internalSignInReducer(state, {
|
||||
type: T.AUTH.SIGN_IN_ERROR,
|
||||
error: null
|
||||
});
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
default: {
|
||||
return internalSignInReducer(state, action);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,9 +4,30 @@
|
||||
import T from '../../constants/ACTION_TYPES';
|
||||
import createFormReducer from '../createFormReducer';
|
||||
|
||||
export const signUpReducer = createFormReducer([
|
||||
export const internalSignUpReducer = createFormReducer([
|
||||
T.AUTH.SIGN_UP_START,
|
||||
T.AUTH.SIGN_UP_COMPLETE,
|
||||
T.AUTH.SIGN_UP_ERROR,
|
||||
T.AUTH.SIGN_UP_FORM_UPDATE
|
||||
]);
|
||||
|
||||
|
||||
export const signUpReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case T.LOCATION.ENTER: {
|
||||
const { location } = action;
|
||||
const { pathname } = location;
|
||||
if (pathname == '/register') {
|
||||
return internalSignUpReducer(state, {
|
||||
type: T.AUTH.SIGN_UP_ERROR,
|
||||
error: null
|
||||
});
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
default: {
|
||||
return internalSignUpReducer(state, action);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
*/
|
||||
export function makeActionCreator(type, ...argNames) {
|
||||
return function(...args) {
|
||||
const action = { type };
|
||||
argNames.forEach((arg, index) => {
|
||||
action[argNames[index]] = args[index]
|
||||
});
|
||||
return action;
|
||||
return argNames.reduce((action, arg, index) => {
|
||||
action[arg] = args[index];
|
||||
return action;
|
||||
}, { type });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Created by andrew on 12/03/16.
|
||||
*/
|
||||
import fetch from './fetch';
|
||||
import authedFetch from './fetch';
|
||||
import {
|
||||
getEmailSignInUrl,
|
||||
getEmailSignUpUrl,
|
||||
@@ -11,163 +11,97 @@ import {
|
||||
getTransfersUrl
|
||||
} from "./sessionStorage";
|
||||
import root from './root';
|
||||
|
||||
|
||||
import { parseResponse } from "./handleFetchResponse";
|
||||
|
||||
const fetch = (...args) => authedFetch(...args).then(parseResponse);
|
||||
|
||||
function makeQuery(params) {
|
||||
return Object.keys(params).map(key => [encodeURIComponent(key), encodeURIComponent(params[key])].join('=')).join('&');
|
||||
}
|
||||
|
||||
export function apiSignIn(body) {
|
||||
return fetch(getEmailSignInUrl(), {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
const JSON_HEADERS = {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "post",
|
||||
body: root.JSON.stringify(body)
|
||||
}).then(parseResponse);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function apiSignUp(body) {
|
||||
return fetch(getEmailSignUpUrl(), {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "post",
|
||||
body: root.JSON.stringify(body)
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiGetCurrentUser() {
|
||||
return fetch(getCurrentUserUrl(), {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "get"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiCreateAccount(customerId, {
|
||||
title, balance: initialBalance, description }) {
|
||||
//{
|
||||
//"accountId": "0000015377cf131b-a250093f26850000"
|
||||
//}
|
||||
|
||||
return fetch(getAccountsUrl(), {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "post",
|
||||
body: root.JSON.stringify({
|
||||
customerId,
|
||||
title,
|
||||
initialBalance,
|
||||
description })
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiCreateRefAccount(customerId, {
|
||||
owner, account: accountId, title, description }) {
|
||||
|
||||
return fetch(`${getCustomersUrl()}/${customerId}/toaccounts`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "post",
|
||||
body: root.JSON.stringify({
|
||||
owner,
|
||||
id: accountId,
|
||||
title,
|
||||
description })
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiMakeTransfer(fromAccountId, {
|
||||
account, amount, description }) {
|
||||
|
||||
return fetch(getTransfersUrl(), {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "post",
|
||||
body: root.JSON.stringify({
|
||||
"amount": amount,
|
||||
"fromAccountId": fromAccountId,
|
||||
"toAccountId": account,
|
||||
description
|
||||
})
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiRetrieveAccounts(customerId) {
|
||||
|
||||
return fetch(`${getCustomersUrl()}/${customerId}/accounts`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "get"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiRetrieveTransfers(accountId) {
|
||||
|
||||
return fetch(`${getAccountsUrl()}/${accountId}/history`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "get"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiRetrieveAccount(accountId) {
|
||||
return fetch(`${getAccountsUrl()}/${accountId}`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "get"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiDeleteAccount(accountId) {
|
||||
return Promise.reject({
|
||||
message: '\'Delete Account\' is not implemented.'
|
||||
});
|
||||
|
||||
return fetch(`${getAccountsUrl()}/${accountId}`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
const METHODS = {
|
||||
DELETE: {
|
||||
...JSON_HEADERS,
|
||||
method: "delete"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
|
||||
export function apiRetrieveUsers(email) {
|
||||
return fetch(`${getCustomersUrl()}?${makeQuery({ email })}`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
...JSON_HEADERS,
|
||||
method: "get"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
},
|
||||
POST: {
|
||||
...JSON_HEADERS,
|
||||
method: "post"
|
||||
}
|
||||
};
|
||||
|
||||
export function apiRetrieveUser(customerId) {
|
||||
return fetch(`${getCustomersUrl()}/${ customerId }`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "get"
|
||||
}).then(parseResponse);
|
||||
}
|
||||
export const apiSignIn = (body) => fetch(getEmailSignInUrl(), {
|
||||
...METHODS.POST,
|
||||
body: root.JSON.stringify(body)
|
||||
});
|
||||
|
||||
export const apiSignUp = (body) => fetch(getEmailSignUpUrl(), {
|
||||
...METHODS.POST,
|
||||
body: root.JSON.stringify(body)
|
||||
});
|
||||
|
||||
export const apiGetCurrentUser = () => fetch(getCurrentUserUrl(), { ...METHODS.GET });
|
||||
|
||||
export const apiCreateAccount = (customerId, {
|
||||
title,
|
||||
balance: initialBalance,
|
||||
description }) => fetch(getAccountsUrl(), {
|
||||
...METHODS.POST,
|
||||
body: root.JSON.stringify({
|
||||
customerId,
|
||||
title,
|
||||
initialBalance,
|
||||
description })
|
||||
});
|
||||
|
||||
export const apiCreateRefAccount = (customerId, {
|
||||
owner, account: accountId, title, description }) => fetch(`${getCustomersUrl()}/${customerId}/toaccounts`, {
|
||||
...METHODS.POST,
|
||||
body: root.JSON.stringify({
|
||||
owner,
|
||||
id: accountId,
|
||||
title,
|
||||
description })
|
||||
});
|
||||
|
||||
export const apiMakeTransfer = (fromAccountId, {
|
||||
account, amount, description }) => fetch(getTransfersUrl(), {
|
||||
...METHODS.POST,
|
||||
body: root.JSON.stringify({
|
||||
"amount": amount,
|
||||
"fromAccountId": fromAccountId,
|
||||
"toAccountId": account,
|
||||
description
|
||||
})
|
||||
});
|
||||
|
||||
export const apiRetrieveAccounts = (customerId) => fetch(`${getCustomersUrl()}/${customerId}/accounts`, {
|
||||
...METHODS.GET
|
||||
});
|
||||
|
||||
export const apiRetrieveTransfers = (accountId) => fetch(`${getAccountsUrl()}/${accountId}/history`, {
|
||||
...METHODS.GET
|
||||
});
|
||||
|
||||
export const apiRetrieveAccount = (accountId) => fetch(`${getAccountsUrl()}/${accountId}`, {
|
||||
...METHODS.GET
|
||||
});
|
||||
|
||||
export const apiDeleteAccount = (customerId, accountId) => fetch(`${getCustomersUrl()}/${customerId}/accounts/${accountId}`, {
|
||||
...METHODS.DELETE
|
||||
});
|
||||
|
||||
export const apiRetrieveUsers = (email) => fetch(`${getCustomersUrl()}?${makeQuery({ email })}`, {
|
||||
...METHODS.GET
|
||||
});
|
||||
|
||||
@@ -2,28 +2,24 @@
|
||||
* Created by andrew on 12/02/16.
|
||||
*/
|
||||
import React from "react";
|
||||
//import { PageHeader } from "react-bootstrap";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { PageHeader, OverlayTrigger, Tooltip, Grid, Col, Row, Nav, NavItem, ButtonGroup, Button, Table } from "react-bootstrap";
|
||||
import * as BS from "react-bootstrap";
|
||||
import Spinner from "react-loader";
|
||||
// import Spinner from "react-loader";
|
||||
import Select from "react-select";
|
||||
import Input from "../controls/bootstrap/Input";
|
||||
import { Money, moneyText } from '../components/Money';
|
||||
import { TransfersTable } from '../components/TransfersTable';
|
||||
|
||||
import { Link, IndexLink} from "react-router";
|
||||
|
||||
import { Link, IndexLink } from "react-router";
|
||||
|
||||
import IndexPanel from "./../components/partials/IndexPanel";
|
||||
import * as Modals from './modals';
|
||||
import * as A from '../actions/entities';
|
||||
import read from '../utils/readProp';
|
||||
|
||||
import { blocked } from '../utils/blockedExecution';
|
||||
|
||||
|
||||
const resetModals = {
|
||||
showAccountModal: false,
|
||||
unsaved: false
|
||||
@@ -162,7 +158,10 @@ export class Account extends React.Component {
|
||||
|
||||
if (!account) {
|
||||
if (errors.length) {
|
||||
return (<h2>Error loading specified account</h2>);
|
||||
return (<div>
|
||||
<h2>Error loading specified account</h2>
|
||||
<div>Return <Link to="/">Home</Link> to pick another</div>
|
||||
</div>);
|
||||
} else {
|
||||
return spinnerResult;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,12 @@
|
||||
import React from "react";
|
||||
import { PageHeader, OverlayTrigger, Tooltip, Grid, Col, Row, Nav, NavItem, ButtonGroup, Button, Table } from "react-bootstrap";
|
||||
import * as BS from "react-bootstrap";
|
||||
import { Link, IndexLink} from "react-router";
|
||||
import { connect } from "react-redux";
|
||||
//import * as DefaultTheme from "redux-auth";
|
||||
import Select from "react-select";
|
||||
import AccountInfo from '../components/AccountInfo';
|
||||
import * as Modals from './modals';
|
||||
import IndexPanel from "./../components/partials/IndexPanel";
|
||||
|
||||
import * as A from '../actions/entities';
|
||||
import * as AU from '../actions/authenticate';
|
||||
import read from '../utils/readProp';
|
||||
import { Money } from '../components/Money';
|
||||
|
||||
@@ -73,15 +70,19 @@ class MyAccounts extends React.Component {
|
||||
|
||||
create3rdPartyAccountModalConfirmed(payload) {
|
||||
const {
|
||||
id: customerId
|
||||
id: customerId,
|
||||
dispatch
|
||||
} = this.props.auth.user.attributes;
|
||||
|
||||
this.props.dispatch(A.accountRefCreate(customerId, payload))
|
||||
dispatch(A.accountRefCreate(customerId, payload))
|
||||
.then(() => {
|
||||
this.close();
|
||||
return new Promise((rs, rj) => {
|
||||
setTimeout(() => {
|
||||
this.props.dispatch(A.fetchOwnAccounts(customerId)).then(rs, rj);
|
||||
Promise.all([
|
||||
dispatch(AU.authenticate(true)),
|
||||
dispatch(A.fetchOwnAccounts(customerId))
|
||||
]).then(rs, rj);
|
||||
}, 1500);
|
||||
})
|
||||
})
|
||||
@@ -100,13 +101,19 @@ class MyAccounts extends React.Component {
|
||||
}
|
||||
|
||||
remove3rdPartyAccountModalConfirmed(accountId) {
|
||||
const { customerId } = this.props;
|
||||
this.props.dispatch(A.deleteAccount(customerId, accountId))
|
||||
const { customerId, dispatch } = this.props;
|
||||
dispatch(A.deleteAccount(customerId, accountId))
|
||||
.then(() => {
|
||||
this.close();
|
||||
setTimeout(() => {
|
||||
return Promise.all([
|
||||
dispatch(AU.authenticate(true)),
|
||||
dispatch(A.fetchOwnAccounts(customerId))
|
||||
]);
|
||||
}, 1500);
|
||||
},
|
||||
err => {
|
||||
this.props.dispatch(A.errorMessageTimedOut(err && err.message || err));
|
||||
dispatch(A.errorMessageTimedOut(err && err.message || err));
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
@@ -190,7 +197,7 @@ class MyAccounts extends React.Component {
|
||||
]: null
|
||||
}
|
||||
</td>
|
||||
<td key={1}></td>
|
||||
<td key={1} />
|
||||
<td key={2}><Button pullRight={true} bsStyle={"link"} onClick={this.remove3rdPartyAccountModal.bind(this, id)}><BS.Glyphicon glyph="remove" /></Button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -4,27 +4,21 @@
|
||||
import React from "react";
|
||||
import { PageHeader } from "react-bootstrap";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
//import ButtonLoader from "./ButtonLoader";
|
||||
import * as BS from "react-bootstrap";
|
||||
//import ButtonLoader from "../controls/bootstrap/ButtonLoader";
|
||||
|
||||
|
||||
import {pushState} from "redux-router";
|
||||
|
||||
|
||||
//export {bootstrap, materialUi} from "./views";
|
||||
|
||||
|
||||
// bootstrap theme
|
||||
//import { EmailSignInForm } from "redux-auth/bootstrap-theme";
|
||||
import EmailSignInForm from "../controls/bootstrap/EmailSignInForm";
|
||||
import { pushState } from "redux-router";
|
||||
import { EmailSignInForm } from "../controls/bootstrap/EmailSignInForm";
|
||||
import read from '../utils/readProp';
|
||||
|
||||
export class SignIn extends React.Component {
|
||||
|
||||
checkRedirect(props) {
|
||||
if (props.auth.user.isSignedIn) {
|
||||
props.dispatch(pushState(null, props.location.query.next));
|
||||
|
||||
const isSignedIn = read(props.auth, 'user.isSignedIn');
|
||||
if (isSignedIn) {
|
||||
|
||||
const nextLocation = read(props.location, 'query.next');
|
||||
props.dispatch(pushState(null, nextLocation));
|
||||
|
||||
//// redirect to login and add next param so we can redirect again after login
|
||||
//const redirectAfterLogin = this.props.location.pathname;
|
||||
//this.props.dispatch(pushState(null, `/signin?next=${redirectAfterLogin}`));
|
||||
@@ -40,16 +34,6 @@ export class SignIn extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const signInProps = {
|
||||
inputProps: {
|
||||
password: {
|
||||
className: 'hide hidden',
|
||||
style: { display: 'none' },
|
||||
value: null,
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BS.Well>
|
||||
|
||||
@@ -34,14 +34,10 @@ export class SignUp extends React.Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader>
|
||||
Register
|
||||
</PageHeader>
|
||||
<BS.Well>
|
||||
<EmailSignUpForm />
|
||||
</BS.Well>
|
||||
</div>
|
||||
<BS.Well>
|
||||
<BS.PageHeader>Register</BS.PageHeader>
|
||||
<EmailSignUpForm />
|
||||
</BS.Well>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ export class Add3rdPartyAccountModal extends React.Component {
|
||||
options={read(this.props.data, 'accountsLookup.options', [])}
|
||||
onChange={this.handleInput.bind(this, 'account')} />
|
||||
<AuxErrorLabel
|
||||
label="Owner:"
|
||||
label="Account:"
|
||||
errors={read(this.props.data, 'errors.account', [])}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -40,9 +40,9 @@ export class RemoveAccountBookmarkModal extends React.Component {
|
||||
|
||||
const entityId = id || accountId;
|
||||
|
||||
const title = titleRaw || '[No title]';
|
||||
const balance = ((balanceRaw > 0 && balanceRaw < 1) ? '$0' : '$') + Number(balanceRaw).toFixed(2);
|
||||
const description = descriptionRaw || '[No description]';
|
||||
const title = titleRaw || '—';
|
||||
const balance = isNaN(balanceRaw) ? '—' : ((balanceRaw > 0 && balanceRaw < 1) ? '$0' : '$') + Number(balanceRaw).toFixed(2);
|
||||
const description = descriptionRaw || '—';
|
||||
|
||||
return (<Modal show={this.props.show} onHide={this.props.onHide} key={0}>
|
||||
<Modal.Header closeButton>
|
||||
|
||||
Reference in New Issue
Block a user