article

Friday, October 22, 2021

ReactJS Pagination - React axios.get

ReactJS Pagination - React axios.get

REST API at https://jsonplaceholder.typicode.com/posts for testing

What is Axios?
https://axios-http.com/docs/intro
Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and nodejs with the same codebase). On the server-side it uses the native node.js http module, while on the client (browser) it uses XMLHttpRequests.
Installing Using npm: $ npm install axios
src/index.js
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css'

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
src/index.css
//src/index.css
.pagination-container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  font-weight: 500;
  font-size: 15px;
}

.pagination-container a {
  display: flex;
  justify-content: center;
  align-items: center;
  color: black;
  height: 40px;
  width: 40px;
  text-decoration: none;
  transition: background-color .2s;
  border: 1px solid #ddd;
  cursor: pointer;
  
}

.pagination-container a.active {
  background-color: #007bff;
  color: white;
  border: 1px solid #7cbddb;
}

.pagination-container a.disabled { 
  opacity: 0.2;
}
.pagination-container a:hover:not(.active) {background-color: rgb(238, 238, 238);}
src/App.js
//src/App.js
import React, { useState, useEffect } from 'react';
import Pagination from './Pagination'
import axios from 'axios'

function App() {
  const [posts, setPosts] = useState([])
  const [loading, setLoading] = useState(false)
  const [currentPage, setCurrentPage] = useState(1)
  const [postsPerPage] = useState(7) //7 Per Page

  useEffect(() => {
    const fetchPosts = async () => {
      setLoading(true)
      const res = await axios.get('https://jsonplaceholder.typicode.com/posts')
      setPosts(res.data)
      setLoading(false)
    }

    fetchPosts()
  }, [])

  if (loading && posts.length === 0) {
    return <h2>Loading...</h2>
  }
  //Get current posts
  const indexOfLastPost = currentPage * postsPerPage;
  const indexOfFirstPost = indexOfLastPost - postsPerPage;
  const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost)
  const howManyPages = Math.ceil(posts.length/postsPerPage)
  
  return (
    <div className="container" style={{padding: 20}}>
      <h4 className="d-inline-block">ReactJS Pagination - React axios.get</h4>

      <table className="table table-striped">
          <thead className="thead-light ">
            <th>ID</th>
            <th>Title</th>
            <th>Body</th>
          </thead>
          <tbody>
          {currentPosts.map(row => <tr>
            <td>{row.id}</td>
              <td>{row.title}</td>
              <td>{row.body}</td>
            </tr>)}
          </tbody>
        </table>

      <Pagination pages = {howManyPages} setCurrentPage={setCurrentPage}/>
    </div>
  );
}

export default App;
src/Pagination.js
//src/Pagination.js
import React, { useState, useEffect } from 'react';

function Pagination({ pages = 7, setCurrentPage }) {

  //Set number of pages
  const numberOfPages = []
  for (let i = 1; i <= pages; i++) {
    numberOfPages.push(i)
  }

  // Current active button number
  const [currentButton, setCurrentButton] = useState(1)

  // Array of buttons what we see on the page
  const [arrOfCurrButtons, setArrOfCurrButtons] = useState([])

  useEffect(() => {
    let tempNumberOfPages = [...arrOfCurrButtons]

    let dotsInitial = '...'
    let dotsLeft = '... '
    let dotsRight = ' ...'

    if (numberOfPages.length < 6) {
      tempNumberOfPages = numberOfPages
    }

    else if (currentButton >= 1 && currentButton <= 3) {
      tempNumberOfPages = [1, 2, 3, 4, dotsInitial, numberOfPages.length]
    }

    else if (currentButton === 4) {
      const sliced = numberOfPages.slice(0, 5)
      tempNumberOfPages = [...sliced, dotsInitial, numberOfPages.length]
    }

    else if (currentButton > 4 && currentButton < numberOfPages.length - 2) {               
      const sliced1 = numberOfPages.slice(currentButton - 2, currentButton)               
      const sliced2 = numberOfPages.slice(currentButton, currentButton + 1)                
      tempNumberOfPages = ([1, dotsLeft, ...sliced1, ...sliced2, dotsRight, numberOfPages.length]) 
    }
    
    else if (currentButton > numberOfPages.length - 3) {                
      const sliced = numberOfPages.slice(numberOfPages.length - 4)       
      tempNumberOfPages = ([1, dotsLeft, ...sliced])                        
    }
    
    else if (currentButton === dotsInitial) {
      setCurrentButton(arrOfCurrButtons[arrOfCurrButtons.length-3] + 1) 
    }
    else if (currentButton === dotsRight) {
      setCurrentButton(arrOfCurrButtons[3] + 2)
    }

    else if (currentButton === dotsLeft) {
      setCurrentButton(arrOfCurrButtons[3] - 2)
    }

    setArrOfCurrButtons(tempNumberOfPages)
    setCurrentPage(currentButton)
  }, [currentButton])


  return (
    <div className="pagination-container">
      <a
        href="#"
        className={`${currentButton === 1 ? 'disabled' : ''}`}
        onClick={() => setCurrentButton(prev => prev <= 1 ? prev : prev - 1)}
      >
        Prev
      </a>

      {arrOfCurrButtons.map(((item, index) => {
        return <a
          href="#"
          key={index}
          className={`${currentButton === item ? 'active' : ''}`}
          onClick={() => setCurrentButton(item)}
        >
          {item}
        </a>
      }))}

      <a
        href="#"
        className={`${currentButton === numberOfPages.length ? 'disabled' : ''}`}
        onClick={() => setCurrentButton(prev => prev >= numberOfPages.length ? prev : prev + 1)}
      >
        Next
      </a>
    </div>
  );
}
export default Pagination

Thursday, October 21, 2021

ReactJS Axios JSON REST API

ReactJS Axios JSON REST API

REST API at http://jsonplaceholder.typicode.com/users for testing 

The componentDidMount() life-cycle hook. This is executed when your React component is inserted/mounted in the DOM.

Installing the Axios Client
$ npm install axios

C:\reactdev\myreactdev>npm install axios

https://axios-http.com/docs/intro
https://github.com/axios/axios

src/index.js
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
src/App.js
//src/App.js
import React, { Component } from 'react';
import axios from 'axios';
const API_URL = 'http://jsonplaceholder.typicode.com';

class App extends Component {
  state = {
    users: []
  }
  //https://jsonplaceholder.typicode.com/users
  componentDidMount() {
    const url = `${API_URL}/users/`; //alt + 96 = `
    axios.get(url).then(response => response.data)
    .then((data) => {
      this.setState({ users: data })
      console.log(this.state.users)
     })
  }
  render() {

    return (
       <div className="container" style={{padding: 20}}>
        <div className="col-xs-8">
        <h1>ReactJS Axios JSON REST API</h1>
		<table className="table table-striped">
          <thead className="thead-light ">
            <th>Name</th>
            <th>User Name</th>
            <th>Email</th>
            <th>Website</th>
          </thead>
          <tbody>
		  {this.state.users.map((user) => (
			<tr>
              <td>{user.name}</td>
              <td>{user.username}</td>
              <td>{user.email}</td>
              <td>{user.website}</td>
            </tr>
			))}
          </tbody>
        </table>
        </div>
       </div>
    );
  }
}
export default App;

Wednesday, October 20, 2021

ReactJS REST API Post Request

ReactJS REST API Post Request

REST API at https://jsonplaceholder.typicode.com/posts for testing

src/index.js
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
src/App.js
//src/App.js
import React from 'react';

class RestController extends React.Component {
	constructor(props) {
		super(props);
		this.state = {user: []};
	}
	
	componentDidMount() {
		fetch('https://jsonplaceholder.typicode.com/posts', {
			method: 'POST',
			body: JSON.stringify({
				title: 'New title added',
				body: 'New body added unt aut facere repellat provident occaecati excepturi optio reprehenderit',
				userId: 10
			}),
			headers: {
				"Content-type": "application/json; charset=UTF-8"
			}
		}).then(response => {
				return response.json()
			}).then(json => {
				this.setState({
					user:json
				});
			});
	}
	render() {                            
		return (
			<div style={{padding: 20}}>
        <p><h2>ReactJS REST API Post Request</h2></p>
				<p><b>New Resource created in the server as shown below</b></p>
				<p>Id : {this.state.user.id}</p>
				<p>Title : {this.state.user.title}</p>
				<p>Body : {this.state.user.body}</p>
				<p>UserId : {this.state.user.userId}</p>
			</div>
		)
	}
}

export default RestController;

Tuesday, October 19, 2021

SwiftUI Star Rating

SwiftUI Star Rating

In this tutorial I'm going to show how to create rating bar using SwiftUI

ContentView.swift
 
//
//  ContentView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct ContentView: View {
    
    @State var selected = -1
    @State var message = false
    
    var body: some View {
        VStack {
            Image("1")
                .resizable()
                .aspectRatio(contentMode: .fill)
            Text("Pinoy Food Recipes")
                .font(.title)
                .fontWeight(.medium)
            Form {
                Section {
                    HStack {
                        VStack {
                            Text("Detail")
                                .foregroundColor(.gray)
                                .font(.title)
                            
                            Text("Traditional Foods You Want to Eat When in the Philippines Visiting the Philippines anytime soon? If you are, you want to enjoy some of the fine foods that people have eaten there for generations. While some go on vacation and want to stick with their traditional favorites, you want the full experience in a great nation like the Philippines.")
                                .foregroundColor(.gray)
                                .font(.callout)
                                .frame(alignment: .leading)
                        }
                    }
                    HStack {
                        Text("Rating")
                        Spacer()
                        
                        HStack {
                            RatingView(selected: $selected, message: $message)
                        }
                    }.alert(isPresented: $message) {
                        Alert(title: Text("Rating Submit"), message: Text("You Rated \(self.selected + 1) out of 5 Star Rating"), dismissButton: .none)
                    }
                     
                }
 
            }
        }
        .environment(\.colorScheme, .light)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
 
struct RatingView : View {
    
    @Binding var selected : Int
    @Binding var message : Bool
    
    var body: some View {
        ForEach(0..<5) { rating in
            Image(systemName: "star.fill")
                .resizable()
                .frame(width: 30, height: 30)
                .foregroundColor(self.selected >= rating ? .yellow : .gray)
                .onTapGesture {
                    self.selected = rating
                    self.message.toggle()
                }
        }
    }
}

ReactJS How to call API - fetch then json

ReactJS How to call API - fetch then json

In this tutorial I will show show how to fetch data via API call in React JS.
https://reqres.in/api/users/

src/index.js
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
	 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
src/App.js
//src/App.js
import React from 'react';
import './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userList: [],
      loading: false
    }
    this.getUserList = this.getUserList.bind(this);
  }

  getUserList() {
    this.setState({ loading: true });
    fetch('https://reqres.in/api/users/?per_page=5&page=2')
      .then(res => res.json())
      .then(res => {
        setTimeout(() => {
          this.setState({ loading: false, userList: res.data });
        }, 2000);
      })
  }

  render() {
    const { userList, loading } = this.state;

    return (
      <div className="container App">

        <h4 className="d-inline-block">ReactJS How to call API - fetch then json</h4>
        <button className="btn btn-info float-right" onClick={this.getUserList} disabled={loading}>{loading ? 'Loading...' : 'Show User List'}</button>
        <div className="clearfix"></div>

        <table className="table table-striped">
          <thead className="thead-light ">
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email</th>
            <th>Avatar</th>
          </thead>
          <tbody>
            {userList.map(row => <tr>
              <td>{row.first_name}</td>
              <td>{row.last_name}</td>
              <td>{row.email}</td>
              <td><img src={row.avatar} width="50" height="50" /></td>
            </tr>)}
            {userList.length == 0 && <tr>
              <td className="text-center" colSpan="4">
                <b>No data found to display.</b>
              </td>
            </tr>}
          </tbody>
        </table>

      </div>
    );
  }
}

export default App;
src/App.css
.App {
  padding:20px;
}

Vue js PHP MySQLi Registration

Vue js PHP MySQLi Registration

In this tutorial I will show how to create a registration form and also read data and delete data from the database mysqli

vuejs CDN https://vuejs.org/v2/guide/installation.html

https://vuejs.org/

Axios is a promise-based HTTP Client for node.js and the browser. https://axios-http.com/docs/intro using Using jsDelivr CDN: https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js https://axios-http.com/docs/post_example

CREATE TABLE `user` (
  `user_id` bigint(20) NOT NULL,
  `user_name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `user_email` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `user_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `user`
--

INSERT INTO `user` (`user_id`, `user_name`, `user_email`, `user_password`) VALUES
(1, 'cairocoders', 'cairocoders@gmail.com', 'pass'),
(2, 'tutorial101.blogspot.com', 'cairocoders08@gmail.com', 'pass');


ALTER TABLE `user`
  ADD PRIMARY KEY (`user_id`);

ALTER TABLE `user`
  MODIFY `user_id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21;

index.php
//index.php
<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Vue js PHP MySQLi Registration</title>
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
 </head>
 <body>
<div class="container">
	<h1 class="page-header text-center">Vue js PHP MySQLi Registration</h1>
	<div id="vueregapp">
		<div class="col-md-4">
 
			<div class="panel panel-primary">
			  	<div class="panel-heading"><span class="glyphicon glyphicon-user"></span> User Registration</div>
			  	<div class="panel-body">
			    	<label>User Name:</label>
			    	<input type="text" class="form-control" ref="username" v-model="regDetails.username" v-on:keyup="keymonitor">
			    	<div class="text-center" v-if="errorUsername">
			    		<span style="font-size:13px;">{{ errorUsername }}</span>
			    	</div>
					<label>Email:</label>
			    	<input type="text" class="form-control" ref="email" v-model="regDetails.email" v-on:keyup="keymonitor">
			    	<div class="text-center" v-if="errorEmail">
			    		<span style="font-size:13px;">{{ errorEmail }}</span>
			    	</div>
			    	<label>Password:</label>
			    	<input type="password" class="form-control" ref="password" v-model="regDetails.password" v-on:keyup="keymonitor">
			    	<div class="text-center" v-if="errorPassword">
			    		<span style="font-size:13px;">{{ errorPassword }}</span>
			    	</div>
			  	</div>
			  	<div class="panel-footer">
			  		<button class="btn btn-primary btn-block" @click="userReg();"><span class="glyphicon glyphicon-check"></span> Sign up</button>
			  	</div>
			</div>
 
			<div class="alert alert-danger text-center" v-if="errorMessage">
				<button type="button" class="close" @click="clearMessage();"><span aria-hidden="true">×</span></button>
				<span class="glyphicon glyphicon-alert"></span> {{ errorMessage }}
			</div>
 
			<div class="alert alert-success text-center" v-if="successMessage">
				<button type="button" class="close" @click="clearMessage();"><span aria-hidden="true">×</span></button>
				<span class="glyphicon glyphicon-check"></span> {{ successMessage }}
			</div>
 
		</div>
 
		<div class="col-md-8">
			<h3>Users Table</h3>
			<table class="table table-bordered table-striped">
				<thead>
					<th>Username</th>
					<th>Email</th>
					<th>Password</th>
					<th>Delete</th>
				</thead>
				<tbody>
					<tr v-for="user in users">
						<td>{{ user.user_name }}</td>
						<td>{{ user.user_email }}</td>
						<td>{{ user.user_password	 }}</td>
						<td><button type="button" name="delete" class="btn btn-danger btn-xs delete" @click="deleteData(user.user_id);">Delete</button></td>
					</tr>
				</tbody>
			</table>
		</div>
	</div>
</div>
<script src="vuejs.js"></script>
</body>
</html>
vuejs.js
//vuejs.js
var app = new Vue({
	el: '#vueregapp',
	data:{
		successMessage: "",
		errorMessage: "",
		errorUsername: "",
		errorEmail: "",
		errorPassword: "",
		users: [],
		regDetails: {username: '', email: '', password: ''},
	},
 
	mounted: function(){
		this.getAllUsers();
	},
 
	methods:{
		getAllUsers: function(){
			axios.get('action.php')
				.then(function(response){
					if(response.data.error){
						app.errorMessage = response.data.message;
					}
					else{
						app.users = response.data.users;
					}
				});
		},
 
		userReg: function(){
			var regForm = app.toFormData(app.regDetails);
			axios.post('action.php?action=register', regForm)
				.then(function(response){
					console.log(response);
					if(response.data.username){
						app.errorUsername = response.data.message;
						app.focusUsername();
						app.clearMessage();
					}
					else if(response.data.email){
						app.errorEmail = response.data.message;
						app.focusEmail();
						app.clearMessage();
					}
					else if(response.data.password){
						app.errorPassword = response.data.message;
						app.errorEmail='';
						app.focusPassword();
						app.clearMessage();
					}
					else if(response.data.error){
						app.errorMessage = response.data.message;
						app.errorEmail='';
						app.errorPassword='';
					}
					else{
						app.successMessage = response.data.message;
					 	app.regDetails = {email: '', password:''};
					 	app.errorEmail='';
						app.errorPassword='';
					 	app.getAllUsers();
					}
				});
		},
 
		focusUsername: function(){
			this.$refs.username.focus();
		},		
		
		focusEmail: function(){
			this.$refs.email.focus();
		},
 
		focusPassword: function(){
			this.$refs.password.focus();
		},
 
		keymonitor: function(event) {
       		if(event.key == "Enter"){
         		app.userReg();
        	}
       	},
 
		toFormData: function(obj){
			var form_data = new FormData();
			for(var key in obj){
				form_data.append(key, obj[key]);
			}
			return form_data;
		},
 
		clearMessage: function(){
			app.errorMessage = '';
			app.successMessage = '';
		},
		
		deleteData: function(user_id){ //alert(user_id)
			if(confirm("Are you sure you want to remove this data?")){ 
				axios.post('action.php?action=delete&id='+ user_id +'', {
				 action:'delete',
				}).then(function(response){
					alert(response.data.message);
					app.getAllUsers();
				});
			}
		}
 
	}
});
action.php
//action.php
<?php
 
$conn = new mysqli("localhost", "root", "", "testingdb");
 
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
 
$out = array('error' => false, 'username'=> false, 'email'=> false, 'password' => false);
 
$action = 'alldata';
 
if(isset($_GET['action'])){
	$action = $_GET['action'];
}
 
 
if($action == 'alldata'){
	$sql = "select * from user";
	$query = $conn->query($sql);
	$users = array();
 
	while($row = $query->fetch_array()){
		array_push($users, $row);
	}
 
	$out['users'] = $users;
}
 
if($action == 'register'){
 
	function check_input($data) {
	  $data = trim($data);
	  $data = stripslashes($data);
	  $data = htmlspecialchars($data);
	  return $data;
	}
 
	$username = check_input($_POST['username']);
	$email = check_input($_POST['email']);
	$password = check_input($_POST['password']);
 
	if($username==''){
		$out['username'] = true;
		$out['message'] = "User Name is required";
	}	
	
	elseif($email==''){
		$out['email'] = true;
		$out['message'] = "Email is required";
	}
 
	elseif(!filter_var($email, FILTER_VALIDATE_EMAIL)){
		$out['email'] = true;
		$out['message'] = "Invalid Email Format";
	}
 
	elseif($password==''){
		$out['password'] = true;
		$out['message'] = "Password is required";
	}
 
	else{
		$sql="select * from user where user_email='$email'";
		$query=$conn->query($sql);
 
		if($query->num_rows > 0){
			$out['email'] = true;
			$out['message'] = "Email already exist";
		}
 
		else{
			$sql = "insert into user (user_name, user_email, user_password) values ('$username', '$email', '$password')";
			$query = $conn->query($sql);
 
			if($query){
				$out['message'] = "User Added Successfully";
			}
			else{
				$out['error'] = true;
				$out['message'] = "Could not add User";
			}
		}
	}
}

if($action == 'delete'){

	$del = $_GET['id'];                              
	$sqldel = "DELETE FROM user WHERE user_id = '".$del."'";
    $conn->query($sqldel);
	  
	$out['message'] = "Deleted $del";
	
}

$conn->close();
 
header("Content-type: application/json");
echo json_encode($out);
die();
 
 
?>

Monday, October 18, 2021

SwiftUI Login Sign Up LinearGradient Screen

SwiftUI Login Sign Up LinearGradient Screen

ContentView.swift
 
//
//  ContentView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct ContentView: View {
    
    @State var user = ""
    @State var pass = ""
    @State var login = false
    @State var signup = false
    
    var body: some View {
        
        ZStack{
            
            LinearGradient(gradient: .init(colors: [Color("1"),Color("2")]), startPoint: .leading, endPoint: .trailing).edgesIgnoringSafeArea(.all)
            
            Login(login: $login, signup: $signup, user: $user, pass: $pass)

        }.alert(isPresented: $login) {
            Alert(title: Text(self.user), message: Text(self.pass), dismissButton: .none)
        }
        .sheet(isPresented: $signup) {
            signUp(signup: self.$signup)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Login.swift
 
//
//  Login.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct Login : View {
    
    @Binding var login : Bool
    @Binding var signup : Bool
    @Binding var user : String
    @Binding var pass : String
    
    var body : some View{
        
        VStack(alignment: .center, spacing: 22, content: {
            Image("logo").resizable().frame(width: 100, height: 100).padding(.bottom, 15)
            HStack{
                Image(systemName: "person.fill").resizable().frame(width: 20, height: 20)
                TextField("Username", text: $user).padding(.leading, 12).font(.system(size: 20))
                
            }.padding(12)
            .background(Color("Color"))
            .cornerRadius(20)
            
            HStack{
                Image(systemName: "lock.fill").resizable().frame(width: 15, height: 20).padding(.leading, 3)
                SecureField("Password", text: $pass).padding(.leading, 12).font(.system(size: 20))
                
            }.padding(12)
            .background(Color("Color"))
            .cornerRadius(20)
            
            Button(action: {
                self.login.toggle()
            }) {
                Text("Login").foregroundColor(.white).padding().frame(width: 150)
            }
            .background(LinearGradient(gradient: .init(colors: [Color("3"),Color("4")]), startPoint: .leading, endPoint: .trailing))
            .cornerRadius(20)
            .shadow(radius: 25)
            
            Button(action: {
                
            }) {
                Text("Forget password?").foregroundColor(.white)
            }
            
            VStack{
                Text("Dont have account yet").foregroundColor(.white)
                Button(action: {
                    self.signup.toggle()
                }) {
                    Text("SignUp").foregroundColor(.white).padding().frame(width: 150)
                    
                }
                .background(LinearGradient(gradient: .init(colors: [Color("3"),Color("4")]), startPoint: .leading, endPoint: .trailing))
                .cornerRadius(20)
                .shadow(radius: 25)
                
            }.padding(.top, 35)
        })
        .padding(.horizontal, 18)
    }
}
signUp.swift
 
//
//  signUp.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct signUp : View  {
    
    @Binding var signup : Bool
    @State var user : String = ""
    @State var pass : String = ""
    @State var repass : String = ""
    
    var body : some View{
        
        ZStack{
            
            LinearGradient(gradient: .init(colors: [Color("1"),Color("2")]), startPoint: .leading, endPoint: .trailing).edgesIgnoringSafeArea(.all)
            
            VStack(alignment: .center, spacing: 22, content: {
                     Image("logo").resizable().frame(width: 100, height: 100).padding(.bottom, 15)
                     HStack{
                         Image(systemName: "person.fill").resizable().frame(width: 20, height: 20)
                         TextField("Username", text: $user).padding(.leading, 12).font(.system(size: 20))
                         
                     }.padding(12)
                     .background(Color("Color"))
                     .cornerRadius(20)
                     
                    HStack {
                         Image(systemName: "lock.fill").resizable().frame(width: 15, height: 20).padding(.leading, 3)
                         
                         SecureField("Password", text: $repass).padding(.leading, 12).font(.system(size: 20))
                         
                     }.padding(12)
                     .background(Color("Color"))
                     .cornerRadius(20)
                     
                     HStack{
                         Image(systemName: "lock.fill").resizable().frame(width: 15, height: 20).padding(.leading, 3)
                         
                         SecureField("Re-Password", text: $pass).padding(.leading, 12).font(.system(size: 20))
                         
                     }.padding(12)
                     .background(Color("Color"))
                     .cornerRadius(20)
                     
                     Button(action: {
                         self.signup.toggle()
                     }) {
                         Text("SignUp").foregroundColor(.white).padding().frame(width: 150)
                     }
                     .background(LinearGradient(gradient: .init(colors: [Color("1"),Color("2")]), startPoint: .leading, endPoint: .trailing))
                     .cornerRadius(20)
                     .shadow(radius: 25)
                 })
                 .padding(.horizontal, 18)
        }
    }
}

Sunday, October 17, 2021

SwiftUI Firebase Simple Contact List

SwiftUI Firebase Simple Contact List In this tutorial I will show how Add document and fetch documents

ContentView.swift
 
//
//  ContentView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import Firebase

struct ContentView: View {
    
    @ObservedObject private var viewModel = ContactViewModel()

    @State private var showModal = false
    
    var body: some View {
        NavigationView {
            List() {
                ForEach(viewModel.contacts) { contact in
                    VStack(alignment: .leading) {
                        Text(contact.name ?? "").font(.title)
                    }
                }
            }.onAppear() {
                self.viewModel.fetchData()
            }
            .navigationTitle("Contact List")
            .navigationBarItems(trailing:
                Button(action: {
                    self.showModal = true
                }) {
                    VStack {
                        Image(systemName: "plus")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 32, height: 32, alignment: .center)
                            .foregroundColor(.green)
                    }
                }
                .sheet(isPresented: self.$showModal, onDismiss: {  }) {
                    AddView(showModal: self.$showModal)
                })
            .navigationBarTitle(Text("Add New"), displayMode: .inline)

        }
    }
    
}
 
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
ContactViewModel.swift
 
//
//  ContactViewModel.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import Foundation
import FirebaseFirestore
class ContactViewModel: ObservableObject {
    
    @Published var contacts = [Contact]()
    private var db = Firestore.firestore()
    
    
    func fetchData() {
        db.collection("contacts").addSnapshotListener { (querySnapshot, error) in
            guard let documents = querySnapshot?.documents else {
                print("No documents")
                return
            }
            self.contacts = documents.map { (queryDocumentSnapshot) -> Contact in
                let data = queryDocumentSnapshot.data()
                let name = data["name"] as? String ?? ""
                return Contact(name: name)
            }
        }
    }
    
    func addData(name: String) {
        db.collection("contacts").addDocument(data: ["name": name]) { error in
            if let error = error {
                print(error.localizedDescription)
            }
        }
    }

}
Contact.swift
 
//
//  Contact.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import FirebaseFirestoreSwift

struct Contact: Codable, Identifiable {
    var id: String = UUID().uuidString
    var name: String?
}
AddView.swift
 
//
//  AddView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct AddView: View {
    
    @Binding var showModal: Bool
    @State private var name: String = ""
    @ObservedObject private var viewModel = ContactViewModel()
    
    var body: some View {
        NavigationView {
            Form {
              Section(header: Text("Name")) {
                  TextField("Enter your name", text: $name)
              }
              Section() {
                  Button(action: {
                      self.viewModel.addData(name: name)
                      self.showModal.toggle()
                  }) {
                      HStack {
                          Text("Add New")
                              .font(.title)
                      }
                  .padding()
                  .foregroundColor(.white)
                  .background(Color.blue)
                  .cornerRadius(30)
                  }
               }
            }
            .navigationTitle("New Contact")
        }
    }
}
DevSwiftUIApp.swift
 
//
//  DevSwiftUIApp.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import Firebase

@main
struct DevSwiftUIApp: App {
     
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject,UIApplicationDelegate{
      
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
          
        FirebaseApp.configure()
        return true
    }
}

Vue.js PHP fetchAll Data from Mysql Database

Vue.js PHP fetchAll Data from Mysql Database

https://vuejs.org/

Axios is a promise-based HTTP Client for node.js and the browser. https://axios-http.com/docs/intro using Using jsDelivr CDN: https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js https://axios-http.com/docs/post_example


CREATE TABLE `employee` (
  `id` int(11) NOT NULL,
  `name` varchar(100) NOT NULL,
  `position` varchar(100) NOT NULL,
  `office` varchar(100) NOT NULL,
  `age` int(11) NOT NULL,
  `salary` int(11) NOT NULL,
  `photo` varchar(150) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `employee` (`id`, `name`, `position`, `office`, `age`, `salary`, `photo`) VALUES
(1, 'Tiger Wood', 'Accountant', 'Tokyo', 36, 5689, '01.jpg'),
(2, 'Mark Oto Ednalan', 'Chief Executive Officer (CEO)', 'London', 56, 5648, '02.jpg'),
(3, 'Jacob thompson', 'Junior Technical Author', 'San Francisco', 23, 5689, '03.jpg'),
(4, 'cylde Ednalan', 'Software Engineer', 'Olongapo', 23, 54654, '04.jpg'),
(5, 'Rhona Davidson', 'Software Engineer', 'San Francisco', 26, 5465, '05.jpg'),
(6, 'Quinn Flynn', 'Integration Specialist', 'New York', 53, 56465, '06.jpg'),
(8, 'Tiger Nixon', 'Software Engineer', 'London', 45, 456, '07.jpg'),
(9, 'Airi Satou updated', 'Pre-Sales Support updated', 'New York', 25, 4568, '08.jpg'),
(10, 'Angelica Ramos updated', 'Sales Assistant updated', 'New York', 45, 456, '09.jpg'),
(11, 'Ashton updated', 'Senior Javascript Developer', 'Olongapo', 45, 54565, '01.jpg'),
(12, 'Bradley Greer', 'Regional Director', 'San Francisco', 27, 5485, '02.jpg'),
(13, 'Brenden Wagner', 'Javascript Developer', 'San Francisco', 38, 65468, '03.jpg'),
(14, 'Brielle Williamson', 'Personnel Lead', 'Olongapo', 56, 354685, '04.jpg'),
(15, 'Bruno Nash', 'Customer Support', 'New York', 36, 65465, '05.jpg'),
(16, 'cairocoders', 'Sales Assistant', 'Sydney', 45, 56465, '06.jpg'),
(17, 'Zorita Serrano', 'Support Engineer', 'San Francisco', 38, 6548, '07.jpg'),
(18, 'Zenaida Frank', 'Chief Operating Officer (COO)', 'San Francisco', 39, 545, '08.jpg'),
(19, 'Sakura Yamamoto', 'Support Engineer', 'Tokyo', 48, 5468, '05.jpg'),
(20, 'Serge Baldwin', 'Data Coordinator', 'Singapore', 85, 5646, '05.jpg'),
(21, 'Shad Decker', 'Regional Director', 'Tokyo', 45, 4545, '05.jpg');

ALTER TABLE `employee`
  ADD PRIMARY KEY (`id`);


ALTER TABLE `employee`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=22;

fetchdata.php
//fetchdata.php
<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Vue.js PHP fetchAll Data from Mysql Database</title>
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <div class="container" id="fetchAlldiv">
   <br />
   <h3 align="center">Vue.js PHP fetchAll Data from Mysql Database</h3>
   <br />
   <div class="panel panel-default">
    <div class="panel-heading">
     <div class="row">
       <h3 class="panel-title"> Employee Records</h3>
     </div>
    </div>
    <div class="panel-body">
		<div class="table-responsive">
		  <table class="table table-bordered table-striped">
		   <tr>
			<th>Name</th>
			<th>Position</th>
			<th>Office</th>
			<th>Edit</th>
			<th>Delete</th>
		   </tr>
		   <tr v-for="row in allData">
			<td>{{ row.name }}</td>
			<td>{{ row.position }}</td>
			<td>{{ row.office }}</td>
			<td><button type="button" name="edit" class="btn btn-primary btn-xs edit">Edit</button></td>
			<td><button type="button" name="delete" class="btn btn-danger btn-xs delete">Delete</button></td>
		   </tr>
		  </table>
		</div>
    </div>
   </div>
  </div>
<script src="js_fetchdata.js"></script>
</body>
</html>
js_fetchdata.js
//js_fetchdata.js
var application = new Vue({
	el:'#fetchAlldiv',
		data:{
		  allData:'',
	},
	methods:{
		fetchAllData:function(){ //show records
		   axios.post('fetchall.php', {
			action:'fetchall'
		   }).then(function(response){
			application.allData = response.data;
		   });
		}
	}, 
	created:function(){
	  this.fetchAllData();
	}
});
fetchall.php
//fetchall.php
<?php 
$connect = new PDO("mysql:host=localhost;dbname=testingdb", "root", "");
$received_data = json_decode(file_get_contents("php://input"));

$data = array();
if($received_data->action == 'fetchall') //axios.post('fetchall.php', { action:'fetchall'
{
	$query = "
	 SELECT * FROM employee 
	 ORDER BY id DESC
	 ";
	$statement = $connect->prepare($query);
	$statement->execute();
	while($row = $statement->fetch(PDO::FETCH_ASSOC))
	{
	  $data[] = $row;
	}
	echo json_encode($data);
	
}

?>

Thursday, October 14, 2021

ReactJS Shopping Cart

ReactJS Shopping Cart

install react font awesome

https://fontawesome.com/v5.15/how-to-use/on-the-web/using-with/react
npm i --save @fortawesome/fontawesome-svg-core

Install react-bootstrap
npm install --save react-bootstrap

src/index.js
 
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
src/App.js
//src/App.js
import React from 'react';
import Header from './components/Header';
import Shop from './components/Shop';

function App() {
  return (
    <div>
      <Header></Header>
      <Shop></Shop>  
    </div>
  );
}

export default App; 
src/components/Header.js
//src/components/Header.js
import React from 'react';
import './Header.css';

const Header = () => {
    return (
        <div className="header">
            <div className="p-3">
                <h1>ReactJS Shopping Cart</h1>
            </div>
        
            <nav>
                <a href="/">Home</a>
                <a href="/shop">Shop</a>
                <a href="/order">Order</a>
                <a href="/profile">profile</a>
            </nav>
        </div>
    );
};
export default Header;
src/components/Header.css
//src/components/Header.css
.header .p-3 h1 {
	text-align:center;	
}	
.header nav{
    background-color: #1a6600;
    padding: 5px;
    line-height: 40px;
    font-size: 18px;
}
nav a{
    text-decoration: none;
    color: white;
    margin-right: 20px;
    padding: 5px;
}
nav a:hover{
    background-color: gray;
}

.search-box{
    background-color: #1a6600;
    padding: 10px;
}
#search-input{
    width: 700px;
    padding: 6px;
}
.shipping-card{
    font-size: 18px;
    color: white;
    margin-left: 10px;
    cursor: pointer;
}
src/components/Shop.js
//src/components/Shop.js
import React, { useEffect, useState } from 'react';
import JsonData from './Data/data.json';
import Product from './Product/Product';
import Card from './Card/Card';

const Shop = () => {
    const [product, setProduct] = useState([]);

    const [card, setCard] = useState([]);

    useEffect(() => {
        setProduct(JsonData);
    }, []);

    const addProduct = (product) => {
        const newCard = [...card, product]
        setCard(newCard);
    }
    return (
        <div className="container-fluid d-flex">
            <div className="col-lg-9 product-area border-right">

                {
                    product.map(pd => <Product product={pd} addProduct={addProduct}></Product>)
                }

            </div>

            <div className="col-lg-3 card-area">
                <Card card={card}></Card>
            </div>
        </div>
    );
};

export default Shop;
src/components/Data/data.json
//src/components/data.json
[{
    "id": 1,
    "image": "https://images.pexels.com/photos/7606067/pexels-photo-7606067.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    "name": "Official Executive Chair Styles and Exclusive For Smart Office",
    "price": "50",
    "categories": "furniture"
  }, {
    "id": 2,
    "image": "https://images.pexels.com/photos/4440214/pexels-photo-4440214.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    "name": "Official Executive Chair Styles and Exclusive For Smart Office",
    "price": "200",
    "categories": "Bed"
  }, {
    "id": 3,
    "image": "https://images.pexels.com/photos/945688/pexels-photo-945688.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    "name": "Official Executive Chair Styles and Exclusive For Smart Office",
    "price": "300",
    "categories": "Sofa"
  }]
src/components/Product/Product.js
//src/components/Product/Product.js
import React from 'react';
import './Product.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { Button } from 'react-bootstrap';

const Product = (props) => {
    const {name, image, price, categories} = props.product;   
    return (
        <div className="container">
            <div className="product-card shadow-sm">
            <img className="product-img img-fluid" src={image} alt=""/>
            <h5 className="categories">{categories}</h5>
            <h5 className="product-title">{name}</h5>
            <h5>Price: ${price}</h5>

            <Button onClick={() => props.addProduct(props.product)}
                className="btn btn-success">
                <FontAwesomeIcon icon={faPlus} /> Add to Card</Button>
            </div>
        </div>
    );
};

export default Product;
src/components/Product/Product.css
////src/components/Product/Product.css
.product-img{
    height: 200px;
}
.product-card{
    width: 260px;
    padding: 10px;
    border-radius: 4px;
    margin: 20px;
    float: left;border:1px #1a6600 solid;
}
.categories{
    font-size: 14px;
    font-weight: 400;
    color: gray;
}
.product-title{
    font-size: 16px;
    color: grey;
}
src/Components/Card/Card.js
//src/Components/Card/Card.js
import React from 'react';
import './Card.css';
import { Button } from 'react-bootstrap';

const Card = (props) => {
    const card = props.card;
    
    let total = 0;
    for (let i = 0; i < card.length; i++) {
        const product = card[i];
        total = total + Number(product.price);
    }

    let shipping = 0;
    if(total > 10){
        shipping = 0;
    }
    else if (total > 5) {
        shipping = 4.99;
    }
    else if (total > 0) {
        shipping = 12.99;
    }
    const tax = (total / 10).toFixed(2); 
    const grandTotal = (total + shipping + Number(tax)).toFixed(2);
    return (
        <div className="summary-box shadow-sm">
            <h4 className="summary-title">Order Summary</h4> <hr/>
            <h6>Items Ordered: {card.length}</h6>
            <h6>Product Price: ${total}</h6>
            <h5><small>Shipping Cost: ${shipping}</small></h5>
            <h5><small>Tax + vat: ${tax}</small></h5>
            <h5>Total Price: ${grandTotal}</h5>
            <Button variant="success mt-2">Product to checkout</Button>
        </div>
    );
};

export default Card;
src/Components/Card/Card.css
//src/Components/Card/Card.css
.summary-box{
    border-radius: 4px;
    margin-top: 5px;
    position: fixed;
    width: 280px;
    height: 300px;
    padding: 15px;
}
.summary-title{
    text-align: center;
    color: green;
    margin-bottom: 15px;
}
h5{
    color: gray;
}
h6{
    color: gray;
}

Wednesday, October 13, 2021

SwiftUI Firebase Booklist - Product List, Product Details and Checkout

SwiftUI Firebase Booklist - Product List, Product Details and Checkout
Learn step by step how to create a SwiftUI Firebase Booklist, SwiftUI and Combine, This is the MVVM architecture

https://github.com/firebase/firebase-ios-sdk.git

https://github.com/SDWebImage/SDWebImageSwiftUI.git

ContentView.swift
 
//
//  ContentView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import Firebase
import SDWebImageSwiftUI

struct ContentView: View {
     
    @StateObject var viewModel = BooksViewModel()
    @State var presentAddBookSheet = false
     
    private func bookRowView(book: Book) -> some View {
      NavigationLink(destination: BookDetailsView(book: book)) {
          HStack {

              AnimatedImage(url: URL(string: book.image)!).resizable().frame(width: 70, height: 110)
                  .aspectRatio(contentMode: .fit)
                  .clipShape(RoundedRectangle(cornerRadius: 10))
                  .shadow(color: .gray, radius: 10, x: 5, y: 5)
                  .padding()
              
              VStack(alignment: .leading) {
                  Text(book.title).font(.headline)
                  Text("by " + book.author).font(.subheadline).foregroundColor(.gray)
                  Spacer().frame(height: 15)
                  Text("$" + String(book.price)).font(.title)
              }
          }
      }
    }
     
    var body: some View {
      NavigationView {
        List {
          ForEach (viewModel.books) { book in
            bookRowView(book: book)
          }
        }
        .navigationBarTitle("Book List")
        .onAppear() {
          print("BooksListView appears. Subscribing to data updates.")
          self.viewModel.subscribe()
        }
      }
    }
  }
 
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Book.swift
 
//
//  Book.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import Foundation
import FirebaseFirestoreSwift
import Combine

struct Book: Identifiable, Codable {
  @DocumentID var id: String?
  var title: String
  var author: String
  var numberOfPages: Int
  var image: String
  var price: Double
  var description: String
  var isAvailable: Bool
    
  enum CodingKeys: String, CodingKey {
    case id
    case title
    case author
    case numberOfPages = "pages"
    case image
    case price
    case description
    case isAvailable
  }
}

struct Order: Identifiable, Codable {
  @DocumentID var id: String?
  var username: String
  var title: String
  var price: Double
  var image: String
}
BookViewModel.swift
 
//
//  BookViewModel.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import Foundation
import Combine
import FirebaseFirestore

class BookViewModel: ObservableObject {
   
  @Published var book: Book
  @Published var modified = false
   
  private var cancellables = Set<AnyCancellable>()
   
    init(book: Book = Book(title: "", author: "", numberOfPages: 0, image: "", price: 0, description: "", isAvailable: false)) {
    self.book = book
     
    self.$book
      .dropFirst()
      .sink { [weak self] book in
        self?.modified = true
      }
      .store(in: &self.cancellables)
  }
}
BooksViewModel.swift
 
//
//  BooksViewModel.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import Foundation
import Combine
import FirebaseFirestore
 
class BooksViewModel: ObservableObject {
  @Published var books = [Book]()
   
  private var db = Firestore.firestore()
  private var listenerRegistration: ListenerRegistration?
   
  deinit {
    unsubscribe()
  }
   
  func unsubscribe() {
    if listenerRegistration != nil {
      listenerRegistration?.remove()
      listenerRegistration = nil
    }
  }
   
  func subscribe() {
    if listenerRegistration == nil {
      listenerRegistration = db.collection("books").addSnapshotListener { (querySnapshot, error) in
        guard let documents = querySnapshot?.documents else {
          print("No documents")
          return
        }
         
        self.books = documents.compactMap { queryDocumentSnapshot in
          try? queryDocumentSnapshot.data(as: Book.self)
        }
      }
    }
  }
   
  func removeBooks(atOffsets indexSet: IndexSet) {
    let books = indexSet.lazy.map { self.books[$0] }
    books.forEach { book in
      if let documentId = book.id {
        db.collection("books").document(documentId).delete { error in
          if let error = error {
            print("Unable to remove document: \(error.localizedDescription)")
          }
        }
      }
    }
  }
 
   
}
BookDetailsView.swift
 
//
//  BookDetailsView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import SDWebImageSwiftUI
 
struct BookDetailsView: View {
   
  @ObservedObject var viewModel = BookViewModel()
  @ObservedObject var viewModelOrder = OrderViewModel()
    
  @State private var showModal = false
  @State private var showAlert = false
  @State private var showModalbookreading = false
    
  var book: Book
   
  var body: some View {
      VStack {

          AnimatedImage(url: URL(string: book.image)!)
              .resizable()
              .frame(width: 180, height: 280)
              .clipShape(RoundedRectangle(cornerRadius: 10))
              .shadow(color: .gray, radius: 10, x: 5, y: 5)
          
          Spacer()
              .frame(height: 30)

          Text(book.author)
              .foregroundColor(.gray)

          Text(book.title)
              .font(.system(size: 24, weight: .semibold))
              .padding([.leading, .trailing], 20)


          Spacer()
              .frame(height: 20)

          Text(book.description)
              .lineLimit(4)
              .padding([.leading, .trailing], 20)
              .lineSpacing(6)
              .foregroundColor(.gray)

          Spacer()
              .frame(height: 20)
          

          Divider()
            .padding(.bottom, 30)
            .padding([.leading, .trailing], 20)
          
          if book.isAvailable {
              // Read button
              Button(action: { self.showModalbookreading = true }) {
                  HStack {
                      Text("Read")
                          .fontWeight(.semibold)
                  }
                  .frame(width: 200)
                  .padding()
                  .foregroundColor(.white)
                  .background(Color.green)
                  .cornerRadius(40)
              }
              .sheet(isPresented: self.$showModalbookreading) {
                  BookReadingView(titleBook: book.title)
              }
          }else{
              // Add button
              Button(action: {
                  self.showAlert = true
                  self.handleOrderTapped()
              }) {
                  HStack {
                      Text("Buy for " + String(book.price) + "$")
                          .fontWeight(.semibold)
                  }
                  .frame(width: 200)
                  .padding()
                  .foregroundColor(.white)
                  .background(Color.black)
                  .cornerRadius(40)
              }
              .alert(isPresented: $showAlert) {
                  Alert(title: Text("Book added to cart"), message: Text("You're ready to proceed to checkout and complete your order."), dismissButton: .default(Text("Done")))
              }
          }
      }
      // NavBar item - Checkout button
      .navigationBarItems(trailing:
          Button(action: {
              self.showModal = true
          }) {
              CartButtonView(numberOfItems: total())
          }.sheet(isPresented: self.$showModal, onDismiss: {  }) {
              CartView(showModal: self.$showModal)
          })
      .navigationBarTitle(Text(""), displayMode: .inline)
      .onAppear() {
        print("BookDetailsView.onAppear() for \(self.book.title)")
      }
      .onDisappear() {
        print("BookDetailsView.onDisappear()")
      }
  }
   
    func handleOrderTapped() {
        self.viewModelOrder.handleOrderTapped(username: "Cairocoders", title: book.title, price: book.price, image: book.image)
    }
    func total() -> Int {
        let totalprice = 3
        return totalprice
    }
}
 
struct BookDetailsView_Previews: PreviewProvider {
  static var previews: some View {
      let book = Book(title: "Coder", author: "Cairocoders", numberOfPages: 23, image: "photo1", price: 12.5, description: "Book Description", isAvailable: false)
    return
      NavigationView {
        BookDetailsView(book: book)
      }
  }
}
CartButtonView.swift
 
//
//  CartButtonView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct CartButtonView: View {
    var numberOfItems: Int

    var body: some View {
        VStack {
            Image(systemName: "cart")
                .resizable()
                .scaledToFit()
                .frame(width: 32, height: 32, alignment: .center)
                .foregroundColor(.green)
                .overlay(ImageOverlay(numberOfItems: numberOfItems), alignment: .center)
            Spacer()
        }
    }

    struct ImageOverlay: View {
        var numberOfItems: Int

        var body: some View {
            ZStack {
                Text(String(numberOfItems))
                    .font(.system(size: 22))
                    .fontWeight(.bold)
                    .foregroundColor(.black)
                    .padding(5)
            }
        }
    }
}

struct CartButtonView_Previews: PreviewProvider {
    static var previews: some View {
        CartButtonView(numberOfItems: 3)
    }
}
CartView.swift
 
//
//  CartView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct CartView: View {
    
    @Binding var showModal: Bool

    @State private var showingAlert = false
    
    @StateObject var viewModel = OrdersViewModel()
    var body: some View {
        ScrollView(.vertical) {
            VStack {
                // Dismiss button
                HStack() {
                    Image(systemName: "multiply") //Close
                    .resizable()
                    .frame(width: 25, height: 25)
                    .padding(20)

                    Spacer()
                }
                
                // Title
                VStack {
                    Text("Your bag")
                        .font(.system(size: 34))
                        .fontWeight(.bold)
                    Text(String(viewModel.orders.count) + " items")
                        .font(.system(size: 18))
                        .foregroundColor(.gray)
                }

                // Item list
                VStack(alignment: .leading) {
                    ForEach (viewModel.orders) { items in
                        CartRow(title: items.title , image: items.image, price: Int(items.price))
                    }
                }
                .onAppear() {
                  print("Order appears.")
                  self.viewModel.subscribe()
                }

                Spacer().frame(height: 20)

                // Summary
                HStack {
                    VStack {
                        Image(systemName: "shippingbox")
                            .resizable()
                            .frame(width: 32, height: 32)
                            .padding(.bottom, -8)
                        Text("FREE")
                            .font(.system(size: 16))
                            .fontWeight(.bold)
                            .padding(.bottom, 5)
                    }.frame(width: 64, height: 64)
                        .background(Color.gray.opacity(0.4))
                        .cornerRadius(15)

                    Spacer().frame(width: 40)

                    VStack(alignment: .leading) {
                        Text("Total:")
                            .font(.system(size: 18))
                            .foregroundColor(.gray)
                        Text("$" + "25")
                            .font(.system(size: 34))
                            .fontWeight(.bold)
                    }

                    Spacer().frame(width: 80)
                }

                // Checkout button
                Divider().padding()

                Button(action: {
                    self.showingAlert = true
                }) {
                    HStack {
                        Text("Checkout")
                            .font(.system(size: 18))
                            .fontWeight(.bold)
                    }
                    .frame(width: 200)
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.yellow)
                    .cornerRadius(40)
                }
                .alert(isPresented: $showingAlert) {
                    Alert(title: Text("Order confirmed"),
                          message: Text("Thank you for your purchase."),
                          dismissButton: .default(Text("Done")) {
                            self.showModal.toggle()
                    })
                }
            }
        }
    }
}
CartRow.swift
 
//
//  CartRow.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import SDWebImageSwiftUI

struct CartRow: View {
    
    var id = ""
    var title = ""
    var image = ""
    var price = 0
    @State var presentActionSheet = false
    
    var body: some View {
        HStack {

            AnimatedImage(url: URL(string: image)!)
                .resizable()
                .frame(width: 80, height: 100)
                .clipShape(RoundedRectangle(cornerRadius: 10))
                .shadow(color: .gray, radius: 10, x: 5, y: 5)
                .padding()
            
            VStack(alignment: .leading) {
                Text(title)
                Spacer().frame(height: 15)
                Text("$" + String(price)).font(.system(size: 18)).bold()
            }.padding([.top, .bottom])
                .frame(width: 150)
            
            Text("x" + String("1"))
                .fontWeight(.semibold)
                .padding(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 20)
                        .stroke(Color.gray, lineWidth: 1)
                )
        
                .padding(.leading, 20)
            
        }
        .actionSheet(isPresented: $presentActionSheet) {
          ActionSheet(title: Text("Are you sure?"),
                      buttons: [
                        .destructive(Text("Delete book"),
                                     action: {  }),
                        .cancel()
                      ])
        }
    }
}

struct CartRow_Previews: PreviewProvider {
    static var previews: some View {
        CartRow(title: "Cairocoders", image: "hello", price: 55)
    }
}
OrderModel.swift
 
//
//  OrderModel.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import Foundation
import Combine
import FirebaseFirestore

class OrderViewModel: ObservableObject {
   
  @Published var order: Order
  @Published var modified = false
   
  private var cancellables = Set<AnyCancellable>()
   
  init(order: Order = Order(username: "", title: "", price: 0, image: "")) {
    self.order = order
     
    self.$order
      .dropFirst()
      .sink { [weak self] order in
        self?.modified = true
      }
      .store(in: &self.cancellables)
  }
    
    private var db = Firestore.firestore()
     
    func handleOrderTapped(username: String, title: String, price: Double, image: String) {
          db.collection("order").addDocument(data: ["username": username, "title":title, "price":price, "image": image]) { (err) in
            if err != nil {
                print((err?.localizedDescription) ?? "Error")
                return
            }
        }
    }
    
}
BookReadingView.swift
//
//  BookReadingView.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI

struct BookReadingView: View {
    var titleBook: String

    var body: some View {
        VStack {
            Text(titleBook)
            .font(.largeTitle)

            Divider()
                .padding(.top, 1)
                .padding([.leading, .trailing], 20)
                .padding(.bottom, 20)

            Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent gravida elit vitae quam consequat ullamcorper. Vestibulum turpis est, congue ut posuere et, sollicitudin ac neque. Fusce nec tellus arcu. Nunc consequat lacus et dui vestibulum maximus. In lobortis nibh a facilisis aliquam. Proin eget ornare urna. Aliquam at malesuada massa, vitae accumsan eros. Vestibulum nec dui scelerisque enim rhoncus suscipit. Fusce a magna odio. Vestibulum egestas in tellus a porttitor. Nunc pellentesque, augue eu dignissim ullamcorper, magna eros semper nulla, at rutrum lorem est ut lectus. Nunc id libero lectus. Nunc egestas velit eu quam finibus, et convallis ipsum volutpat. Praesent sodales ultricies nunc, vel molestie quam ornare in. Pellentesque nec commodo est, in commodo nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.")
            .padding(30)
        }
    }
}

struct BookReadingView_Previews: PreviewProvider {
    static var previews: some View {
        BookReadingView(titleBook:"The two towers")
    }
}
OrderViewModel.swift
//
//  OrderViewModel.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import Foundation
import Combine
import FirebaseFirestore
 
class OrdersViewModel: ObservableObject {
  @Published var orders = [Order]()
   
  private var db = Firestore.firestore()
  private var listenerRegistration: ListenerRegistration?
   
  deinit {
    unsubscribe()
  }
   
  func unsubscribe() {
    if listenerRegistration != nil {
      listenerRegistration?.remove()
      listenerRegistration = nil
    }
  }
   
  func subscribe() {
    if listenerRegistration == nil {
      listenerRegistration = db.collection("order").addSnapshotListener { (querySnapshot, error) in
        guard let documents = querySnapshot?.documents else {
          print("No documents")
          return
        }
         
        self.orders = documents.compactMap { queryDocumentSnapshot in
          try? queryDocumentSnapshot.data(as: Order.self)
        }
      }
    }
  }
   
  func removeOrders(atOffsets indexSet: IndexSet) {
    let orders = indexSet.lazy.map { self.orders[$0] }
    orders.forEach { order in
      if let documentId = order.id {
        db.collection("order").document(documentId).delete { error in
          if let error = error {
            print("Unable to remove document: \(error.localizedDescription)")
          }
        }
      }
    }
  }
   
}
DevSwiftUIApp.swift
//
//  DevSwiftUIApp.swift
//  DevSwiftUI
//
//  Created by Cairocoders
//

import SwiftUI
import Firebase

@main
struct DevSwiftUIApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
     
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
 
class AppDelegate: NSObject,UIApplicationDelegate{
     
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
         
        FirebaseApp.configure()
        return true
    }
}

Tuesday, October 12, 2021

SwiftUI Combine Sign Up Form

SwiftUI Combine Sign Up Form
Combine Customize handling of asynchronous events by combining event-processing operators. https://developer.apple.com/documentation/combine

Navajo-Swift - Password Validator & Strength Evaluator https://github.com/jasonnam/Navajo-Swift

ContentView.swift
//
//  ContentView.swift
//  Swiftuitest
//
//  Created by Cairocoders
//

import SwiftUI

struct ContentView: View {
  
  @ObservedObject private var userViewModel = UserViewModel()
  @State var presentAlert = false
  
  var body: some View {
    Form {
      Section(footer: Text(userViewModel.usernameMessage).foregroundColor(.red)) {
        TextField("Username", text: $userViewModel.username)
          .autocapitalization(.none)
      }
      Section(footer: Text(userViewModel.passwordMessage).foregroundColor(.red)) {
        SecureField("Password", text: $userViewModel.password)
        SecureField("Password again", text: $userViewModel.passwordAgain)
      }
      Section {
        Button(action: { self.signUp() }) {
          Text("Sign up")
        }.disabled(!self.userViewModel.isValid)
      }
    }
    .sheet(isPresented: $presentAlert) {
      WelcomeView()
    }
  }
  
  func signUp() {
    self.presentAlert = true
  }
}

struct WelcomeView: View {
  var body: some View {
    Text("Welcome! Great to have you on board!")
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
UserViewModel.swift
//
//  UserViewModel.swift
//  Swiftuitest
//
//  Created by Cairocoders
//

import Foundation
import Combine//Customize handling of asynchronous events by combining event-processing operators.
import Navajo_Swift

class UserViewModel: ObservableObject {
  // input
  @Published var username = ""
  @Published var password = ""
  @Published var passwordAgain = ""
  
  // output
  @Published var usernameMessage = ""
  @Published var passwordMessage = ""
  @Published var isValid = false

  private var cancellableSet: Set<AnyCancellable> = []
  
  private var isUsernameValidPublisher: AnyPublisher<Bool, Never> {
    $username
      .debounce(for: 0.8, scheduler: RunLoop.main)
      .removeDuplicates()
      .map { input in
        return input.count >= 3
      }
      .eraseToAnyPublisher()
  }
  
  private var isPasswordEmptyPublisher: AnyPublisher<Bool, Never> {
    $password
      .debounce(for: 0.8, scheduler: RunLoop.main)
      .removeDuplicates()
      .map { password in
        return password == ""
      }
      .eraseToAnyPublisher()
  }

  private var arePasswordsEqualPublisher: AnyPublisher<Bool, Never> {
    Publishers.CombineLatest($password, $passwordAgain)
      .debounce(for: 0.2, scheduler: RunLoop.main)
      .map { password, passwordAgain in
        return password == passwordAgain
      }
      .eraseToAnyPublisher()
  }
  
  private var passwordStrengthPublisher: AnyPublisher<PasswordStrength, Never> {
    $password
      .debounce(for: 0.2, scheduler: RunLoop.main)
      .removeDuplicates()
      .map { input in
        return Navajo.strength(ofPassword: input)
      }
      .eraseToAnyPublisher()
  }
  
  private var isPasswordStrongEnoughPublisher: AnyPublisher<Bool, Never> {
    passwordStrengthPublisher
      .map { strength in
        print(Navajo.localizedString(forStrength: strength))
        switch strength {
        case .reasonable, .strong, .veryStrong:
          return true
        default:
          return false
        }
      }
      .eraseToAnyPublisher()
  }
  
  enum PasswordCheck {
    case valid
    case empty
    case noMatch
    case notStrongEnough
  }
  
  private var isPasswordValidPublisher: AnyPublisher<PasswordCheck, Never> {
    Publishers.CombineLatest3(isPasswordEmptyPublisher, arePasswordsEqualPublisher, isPasswordStrongEnoughPublisher)
      .map { passwordIsEmpty, passwordsAreEqual, passwordIsStrongEnough in
        if (passwordIsEmpty) {
          return .empty
        }
        else if (!passwordsAreEqual) {
          return .noMatch
        }
        else if (!passwordIsStrongEnough) {
          return .notStrongEnough
        }
        else {
          return .valid
        }
      }
      .eraseToAnyPublisher()
  }
  
  private var isFormValidPublisher: AnyPublisher<Bool, Never> {
    Publishers.CombineLatest(isUsernameValidPublisher, isPasswordValidPublisher)
      .map { userNameIsValid, passwordIsValid in
        return userNameIsValid && (passwordIsValid == .valid)
      }
    .eraseToAnyPublisher()
  }
  
  init() {
    isUsernameValidPublisher
      .receive(on: RunLoop.main)
      .map { valid in
        valid ? "" : "User name must at least have 3 characters"
      }
      .assign(to: \.usernameMessage, on: self)
      .store(in: &cancellableSet)
    
    isPasswordValidPublisher
      .receive(on: RunLoop.main)
      .map { passwordCheck in
        switch passwordCheck {
        case .empty:
          return "Password must not be empty"
        case .noMatch:
          return "Passwords don't match"
        case .notStrongEnough:
          return "Password not strong enough"
        default:
          return ""
        }
      }
      .assign(to: \.passwordMessage, on: self)
      .store(in: &cancellableSet)

    isFormValidPublisher
      .receive(on: RunLoop.main)
      .assign(to: \.isValid, on: self)
      .store(in: &cancellableSet)
  }

}

Monday, October 11, 2021

ReactJS Basic Website using multiple components and pages react router

ReactJS Basic Website using multiple components and pages react router

This one is a basic example of the react application using multiple components. Header Component, Navbar Component, Sidebar Component, Footer Component and Content Pages also a notfound page

ReactJS - Router

React Router is used to define multiple routes in the application.

Routing is a process in which a user is directed to different pages based on their action or request.

React Router Installation

react-router-dom: It is used for web applications design.

https://www.npmjs.com/package/react-router-dom

$ npm install --save react-router-dom

C:\reactdev\myreactdev>npm install --save react-router-dom

src/index.js
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

import { Route, BrowserRouter as Router, Switch} from 'react-router-dom'
import App from './App';
import About from './About' 
import Contact from './Contact' 
import Notfound from './notfound' 

import Header from './pages/Header';
import Navbar from './pages/Navbar';
import Sidebar from './pages/Sidebar';
import Footer from './pages/Footer';

const routing = (  
  <Router>  
    <div>  
      <Header />
      <Navbar />
      <div className="row">
        
      <Switch>  
         <Route exact path="/" component={App} />  
         <Route path="/about" component={About} />  
         <Route path="/contact" component={Contact} />  
         <Route component={Notfound} />  
      </Switch> 

        <Sidebar />
      </div>
      <Footer />
    </div>  
  </Router>  
)  
ReactDOM.render(routing, document.getElementById('root')); 
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
scr/App.js
//scr/App.js
import React from 'react';
import './App.css';

function App() {
  return (
    <div className="main">
      <h2>Home</h2>
      <h5>quam pellentesque</h5>
      <div className="fakeimg" style={{ height: 200 }}>Image</div>
      <p>Nisi vitae suscipit..</p>
      <p>Semper quis lectus nulla at. Nullam ac tortor vitae purus faucibus ornare suspendisse. Nunc faucibus a pellentesque sit. Risus quis varius quam quisque id diam vel quam elementum. Ornare aenean euismod elementum nisi quis eleifend quam.</p>
      <br />
      <h2>Placerat vestibulum</h2>
      <h5>elementum integer enim neque</h5>
      <div className="fakeimg" style={{ height: 200 }}>Image</div>
      <p>Bibendum est ultricies..</p>
      <p>Semper quis lectus nulla at. Nullam ac tortor vitae purus faucibus ornare suspendisse. Nunc faucibus a pellentesque sit. Risus quis varius quam quisque id diam vel quam elementum.</p>
    </div>
  );
}
 
export default App;
src/About.js
//src/About.js
import React from 'react' 
class About extends React.Component {  
  render() {  
    return (
    <div className="main">
      <h2>About Us</h2>
      <p>Semper quis lectus nulla at. Nullam ac tortor vitae purus faucibus ornare suspendisse. Nunc faucibus a pellentesque sit. Risus quis varius quam quisque id diam vel quam elementum.</p>
    </div>
  );
  }  
}  
export default About  
src/Contact.js
//src/Contact.js
import React from 'react' 
 
class Contact extends React.Component {  
  render() {  
    return (
    <div className="main">
      <h2>Contact Us</h2>
      <p>Semper quis lectus nulla at. Nullam ac tortor vitae purus faucibus ornare suspendisse. Nunc faucibus a pellentesque sit. Risus quis varius quam quisque id diam vel quam elementum.</p>
    </div>
  );
  }  
}  
export default Contact 
src/notfound.js
//src/notfound.js
import React from 'react' 
const Notfound = () => <div className="main"><h1>Not found</h1></div>  
export default Notfound  
src/Header.js
//src/Header.js
import React, { Component } from 'react';
class Header extends Component {
  render() {
    return (
      <div className="header">
        <h1>ReactJS Basic Website using multiple components and pages react router</h1>
        <p>React Router is used to define multiple routes in the application. Routing is a process in which a user is directed to different pages based on their action or request.</p>
      </div>
    );
  }
}
export default Header
src/Nabbar.js
//src/Nabbar.js
import React, { Component } from 'react';
import { NavLink} from 'react-router-dom'

class Navbar extends Component {
  render() {
    return (
      <div className="navbar">
          <NavLink to="/" exact activeStyle={  {color:'red'}  }>Home</NavLink>  
          <NavLink to="/about" exact activeStyle={  {color:'green'}  }>About</NavLink>  
          <NavLink to="/contact" exact activeStyle={  {color:'magenta'}  }>Contact</NavLink>  
      </div>
    );
  }
}
export default Navbar
scr/Sidebar.js
//scr/Sidebar.js
import React, { Component } from 'react';
 
class Sidebar extends Component {
  render() {
    return (
      <div className="side">
        <h2>Arcu bibendum</h2>
        <h5>Sit amet mattis vulputate</h5>
        <div className="fakeimg" style={{ height: 200 }}>Image</div>
        <p>Non blandit massa enim nec dui nunc mattis enim. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla..</p>
        <h3>Massa enim</h3>
        <p>Lorem ipsum dolor sit ame.</p>
        <div className="fakeimg" style={{ height: 60 }}>Image</div><br />
        <div className="fakeimg" style={{ height: 60 }}>Image</div><br />
        <div className="fakeimg" style={{ height: 60 }}>Image</div>
      </div>
    );
  }
}
export default Sidebar
src/footer.js
//src/footer.js
import React, { Component } from 'react';
 
class Footer extends Component {
  render() {
    return (
      <div className="footer">
        <h2>Footer</h2>
      </div>
    );
  }
}
export default Footer
src/App.css
//src/App.css
* {
  box-sizing: border-box;
}

body {
  font-family: Arial, Helvetica, sans-serif;
  margin: 0;
}

.header {
  padding: 80px;
  text-align: center;
  background: #113452;
  color: white;
}

.header h1 {
  font-size: 40px;
}

.navbar {
  overflow: hidden;
  background-color: #000;
}

.navbar a {
  float: left;
  display: block;
  color: white;
  text-align: center;
  padding: 14px 20px;
  text-decoration: none;
}

.navbar a.right {
  float: right;
}

.navbar a:hover {
  background-color: #ddd;
  color: black;
} 
.row {  
  display: flex;
  flex-wrap: wrap;
}
.side {
  flex: 30%;
  background-color: #f1f1f1;
  padding: 20px;
}

.main {   
  flex: 70%;
  background-color: white;
  padding: 20px;
}

.fakeimg {
  background-color: #aaa;
  width: 100%;
  padding: 20px;
}
.footer {
  padding: 20px;
  text-align: center;
  background: #ddd;
}

Sunday, October 10, 2021

ReactJS Star Rating

ReactJS Star Rating
scr/index.js
//scr/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
public/index.html
//public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ReactJS </title>
  </head>
  <body>
    <div id="root"></div>
</body>
</html>
src/App.js
//src/App.js
import React, { useState, useEffect } from "react";
import './App.css';
import { ReactComponent as Logo } from "./logo.svg";

const Star = ({ starId, rating, onMouseEnter, onMouseLeave, onClick }) => {
  let styleClass = "star-rating-blank";
  if (rating && rating >= starId) {
    styleClass = "star-rating-filled";
  }

  return (
    <div
      className="star"
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
      onClick={onClick}
    >
      <svg
        height="55px"
        width="53px"
        class={styleClass}
        viewBox="0 0 25 23"
        data-rating="1"
      >
        <polygon
          stroke-width="0"
          points="9.9, 1.1, 3.3, 21.78, 19.8, 8.58, 0, 8.58, 16.5, 21.78"
        />
      </svg>
    </div>
  );
};

function App() {
  const [rating, setRating] = useState(0);
  const [hoverRating, setHoverRating] = useState(0);
  const stars = [1, 2, 3, 4, 5];

  return (
    <div className="App">
      <div class="header">
        <h1>Star Rating</h1>
        <Logo />
        <div class="logo"></div>
      </div>
      <div class="flex-container">
        {stars.map((star, i) => (
          <Star
            key={i}
            starId={i}
            rating={hoverRating || rating}
            onMouseEnter={() => setHoverRating(i)}
            onMouseLeave={() => setHoverRating(0)}
            onClick={() => setRating(i)}
          />
        ))}
      </div>
    </div>
  );
}

export default App;
src/App.css
//src/App.css
.App {
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

body {
  background-color: rgb(244, 236, 244);
}

.star-rating-filled {
  fill: gold;
}

.star-rating-blank {
  fill: #d8d8d8;
}

.logo {
  background-image: url('./logo.png');
  height: 70px;
  width: 200px;
  background-size: contain;
  background-repeat: no-repeat;
}

.flex-container {
  display: flex;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

Related Post