0% found this document useful (0 votes)
6 views14 pages

Easy Sell

The document outlines the creation of a simple online shopping web application using JHipster, Spring Boot, and React, featuring two main entities: Customer/Seller and Products. It details the structure, fields, and relationships of these entities, as well as the application flow, including components for homepage, seller registration, login, and product creation. Additionally, it provides implementation details for frontend components using Formik, Bootstrap, and Fetch API for form handling and data communication.

Uploaded by

tnyange909
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as ODT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views14 pages

Easy Sell

The document outlines the creation of a simple online shopping web application using JHipster, Spring Boot, and React, featuring two main entities: Customer/Seller and Products. It details the structure, fields, and relationships of these entities, as well as the application flow, including components for homepage, seller registration, login, and product creation. Additionally, it provides implementation details for frontend components using Formik, Bootstrap, and Fetch API for form handling and data communication.

Uploaded by

tnyange909
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as ODT, PDF, TXT or read online on Scribd
You are on page 1/ 14

Title: ONLINESHOPPING

I want to create simple onlineshopping web application using JHipster with Spring Boot and
React.

My application has two entities which are:


1. Customer/Seller
• Fields:
• sName
• sPassword
• sUsername
• sProducts
• sLocation
• sPhonenumber
• sEmail
• sRole (added: to differentiate between BUYER and SELLER)

• sProfileImage (optional: profile picture)

• Functions/Roles:
• createAccount()
• createProduct() / registerProduct()

2. Products
• Fields:
• pName
• pDescription
• pPrice
• pImage
• sLocation
• sPhonenumber
• sName
• sId (added: foreign key to relate product to the seller)

Entity Relationship:
• A Customer/Seller can have many Products
• A Product belongs to one Customer/Seller
Relationship type:
• OneToMany from Customer/Seller to Products

• ManyToOne from Products to Customer/Seller

The flow of my application will look like this:


1. <HOMEPAGE/>
• On the header:
• Navigation buttons for Register (for new sellers) and Login

• On the header:
• A search panel for buyers to search any products they want
• Below the header:
• An animation toggle for new products like in Amazon
• Body section:
• This component displays all products so that buyers can see and buy

2. <SELLERREGISTER/>
• This component should display a form with all required fields for customer/seller
registration
• After seller registration, it should navigate to the login component
• Validate phone, email, password strength
• Check if username/email already exists

3. <SELLERLOGIN/>
• This component should have a form containing seller username and password
• After seller login is successful, it should navigate the seller to the product creation
component

4. <SELLERPRODUCTCREATION/>
• This component should contain a form with all product fields for the logged-in seller to
create/register a product
• After the product is created, it should navigate to the home component which will display
the ad and all products
• Image upload preview
• Limit product name length
• Save seller reference (seller ID) to each product

application {
config {
baseName onlineshopping
applicationType monolith
packageName com.onlineshopping
authenticationType jwt
prodDatabaseType postgresql
buildTool maven
clientFramework react
reactive false
}

entities Customer, Product


}

entity Customer {
sName String required
sUsername String required
sPassword String required minlength(6)
sLocation String
sPhonenumber String pattern(/^\+255[0-9]{8}$/)
sEmail String required pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)
sRole Role required
sProfileImage byte[] contentType(image/*)
}

enum Role {
BUYER, SELLER
}

entity Product {
pName String required maxlength(100)
pDescription String required
pPrice BigDecimal required
pImage byte[] required contentType(image/*)
sLocation String
sPhonenumber String
sName String
}

relationship OneToMany {
Customer{products} to Product{owner(name)}
}
dto Customer, Product with mapstruct
service Customer, Product with serviceClass
paginate Customer, Product with pagination

Online Shopping React Components with


Formik, Bootstrap, and Fetch API
Here's the complete implementation of all frontend components using Formik for form handling,
along with Bootstrap for styling and Fetch API for data communication.

1. HomePage Component (No changes needed)


jsx
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Navbar, Nav, Form, FormControl, Button, Carousel, Card, Row, Col,
Container } from 'react-bootstrap';

const HomePage = () => {


const [products, setProducts] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const navigate = useNavigate();

useEffect(() => {
fetchProducts();
}, []);

const fetchProducts = async () => {


try {
const response = await fetch('/api/products');
if (!response.ok) throw new Error('Failed to fetch products');
const data = await response.json();
setProducts(data);
} catch (error) {
console.error('Error fetching products:', error);
}
};

const handleSearch = async (e) => {


e.preventDefault();
try {
const response = await fetch(`/api/products/search?query=${searchTerm}`);
if (!response.ok) throw new Error('Search failed');
const data = await response.json();
setProducts(data);
} catch (error) {
console.error('Search error:', error);
}
};
return (
<div>
<Navbar bg="dark" variant="dark" expand="lg">
<Container>
<Navbar.Brand href="/">OnlineShopping</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Nav.Link onClick={() =>
navigate('/register')}>Register</Nav.Link>
<Nav.Link onClick={() => navigate('/login')}>Login</Nav.Link>
</Nav>
<Form className="d-flex" onSubmit={handleSearch}>
<FormControl
type="search"
placeholder="Search products"
className="me-2"
aria-label="Search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Button variant="outline-light" type="submit">Search</Button>
</Form>
</Navbar.Collapse>
</Container>
</Navbar>

<Container className="mt-4">
<Carousel>
{products.slice(0, 5).map((product) => (
<Carousel.Item key={product.id}>
<img
className="d-block w-100"
src={product.pImage || 'https://via.placeholder.com/800x300'}
alt={product.pName}
style={{ height: '300px', objectFit: 'cover' }}
/>
<Carousel.Caption>
<h3>{product.pName}</h3>
<p>${product.pPrice}</p>
</Carousel.Caption>
</Carousel.Item>
))}
</Carousel>

<h2 className="my-4">All Products</h2>


<Row>
{products.map((product) => (
<Col key={product.id} sm={6} md={4} lg={3} className="mb-4">
<Card>
<Card.Img variant="top" src={product.pImage ||
'https://via.placeholder.com/300'} />
<Card.Body>
<Card.Title>{product.pName}</Card.Title>
<Card.Text>{product.pDescription.substring(0,
50)}...</Card.Text>
<Card.Text className="fw-bold">${product.pPrice}</Card.Text>
<Card.Text>
<small className="text-muted">Sold by:
{product.sName}</small>
</Card.Text>
<Button variant="primary">View Details</Button>
</Card.Body>
</Card>
</Col>
))}
</Row>
</Container>
</div>
);
};

export default HomePage;

2. SellerRegister Component with Formik


jsx
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Form, Button, Container, Alert, Card } from 'react-bootstrap';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SellerRegister = () => {


const [apiError, setApiError] = useState('');
const navigate = useNavigate();

const validationSchema = Yup.object().shape({


sName: Yup.string().required('Name is required'),
sUsername: Yup.string().required('Username is required'),
sEmail: Yup.string().email('Invalid email').required('Email is required'),
sPassword: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
sConfirmPassword: Yup.string()
.oneOf([Yup.ref('sPassword'), null], 'Passwords must match')
.required('Confirm Password is required'),
sLocation: Yup.string().required('Location is required'),
sPhonenumber: Yup.string().required('Phone number is required'),
sRole: Yup.string().required('Role is required'),
});

const handleSubmit = async (values, { setSubmitting }) => {


try {
const response = await fetch('/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(values),
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Registration failed');
}

navigate('/login');
} catch (error) {
setApiError(error.message);
} finally {
setSubmitting(false);
}
};
return (
<Container className="mt-5">
<Card className="p-4" style={{ maxWidth: '600px', margin: '0 auto' }}>
<h2 className="text-center mb-4">Seller Registration</h2>
{apiError && <Alert variant="danger">{apiError}</Alert>}

<Formik
initialValues={{
sName: '',
sUsername: '',
sEmail: '',
sPassword: '',
sConfirmPassword: '',
sLocation: '',
sPhonenumber: '',
sRole: 'SELLER',
}}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ handleSubmit, handleChange, values, touched, errors,
isSubmitting }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group className="mb-3">
<Form.Label>Full Name</Form.Label>
<Form.Control
type="text"
name="sName"
value={values.sName}
onChange={handleChange}
isInvalid={touched.sName && !!errors.sName}
/>
<Form.Control.Feedback
type="invalid">{errors.sName}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Username</Form.Label>
<Form.Control
type="text"
name="sUsername"
value={values.sUsername}
onChange={handleChange}
isInvalid={touched.sUsername && !!errors.sUsername}
/>
<Form.Control.Feedback
type="invalid">{errors.sUsername}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
name="sEmail"
value={values.sEmail}
onChange={handleChange}
isInvalid={touched.sEmail && !!errors.sEmail}
/>
<Form.Control.Feedback
type="invalid">{errors.sEmail}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
name="sPassword"
value={values.sPassword}
onChange={handleChange}
isInvalid={touched.sPassword && !!errors.sPassword}
/>
<Form.Control.Feedback
type="invalid">{errors.sPassword}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Confirm Password</Form.Label>
<Form.Control
type="password"
name="sConfirmPassword"
value={values.sConfirmPassword}
onChange={handleChange}
isInvalid={touched.sConfirmPassword && !!
errors.sConfirmPassword}
/>
<Form.Control.Feedback
type="invalid">{errors.sConfirmPassword}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Location</Form.Label>
<Form.Control
type="text"
name="sLocation"
value={values.sLocation}
onChange={handleChange}
isInvalid={touched.sLocation && !!errors.sLocation}
/>
<Form.Control.Feedback
type="invalid">{errors.sLocation}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Phone Number</Form.Label>
<Form.Control
type="text"
name="sPhonenumber"
value={values.sPhonenumber}
onChange={handleChange}
isInvalid={touched.sPhonenumber && !!errors.sPhonenumber}
/>
<Form.Control.Feedback
type="invalid">{errors.sPhonenumber}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Register as</Form.Label>
<Form.Select
name="sRole"
value={values.sRole}
onChange={handleChange}
isInvalid={touched.sRole && !!errors.sRole}
>
<option value="SELLER">Seller</option>
<option value="BUYER">Buyer</option>
</Form.Select>
<Form.Control.Feedback
type="invalid">{errors.sRole}</Form.Control.Feedback>
</Form.Group>

<Button
variant="primary"
type="submit"
className="w-100"
disabled={isSubmitting}
>
{isSubmitting ? 'Registering...' : 'Register'}
</Button>
</Form>
)}
</Formik>
</Card>
</Container>
);
};

export default SellerRegister;

3. SellerLogin Component with Formik


jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Form, Button, Container, Alert, Card } from 'react-bootstrap';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SellerLogin = () => {


const [apiError, setApiError] = useState('');
const navigate = useNavigate();

const validationSchema = Yup.object().shape({


sUsername: Yup.string().required('Username is required'),
sPassword: Yup.string().required('Password is required'),
});

const handleSubmit = async (values, { setSubmitting }) => {


try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(values),
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Login failed');
}

const data = await response.json();


localStorage.setItem('authToken', data.token);
localStorage.setItem('userRole', data.role);

if (data.role === 'SELLER') {


navigate('/product-creation');
} else {
navigate('/');
}
} catch (error) {
setApiError(error.message);
} finally {
setSubmitting(false);
}
};

return (
<Container className="mt-5">
<Card className="p-4" style={{ maxWidth: '500px', margin: '0 auto' }}>
<h2 className="text-center mb-4">Seller Login</h2>
{apiError && <Alert variant="danger">{apiError}</Alert>}

<Formik
initialValues={{
sUsername: '',
sPassword: '',
}}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ handleSubmit, handleChange, values, touched, errors,
isSubmitting }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group className="mb-3">
<Form.Label>Username</Form.Label>
<Form.Control
type="text"
name="sUsername"
value={values.sUsername}
onChange={handleChange}
isInvalid={touched.sUsername && !!errors.sUsername}
/>
<Form.Control.Feedback
type="invalid">{errors.sUsername}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
name="sPassword"
value={values.sPassword}
onChange={handleChange}
isInvalid={touched.sPassword && !!errors.sPassword}
/>
<Form.Control.Feedback
type="invalid">{errors.sPassword}</Form.Control.Feedback>
</Form.Group>

<Button
variant="primary"
type="submit"
className="w-100"
disabled={isSubmitting}
>
{isSubmitting ? 'Logging in...' : 'Login'}
</Button>
</Form>
)}
</Formik>
</Card>
</Container>
);
};

export default SellerLogin;

4. SellerProductCreation Component with Formik


jsx
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Form, Button, Container, Alert, Card, Spinner } from 'react-bootstrap';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SellerProductCreation = () => {


const [apiError, setApiError] = useState('');
const [sellerInfo, setSellerInfo] = useState(null);
const navigate = useNavigate();

useEffect(() => {
const fetchSellerInfo = async () => {
const token = localStorage.getItem('authToken');
if (!token) {
navigate('/login');
return;
}

try {
const response = await fetch('/api/seller-info', {
headers: {
'Authorization': `Bearer ${token}`,
},
});

if (!response.ok) throw new Error('Failed to fetch seller info');

const data = await response.json();


setSellerInfo(data);
} catch (error) {
console.error('Error fetching seller info:', error);
navigate('/login');
}
};

fetchSellerInfo();
}, [navigate]);

const validationSchema = Yup.object().shape({


pName: Yup.string()
.required('Product name is required')
.max(100, 'Name must be less than 100 characters'),
pDescription: Yup.string().required('Description is required'),
pPrice: Yup.number()
.required('Price is required')
.positive('Price must be positive'),
pImage: Yup.mixed().required('Product image is required'),
});

const handleSubmit = async (values, { setSubmitting }) => {


if (!sellerInfo) return;

const formDataToSend = new FormData();


formDataToSend.append('pName', values.pName);
formDataToSend.append('pDescription', values.pDescription);
formDataToSend.append('pPrice', values.pPrice);
formDataToSend.append('pImage', values.pImage);
formDataToSend.append('sId', sellerInfo.id);
formDataToSend.append('sName', sellerInfo.sName);
formDataToSend.append('sLocation', sellerInfo.sLocation);
formDataToSend.append('sPhonenumber', sellerInfo.sPhonenumber);

try {
const token = localStorage.getItem('authToken');
const response = await fetch('/api/products', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
body: formDataToSend,
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Product creation failed');
}

navigate('/');
} catch (error) {
setApiError(error.message);
} finally {
setSubmitting(false);
}
};

if (!sellerInfo) {
return (
<Container className="d-flex justify-content-center align-items-center"
style={{ height: '100vh' }}>
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</Container>
);
}

return (
<Container className="mt-5">
<Card className="p-4" style={{ maxWidth: '800px', margin: '0 auto' }}>
<h2 className="text-center mb-4">Create New Product</h2>
{apiError && <Alert variant="danger">{apiError}</Alert>}

<Formik
initialValues={{
pName: '',
pDescription: '',
pPrice: '',
pImage: null,
pImagePreview: '',
}}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({
handleSubmit,
setFieldValue,
values,
touched,
errors,
isSubmitting
}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group className="mb-3">
<Form.Label>Product Name</Form.Label>
<Form.Control
type="text"
name="pName"
value={values.pName}
onChange={(e) => setFieldValue('pName', e.target.value)}
isInvalid={touched.pName && !!errors.pName}
/>
<Form.Control.Feedback
type="invalid">{errors.pName}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows={3}
name="pDescription"
value={values.pDescription}
onChange={(e) => setFieldValue('pDescription',
e.target.value)}
isInvalid={touched.pDescription && !!errors.pDescription}
/>
<Form.Control.Feedback
type="invalid">{errors.pDescription}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Price ($)</Form.Label>
<Form.Control
type="number"
step="0.01"
name="pPrice"
value={values.pPrice}
onChange={(e) => setFieldValue('pPrice', e.target.value)}
isInvalid={touched.pPrice && !!errors.pPrice}
/>
<Form.Control.Feedback
type="invalid">{errors.pPrice}</Form.Control.Feedback>
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Product Image</Form.Label>
<Form.Control
type="file"
accept="image/*"
onChange={(e) => {
const file = e.target.files[0];
if (file) {
setFieldValue('pImage', file);
const reader = new FileReader();
reader.onloadend = () => {
setFieldValue('pImagePreview', reader.result);
};
reader.readAsDataURL(file);
}
}}
isInvalid={touched.pImage && !!errors.pImage}
/>
<Form.Control.Feedback
type="invalid">{errors.pImage}</Form.Control.Feedback>
{values.pImagePreview && (
<div className="mt-2">
<img
src={values.pImagePreview}
alt="Preview"
style={{ maxWidth: '200px', maxHeight: '200px' }}
/>
</div>
)}
</Form.Group>

<Form.Group className="mb-3">
<Form.Label>Seller Information</Form.Label>
<div className="p-3 bg-light rounded">
<p><strong>Name:</strong> {sellerInfo.sName}</p>
<p><strong>Location:</strong> {sellerInfo.sLocation}</p>
<p><strong>Phone:</strong> {sellerInfo.sPhonenumber}</p>
</div>
</Form.Group>

<Button
variant="primary"
type="submit"
className="w-100"
disabled={isSubmitting}
>
{isSubmitting ? 'Creating Product...' : 'Create Product'}
</Button>
</Form>
)}
</Formik>
</Card>
</Container>
);
};

export default SellerProductCreation;

You might also like