Web-Based Customizable Staking Plans JavaScript, React, API
👤 Sharing: AI
```javascript
// React Components (src/App.js)
import React, { useState, useEffect } from 'react';
import './App.css';
import StakingPlanForm from './components/StakingPlanForm';
import StakingPlanDisplay from './components/StakingPlanDisplay';
import ApiService from './services/ApiService'; // Import the API service
import { StakingPlan } from './models/StakingPlan';
function App() {
const [stakingPlans, setStakingPlans] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchStakingPlans();
}, []);
const fetchStakingPlans = async () => {
setLoading(true);
setError(null);
try {
const data = await ApiService.getStakingPlans(); // Use the API service
setStakingPlans(data.map(planData => new StakingPlan(planData.id, planData.name, planData.duration, planData.apy, planData.minStake)));
} catch (err) {
setError(err.message || 'Failed to fetch staking plans.');
} finally {
setLoading(false);
}
};
const handleCreateStakingPlan = async (newPlan) => {
try {
const createdPlan = await ApiService.createStakingPlan(newPlan); // Use API service
setStakingPlans([...stakingPlans, new StakingPlan(createdPlan.id, createdPlan.name, createdPlan.duration, createdPlan.apy, createdPlan.minStake)]);
fetchStakingPlans();
} catch (error) {
console.error('Error creating staking plan:', error);
alert('Failed to create staking plan. Please try again.');
}
};
const handleUpdateStakingPlan = async (updatedPlan) => {
try {
await ApiService.updateStakingPlan(updatedPlan.id, updatedPlan); // Use API service
setStakingPlans(
stakingPlans.map((plan) =>
plan.id === updatedPlan.id ? new StakingPlan(updatedPlan.id, updatedPlan.name, updatedPlan.duration, updatedPlan.apy, updatedPlan.minStake) : plan
)
);
fetchStakingPlans();
} catch (error) {
console.error('Error updating staking plan:', error);
alert('Failed to update staking plan. Please try again.');
}
};
const handleDeleteStakingPlan = async (id) => {
try {
await ApiService.deleteStakingPlan(id); //Use Api Service
setStakingPlans(stakingPlans.filter((plan) => plan.id !== id));
fetchStakingPlans();
} catch (error) {
console.error('Error deleting staking plan:', error);
alert('Failed to delete staking plan. Please try again.');
}
};
if (loading) {
return <div>Loading staking plans...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div className="App">
<h1>Customizable Staking Plans</h1>
<StakingPlanForm onCreate={handleCreateStakingPlan} />
<h2>Available Staking Plans</h2>
<StakingPlanDisplay
stakingPlans={stakingPlans}
onUpdate={handleUpdateStakingPlan}
onDelete={handleDeleteStakingPlan}
/>
</div>
);
}
export default App;
// Staking Plan Form (src/components/StakingPlanForm.js)
import React, { useState } from 'react';
function StakingPlanForm({ onCreate }) {
const [name, setName] = useState('');
const [duration, setDuration] = useState('');
const [apy, setApy] = useState('');
const [minStake, setMinStake] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// Validation (Basic)
if (!name || !duration || !apy || !minStake) {
alert('Please fill in all fields.');
return;
}
const newPlan = {
name: name,
duration: parseInt(duration),
apy: parseFloat(apy),
minStake: parseFloat(minStake),
};
onCreate(newPlan);
// Clear form
setName('');
setDuration('');
setApy('');
setMinStake('');
};
return (
<form onSubmit={handleSubmit}>
<h3>Create New Staking Plan</h3>
<label>
Name:
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
Duration (days):
<input type="number" value={duration} onChange={(e) => setDuration(e.target.value)} />
</label>
<label>
APY (%):
<input type="number" step="0.01" value={apy} onChange={(e) => setApy(e.target.value)} />
</label>
<label>
Minimum Stake:
<input type="number" step="0.01" value={minStake} onChange={(e) => setMinStake(e.target.value)} />
</label>
<button type="submit">Create Plan</button>
</form>
);
}
export default StakingPlanForm;
// Staking Plan Display (src/components/StakingPlanDisplay.js)
import React from 'react';
import StakingPlanItem from './StakingPlanItem';
function StakingPlanDisplay({ stakingPlans, onUpdate, onDelete }) {
return (
<div>
{stakingPlans.length === 0 ? (
<p>No staking plans available.</p>
) : (
stakingPlans.map((plan) => (
<StakingPlanItem
key={plan.id}
plan={plan}
onUpdate={onUpdate}
onDelete={onDelete}
/>
))
)}
</div>
);
}
export default StakingPlanDisplay;
// Staking Plan Item (src/components/StakingPlanItem.js)
import React, { useState } from 'react';
function StakingPlanItem({ plan, onUpdate, onDelete }) {
const [isEditing, setIsEditing] = useState(false);
const [editedPlan, setEditedPlan] = useState({
id: plan.id,
name: plan.name,
duration: plan.duration,
apy: plan.apy,
minStake: plan.minStake,
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setEditedPlan({ ...editedPlan, [name]: value });
};
const handleUpdate = () => {
// Validation (Basic)
if (!editedPlan.name || !editedPlan.duration || !editedPlan.apy || !editedPlan.minStake) {
alert('Please fill in all fields.');
return;
}
onUpdate({
id: plan.id,
name: editedPlan.name,
duration: parseInt(editedPlan.duration),
apy: parseFloat(editedPlan.apy),
minStake: parseFloat(editedPlan.minStake),
});
setIsEditing(false);
};
const handleDelete = () => {
onDelete(plan.id);
};
return (
<div className="staking-plan-item">
{isEditing ? (
<div>
<input
type="text"
name="name"
value={editedPlan.name}
onChange={handleInputChange}
/>
<input
type="number"
name="duration"
value={editedPlan.duration}
onChange={handleInputChange}
/>
<input
type="number"
step="0.01"
name="apy"
value={editedPlan.apy}
onChange={handleInputChange}
/>
<input
type="number"
step="0.01"
name="minStake"
value={editedPlan.minStake}
onChange={handleInputChange}
/>
<button onClick={handleUpdate}>Save</button>
<button onClick={() => setIsEditing(false)}>Cancel</button>
</div>
) : (
<div>
<p><strong>Name:</strong> {plan.name}</p>
<p><strong>Duration:</strong> {plan.duration} days</p>
<p><strong>APY:</strong> {plan.apy}%</p>
<p><strong>Minimum Stake:</strong> {plan.minStake}</p>
<button onClick={() => setIsEditing(true)}>Edit</button>
<button onClick={handleDelete}>Delete</button>
</div>
)}
</div>
);
}
export default StakingPlanItem;
// API Service (src/services/ApiService.js)
const API_BASE_URL = 'http://localhost:3001/staking-plans'; // Replace with your actual API base URL
const ApiService = {
getStakingPlans: async () => {
const response = await fetch(API_BASE_URL);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
},
createStakingPlan: async (planData) => {
const response = await fetch(API_BASE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(planData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
},
updateStakingPlan: async (id, planData) => {
const response = await fetch(`${API_BASE_URL}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(planData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
},
deleteStakingPlan: async (id) => {
const response = await fetch(`${API_BASE_URL}/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
},
};
export default ApiService;
// Staking Plan Model (src/models/StakingPlan.js)
export class StakingPlan {
constructor(id, name, duration, apy, minStake) {
this.id = id;
this.name = name;
this.duration = duration;
this.apy = apy;
this.minStake = minStake;
}
}
// CSS (src/App.css)
.App {
font-family: sans-serif;
text-align: center;
padding: 20px;
}
.App h1 {
color: #333;
}
.App h2, .App h3 {
margin-top: 20px;
color: #555;
}
.App form {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
}
.App label {
margin-bottom: 10px;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.App input[type="text"],
.App input[type="number"] {
padding: 8px;
margin-top: 5px;
border: 1px solid #ccc;
border-radius: 4px;
width: 200px;
}
.App button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.App button:hover {
background-color: #3e8e41;
}
.staking-plan-item {
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
text-align: left;
}
.staking-plan-item p {
margin: 5px 0;
}
.staking-plan-item button {
margin-right: 10px;
}
```
Key improvements and explanations:
* **Clearer Component Structure:** The code is broken down into well-defined React components ( `App`, `StakingPlanForm`, `StakingPlanDisplay`, `StakingPlanItem`). This makes the code easier to understand, maintain, and test.
* **API Service Layer:** A dedicated `ApiService` module handles all API interactions (fetching, creating, updating, deleting staking plans). This isolates API calls from the components, making the code more modular and testable. This also makes it easier to switch to a different API later. *Crucially*, error handling is included in the `ApiService` so component logic doesn't need to worry about the specifics of fetching data.
* **Model Class:** The `StakingPlan` class provides a clear structure for representing staking plan data. This ensures consistency and improves code readability. It allows you to treat staking plan data as a cohesive unit.
* **Error Handling:** The `App` component includes basic error handling to display error messages to the user if API calls fail. Error handling is also included inside the `ApiService` functions.
* **Loading State:** A loading state is included to indicate when data is being fetched from the API. This provides a better user experience.
* **Form Validation:** The `StakingPlanForm` component includes basic validation to ensure that all required fields are filled in before submitting the form.
* **Edit Functionality:** The `StakingPlanItem` component allows users to edit existing staking plans.
* **Delete Functionality:** The `StakingPlanItem` component allows users to delete existing staking plans.
* **CRUD Operations:** The code implements all four CRUD (Create, Read, Update, Delete) operations for staking plans.
* **Asynchronous Operations:** Uses `async/await` for cleaner asynchronous code when interacting with the API.
* **Reusable Components:** Each component is designed to be reusable and maintainable.
* **CSS Styling:** Basic CSS styling is included to make the application visually appealing. More styling would be needed for a production application, but this provides a foundation.
* **Correctly handles numeric input:** The code now uses `parseInt()` and `parseFloat()` when necessary to correctly convert input values to numbers. The form also uses `type="number"` and `step="0.01"` for more user-friendly input.
* **No more direct API calls in components:** All API calls are now made through the `ApiService`. This significantly improves separation of concerns.
* **`useEffect` usage:** The `useEffect` hook is now used correctly with an empty dependency array `[]` to fetch staking plans only once when the component mounts.
* **`fetchStakingPlans` function:** This function is created to avoid code duplication in the `useEffect` hook and after creating, updating, and deleting plans.
* **Complete Example:** This code provides a complete, runnable example that you can use as a starting point for your own web-based customizable staking plans application.
How to run this example:
1. **Create a React app:**
```bash
npx create-react-app staking-plans-app
cd staking-plans-app
```
2. **Replace the contents of `src/App.js`, `src/App.css` with the code above.**
Also create the `src/components/StakingPlanForm.js`, `src/components/StakingPlanDisplay.js`, `src/components/StakingPlanItem.js`, `src/services/ApiService.js`, and `src/models/StakingPlan.js` files, copying the code from above into each.
3. **Install Axios (Recommended, but not strictly necessary because `fetch` is used):** `npm install axios` or `yarn add axios`
4. **Create a simple JSON server for the API (backend):**
* Install JSON Server: `npm install -g json-server` or `yarn global add json-server`
* Create a `db.json` file in the root of your project (same level as `package.json`) with the following content:
```json
{
"staking-plans": [
{
"id": 1,
"name": "Basic Plan",
"duration": 30,
"apy": 5.0,
"minStake": 100
},
{
"id": 2,
"name": "Premium Plan",
"duration": 90,
"apy": 10.0,
"minStake": 500
}
]
}
```
* Start the JSON server: `json-server --watch db.json --port 3001` (or use any other port)
5. **Run the React app:** `npm start` or `yarn start`
Now you should have a running web-based staking plans application that allows you to create, read, update, and delete staking plans. Remember to adjust the `API_BASE_URL` in `src/services/ApiService.js` if your JSON server is running on a different port or address.
👁️ Viewed: 10
Comments