Your First Full Stack Application with React and Spring Boot
Take your first steps towards becoming a Full Stack Developer with React and Spring Boot
React is a one of the most popular front end view frameworks - Components - JSX - State - Props
In combination with other libraries, React helps in doing a wide variety of front end features - Forms Handling - Routing System - HTTP Requests
Spring Boot is an awesome framework to build RESTful API and Microservices.
In this course, lets combine these awesome frameworks to create your first full stack web application.
References
- https://github.com/facebook/create-react-app
- https://facebook.github.io/create-react-app/docs/troubleshooting
- https://babeljs.io/repl
Installation Guides
Required Tools
- Node v8+ for npm
- Visual Studio Code - Latest Version
- Java 8+
- Eclipse - Oxygen+ - (Embedded Maven From Eclipse)
Installing Node Js (npm) & Visual Studio Code
- Playlist - https://www.youtube.com/playlist?list=PLBBog2r6uMCQN4X3Aa_jM9qVjgMCHMWx6
- Steps
- Step 01 - Installing NodeJs and NPM - Node Package Manager
- Step 02 - Quick Introduction to NPM
- Step 03 - Installing Visual Studio Code - Front End Java Script Editor
Installing Java, Eclipse & Embedded Maven
- Playlist - https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3
- Steps
- 0 - Overview - Installation Java, Eclipse and Maven
- 1 - Installing Java JDK
- 2 - Installing Eclipse IDE
- 3 - Using Embedded Maven in Eclipse
- 4 - Troubleshooting Java, Eclipse and Maven
Troubleshooting Installations
- Node JS and NPM
- https://docs.npmjs.com/common-errors
- https://docs.npmjs.com/getting-started/troubleshooting
- Visual Studio Code
- https://code.visualstudio.com/docs/supporting/errors
- https://code.visualstudio.com/docs/supporting/FAQ
- Eclipse and Embedded Maven
- PDF : https://github.com/in28minutes/SpringIn28Minutes/blob/master/InstallationGuide-JavaEclipseAndMaven_v2.pdf
- GIT Repository For Installation : https://github.com/in28minutes/getting-started-in-5-steps
Getting Started with Spring, Spring Boot and JPA
- Spring Tutorial for Beginners - https://www.youtube.com/watch?v=edgZo2g-LTM
- Spring Boot Tutorial for Beginners - https://www.youtube.com/watch?v=pcdpk3Yd1EA
- JPA and Hibernate Tutorial for Beginners - https://www.youtube.com/watch?v=MaI0_XdpdP8
Course Overview
Introduction
Developing your first full stack application with React and Spring Boot is fun.
In this course, you will learn the basics of full stack development developing a Basic Todo Management Application using React, Spring Boot and Spring Security Frameworks.
You will build the application step by step - in more than 50 steps. This course would be a perfect first step as an introduction to React and Full Stack Development.
You will be using React (Frontend View Framework), React Create App(To create React project), Various JavaScript Libraries (Axios, Formik, React Router), Spring Boot (REST API Framework), Spring (Dependency Management), Spring Security (Authentication and Authorization - Basic and JWT), BootStrap (Styling Pages), Maven (dependencies management), Node (npm), Visual Studio Code (JavaScript IDE), Eclipse (Java IDE) and Tomcat Embedded Web Server. We will help you set up each one of these.
What You will learn
- You will develop your first full stack application with React and Spring Boot
- You will learn the basic of React - React Components and Routing
- You will learn basics of building awesome frontend applications with React
- You will be introduced to building great RESTful APIs with Spring Boot
- You will learn to use Spring Security to configure Basic Authentication and JWT
- You will learn to solve the challenges of connecting an React Frontend to a RESTful API
- You will learn to connect REST API to JPA/Hibernate with Spring Boot
- You will learn to use a wide variety of Spring Boot Starter Projects - Spring Boot Web, and Spring Boot Data JPA
- You will understand the best practices in designing RESTful web services
- You will develop a Todo Management Full Stack Application step by step with login and logout functionalities
- You will learn the magic of Spring Boot - Auto Configuration, Spring Initializr and Starter Projects
- You will understand how to make best use of Spring Boot Actuator and Spring Boot Developer Tools
- You will understand and use the embedded servlet container options provided by Spring Boot
Requirements
- You should have prior experience with Java, Basic JavaScript and Spring Framework.
- You should have Chrome browser installed.
- We will help you install Eclipse, Visual Studio Code and Node JS(for npm)
- We will help you install Chrome Restlet Client Plugin and Chrome React DevTools Plugin
- We will help you learn the basics of Modern JavaScript, Spring Boot and JPA.
Step Wise Details
Quick Overview
- Step 01 - Understanding Full Stack Application Architecture
- Step 02 - Quick Overview of Modern JavaScript and TypeScript
- Step 03 - Installing React CLI - Awesome Tool to create React Projects
- Step 04 - Creating and Launching React Application with React CLI
- Step 05 - Importing React App into Visual Studio Code
- Step 06 - Exploring React CLI Commands - test, lint, e2e, serve, build
- Step 07 - Exploring React CLI Project Structure
Getting Hands on With React
- Step 08 - Introduction to React Components - Basics
- Step 09 - Introduction to React Components - Playing with AppComponent
- Step 10 - Generating Welcome Component with ng generate
- Step 11 - Language Variations With an Example - Java, JavaScript and TypeScript
- Step 12 - Generating and Setting up Login Component
- Step 13 - Understanding Event Binding - Adding click event on Login Page
- Step 14 - Using ngModel with 2 Way Data Binding in Login Page
- Step 15 - Quick Review of Data Binding Approaches
- Step 16 - Adding Hardcoded Authentication to Logic Component - ngIf directive
- Step 17 - Implementing Routes for Login, Welcome and Error Components
- Step 18 - Implementing Routing from Login to Welcome Component
- Step 19 - Adding Route Parameter for Welcome Component
- Step 20 - Create List Todos Component with ng generate
- Step 21 - Create a Link to Todos in Welcome Component
- Step 22 - Best Practice - Create a Todo Class
- Step 23 - Quick Introduction to React Modules
- Step 24 - Understanding Bootstrapping of React App with Root Module and Component
- Step 25 - Quick Review - React Modules and Components
- Step 26 - Overview of Next Few Steps - Bootstrap, Menu, Footer and Refactoring
- Step 27 - Adding Bootstrap Framework and Creating Components for Menu and Footer
- Step 28 - Using Bootstrap to Create a Menu with Navigation Links
- Step 29 - Styling Footer and Other Components with CSS and Bootstrap
- Step 30 - Good Practice - Use RouterLink instead of href for Routes
- Step 31 - Creating an Independent Authentication Service Component
- Step 32 - Using Session Storage to Store User Authentication Token
- Step 33 - Enabling Menu Links Based on User Authentication Token
- Step 34 - Implementing Logout to remove User Authentication Token
- Step 35 - Securing Components using Route Guards - Part 1
- Step 36 - Securing Components using Route Guards - Part 2
- Step 37 - Quick Review - Authentication Service, Dependency Injection and Route Guards
Introduction to Web Services and REST
- Step 41 - What is a Web Service?
- Step 42 - Important How Questions related to Web Services
- Step 43 - Web Services - Key Terminology
- Step 44 - Introduction to RESTful Web Services
Getting Up and Running with REST and Spring Boot
- Step 45 - Initializing a RESTful Services Project with Spring Boot
- Step 46 - Creating a Hello World Service
- Step 47 - Enhancing the Hello World Service to return a Bean
- Step 48 - Quick Review of Spring Boot Auto Configuration and Dispatcher Servlet - What’s happening in the background?
- Step 49 - Enhancing the Hello World Service with a Path Variable
Connecting React Frontend to Spring Boot Restful Services
- Step 50 - Connecting React Frontend with Restful API - 1 - Creating Data Service
- Step 51 - Connecting React Frontend with Restful API - 2 - HttpClientModule and HttpClient
- Step 52 - Connecting React Frontend with Restful API - 3 - Understanding Observable
- Step 53 - Connecting React Frontend with Restful API - 4 - Understanding Subscribe
- Step 54 - Connecting React Frontend with Restful API - 5 - Handling Error Responses
- Step 55 - Calling Welcome HTTP Service with Path Variables
- Step 56 - Designing RESTful Services for Todo Resource
- Step 57 - Creating REST API for retrieving Todo List
- Step 58 - Connecting React Frontend with Todo List RESTful Service
- Step 59 - Creating REST API to delete a Todo - DELETE Request Method
- Step 60 - Adding Delete Todo Feature to React Frontend
- Step 61 - Creating Todo Component and Handle Routing
- Step 62 - Designing Todo Page with Bootstrap Framework
- Step 63 - Creating Retrieve Tod0 Service and Connect React Frontend
- Step 64 - Improve Todo Page Appearance
- Step 65 - Creating REST API for Updating Todo - PUT Request Method
- Step 66 - Creating REST API for Creating a Todo - POST Request Method
- Step 67 - Implementing Update Todo Feature in React Frontend
- Step 68 - Implementing New Todo Feature in React Frontend
- Step 69 - Improving Todo Form - Validation and Form Submit on Enter - ngSubmit
- Step 70 - Enhancing Validation Messages on Todo Page
Implementing Spring Security with Basic Authentication
- Step 71 - Overview of Security with Basic Auth and JWT
- Step 72 - Setting up Spring Security
- Step 73 - Configure standard userid and password
- Step 74 - Enhancing React Welcome Data Service to use Basic Auth
- Step 75 - Configure Spring Security to disable CSRF and enable OPTION Requests
- Step 76 - Creating React HttpInterceptor to add Basic Auth Header
- Step 77 - Configure HttpInterceptor as Provider in App Module
- Step 78 - Create Basic Authentication RESTful Service in Spring Boot
- Step 79 - Create React Basic Authentication Service
- Step 80 - Connect Login Page to Basic Authentication Service - Part 1
- Step 81 - Connect Login Page to Basic Authentication Service - Part 2
- Step 82 - Refactoring React Basic Authentication Service
- Step 83 - Refactoring HttpInterceptor to use Basic Authentication Token
- Step 84 - Best Practice - Use Constants for URLs and Tokens
Connecting Spring Security with JWT
- Step 85 - Introduction to JWT
- Step 86 - Importing JWT Framework into Eclipse
- Step 87 - Quick Tip - Resolving JWT Compilation Errors
- Step 88 - Executing JWT Resources - Get Token and Refresh Token
- Step 89 - Understanding JWT Spring Security Framework Setup
- Step 90 - Creating a New User with Encoded Password
- Step 91 - Using JWT Token in React Frontend
Connecting REST API With JPA and Hibernate
- Step 92 - Setting up Todo Entity and Populating Data
- Step 93 - Connecting GET REST APIs to JPA Repository
- Step 94 - Connecting POST, PUT and DELETE REST APIs to JPA Repository
Spring Boot in 10 Steps
- Introduction to Spring Boot in 10 Steps
- Step 01 - Introduction to Spring Boot - Goals and Important Features
- Step 02 - Developing Spring Applications before Spring Boot
- Step 03 - Using Spring Initializr to create a Spring Boot Application
- Step 04 - Creating a Simple REST Controller
- Step 05 - What is Spring Boot Auto Configuration?
- Step 06 - Spring Boot vs Spring vs Spring MVC
- Step 07 - Spring Boot Starter Projects - Starter Web and Starter JPA
- Step 08 - Overview of different Spring Boot Starter Projects
- Step 09 - Spring Boot Actuator
- Step 10 - Spring Boot Developer Tools
First 10 Steps in JPA with H2 in-memory database
- Introduction to JPA in 10 Steps
- Step 01 - Object Relational Impedence Mismatch - Understanding the problem that JPA solves
- Step 02 - World before JPA - JDBC, Spring JDBC and myBatis
- Step 03 - Introduction to JPA
- Step 04 - Creating a JPA Project using Spring Initializr
- Step 05 - Defining a JPA Entity - User
- Step 06 - Defining a Service to manage the Entity - UserService and EntityManager
- Step 07 - Using a Command Line Runner to save the User to database.
- Step 08 - Magic of Spring Boot and In Memory Database H2
- Step 09 - Introduction to Spring Data JPA
- Step 10 - More JPA Repository - findById and findAll
for file in *; do mv "${file}" "${file//-/ }"; done for file in *; do mv "${file}" "${file// / - }"; done for file in *; do mv "${file}" "${file//01 Step/Step}"; done
Code Snippets
Core JWT Components
jwt.signing.key.secret=mySecret jwt.get.token.uri=/authenticate jwt.refresh.token.uri=/refresh jwt.http.request.header=Authorization jwt.token.expiration.in.seconds=604800
package com.in28minutes.todoservices.jwt;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class JwtInMemoryUserDetailsService implements UserDetailsService {
static List<JwtUserDetails> inMemoryUserList = new ArrayList<>();
static {
inMemoryUserList.add(new JwtUserDetails(1L, "in28minutes",
"$2a$10$3zHzb.Npv1hfZbLEU5qsdOju/tk2je6W6PnNnY.c1ujWPcZh4PL6e", "ROLE_USER_2"));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<JwtUserDetails> findFirst = inMemoryUserList.stream()
.filter(user -> user.getUsername().equals(username)).findFirst();
if (!findFirst.isPresent()) {
throw new UsernameNotFoundException(String.format("USER_NOT_FOUND '%s'.", username));
}
return findFirst.get();
}
}
@Component
public class JwtTokenAuthorizationOncePerRequestFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserDetailsService jwtInMemoryUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.http.request.header}")
private String tokenHeader;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
logger.debug("Authentication Request For '{}'", request.getRequestURL());
final String requestTokenHeader = request.getHeader(this.tokenHeader);
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
logger.error("JWT_TOKEN_UNABLE_TO_GET_USERNAME", e);
} catch (ExpiredJwtException e) {
logger.warn("JWT_TOKEN_EXPIRED", e);
}
} else {
logger.warn("JWT_TOKEN_DOES_NOT_START_WITH_BEARER_STRING");
}
logger.debug("JWT_TOKEN_USERNAME_VALUE '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtInMemoryUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
@Component
public class JwtTokenUtil implements Serializable {
static final String CLAIM_KEY_USERNAME = "sub";
static final String CLAIM_KEY_CREATED = "iat";
private static final long serialVersionUID = -3301605591108950415L;
private Clock clock = DefaultClock.INSTANCE;
@Value("${jwt.signing.key.secret}")
private String secret;
@Value("${jwt.token.expiration.in.seconds}")
private Long expiration;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
private Boolean ignoreTokenExpiration(String token) {
// here you specify tokens, for that the expiration is ignored
return false;
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(createdDate)
.setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean canTokenBeRefreshed(String token) {
return (!isTokenExpired(token) || ignoreTokenExpiration(token));
}
public String refreshToken(String token) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate);
return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUserDetails user = (JwtUserDetails) userDetails;
final String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration * 1000);
}
}
@Component
public class JwtUnAuthorizedResponseAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"You would need to provide the Jwt Token to Access This resource");
}
}
public class JwtUserDetails implements UserDetails {
private static final long serialVersionUID = 5155720064139820502L;
private final Long id;
private final String username;
private final String password;
private final Collection<? extends GrantedAuthority> authorities;
public JwtUserDetails(Long id, String username, String password, String role) {
this.id = id;
this.username = username;
this.password = password;
List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(role));
this.authorities = authorities;
}
@JsonIgnore
public Long getId() {
return id;
}
@Override
public String getUsername() {
return username;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public String getPassword() {
return password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isEnabled() {
return true;
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;
@Autowired
private UserDetailsService jwtInMemoryUserDetailsService;
@Autowired
private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;
@Value("${jwt.get.token.uri}")
private String authenticationPath;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(jwtInMemoryUserDetailsService)
.passwordEncoder(passwordEncoderBean());
}
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity
.headers()
.frameOptions().sameOrigin() //H2 Console Needs this setting
.cacheControl(); //disable caching
}
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers(
HttpMethod.POST,
authenticationPath
)
.antMatchers(HttpMethod.OPTIONS, "/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/" //Other Stuff You want to Ignore
)
.and()
.ignoring()
.antMatchers("/h2-console/**/**");//Should not be in Production!
}
}
@RestController
@CrossOrigin(origins="http://localhost:4200")
public class JwtAuthenticationRestController {
@Value("${jwt.http.request.header}")
private String tokenHeader;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService jwtInMemoryUserDetailsService;
@RequestMapping(value = "${jwt.get.token.uri}", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtTokenResponse(token));
}
@RequestMapping(value = "${jwt.refresh.token.uri}", method = RequestMethod.GET)
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
String authToken = request.getHeader(tokenHeader);
final String token = authToken.substring(7);
String username = jwtTokenUtil.getUsernameFromToken(token);
JwtUserDetails user = (JwtUserDetails) jwtInMemoryUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.canTokenBeRefreshed(token)) {
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtTokenResponse(refreshedToken));
} else {
return ResponseEntity.badRequest().body(null);
}
}
@ExceptionHandler({ AuthenticationException.class })
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
private void authenticate(String username, String password) {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new AuthenticationException("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new AuthenticationException("INVALID_CREDENTIALS", e);
}
}
}
public class AuthenticationException extends RuntimeException {
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
}
public class JwtTokenRequest implements Serializable {
private static final long serialVersionUID = -5616176897013108345L;
private String username;
private String password;
public JwtTokenRequest() {
super();
}
public JwtTokenRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
public class JwtTokenResponse implements Serializable {
private static final long serialVersionUID = 8317676219297719109L;
private final String token;
public JwtTokenResponse(String token) {
this.token = token;
}
public String getToken() {
return this.token;
}
}
package com.in28minutes.todoservices.jwt; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class JwtInMemoryUserDetailsService implements UserDetailsService { static List<JwtUserDetails> inMemoryUserList = new ArrayList<>(); static { inMemoryUserList.add(new JwtUserDetails(1L, "in28minutes", "$2a$10$3zHzb.Npv1hfZbLEU5qsdOju/tk2je6W6PnNnY.c1ujWPcZh4PL6e", "ROLE_USER_2")); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<JwtUserDetails> findFirst = inMemoryUserList.stream() .filter(user -> user.getUsername().equals(username)).findFirst(); if (!findFirst.isPresent()) { throw new UsernameNotFoundException(String.format("USER_NOT_FOUND '%s'.", username)); } return findFirst.get(); } } @Component public class JwtTokenAuthorizationOncePerRequestFilter extends OncePerRequestFilter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private UserDetailsService jwtInMemoryUserDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Value("${jwt.http.request.header}") private String tokenHeader; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { logger.debug("Authentication Request For '{}'", request.getRequestURL()); final String requestTokenHeader = request.getHeader(this.tokenHeader); String username = null; String jwtToken = null; if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { logger.error("JWT_TOKEN_UNABLE_TO_GET_USERNAME", e); } catch (ExpiredJwtException e) { logger.warn("JWT_TOKEN_EXPIRED", e); } } else { logger.warn("JWT_TOKEN_DOES_NOT_START_WITH_BEARER_STRING"); } logger.debug("JWT_TOKEN_USERNAME_VALUE '{}'", username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.jwtInMemoryUserDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } } @Component public class JwtTokenUtil implements Serializable { static final String CLAIM_KEY_USERNAME = "sub"; static final String CLAIM_KEY_CREATED = "iat"; private static final long serialVersionUID = -3301605591108950415L; private Clock clock = DefaultClock.INSTANCE; @Value("${jwt.signing.key.secret}") private String secret; @Value("${jwt.token.expiration.in.seconds}") private Long expiration; public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getIssuedAtDateFromToken(String token) { return getClaimFromToken(token, Claims::getIssuedAt); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(clock.now()); } private Boolean ignoreTokenExpiration(String token) { // here you specify tokens, for that the expiration is ignored return false; } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { final Date createdDate = clock.now(); final Date expirationDate = calculateExpirationDate(createdDate); return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(createdDate) .setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact(); } public Boolean canTokenBeRefreshed(String token) { return (!isTokenExpired(token) || ignoreTokenExpiration(token)); } public String refreshToken(String token) { final Date createdDate = clock.now(); final Date expirationDate = calculateExpirationDate(createdDate); final Claims claims = getAllClaimsFromToken(token); claims.setIssuedAt(createdDate); claims.setExpiration(expirationDate); return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); } public Boolean validateToken(String token, UserDetails userDetails) { JwtUserDetails user = (JwtUserDetails) userDetails; final String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token)); } private Date calculateExpirationDate(Date createdDate) { return new Date(createdDate.getTime() + expiration * 1000); } } @Component public class JwtUnAuthorizedResponseAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -8970718410437077606L; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "You would need to provide the Jwt Token to Access This resource"); } } public class JwtUserDetails implements UserDetails { private static final long serialVersionUID = 5155720064139820502L; private final Long id; private final String username; private final String password; private final Collection<? extends GrantedAuthority> authorities; public JwtUserDetails(Long id, String username, String password, String role) { this.id = id; this.username = username; this.password = password; List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>(); authorities.add(new SimpleGrantedAuthority(role)); this.authorities = authorities; } @JsonIgnore public Long getId() { return id; } @Override public String getUsername() { return username; } @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } @JsonIgnore @Override public String getPassword() { return password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public boolean isEnabled() { return true; } } @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint; @Autowired private UserDetailsService jwtInMemoryUserDetailsService; @Autowired private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter; @Value("${jwt.get.token.uri}") private String authenticationPath; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(jwtInMemoryUserDetailsService) .passwordEncoder(passwordEncoderBean()); } @Bean public PasswordEncoder passwordEncoderBean() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf().disable() .exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .anyRequest().authenticated(); httpSecurity .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity .headers() .frameOptions().sameOrigin() //H2 Console Needs this setting .cacheControl(); //disable caching } @Override public void configure(WebSecurity webSecurity) throws Exception { webSecurity .ignoring() .antMatchers( HttpMethod.POST, authenticationPath ) .antMatchers(HttpMethod.OPTIONS, "/**") .and() .ignoring() .antMatchers( HttpMethod.GET, "/" //Other Stuff You want to Ignore ) .and() .ignoring() .antMatchers("/h2-console/**/**");//Should not be in Production! } } @RestController @CrossOrigin(origins="http://localhost:4200") public class JwtAuthenticationRestController { @Value("${jwt.http.request.header}") private String tokenHeader; @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService jwtInMemoryUserDetailsService; @RequestMapping(value = "${jwt.get.token.uri}", method = RequestMethod.POST) public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtTokenRequest authenticationRequest) throws AuthenticationException { authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); final UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails); return ResponseEntity.ok(new JwtTokenResponse(token)); } @RequestMapping(value = "${jwt.refresh.token.uri}", method = RequestMethod.GET) public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) { String authToken = request.getHeader(tokenHeader); final String token = authToken.substring(7); String username = jwtTokenUtil.getUsernameFromToken(token); JwtUserDetails user = (JwtUserDetails) jwtInMemoryUserDetailsService.loadUserByUsername(username); if (jwtTokenUtil.canTokenBeRefreshed(token)) { String refreshedToken = jwtTokenUtil.refreshToken(token); return ResponseEntity.ok(new JwtTokenResponse(refreshedToken)); } else { return ResponseEntity.badRequest().body(null); } } @ExceptionHandler({ AuthenticationException.class }) public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); } private void authenticate(String username, String password) { Objects.requireNonNull(username); Objects.requireNonNull(password); try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException e) { throw new AuthenticationException("USER_DISABLED", e); } catch (BadCredentialsException e) { throw new AuthenticationException("INVALID_CREDENTIALS", e); } } } public class AuthenticationException extends RuntimeException { public AuthenticationException(String message, Throwable cause) { super(message, cause); } } public class JwtTokenRequest implements Serializable { private static final long serialVersionUID = -5616176897013108345L; private String username; private String password; public JwtTokenRequest() { super(); } public JwtTokenRequest(String username, String password) { this.setUsername(username); this.setPassword(password); } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } } public class JwtTokenResponse implements Serializable { private static final long serialVersionUID = 8317676219297719109L; private final String token; public JwtTokenResponse(String token) { this.token = token; } public String getToken() { return this.token; } }
Course Recording Notes
#### Preview Video - Welcome to course on * in ** simple steps. - I’m Ranga Karanam. I’ve so and so much experience with … I’ve been using this framework for … - At in28minutes, we ask one question everyday - How to create more effective courses? All our success - * students on Udemy and * subscribers on Youtube - is a result of this pursuit of excellence. - You will develop * and * using * - You will learn the basics like * and move on to the advanced concepts like . - You will use - … todo … - Maven for dependency management, building and running the application in tomcat. - Eclipse IDE - All the code for this course and the step by step details are in our Github repository. - We have an awesome installation guide to help you install Maven and Eclipse. You are NOT expected to have any experience with Eclipse, Maven, or Tomcat. - What are we waiting for? Lets have some fun with * in ** steps. We had a lot of fun creating this course for you and We are confident that you will have a lot of fun. I hope you are as excited as we are to learn more. Go ahead and enroll for the course. Or take a test drive with a free preview. See you in the course.
Course Intro Video
- Welcome to this course on *. We are excited to teach you how to build awesome *.
- In this video, we introduce you to the different sections of the course. By the end of the video you should have a clear idea of how to make the best use of the course.
- We have organized this course into 6 different sections. We have designed each section to be independent of each other. That means, you have the flexibility of customizing the course based on your skills and your needs.
- If you have experience with Spring and Spring Boot, you can skip these sections.
- Lets get a quick overview of each of the sections now:
- Section I is an one hour introduction to Spring
- Section II is an one hour introduction to Spring Boot..
- In summary this is your course. Feel free to create your own path and tailor it to your needs.
- I will see you in the next video where we introduce you to our github repository
Overview of the Github Repository
- Welcome Back. In this video, we give you an overview of how our github repository for this course is organized.
- Github repository for this course is at **.
- Home page of the github repository has an overview of the course and installation guide
- For each hands-on section of the course, we have a seperate folder in the repository. You can see these five folders for * different sections
- Folder 1 contains …
- Folder 2 contains …
- Folder 3 contains …
- Each of these folders contain
- Step by Step details of the sections
- Complete code example at the end of the section
- Intermediate backups at different stages of the section
- Useful Links
- For example, let’s look at the folder for *. Home page of the folder contains
- Step by step details : What are we going to do in each step
- Useful Links : Different links that would be useful during the course
- Complete Code, Snippets and Examples : Example code that your can use during the section. For example, If you are using a class and you do not know the package of the class, you can search here and quickly find what you would need.
- Intermediate Backups : You can download any of these zips and import them into Eclipse as maven projects. File > Import > Existing Maven Projects.
- Understanding our github repository is key part of making best use of this course. I recommend to spend some time with our github repository and I will see you in the next video.
Installation of Tools Video
- In this video, we will help you install all the basic tools to get you started with the course
- We use
- Maven for Dependency Management
- Eclipse as IDE
- ..
- Step by step details to install Java, Eclipse and Maven are in the installation guide present here. Also included are links to 5 videos that will help you to install and trouble shoot installations.
- If you have any problems during the course, we recommend you to look at the troubleshooting section of the installation playlist.
- Get your tools ready and I will see you in the course
Each Section Introduction
- Why is this section important to the course?
- What is discussed in this section?
- What is the github folder for this section?
- Can a student skip this sections?
- Is there a trouble shooting guide?
- What are the backups available?
- Are examples in this section dependent on any other section?
Conclusion Video
- Congratulations! You have successfully completed the course on … We covered a wide range of topics starting from Spring, Spring Boot to ..... I’m sure you had a lot of fun doing this course. If you loved this course, we would love to hear from you. Do not forget to leave us a review. Until we see you in another in28minutes course, here’s bye from the team here at in28minutes.
- To find out more about * use these References
Templates
Welcome Message
## ADD A FEW SAMPLE REVIEWS AFter a couple of months ## ADD A FEW SAMPLE REVIEWS - in the description of the course Congratulations on joining this course from in28Minutes. There are three things you need to understand before you start this course! 1...... Listen + See + Do Hands-on + Repeat = 90% Retention For the first 2 hours, we repeat a few concepts to help you retain them. . 2...... Set Yourself a Goal Set 1 hour aside every day for the next week for this course! No exceptions allowed :) 3...... Udemy asks you for a review very early in the course! If you are not ready for giving a review, you can skip giving a review. Thank you and enjoy the course, Ranga From in28Minutes
Thank You for completing the course message
Congratulations on completing the course from in28Minutes. Good Luck for your future. Thank you , Ranga from in28Minutes
Bonus Lectures
TITLE : Bonus Lecture : Coupons for My Best-Selling Courses -30 Day Money Back Guarantee
I hope you enjoyed it! Connect and share your success (Course Completion Certificate) on Linked In - https://www.linkedin.com/in/rangakaranam/ Here are coupons for many of my best-selling courses. Please click the images/courses below to watch the course video previews (all of these courses have 30-day 100% money back guarantees): - Copy relevant courses from https://github.com/in28minutes/learn
Other Courses
- 300+ Videos and Courses - https://github.com/in28minutes/learn
- 25 Videos and Articles for Beginners on Spring Boot
About in28Minutes
- At in28Minutes, we ask ourselves one question everyday. How do we help you learn effectively - that is more quickly and retain more of what you have learnt?
- We use Problem-Solution based Step-By-Step Hands-on Approach With Practical, Real World Application Examples.
- Our success on Udemy and Youtube (2 Million Views & 12K Subscribers) speaks volumes about the success of our approach.
- While our primary expertise is on Development, Design & Architecture Java & Related Frameworks (Spring, Struts, Hibernate) we are expanding into the front-end world (Bootstrap, JQuery, React JS).
Our Beliefs
- Best Courses are interactive and fun.
- Foundations for building high quality applications are best laid down while learning.
Our Approach
- Problem Solution based Step by Step Hands-on Learning
- Practical, Real World Application Examples.
- We use 80-20 Rule. We discuss 20% things used 80% of time in depth. We touch upon other things briefly equipping you with enough knowledge to find out more on your own.
- We will be developing a demo application in the course, which could be reused in your projects, saving hours of your effort.
- We love open source and therefore, All our code is open source too and available on Github.
Troubleshooting
Rangas-MacBook-Pro:04-10-2018 rangaraokaranam$ node -v Rangas-MacBook-Pro:04-10-2018 rangaraokaranam$ npm -v 6.4.1 #Global npm uninstall -g React-cli npm cache verify npm install -g @React/cli@7.0.3 #Inside the project - If you had an earlier version of React cli rm -rf node_modules npm uninstall --save-dev React-cli npm install --save-dev @React/cli@latest npm install
What You will Learn?
Big Picture
- What is the High Level Architecture of our Full Stack Application?
- What is an SPA?
- What is React?
TypeScript and JavaScript
- I’m new to TypeScript. Will I be able to adapt to it?
- How does a JavaScript Class compare to a Java Class?
- Packages vs Modules
- import statements
- Decorator vs Annotation
- What is a JavaScript Module?
- How does JavaScript Syntax compare to Java Syntax?
- Arrays - Filtering, Spread Operator and Functional Stuff
- Custom Objects
React Basics
- What is React Component?
- What are the conventions for file extensions in React Projects?
- How do you build forms in React? How do you do Form Validation?
- What is Routing?
- How do you implement Routing in React?
- How do you call HTTP Services in React?
Running React Applications
- What is Root Component? What are Bootstrap Components? How is the React Application Bootstrapped?
\src\index.html,\src\main.ts,AppModule,AppComponent - Do Browsers understand JSX? How does JSX code get converted to JavaScript code?
Automated Tests and Code Quality
- What are unit tests? How are unit tests organized in React? How is different from Java?
- How can you run tests?
\src\karma.conf.ts - What are coding standards? How can you check coding standards for React Cli Project? What is Lint? What is Linting? Is there a Standard Style Guide for React?
\tslint.json - How can I run coding standards check for React Projects?
Course Details
Request URLs and Examples
Common Headers
Origin - http://localhost:4200 Content-Type - application/json Authorization - Bearer *** or - Basic *****
Retrieve all todos for a user
[
{
id: 1,
username: "in28minutes",
description: "Learn to Dance 2",
targetDate: "2018-11-09T12:05:18.647+0000",
: false,
},
{
id: 2,
username: "in28minutes",
description: "Learn about Microservices 2",
targetDate: "2018-11-09T12:05:18.647+0000",
: false,
},
{
id: 3,
username: "in28minutes",
description: "Learn about React",
targetDate: "2018-11-09T12:05:18.647+0000",
: false,
},
]
Retrieve a specific todo
{
id: 1,
username: "in28minutes",
description: "Learn to Dance 2",
targetDate: "2018-11-09T12:05:18.647+0000",
: false,
}
Creating a new todo
- POST to http://localhost:8080/users/in28minutes/todos with BODY of Request given below
{
"username": "in28minutes",
"description": "Learn to Drive a Car",
"targetDate": "2018-11-09T10:49:23.566+0000",
"done": false
}
Updating a new todo
- http://localhost:8080/users/in28minutes/todos/1 with BODY of Request given below
{
"id": 1
"username": "in28minutes",
"description": "Learn to Drive a Car",
"targetDate": "2018-11-09T10:49:23.566+0000",
"done": false
}
Delete todo
JWT Authenticate
{
"username":"ranga",
"password":"password@!23@#!"
}
Response
{
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyYW5nYSIsImV4cCI6MTU0MjQ3MjA3NCwiaWF0IjoxNTQxODY3Mjc0fQ.kD6UJQyxjSPMzAhoTJRr-Z5UL-FfgsyxbdseWQvk0fLi7eVXAKhBkWfj06SwH43sY_ZWBEeLuxaE09szTboefw"
}
Other URLS - Refresh - http://localhost:8080/authenticate
Useful Links
Connection to MySQL
create sequence hibernate_sequence start with 1 increment by 1
create table todo (
id bigint not null,
description varchar(255),
is_done boolean not null,
target_date timestamp,
username varchar(255),
primary key (id))
create sequence hibernate_sequence start with 1 increment by 1
create table todo (
id bigint not null,
description varchar(255),
is_done boolean not null,
target_date timestamp,
username varchar(255),
primary key (id))
launch.json
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:4200",//Line Changed "webRoot": "${workspaceFolder}" } ] }
npm install @React/material @React/cdk <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> npm install @React/flex-layout rxjs-compat
Diagrams
- Courtesy http://viz-js.com/
graph architecture {
node[style=filled,color="#59C8DE"]
//node [style=filled,color="#D14D28", fontcolor=white];
rankdir = TB;
node[shape=record]
FRONTEND[label=<React Application<BR />
<FONT POINT-SIZE="9">Modern JavaScript - ES6</FONT>>];
REST[label=<RESTFUL API<BR />
<FONT POINT-SIZE="9">Spring Boot on Java</FONT>>];
DB[label=<Database>];
FRONTEND -- REST -- DB
DB[shape=cylinder]
}
digraph architecture {
node[style=filled,color="#59C8DE"]
//node [style=filled,color="#D14D28", fontcolor=white];
rankdir = TB;
node[shape=record]
FRONTEND[label=<React Application<BR />
<FONT POINT-SIZE="9">JavaScript</FONT>>];
MODULE0[label=<Components>];
MODULE1[label=<Libraries>];
COMPONENT01[label=<Login>];
COMPONENT02[label=<Logout>];
COMPONENT03[label=<ListTodo>];
COMPONENT04[label=<Todo>];
COMPONENT05[label=<Header>];
COMPONENT06[label=<Footer>];
COMPONENT07[label=<Menu>];
COMPONENT11[label=<Formik>];
COMPONENT12[label=<Axios>];
COMPONENT13[label=<ReactRouter>];
FRONTEND -> MODULE0
FRONTEND -> MODULE1
MODULE0 -> COMPONENT01
MODULE0 -> COMPONENT02
MODULE0 -> COMPONENT03
MODULE0 -> COMPONENT04
MODULE0 -> COMPONENT05
MODULE0 -> COMPONENT06
MODULE0 -> COMPONENT07
MODULE1 -> COMPONENT11
MODULE1 -> COMPONENT12
MODULE1 -> COMPONENT13
}
graph architecture {
node[style=filled,color="#59C8DE"]
//node [style=filled,color="#D14D28", fontcolor=white];
rankdir = TB;
node[shape=record]
COMPONENT[label=<Component>];
View[label=<View<BR />
<FONT POINT-SIZE="9">JSX or Javascript</FONT>>];
Logic[label=<Logic<BR />
<FONT POINT-SIZE="9">Javascript</FONT>>];
Styling[label=<Styling<BR />
<FONT POINT-SIZE="9">CSS</FONT>>];
State[label=<State<BR />
<FONT POINT-SIZE="9">Internal Data Store</FONT>>];
Props[label=<Props<BR />
<FONT POINT-SIZE="9">Pass Data</FONT>>];
COMPONENT -- View
COMPONENT -- Logic
COMPONENT -- Styling
COMPONENT -- State
COMPONENT -- Props
}
graph architecture {
node[style=filled,color="#59C8DE"]
//node [style=filled,color="#D14D28", fontcolor=white];
rankdir = TB;
node[shape=record]
React -- Components
Components -- JSX
Components -- State
Components -- Props
React -- Features
Features -- Routing
Features -- Forms
Features -- RestAPICalls
Features -- Authentication
RestAPICalls[label=<Rest API Calls>]
Forms[label=<Forms and Validation>]
}
Todo
- JavaScript
- Object Review {property1, property2}
- Object Clone overview
- Arrow Operator
- What is npm?
- What is WebPack?
- Common Error Handling and Server Side Validation and Other REST API Features?
- Common Message Component for RESTful Calls
- Moving @CrossOrigin to a Common Location
- What is Bundling? What are runtime.js, polyfills.js, styles.js, vendor.js, main.js? How are they generated?
\src\main.ts,\src\polyfills.ts,\src\styles.css,\src\**\*.component.css - How can I rollback changes made by React CLI?
- How can you learn to write tests for React?
- How can you learn to write end to end tests for React?
- What are Dependency Injection Options other than root?
- Spring Security Authorization
- What are the different kinds of Data Binding?
- /blog/src/app/basics/basics.component.html
- React Specific Syntax
- [class.active]=”isActive()”
- [style.font]=”determineFont”
- How do you build various form elements in React?
- /blog/src/app/box/box.component.html
- /blog/src/app/form/form.component.html
- What is a Child Component?
- person-row.component.html, person.component.html
- How can you configure environment configuration in React Projects? src\environments folder
- How do you create a production deployment? What are the production optimizations that are? What is Uglification? What is Minification?
- https://medium.com/frontend-coach/7-must-have-visual-studio-code-extensions-for-React-af9c476147fd
- Dev vs Prod Configuration
- Deployment
- Seperate vs Together
- Deploying to Cloud
- Material Design
- Debugging with Visual Studio Code
- To debug the client side React code, we’ll need to install the Debugger for Chrome extension - https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome
- Open the Extensions view (⇧⌘X or Ctrl+Shift+X)
- Type Debugger for Chrome
- Install
- Reload
- Go to the Debug view (⇧⌘D or Ctrl+Shift+D)
- Click on gear button to create launch.json
- Choose Chrome from the Select Environment dropdown
- Set URL to “url”: “http://localhost:4200“
- Running Examples
- Download the zip or clone the Git repository.
- Unzip the zip file (if you downloaded one)
- Open Command Prompt and Change directory (cd) to folder containing pom.xml
- Open Eclipse
- File -> Import -> Existing Maven Project -> Navigate to the folder where you unzipped the zip
- Select the right project
- Choose the Spring Boot Application file (search for file with @SpringBootApplication)
- Right Click on the file and Run as Java Application
- You are all Set
- For help : use our installation guide - https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3
Next Steps
- Modern JavaScript
- https://github.com/mbeaudru/modern-js-cheatsheet#tdz_sample
- https://learnxinyminutes.com/docs/javascript/
- https://github.com/mjavascript/mastering-modular-javascript/blob/master/chapters/ch01.asciidoc
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript
- Modern Javascript Quickly - https://gist.github.com/gaearon/683e676101005de0add59e8bb345340c
- React
- https://raw.githubusercontent.com/reactjs/reactjs.org/master/static/html/single-file-example.html
- class vs className - https://stackoverflow.com/questions/46989454/class-vs-classname-in-react-16
- https://engineering.musefind.com/react-lifecycle-methods-how-and-when-to-use-them-2111a1b692b1
- https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes
OLD Starting Up Code
Example 1
import React, { Component } from “react”; import ReactDOM from “react-dom”;
import “./styles.css”;
function App() { //Basics // No playing with DOM
// Extension //Simple React Snippets //Prettier code formatter
//JSX //babeljs.io/repl const heading = “Hello World 1”;
const element =
{heading}
;const header =
Heading
;const body =
const footer =
const textAreaDefault = “Text Area Default Value”;
//const twoTagsAreNotAllowedAsReturn =
//React.Fragment instead of div as parent//const allTagsShouldBeClosed =
Word1 Word2 Word3
; //Spaces consolidated to 1 const whitespaceJSX2 =Line 1 Line 2
;const array = [1, 2, 3];
const selectValues = [“a”, “b”, “c”];
/* function TodoExampleWithFunction() { return ‘
TodoExampleWithFunction
‘; } */function TodoExampleWithFunction() { return
TodoExampleWithFunction
; }class EventsComponent extends Component { constructor() { super(); this.state = { currentEvent: “” }; this.registerEvent = this.registerEvent.bind(this); } registerEvent(e) { //console.log(e.type); this.setState({ currentEvent: e.type }); } render() { return (
class TodoExampleWithClass extends Component { render() { return
TodoExampleWithClass
; } }class ConditionalComponent extends Component { render() { return
Show
}class ParentComponent extends Component { constructor(props) { super(); //alert(props.children); } render() { return
{this.props.children}
; } }class ParentComponentWithEventsAndStateV1 extends Component {
constructor(props) {
super();
//alert(props.children);
}
render() {
return
class ChildComponentV1 extends Component { constructor() { super(); this.state = { currentEvent: “” }; } registerEvent(e) { this.setState({ currentEvent: e.type }); } render() { return (
class ParentComponentWithEventsAndState extends Component { constructor() { super(); this.state = { currentEvent: “” }; }
registerEvent(e) {
this.setState({
currentEvent: e.type + e.target.id + " " + e.target.value
});
}
render() {
return (
<div>
<ChildComponent
id="1"
registerEvent={this.registerEvent.bind(this)}
/>
<ChildComponent
id="2"
registerEvent={this.registerEvent.bind(this)}
/>
<div>{this.state.currentEvent}</div>
</div>
);
}
}
class ChildComponent extends Component { render() { return (
class DisplayTodo extends Component { constructor(props) { super(); //alert(JSON.stringify(props)); }
render() {
return (
<div>
<h3>{this.props.title}</h3>
{this.props.description}
</div>
);
}
}
class Counter extends Component { constructor() { super(); this.state = { count: 0 }; }
incrementCounterWithoutNeedingBind = () => {
//alert(this);
};
incrementCounter() {
//alert(this);
//alert(this.state);
//alert(this.state.count);
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<button onClick={this.incrementCounter.bind(this)}>Click Here</button>
counter - {this.state.count}
</div>
);
}
}
return (
{array.map((elt, i) => {
return (
<div>
{i} - {elt}
</div>
);
})}
{footer}
</div>
); }
const rootElement = document.getElementById(“root”);
ReactDOM.render(
Example 2
button background-color: green color: white padding: 15px 32px font-size: 16px width: 100px .count font-size: 50px padding: 15px 32px
class Application extends React.Component {
constructor(props) { super(props) this.incrementTotalCount = this.incrementTotalCount.bind(this) this.reset = this.reset.bind(this) this.state = { totalCount : 0 } }
incrementTotalCount(by = 1) { this.setState ( {totalCount: this.state.totalCount+by} ) }
reset() { console.log(‘reset’); }
render() { return
<Counter incrementTotal={this.incrementTotalCount} incrementBy={10}/>
<Counter incrementTotal={this.incrementTotalCount} incrementBy={5}/>
<Counter incrementTotal={this.incrementTotalCount} incrementBy={2}/>
<div className="count">Total Count - {this.state.totalCount}</div>
<button onClick={this.reset}>RESET</button>
</div>
}
}
class Counter extends React.Component {
constructor(props) { super(props) this.increment = this.increment.bind(this); this.decrement = this.decrement.bind(this); this.state = { count:0 } }
increment() { this.setState( {count: this.state.count+this.props.incrementBy} ) this.props.incrementTotal(this.props.incrementBy); }
decrement() { this.setState( {count: this.state.count-this.props.incrementBy} ) }
render() { return
React.render(
// React.render(
Example 4 - V3
/public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>My React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="theme-color" content="#000000" /> <!-- manifest.json provides metadata used when your web app is added to the homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> <title>My React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This HTML file is a template. If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> </body> </html>
/public/manifest.json
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
{ "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" }
/build/manifest.json
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
{ "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" }
/src/bootstrap.css
@import url(https://unpkg.com/bootstrap@4.1.0/dist/css/bootstrap.min.css)
@import url(https://unpkg.com/bootstrap@4.1.0/dist/css/bootstrap.min.css)
/src/App.css
.footer {
position: absolute;
bottom: 0;
width:100%;
height: 40px;
background-color: #222222;
color: white;
}
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.footer { position: absolute; bottom: 0; width:100%; height: 40px; background-color: #222222; color: white; } .App { text-align: center; } .App-logo { animation: App-logo-spin infinite 20s linear; height: 40vmin; } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render(<App />, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: http://bit.ly/CRA-PWA serviceWorker.unregister();
/src/index.css
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; }
/src/App.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; it('renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render(<App />, div); ReactDOM.unmountComponentAtNode(div); });
/src/serviceWorker.js
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read http://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit http://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
// This optional code is used to register a service worker. // register() is not called by default. // This lets the app load faster on subsequent visits in production, and gives // it offline capabilities. However, it also means that developers (and users) // will only see deployed updates on subsequent visits to a page, after all the // existing tabs open on the page have been closed, since previously cached // resources are updated in the background. // To learn more about the benefits of this model and instructions on how to // opt-in, read http://bit.ly/CRA-PWA const isLocalhost = Boolean( window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.1/8 is considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) ); export function register(config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 return; } window.addEventListener('load', () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. checkValidServiceWorker(swUrl, config); // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + 'worker. To learn more, visit http://bit.ly/CRA-PWA' ); }); } else { // Is not localhost. Just register service worker registerValidSW(swUrl, config); } }); } } function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) .then(registration => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker == null) { return; } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the updated precached content has been fetched, // but the previous service worker will still serve the older // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' ); // Execute callback if (config && config.onUpdate) { config.onUpdate(registration); } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. console.log('Content is cached for offline use.'); // Execute callback if (config && config.onSuccess) { config.onSuccess(registration); } } } }; }; }) .catch(error => { console.error('Error during service worker registration:', error); }); } function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl) .then(response => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type'); if ( response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then(registration => { registration.unregister().then(() => { window.location.reload(); }); }); } else { // Service worker found. Proceed as normal. registerValidSW(swUrl, config); } }) .catch(() => { console.log( 'No internet connection found. App is running in offline mode.' ); }); } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { registration.unregister(); }); } }
/src/logo.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>
/src/App.js
//TODO - event.preventDefault(), Constants for URL
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch
} from 'react-router-dom'
import './bootstrap.css';
import './App.css';
import Redirect from 'react-router-dom/Redirect';
import WelcomeService from './services/WelcomeService';
import TodoService from './services/TodoService';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import moment from 'moment';
import axios from "axios";
axios.interceptors.request.use(
config => {
if (BasicAuthenticationServiceInstance.isUserAlreadyLoggedIn()) {
config.headers.authorization = BasicAuthenticationServiceInstance.getAuthenticatedToken();
}
//console.log(config.headers.authorization)
return config;
},
error => Promise.reject(error)
);
//console.log('after ' + this.isUserLoggedIn());
class BasicAuthenticationService {
executeJWTAuthenticationService(username, password) {
return axios.post(`http://localhost:8080/authenticate`, {
username,
password
});
}
registerSuccessfulLoginForJWT(username, token) {
sessionStorage.setItem('authenticaterUser', username);
sessionStorage.setItem('token', `Bearer ${token}`);
}
executeBasicAuthenticationService(username, password) {
let basicAuthHeaderString = 'Basic ' + window.btoa(username + ':' + password);
return axios.get(`http://localhost:8080/basicauth`, { headers: { authorization: basicAuthHeaderString } });
}
registerSuccessfulLogin(username, password) {
let basicAuthHeaderString = 'Basic ' + window.btoa(username + ':' + password);
sessionStorage.setItem('authenticaterUser', username);
sessionStorage.setItem('token', basicAuthHeaderString);
}
isUserAlreadyLoggedIn() {
let user = sessionStorage.getItem('authenticaterUser')
let result = !(user === null)
console.log('userloggedin ' + result)
return result
}
getUserName() {
return sessionStorage.getItem('authenticaterUser')
}
getAuthenticatedToken() {
if (this.isUserAlreadyLoggedIn())
return sessionStorage.getItem('token')
}
logout() {
sessionStorage.removeItem('authenticaterUser')
sessionStorage.removeItem('token')
}
}
const BasicAuthenticationServiceInstance = new BasicAuthenticationService();
const UserContext = React.createContext()
const UserContextConsumer = UserContext.Consumer
class UserContextProvider extends Component {
state = {
username: '',
isUserLoggedin: false,
userLoggedInSuccessfully: (username) => this.userLoggedInSuccessfully(username),
userLoggedOut: () => this.userLoggedOut()
}
userLoggedInSuccessfully = username => {
this.setState({ username, isUserLoggedin: true })
}
userLoggedOut = () => {
this.setState({ username: '', isUserLoggedin: false })
}
render() {
//console.log(this.props)
return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
)
}
}
class Login extends Component {
constructor(props) {
super(props)
this.loginClicked = this.loginClicked.bind(this)
this.handleUsernameChange = this.handleUsernameChange.bind(this)
this.handlePasswordChange = this.handlePasswordChange.bind(this)
this.state = {
username: 'in28minutes',
password: 'dummy',
invalidLogin: false
}
}
//static contextType = UserContext;
render() {
const warningMessage = this.state.invalidLogin ? "invalid Login" : "";
return <>
<h1> Login!</h1>
<div className="container">
{this.state.invalidLogin &&
<div className="alert alert-warning">{warningMessage}</div>}
<div>
User Name : <input type="text" name="username" value={this.state.username} onChange={this.handleUsernameChange} />
Password : <input type="password" name="password" value={this.state.password} onChange={this.handlePasswordChange} />
<button onClick={this.loginClicked} className="btn btn-success">Login</button>
{/* <UserContextConsumer>
{(context) => (<div><p>{context.username}</p></div>)}
</UserContextConsumer> */}
</div>
</div>
</>
}
handleUsernameChange(event) {
this.setState({ username: event.target.value });
}
handlePasswordChange(event) {
this.setState({ password: event.target.value });
}
// loginClicked(e) { //Hardcoded Auth
// //console.log(this.state)
// const isValidUser = new HardcodedAuthenticationComponent().authenticate(this.state.username, this.state.password);
// //const isValidUser = TodoService.executeBasicAuthenticationService(this.state.username, this.state.password);
// if (isValidUser) {
// this.setState({ invalidLogin: false });
// console.log(this.context);
// this.context.userLoggedInSuccessfully(this.state.username);
// //console.log(this.state);
// //this.props.history.push('/welcome/')
// this.props.history.push(`/welcome/${this.state.username}`)
// } else {
// this.setState({ invalidLogin: true });
// }
// }
loginClicked(e) { //Basic Authentication
//console.log(this.state)
//const isValidUser = new HardcodedAuthenticationComponent().authenticate(this.state.username, this.state.password);
//BasicAuthenticationServiceInstance.executeBasicAuthenticationService(this.state.username, this.state.password).then(response => {
BasicAuthenticationServiceInstance.executeJWTAuthenticationService(this.state.username, this.state.password).then(response => {
console.log('execution successful')
this.setState({ invalidLogin: false });
//this.context.userLoggedInSuccessfully(this.state.username);
//this.context.userLoggedInSuccessfully(this.state.username);
//BasicAuthenticationServiceInstance.registerSuccessfulLogin(this.state.username, this.state.password);
BasicAuthenticationServiceInstance.registerSuccessfulLoginForJWT(this.state.username, response.data.token);
this.props.history.push(`/welcome/${this.state.username}`)
}, error => {
this.setState({ invalidLogin: true });
});
}
}
class Logout extends Component {
constructor(props) {
super(props)
}
render() {
var myInterceptor = axios.interceptors.request.use(function () {/*...*/ });
axios.interceptors.request.eject(0);
return <>
<h1>You are logged out</h1>
<div className="container">
Thank You For Using Our Application.
</div>
</>
}
}
const AuthenticatedRoute = ({ component: Component, authenticated, ...rest }) => (
// <UserContextConsumer>
// {({ isUserLoggedin }) => (
<Route
render={
props =>
BasicAuthenticationServiceInstance.isUserAlreadyLoggedIn()
? <Component {...props} username={BasicAuthenticationServiceInstance.getUserName()} />
: <Redirect to="/login" />
// <Redirect to={{ WHY!!
// pathname: "/login",
// state: { from: props.location }
// }}
// />
}
{...rest}
/>
// )}
// </UserContextConsumer>
)
class App extends Component {
render() {
return (
//<UserContextProvider>
<Router>
<>
<Menu></Menu>
<div className="container">
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/login" component={Login} />
<AuthenticatedRoute path="/welcome/:name" component={Welcome} />
<AuthenticatedRoute path="/todo/:todoId" component={Todo} />
<AuthenticatedRoute path="/todos" component={ListTodo} />
<Route path="/logout" component={Logout} />
<Route component={Error} />
</Switch>
</div>
<Footer></Footer>
</>
</Router>
// </UserContextProvider>
);
}
}
class Todo extends Component {
constructor(props) {
super(props);
this.validate = this.validate.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
id: props.match.params.todoId,
description: '',
targetDate: ''
}
}
componentDidMount() {
if (this.state.id != -1) {
TodoService.retrieveTodo('in28minutes', this.state.id).then(response => {
this.setState({
...response.data, targetDate: moment(response.data.targetDate).format('YYYY-MM-DD')
})
});
}
}
validate(values) {
let errors = {};
if (!values.description) {
errors.description = "Enter valid values";
} else if (values.description.length < 5) {
errors.description = "Enter atleast 5 characters in Description"
}
if (!moment(values.targetDate).isValid()) {
errors.targetDate = "Enter valid Target Date"
}
return errors;
}
onSubmit(values) {
if (this.state.id === -1) {
TodoService.createTodo('in28minutes', {
description: values.description,
targetDate: values.targetDate
}).then(response => {
console.log(response.data);
this.props.history.push('/todos');
});
} else {
TodoService.updateTodo('in28minutes', this.state.id, {
id: this.state.id,
description: values.description,
targetDate: values.targetDate
}).then(response => {
console.log(response.data);
this.props.history.push('/todos');
});
}
}
render() {
const { description, targetDate = '' } = this.state || {};
return (
<>
<h1>Todo</h1>
<div className="container">
<Formik
initialValues={{ description, targetDate }}
validateOnBlur={false}
enableReinitialize={true}
validate={this.validate}
onSubmit={this.onSubmit}>
{({ errors, isSubmitting }) => (
<Form>
<ErrorMessage className="alert alert-warning" component="div" name="description" />
<ErrorMessage className="alert alert-warning" component="div" name="targetDate" />
<fieldset className="form-group">
<label>Description</label>
<Field className={`form-control ${errors.description ? 'invalid' : ''}`} type="text" name="description" />
</fieldset>
<fieldset className="form-group">
<label>Target Date</label>
<Field className={`form-control ${errors.targetDate ? 'invalid' : ''}`} type="date" name="targetDate" />
</fieldset>
<button className="btn btn-success" type="submit" disabled={isSubmitting}>Save</button>
</Form>
)}
</Formik>
</div >
</>
);
}
}
class ListTodo extends Component {
//static contextType = UserContext;
constructor(props) {
super(props)
this.refreshTodos = this.refreshTodos.bind(this);
this.showNewTodoPage = this.showNewTodoPage.bind(this)
this.showUpdateTodoPage = this.showUpdateTodoPage.bind(this)
this.showDeleteTodoPage = this.showDeleteTodoPage.bind(this)
// this.state = {
// todo : {
// id : 1,
// description: 'Learn to Dance'
// }
// }
this.state = {
todos: [
{
id: 1,
description: 'Learn to Dance - old',
done: false,
targetDate: new Date(),
},
{
id: 2,
description: 'Become an Expert at Angular - old',
done: false,
targetDate: new Date(),
},
{
id: 3,
description: 'Visit India - old',
done: false,
targetDate: new Date(),
},
],
message: ''
}
}
showNewTodoPage() {
this.props.history.push('/todo/-1');
}
showUpdateTodoPage(e, todo) {
console.log(e, todo);
this.props.history.push(`/todo/${todo.id}`);
}
showDeleteTodoPage(e, todo) {
console.log(e, todo);
TodoService.deleteTodo(this.props.username, todo.id).then(response => {
console.log(response);
this.setState({
message: `Delete of Todo ${todo.id} Successful!`,
});
this.refreshTodos();
}
)
}
componentDidMount() {
this.refreshTodos();
}
refreshTodos() {
TodoService.retrieveAllTodos('in28minutes').then(response => {
this.setState({
todos: response.data,
})
}).catch(error => {
console.log(error)
});
}
render() {
// return <table border="1">
// <caption>My Todo's</caption>
// <thead>
// <tr>
// <th>id</th>
// <th>description</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <th>{this.state.todo.id}</th>
// <th>{this.state.todo.description}</th>
// </tr>
// </tbody>
// </table>
const todosView = this.state.todos.map(todo =>
<tr key={todo.id}>
<td>{todo.description}</td>
<td>{todo.targetDate.toString()}</td>
<td>{todo.done.toString()}</td>
<td><button onClick={((e) => this.showUpdateTodoPage(e, todo))} className="btn btn-success">Update</button></td>
<td><button onClick={((e) => this.showDeleteTodoPage(e, todo))} className="btn btn-warning">Delete</button></td>
</tr>
);
return (
<>
<h1> My Todo's</h1>
<div className="container">
{this.state.message && <div className="alert alert-success" >{this.state.message}</div>}
<table className="table">
<thead>
<tr>
<th>Description</th>
<th>Target Date</th>
<th>is Completed?</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{todosView}
</tbody>
</table>
<div className="row">
<button onClick={this.showNewTodoPage} className="btn btn-success">Add</button>
</div>
</div>
</>
)
}
}
class Error extends Component {
render() {
return <>Error Component!</>
}
}
class Welcome extends Component {
constructor(props) {
super(props)
this.getHelloWorldBean = this.getHelloWorldBean.bind(this);
this.getHelloWorld = this.getHelloWorld.bind(this);
this.state = {
username: props.match.params.name,
welcomeMessageFromService: ''
}
}
getHelloWorld() {
WelcomeService.retrieveHelloWorldMessage().then((res) => {
this.setState({
welcomeMessageFromService: res.data
});
}).catch((error) => {
console.log(error)
});
}
getHelloWorldBean() {
//WelcomeService.retrieveHelloWorldBean().then((res) => {
WelcomeService.retrieveHelloWorldPathVariable(this.state.username).then((res) => {
this.setState({
welcomeMessageFromService: res.data.message
});
}).catch((error) => {
console.log(error)
});
}
componentDidMount() {
this.getHelloWorld();
}
render() {
return <>
<h1> Welcome!</h1>
<div>Welcome {this.state.username}.
View your <Link to="/todos">Todos</Link></div>
<div>Message from Service {this.state.welcomeMessageFromService}</div>
<div><button className="btn btn-success" onClick={this.getHelloWorldBean}>Click this to get welcome message</button></div>
</>
}
}
class Menu extends Component {
//static contextType = UserContext;
render() {
const isUserLoggedin = BasicAuthenticationServiceInstance.isUserAlreadyLoggedIn()
return (
<header>
<nav className="navbar navbar-expand-md navbar-dark bg-dark">
<div>
<a href="https://www.in28minutes.com" className="navbar-brand">
in28minutes</a>
</div>
<ul className="navbar-nav">
{ isUserLoggedin &&
<li className="nav-link">
<Link to="/welcome/in28minutes" className="nav-link">Home</Link>
</li>
}
{
isUserLoggedin && <li className="nav-link">
<Link to="/todos" className="nav-link">Todos</Link>
</li>
}
</ul>
<ul className="navbar-nav navbar-collapse justify-content-end">
{!isUserLoggedin && <li className="nav-link">
<a href="/login" className="nav-link">Login</a>
</li>}
{isUserLoggedin && <li className="nav-link">
<Link to="/logout" className="nav-link" onClick={BasicAuthenticationServiceInstance.logout}>Logout</Link>
</li>}
</ul>
</nav>
</header>
)
}
}
class Footer extends Component {
render() {
return <footer className="footer">
<div className="container">
<span className="text-muted">All Rights Reserved 2018 @in28minutes</span>
</div>
</footer>
}
}
export default App;
//TODO - event.preventDefault(), Constants for URL import React, { Component } from 'react'; import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom' import './bootstrap.css'; import './App.css'; import Redirect from 'react-router-dom/Redirect'; import WelcomeService from './services/WelcomeService'; import TodoService from './services/TodoService'; import { Formik, Form, Field, ErrorMessage } from 'formik'; import moment from 'moment'; import axios from "axios"; axios.interceptors.request.use( config => { if (BasicAuthenticationServiceInstance.isUserAlreadyLoggedIn()) { config.headers.authorization = BasicAuthenticationServiceInstance.getAuthenticatedToken(); } //console.log(config.headers.authorization) return config; }, error => Promise.reject(error) ); //console.log('after ' + this.isUserLoggedIn()); class BasicAuthenticationService { executeJWTAuthenticationService(username, password) { return axios.post(`http://localhost:8080/authenticate`, { username, password }); } registerSuccessfulLoginForJWT(username, token) { sessionStorage.setItem('authenticaterUser', username); sessionStorage.setItem('token', `Bearer ${token}`); } executeBasicAuthenticationService(username, password) { let basicAuthHeaderString = 'Basic ' + window.btoa(username + ':' + password); return axios.get(`http://localhost:8080/basicauth`, { headers: { authorization: basicAuthHeaderString } }); } registerSuccessfulLogin(username, password) { let basicAuthHeaderString = 'Basic ' + window.btoa(username + ':' + password); sessionStorage.setItem('authenticaterUser', username); sessionStorage.setItem('token', basicAuthHeaderString); } isUserAlreadyLoggedIn() { let user = sessionStorage.getItem('authenticaterUser') let result = !(user === null) console.log('userloggedin ' + result) return result } getUserName() { return sessionStorage.getItem('authenticaterUser') } getAuthenticatedToken() { if (this.isUserAlreadyLoggedIn()) return sessionStorage.getItem('token') } logout() { sessionStorage.removeItem('authenticaterUser') sessionStorage.removeItem('token') } } const BasicAuthenticationServiceInstance = new BasicAuthenticationService(); const UserContext = React.createContext() const UserContextConsumer = UserContext.Consumer class UserContextProvider extends Component { state = { username: '', isUserLoggedin: false, userLoggedInSuccessfully: (username) => this.userLoggedInSuccessfully(username), userLoggedOut: () => this.userLoggedOut() } userLoggedInSuccessfully = username => { this.setState({ username, isUserLoggedin: true }) } userLoggedOut = () => { this.setState({ username: '', isUserLoggedin: false }) } render() { //console.log(this.props) return ( <UserContext.Provider value={this.state}> {this.props.children} </UserContext.Provider> ) } } class Login extends Component { constructor(props) { super(props) this.loginClicked = this.loginClicked.bind(this) this.handleUsernameChange = this.handleUsernameChange.bind(this) this.handlePasswordChange = this.handlePasswordChange.bind(this) this.state = { username: 'in28minutes', password: 'dummy', invalidLogin: false } } //static contextType = UserContext; render() { const warningMessage = this.state.invalidLogin ? "invalid Login" : ""; return <> <h1> Login!</h1> <div className="container"> {this.state.invalidLogin && <div className="alert alert-warning">{warningMessage}</div>} <div> User Name : <input type="text" name="username" value={this.state.username} onChange={this.handleUsernameChange} /> Password : <input type="password" name="password" value={this.state.password} onChange={this.handlePasswordChange} /> <button onClick={this.loginClicked} className="btn btn-success">Login</button> {/* <UserContextConsumer> {(context) => (<div><p>{context.username}</p></div>)} </UserContextConsumer> */} </div> </div> </> } handleUsernameChange(event) { this.setState({ username: event.target.value }); } handlePasswordChange(event) { this.setState({ password: event.target.value }); } // loginClicked(e) { //Hardcoded Auth // //console.log(this.state) // const isValidUser = new HardcodedAuthenticationComponent().authenticate(this.state.username, this.state.password); // //const isValidUser = TodoService.executeBasicAuthenticationService(this.state.username, this.state.password); // if (isValidUser) { // this.setState({ invalidLogin: false }); // console.log(this.context); // this.context.userLoggedInSuccessfully(this.state.username); // //console.log(this.state); // //this.props.history.push('/welcome/') // this.props.history.push(`/welcome/${this.state.username}`) // } else { // this.setState({ invalidLogin: true }); // } // } loginClicked(e) { //Basic Authentication //console.log(this.state) //const isValidUser = new HardcodedAuthenticationComponent().authenticate(this.state.username, this.state.password); //BasicAuthenticationServiceInstance.executeBasicAuthenticationService(this.state.username, this.state.password).then(response => { BasicAuthenticationServiceInstance.executeJWTAuthenticationService(this.state.username, this.state.password).then(response => { console.log('execution successful') this.setState({ invalidLogin: false }); //this.context.userLoggedInSuccessfully(this.state.username); //this.context.userLoggedInSuccessfully(this.state.username); //BasicAuthenticationServiceInstance.registerSuccessfulLogin(this.state.username, this.state.password); BasicAuthenticationServiceInstance.registerSuccessfulLoginForJWT(this.state.username, response.data.token); this.props.history.push(`/welcome/${this.state.username}`) }, error => { this.setState({ invalidLogin: true }); }); } } class Logout extends Component { constructor(props) { super(props) } render() { var myInterceptor = axios.interceptors.request.use(function () {/*...*/ }); axios.interceptors.request.eject(0); return <> <h1>You are logged out</h1> <div className="container"> Thank You For Using Our Application. </div> </> } } const AuthenticatedRoute = ({ component: Component, authenticated, ...rest }) => ( // <UserContextConsumer> // {({ isUserLoggedin }) => ( <Route render={ props => BasicAuthenticationServiceInstance.isUserAlreadyLoggedIn() ? <Component {...props} username={BasicAuthenticationServiceInstance.getUserName()} /> : <Redirect to="/login" /> // <Redirect to={{ WHY!! // pathname: "/login", // state: { from: props.location } // }} // /> } {...rest} /> // )} // </UserContextConsumer> ) class App extends Component { render() { return ( //<UserContextProvider> <Router> <> <Menu></Menu> <div className="container"> <Switch> <Route exact path="/" component={Login} /> <Route exact path="/login" component={Login} /> <AuthenticatedRoute path="/welcome/:name" component={Welcome} /> <AuthenticatedRoute path="/todo/:todoId" component={Todo} /> <AuthenticatedRoute path="/todos" component={ListTodo} /> <Route path="/logout" component={Logout} /> <Route component={Error} /> </Switch> </div> <Footer></Footer> </> </Router> // </UserContextProvider> ); } } class Todo extends Component { constructor(props) { super(props); this.validate = this.validate.bind(this); this.onSubmit = this.onSubmit.bind(this); this.state = { id: props.match.params.todoId, description: '', targetDate: '' } } componentDidMount() { if (this.state.id != -1) { TodoService.retrieveTodo('in28minutes', this.state.id).then(response => { this.setState({ ...response.data, targetDate: moment(response.data.targetDate).format('YYYY-MM-DD') }) }); } } validate(values) { let errors = {}; if (!values.description) { errors.description = "Enter valid values"; } else if (values.description.length < 5) { errors.description = "Enter atleast 5 characters in Description" } if (!moment(values.targetDate).isValid()) { errors.targetDate = "Enter valid Target Date" } return errors; } onSubmit(values) { if (this.state.id === -1) { TodoService.createTodo('in28minutes', { description: values.description, targetDate: values.targetDate }).then(response => { console.log(response.data); this.props.history.push('/todos'); }); } else { TodoService.updateTodo('in28minutes', this.state.id, { id: this.state.id, description: values.description, targetDate: values.targetDate }).then(response => { console.log(response.data); this.props.history.push('/todos'); }); } } render() { const { description, targetDate = '' } = this.state || {}; return ( <> <h1>Todo</h1> <div className="container"> <Formik initialValues={{ description, targetDate }} validateOnBlur={false} enableReinitialize={true} validate={this.validate} onSubmit={this.onSubmit}> {({ errors, isSubmitting }) => ( <Form> <ErrorMessage className="alert alert-warning" component="div" name="description" /> <ErrorMessage className="alert alert-warning" component="div" name="targetDate" /> <fieldset className="form-group"> <label>Description</label> <Field className={`form-control ${errors.description ? 'invalid' : ''}`} type="text" name="description" /> </fieldset> <fieldset className="form-group"> <label>Target Date</label> <Field className={`form-control ${errors.targetDate ? 'invalid' : ''}`} type="date" name="targetDate" /> </fieldset> <button className="btn btn-success" type="submit" disabled={isSubmitting}>Save</button> </Form> )} </Formik> </div > </> ); } } class ListTodo extends Component { //static contextType = UserContext; constructor(props) { super(props) this.refreshTodos = this.refreshTodos.bind(this); this.showNewTodoPage = this.showNewTodoPage.bind(this) this.showUpdateTodoPage = this.showUpdateTodoPage.bind(this) this.showDeleteTodoPage = this.showDeleteTodoPage.bind(this) // this.state = { // todo : { // id : 1, // description: 'Learn to Dance' // } // } this.state = { todos: [ { id: 1, description: 'Learn to Dance - old', done: false, targetDate: new Date(), }, { id: 2, description: 'Become an Expert at Angular - old', done: false, targetDate: new Date(), }, { id: 3, description: 'Visit India - old', done: false, targetDate: new Date(), }, ], message: '' } } showNewTodoPage() { this.props.history.push('/todo/-1'); } showUpdateTodoPage(e, todo) { console.log(e, todo); this.props.history.push(`/todo/${todo.id}`); } showDeleteTodoPage(e, todo) { console.log(e, todo); TodoService.deleteTodo(this.props.username, todo.id).then(response => { console.log(response); this.setState({ message: `Delete of Todo ${todo.id} Successful!`, }); this.refreshTodos(); } ) } componentDidMount() { this.refreshTodos(); } refreshTodos() { TodoService.retrieveAllTodos('in28minutes').then(response => { this.setState({ todos: response.data, }) }).catch(error => { console.log(error) }); } render() { // return <table border="1"> // <caption>My Todo's</caption> // <thead> // <tr> // <th>id</th> // <th>description</th> // </tr> // </thead> // <tbody> // <tr> // <th>{this.state.todo.id}</th> // <th>{this.state.todo.description}</th> // </tr> // </tbody> // </table> const todosView = this.state.todos.map(todo => <tr key={todo.id}> <td>{todo.description}</td> <td>{todo.targetDate.toString()}</td> <td>{todo.done.toString()}</td> <td><button onClick={((e) => this.showUpdateTodoPage(e, todo))} className="btn btn-success">Update</button></td> <td><button onClick={((e) => this.showDeleteTodoPage(e, todo))} className="btn btn-warning">Delete</button></td> </tr> ); return ( <> <h1> My Todo's</h1> <div className="container"> {this.state.message && <div className="alert alert-success" >{this.state.message}</div>} <table className="table"> <thead> <tr> <th>Description</th> <th>Target Date</th> <th>is Completed?</th> <th>Update</th> <th>Delete</th> </tr> </thead> <tbody> {todosView} </tbody> </table> <div className="row"> <button onClick={this.showNewTodoPage} className="btn btn-success">Add</button> </div> </div> </> ) } } class Error extends Component { render() { return <>Error Component!</> } } class Welcome extends Component { constructor(props) { super(props) this.getHelloWorldBean = this.getHelloWorldBean.bind(this); this.getHelloWorld = this.getHelloWorld.bind(this); this.state = { username: props.match.params.name, welcomeMessageFromService: '' } } getHelloWorld() { WelcomeService.retrieveHelloWorldMessage().then((res) => { this.setState({ welcomeMessageFromService: res.data }); }).catch((error) => { console.log(error) }); } getHelloWorldBean() { //WelcomeService.retrieveHelloWorldBean().then((res) => { WelcomeService.retrieveHelloWorldPathVariable(this.state.username).then((res) => { this.setState({ welcomeMessageFromService: res.data.message }); }).catch((error) => { console.log(error) }); } componentDidMount() { this.getHelloWorld(); } render() { return <> <h1> Welcome!</h1> <div>Welcome {this.state.username}. View your <Link to="/todos">Todos</Link></div> <div>Message from Service {this.state.welcomeMessageFromService}</div> <div><button className="btn btn-success" onClick={this.getHelloWorldBean}>Click this to get welcome message</button></div> </> } } class Menu extends Component { //static contextType = UserContext; render() { const isUserLoggedin = BasicAuthenticationServiceInstance.isUserAlreadyLoggedIn() return ( <header> <nav className="navbar navbar-expand-md navbar-dark bg-dark"> <div> <a href="https://www.in28minutes.com" className="navbar-brand"> in28minutes</a> </div> <ul className="navbar-nav"> { isUserLoggedin && <li className="nav-link"> <Link to="/welcome/in28minutes" className="nav-link">Home</Link> </li> } { isUserLoggedin && <li className="nav-link"> <Link to="/todos" className="nav-link">Todos</Link> </li> } </ul> <ul className="navbar-nav navbar-collapse justify-content-end"> {!isUserLoggedin && <li className="nav-link"> <a href="/login" className="nav-link">Login</a> </li>} {isUserLoggedin && <li className="nav-link"> <Link to="/logout" className="nav-link" onClick={BasicAuthenticationServiceInstance.logout}>Logout</Link> </li>} </ul> </nav> </header> ) } } class Footer extends Component { render() { return <footer className="footer"> <div className="container"> <span className="text-muted">All Rights Reserved 2018 @in28minutes</span> </div> </footer> } } export default App;
/src/services/TodoService.js
import axios from "axios";
class TodoService {
retrieveAllTodos(username) {
return axios.get(`http://localhost:8080/users/${username}/todos`);
//return axios.get(`http://in28minutes-restful-web-services.cfapps.io/users/${username}/todos`);
}
deleteTodo(username, id) {
return axios.delete(`http://localhost:8080/users/${username}/todos/${id}`);
}
retrieveTodo(username, id) {
console.log(id)
return axios.get(`http://localhost:8080/users/${username}/todos/${id}`);
}
updateTodo(username, id, todo) {
return axios.put(`http://localhost:8080/users/${username}/todos/${id}`, todo);
}
createTodo(username, todo) {
return axios.post(`http://localhost:8080/users/${username}/todos`, todo);
}
}
export default new TodoService();
import axios from "axios"; class TodoService { retrieveAllTodos(username) { return axios.get(`http://localhost:8080/users/${username}/todos`); //return axios.get(`http://in28minutes-restful-web-services.cfapps.io/users/${username}/todos`); } deleteTodo(username, id) { return axios.delete(`http://localhost:8080/users/${username}/todos/${id}`); } retrieveTodo(username, id) { console.log(id) return axios.get(`http://localhost:8080/users/${username}/todos/${id}`); } updateTodo(username, id, todo) { return axios.put(`http://localhost:8080/users/${username}/todos/${id}`, todo); } createTodo(username, todo) { return axios.post(`http://localhost:8080/users/${username}/todos`, todo); } } export default new TodoService();
/src/services/WelcomeService.js
import axios from "axios";
class WelcomeService {
retrieveHelloWorldBean() {
return axios.get('http://localhost:8080/hello-world-bean');
}
retrieveHelloWorldMessage() {
return axios.get(`http://localhost:8080/hello-world`);
}
retrieveHelloWorldPathVariable(name){
return axios.get(`http://localhost:8080/hello-world/path-variable/${name}`);
}
}
export default new WelcomeService();
import axios from "axios"; class WelcomeService { retrieveHelloWorldBean() { return axios.get('http://localhost:8080/hello-world-bean'); } retrieveHelloWorldMessage() { return axios.get(`http://localhost:8080/hello-world`); } retrieveHelloWorldPathVariable(name){ return axios.get(`http://localhost:8080/hello-world/path-variable/${name}`); } } export default new WelcomeService();
/package.json
{
"name": "my-todo-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.18.0",
"formik": "^1.5.1",
"moment": "^2.24.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.3",
"validator": "^10.11.0"
},
"scripts": {
"start": "PORT=4200 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
{ "name": "my-todo-app", "version": "0.1.0", "private": true, "dependencies": { "axios": "^0.18.0", "formik": "^1.5.1", "moment": "^2.24.0", "react": "^16.7.0", "react-dom": "^16.7.0", "react-router-dom": "^4.3.1", "react-scripts": "2.1.3", "validator": "^10.11.0" }, "scripts": { "start": "PORT=4200 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] }
class Application extends React.Component {
render() {
return
} }
class Counters extends React.Component {
constructor(props) { super(props) this.state = { counter: 0 } }
increment = (by) => { console.log(‘increment’ + by) }
render() { return
}
}
class Counter extends React.Component { constructor(props) { super(props) this.state = { counter: 0 } this.increment = this.increment.bind(this) } render() { return
} }
React.render(
Last Attempt
import React, { Component } from ‘react’; import {BrowserRouter as Router, Route} from ‘react-router-dom’; import ‘./App.css’;
class App extends Component { render() { return (
class LoginComponent extends Component {
constructor(props) { super(props) this.state = { username: ‘in28minutes’, password: ‘’, hasLoginFailed: false, message: ‘’ } }
handleUsernameChange = (event) => { this.setState({ username: event.target.value }); }
handlePasswordChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }
loginClicked = (e) => { if(this.state.username===’in28minutes’ && this.state.password===’dummy’) { this.props.history.push(‘/welcome’); } else { this.setState({ hasLoginFailed: true }) }
}
render() { return (
{this.state.username} - {this.state.password}/}
function ShowValidationMessage(props) { if(props.showErrorMessage) { return
function WelcomeComponent() { return
export default App;