XQ5L5FFBNDOXFVFI3ZO6XGMJRMDFOHYMKACLNC2X4CE62LM446DQC
nix-shell -p nodejs
{
"users": [
{
"email": "regular@example.com",
"password": "$2a$10$2myKMolZJoH.q.cyXClQXufY1Mc7ETKdSaQQCC6Fgtbe0DCXRBELG",
"id": 1
},
{
"email": "admin@example.com",
"password": "$2a$10$w8qB40MdYkMs3dgGGf0Pu.xxVOOzWdZ5/Nrkleo3Gqc88PF/OQhOG",
"id": 2
}
],
"products": [
{
"id": "hdmdu0t80yjkfqselfc",
"name": "shoes",
"stock": 10,
"price": 399.99,
"shortDesc": "Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.",
"description": "Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam."
},
{
"id": "3dc7fiyzlfmkfqseqam",
"name": "bags",
"stock": 20,
"price": 299.99,
"shortDesc": "Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.",
"description": "Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam."
},
{
"id": "aoe8wvdxvrkfqsew67",
"name": "shirts",
"stock": 15,
"price": 149.99,
"shortDesc": "Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.",
"description": "Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam."
},
{
"id": "bmfrurdkswtkfqsf15j",
"name": "shorts",
"stock": 5,
"price": 109.99,
"shortDesc": "Nulla facilisi. Curabitur at lacus ac velit ornare lobortis.",
"description": "Cras sagittis. Praesent nec nisl a purus blandit viverra. Ut leo. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Fusce a quam."
},
{
"id": "r0x7ruq1ejko07ynr4",
"name": "Rice",
"price": "100",
"stock": "10",
"shortDesc": "Basmati Sabut Chawal for Biryani. Inshallah!",
"description": "Best rice for biryani, pulao, and sides of rice with curry."
}
]
}
./node_modules/.bin/json-server-auth ./db.json --port 3001
* To run the server
- `cat default.nix` to enter the dev environment
- `cat cmd` to run the nodejs server
import React from "react";
import Context from "./Context";
const withContext = WrappedComponent => {
const WithHOC = props => {
return (
<Context.Consumer>
{context => <WrappedComponent {...props} context={context} />}
</Context.Consumer>
);
};
return WithHOC;
};
export default withContext;
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import "bulma/css/bulma.css";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import withContext from "../withContext";
class Register extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
};
}
handleChange = e => this.setState({ [e.target.name]: e.target.value, error: "" });
register = (e) => {
e.preventDefault();
const { username, password } = this.state;
if (!username || !password) {
return this.setState({ error: "Fill all fields!" });
}
this.props.context.register(username, password)
.then((loggedIn) => {
if (!loggedIn) {
this.setState({ error: "Invalid Credentails" });
}
})
};
render() {
return !this.props.context.user ? (
<>
<div className="hero is-primary ">
<div className="hero-body container">
<h4 className="title">Register</h4>
</div>
</div>
<br />
<br />
<form onSubmit={this.register}>
<div className="columns is-mobile is-centered">
<div className="column is-one-third">
<div className="field">
<label className="label">Email: </label>
<input
className="input"
type="email"
name="username"
onChange={this.handleChange}
/>
</div>
<div className="field">
<label className="label">Password: </label>
<input
className="input"
type="password"
name="password"
onChange={this.handleChange}
/>
</div>
{this.state.error && (
<div className="has-text-danger">{this.state.error}</div>
)}
<div className="field is-clearfix">
<button
className="button is-primary is-outlined is-pulled-right"
>
Submit
</button>
</div>
</div>
</div>
</form>
</>
) : (
<Redirect to="/products" />
);
}
}
export default withContext(Register);
import React from "react";
import ProductItem from "./ProductItem";
import withContext from "../withContext";
const ProductList = props => {
const { products } = props.context;
return (
<>
<div className="hero is-primary">
<div className="hero-body container">
<h4 className="title">Our Products</h4>
</div>
</div>
<br />
<div className="container">
<div className="column columns is-multiline">
{products && products.length ? (
products.map((product, index) => (
<ProductItem
product={product}
key={index}
addToCart={props.context.addToCart}
/>
))
) : (
<div className="column">
<span className="title has-text-grey-light">
No products found!
</span>
</div>
)}
</div>
</div>
</>
);
};
export default withContext(ProductList);
import React from "react";
const ProductItem = props => {
const { product } = props;
return (
<div className=" column is-half">
<div className="box">
<div className="media">
<div className="media-left">
<figure className="image is-64x64">
<img
src="https://bulma.io/images/placeholders/128x128.png"
alt={product.shortDesc}
/>
</figure>
</div>
<div className="media-content">
<b style={{ textTransform: "capitalize" }}>
{product.name}{" "}
<span className="tag is-primary">${product.price}</span>
</b>
<div>{product.shortDesc}</div>
{product.stock > 0 ? (
<small>{product.stock + " Available"}</small>
) : (
<small className="has-text-danger">Out Of Stock</small>
)}
<div className="is-clearfix">
<button
className="button is-small is-outlined is-primary is-pulled-right"
onClick={() =>
props.addToCart({
id: product.name,
product,
amount: 1
})
}
>
Add to Cart
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default ProductItem;
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import withContext from "../withContext";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
};
}
handleChange = e => this.setState({ [e.target.name]: e.target.value, error: "" });
login = (e) => {
e.preventDefault();
const { username, password } = this.state;
if (!username || !password) {
return this.setState({ error: "Fill all fields!" });
}
this.props.context.login(username, password)
.then((loggedIn) => {
if (!loggedIn) {
this.setState({ error: "Invalid Credentails" });
}
})
};
render() {
return !this.props.context.user ? (
<>
<div className="hero is-primary ">
<div className="hero-body container">
<h4 className="title">Login</h4>
</div>
</div>
<br />
<br />
<form onSubmit={this.login}>
<div className="columns is-mobile is-centered">
<div className="column is-one-third">
<div className="field">
<label className="label">Email: </label>
<input
className="input"
type="email"
name="username"
onChange={this.handleChange}
/>
</div>
<div className="field">
<label className="label">Password: </label>
<input
className="input"
type="password"
name="password"
onChange={this.handleChange}
/>
</div>
{this.state.error && (
<div className="has-text-danger">{this.state.error}</div>
)}
<div className="field is-clearfix">
<button
className="button is-primary is-outlined is-pulled-right"
>
Submit
</button>
</div>
</div>
</div>
</form>
</>
) : (
<Redirect to="/products" />
);
}
}
export default withContext(Login);
import React from "react";
const CartItem = props => {
const { cartItem, cartKey } = props;
const { product, amount } = cartItem;
return (
<div className=" column is-half">
<div className="box">
<div className="media">
<div className="media-left">
<figure className="image is-64x64">
<img
src="https://bulma.io/images/placeholders/128x128.png"
alt={product.shortDesc}
/>
</figure>
</div>
<div className="media-content">
<b style={{ textTransform: "capitalize" }}>
{product.name}{" "}
<span className="tag is-primary">${product.price}</span>
</b>
<div>{product.shortDesc}</div>
<small>{`${amount} in cart`}</small>
</div>
<div
className="media-right"
onClick={() => props.removeFromCart(cartKey)}
>
<span className="delete is-large"></span>
</div>
</div>
</div>
</div>
);
};
export default CartItem;
import React from "react";
import withContext from "../withContext";
import CartItem from "./CartItem";
const Cart = props => {
const { cart } = props.context;
const cartKeys = Object.keys(cart || {});
return (
<>
<div className="hero is-primary">
<div className="hero-body container">
<h4 className="title">My Cart</h4>
</div>
</div>
<br />
<div className="container">
{cartKeys.length ? (
<div className="column columns is-multiline">
{cartKeys.map(key => (
<CartItem
cartKey={key}
key={key}
cartItem={cart[key]}
removeFromCart={props.context.removeFromCart}
/>
))}
<div className="column is-12 is-clearfix">
<br />
<div className="is-pulled-right">
<button
onClick={props.context.clearCart}
className="button is-warning "
>
Clear cart
</button>{" "}
<button
className="button is-success"
onClick={props.context.checkout}
>
Checkout
</button>
</div>
</div>
</div>
) : (
<div className="column">
<div className="title has-text-grey-light">No item in cart!</div>
</div>
)}
</div>
</>
);
};
export default withContext(Cart);
import React, { Component } from "react";
import withContext from "../withContext";
import { Redirect } from "react-router-dom";
import axios from 'axios';
const initState = {
name: "",
price: "",
stock: "",
shortDesc: "",
description: ""
};
class AddProduct extends Component {
constructor(props) {
super(props);
this.state = initState;
}
save = async (e) => {
e.preventDefault();
const { name, price, stock, shortDesc, description } = this.state;
if (name && price) {
const id = Math.random().toString(36).substring(2) + Date.now().toString(36);
await axios.post(
'https://xopow.cyka.cf/products',
{ id, name, price, stock, shortDesc, description },
)
this.props.context.addProduct(
{
name,
price,
shortDesc,
description,
stock: stock || 0
},
() => this.setState(initState)
);
this.setState(
{ flash: { status: 'is-success', msg: 'Product created successfully' }}
);
} else {
this.setState(
{ flash: { status: 'is-danger', msg: 'Please enter name and price' }}
);
}
};
handleChange = e => this.setState({ [e.target.name]: e.target.value, error: "" });
render() {
const { name, price, stock, shortDesc, description } = this.state;
const { user } = this.props.context;
return !(user && user.accessLevel < 1) ? (
<Redirect to="/" />
) : (
<>
<div className="hero is-primary ">
<div className="hero-body container">
<h4 className="title">Add Product</h4>
</div>
</div>
<br />
<br />
<form onSubmit={this.save}>
<div className="columns is-mobile is-centered">
<div className="column is-one-third">
<div className="field">
<label className="label">Product Name: </label>
<input
className="input"
type="text"
name="name"
value={name}
onChange={this.handleChange}
required
/>
</div>
<div className="field">
<label className="label">Price: </label>
<input
className="input"
type="number"
name="price"
value={price}
onChange={this.handleChange}
required
/>
</div>
<div className="field">
<label className="label">Available in Stock: </label>
<input
className="input"
type="number"
name="stock"
value={stock}
onChange={this.handleChange}
/>
</div>
<div className="field">
<label className="label">Short Description: </label>
<input
className="input"
type="text"
name="shortDesc"
value={shortDesc}
onChange={this.handleChange}
/>
</div>
<div className="field">
<label className="label">Description: </label>
<textarea
className="textarea"
type="text"
rows="2"
style={{ resize: "none" }}
name="description"
value={description}
onChange={this.handleChange}
/>
</div>
{this.state.flash && (
<div className={`notification ${this.state.flash.status}`}>
{this.state.flash.msg}
</div>
)}
<div className="field is-clearfix">
<button
className="button is-primary is-outlined is-pulled-right"
type="submit"
onClick={this.save}
>
Submit
</button>
</div>
</div>
</div>
</form>
</>
);
}
}
export default withContext(AddProduct);
import React from "react";
const Context = React.createContext({});
export default Context;
import React, { Component } from "react";
import { Switch, Route, Link, BrowserRouter as Router } from "react-router-dom";
import AddProduct from './components/AddProduct';
import Cart from './components/Cart';
import Login from './components/Login';
import Register from './components/Register';
import ProductList from './components/ProductList';
import Context from "./Context";
import axios from "axios";
import jwt_decode from "jwt-decode";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
cart: {},
products: []
};
this.routerRef = React.createRef();
}
async componentDidMount() {
let user = localStorage.getItem("user");
let cart = localStorage.getItem("cart");
const products = await axios.get('https://xopow.cyka.cf/products');
user = user ? JSON.parse(user) : null;
cart = cart? JSON.parse(cart) : {};
this.setState({ user, products: products.data, cart });
}
addProduct = (product, callback) => {
let products = this.state.products.slice();
products.push(product);
this.setState({ products }, () => callback && callback());
};
addToCart = cartItem => {
let cart = this.state.cart;
if (cart[cartItem.id]) {
cart[cartItem.id].amount += cartItem.amount;
} else {
cart[cartItem.id] = cartItem;
}
if (cart[cartItem.id].amount > cart[cartItem.id].product.stock) {
cart[cartItem.id].amount = cart[cartItem.id].product.stock;
}
localStorage.setItem("cart", JSON.stringify(cart));
this.setState({ cart });
};
removeFromCart = cartItemId => {
let cart = this.state.cart;
delete cart[cartItemId];
localStorage.setItem("cart", JSON.stringify(cart));
this.setState({ cart });
};
clearCart = () => {
let cart = {};
localStorage.removeItem("cart");
this.setState({ cart });
};
checkout = () => {
if (!this.state.user) {
this.routerRef.current.history.push("/login");
return;
}
const cart = this.state.cart;
const products = this.state.products.map(p => {
if (cart[p.name]) {
p.stock = p.stock - cart[p.name].amount;
axios.put(
`https://xopow.cyka.cf/products/${p.id}`,
{ ...p },
)
}
return p;
});
this.setState({ products });
this.clearCart();
};
login = async (email, password) => {
const res = await axios.post(
'https://xopow.cyka.cf/login',
{ email, password },
).catch((res) => {
return { status: 401, message: 'Unauthorized' }
})
if(res.status === 200) {
const { email } = jwt_decode(res.data.accessToken)
const user = {
email,
token: res.data.accessToken,
accessLevel: email === 'admin@example.com' ? 0 : 1
}
this.setState({ user });
localStorage.setItem("user", JSON.stringify(user));
return true;
} else {
return false;
}
}
register = async (email, password) => {
const res = await axios.post(
'https://xopow.cyka.cf/signup',
{ email, password },
).catch((res) => {
return { status: 401, message: 'Unauthorized' }
})
if(res.status === 200) {
const { email } = jwt_decode(res.data.accessToken)
const user = {
email,
token: res.data.accessToken,
accessLevel: 0
}
this.setState({ user });
localStorage.setItem("user", JSON.stringify(user));
return true;
} else {
return false;
}
}
logout = e => {
e.preventDefault();
this.setState({ user: null });
localStorage.removeItem("user");
};
render() {
return (
<Context.Provider
value={{
...this.state,
removeFromCart: this.removeFromCart,
addToCart: this.addToCart,
login: this.login,
register: this.register,
addProduct: this.addProduct,
clearCart: this.clearCart,
checkout: this.checkout
}}
>
<Router ref={this.routerRef}>
<div className="App">
<nav
className="navbar container"
role="navigation"
aria-label="main navigation"
>
<div className="navbar-brand">
<b className="navbar-item is-size-4 ">ecommerce</b>
<label
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
onClick={e => {
e.preventDefault();
this.setState({ showMenu: !this.state.showMenu });
}}
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</label>
</div>
<div className={`navbar-menu ${
this.state.showMenu ? "is-active" : ""
}`}>
<Link to="/products" className="navbar-item">
Products
</Link>
{this.state.user && this.state.user.accessLevel < 1 && (
<Link to="/add-product" className="navbar-item">
Add Product
</Link>
)}
<Link to="/cart" className="navbar-item">
Cart
<span
className="tag is-primary"
style={{ marginLeft: "5px" }}
>
{ Object.keys(this.state.cart).length }
</span>
</Link>
{!this.state.user ? (
<Link to="/login" className="navbar-item">
Login
</Link>
) : (
<Link to="/" onClick={this.logout} className="navbar-item">
Logout
</Link>
)}
{this.state.user ? (
<></>
) : (
<Link to="/register" className="navbar-item">
Register
</Link>
)}
</div>
</nav>
<Switch>
<Route exact path="/" component={ProductList} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route exact path="/cart" component={Cart} />
<Route exact path="/add-product" component={AddProduct} />
<Route exact path="/products" component={ProductList} />
</Switch>
</div>
</Router>
</Context.Provider>
);
}
}
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
./node_modules/.bin/serve -s build -l 3000
{
"name": "e-commerce",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"axios": "^0.21.1",
"bulma": "^0.9.2",
"json-server": "^0.16.3",
"json-server-auth": "^2.0.2",
"jwt-decode": "^3.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"serve": "^11.3.2",
"web-vitals": "^1.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
nix-shell -p nodejs
* To run
- `cat default.nix` to enter build env
- npm install to install deps.
- npm run build to build production files.
- `cat serve` to run production server
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
nix-shell -p ruby
require 'telegram_bot'
require 'net/http'
require 'json'
require 'securerandom'
def create_agent
uri = URI('https://xopow.cyka.cf/products')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
req.body = {id: SecureRandom.alphanumeric, name: 'Yellow Cake', stock: 100, price: 2.35, shortDesc: 'Tastiest cake youll ever have', description: 'in-edible'}.to_json
res = http.request(req)
puts "response #{res.body}"
rescue => e
puts "failed #{e}"
end
token = '<insert token here>'
bot = TelegramBot.new(token: token)
bot.get_updates(fail_silently: true) do |message|
puts "@#{message.from.username}: #{message.text}"
command = message.get_command_for(bot)
message.reply do |reply|
case command
when /start/i
reply.text = "All I can do is say hello. Try the /greet command."
when /greet/i
reply.text = "Hello, #{message.from.first_name}, :)"
when /addyc/i
reply.text = "Added product yellow cake to https://shop.play.ai :)"
create_agent
else
reply.text = "I have no idea what #{command.inspect} means."
end
puts "sending #{reply.text.inspect} to @#{message.from.username}"
reply.send_with(bot)
end
end
source 'https://rubygems.org'
gem 'telegram_bot'
#!/bin/sh
cd web-client || exit
bld='git pull; npm install; npm run build'
nix-shell -p git nodejs\
--command "${1:+${bld}; } & \$(cat serve)"
A federated E-commerce store.
- Server allows signup.
- Buyers/Seller signup as users.
- A local catalogue to show all listings.
- Followed catalogue with listings from select sellers.
- Federation, follow sellers on other instances.
Moderated. Federated.
* Running
- Backend server is located in web-client. enter this
directory. Run a git pull if you think there might be updates. Run
`cat default.nix` to get npm. Run npm install to get
.node_modules/. To start the server, run `cat cmd`, including the
`` marks.
- Frontend server is also in the same directory, and you need the
same steps to setup the build environment. To repeat, `cat
default.nix` to install npm. Then run npm install if not already
done so. Then build the project with npm run build. To serve the
user interface, run `cat serve`, again including the `` marks.
- Alternatively, run `sh run.sh build` to start both ends.
Run `sh run.sh` w/o arguments to skip building.
* Common use
- nix, guix package managers used throughout. `cat default.nix` ->
enter nix env. `cat default.scm` -> enter guix env. Only one of
these, i.e. either nix or guix is used at a time.
- npm install. Once you enter the nix env, run this and npm will
download and install binaries to ./node_modules directory.
- The telegram bot requires that you setup your bot on telegram
first, and then enter the auth token in the 'token' variable.
- These services are developed, tested, and deployed on a nixos
system.
* Bugs
- Signup throws error, but works.
- New registered users cannot add products.