Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .learn/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"config": {
"editor": {
"agent": "vscode"
},
"autoPlay": true
},
"currentExercise": null
}
Binary file added backend/__pycache__/app.cpython-313.pyc
Binary file not shown.
Binary file added backend/__pycache__/models.cpython-313.pyc
Binary file not shown.
148 changes: 148 additions & 0 deletions backend/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from flask import Flask, jsonify
from models import db, People, Planet, User, Favorite

app = Flask(__name__)

app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///starwars.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db.init_app(app)

with app.app_context():
db.create_all()

@app.route("/")
def home():
return jsonify({"msg": "Star Wars API running 🚀"})


@app.route("/people", methods=["GET"])
def get_people():
people = People.query.all()
return jsonify([p.serialize() for p in people]), 200


@app.route("/people/<int:people_id>", methods=["GET"])
def get_single_person(people_id):
person = People.query.get(people_id)
if not person:
return jsonify({"error": "Person not found"}), 404
return jsonify(person.serialize()), 200


@app.route("/planets", methods=["GET"])
def get_planets():
planets = Planet.query.all()
return jsonify([p.serialize() for p in planets]), 200


@app.route("/planets/<int:planet_id>", methods=["GET"])
def get_single_planet(planet_id):
planet = Planet.query.get(planet_id)
if not planet:
return jsonify({"error": "Planet not found"}), 404
return jsonify(planet.serialize()), 200


@app.route("/users", methods=["GET"])
def get_users():
users = User.query.all()
return jsonify([u.serialize() for u in users]), 200


def get_current_user():
return User.query.first()


@app.route("/users/favorites", methods=["GET"])
def get_user_favorites():
user = get_current_user()
if not user:
return jsonify([]), 200

favorites = []

for fav in user.favorites:
if fav.people:
favorites.append({
"type": "people",
"item": fav.people.serialize()
})
if fav.planet:
favorites.append({
"type": "planet",
"item": fav.planet.serialize()
})

return jsonify(favorites), 200


@app.route("/favorite/people/<int:people_id>", methods=["POST"])
def add_favorite_people(people_id):
user = get_current_user()
person = People.query.get(people_id)

if not user or not person:
return jsonify({"error": "User or person not found"}), 404

favorite = Favorite(user_id=user.id, people_id=person.id)
db.session.add(favorite)
db.session.commit()

return jsonify({"msg": "Favorite person added"}), 201


@app.route("/favorite/planet/<int:planet_id>", methods=["POST"])
def add_favorite_planet(planet_id):
user = get_current_user()
planet = Planet.query.get(planet_id)

if not user or not planet:
return jsonify({"error": "User or planet not found"}), 404

favorite = Favorite(user_id=user.id, planet_id=planet.id)
db.session.add(favorite)
db.session.commit()

return jsonify({"msg": "Favorite planet added"}), 201


@app.route("/favorite/people/<int:people_id>", methods=["DELETE"])
def delete_favorite_people(people_id):
user = get_current_user()

favorite = Favorite.query.filter_by(
user_id=user.id,
people_id=people_id
).first()

if not favorite:
return jsonify({"error": "Favorite not found"}), 404

db.session.delete(favorite)
db.session.commit()

return jsonify({"msg": "Favorite person deleted"}), 200


@app.route("/favorite/planet/<int:planet_id>", methods=["DELETE"])
def delete_favorite_planet(planet_id):
user = get_current_user()

favorite = Favorite.query.filter_by(
user_id=user.id,
planet_id=planet_id
).first()

if not favorite:
return jsonify({"error": "Favorite not found"}), 404

db.session.delete(favorite)
db.session.commit()

return jsonify({"msg": "Favorite planet deleted"}), 200


if __name__ == "__main__":
app.run(debug=True)

Binary file added backend/instance/starwars.db
Binary file not shown.
73 changes: 73 additions & 0 deletions backend/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class User(db.Model):
__tablename__ = "user"

id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)

favorites = db.relationship("Favorite", backref="user", lazy=True)

def serialize(self):
return {
"id": self.id,
"email": self.email
}


class People(db.Model):
__tablename__ = "people"

id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
gender = db.Column(db.String(50))
birth_year = db.Column(db.String(20))

favorites = db.relationship("Favorite", backref="people", lazy=True)

def serialize(self):
return {
"id": self.id,
"name": self.name,
"gender": self.gender,
"birth_year": self.birth_year
}


class Planet(db.Model):
__tablename__ = "planet"

id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
climate = db.Column(db.String(50))
population = db.Column(db.String(50))

favorites = db.relationship("Favorite", backref="planet", lazy=True)

def serialize(self):
return {
"id": self.id,
"name": self.name,
"climate": self.climate,
"population": self.population
}


class Favorite(db.Model):
__tablename__ = "favorite"

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
people_id = db.Column(db.Integer, db.ForeignKey("people.id"), nullable=True)
planet_id = db.Column(db.Integer, db.ForeignKey("planet.id"), nullable=True)

def serialize(self):
return {
"id": self.id,
"user_id": self.user_id,
"people_id": self.people_id,
"planet_id": self.planet_id
}
Binary file added backend/requirements.txt
Binary file not shown.
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added src/assets/img/iconosw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/img/rigo-baby.jpg
Binary file not shown.
33 changes: 33 additions & 0 deletions src/components/Card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import useGlobalReducer from "../hooks/useGlobalReducer.jsx";

export const Card = ({ imgURL, title, children }) => {
const { store, dispatch } = useGlobalReducer();

const addFavorite = () => {
dispatch({ type: "add_favorite", payload: { name: title } });
};

return (
<>
<div className="card">
<img src={imgURL} className="card-img-top" alt="Luke Skywalker" />
<div className="card-body">
<h5 className="card-title">{title}</h5>
{children}
<div className="d-flex gap-5">
<a href="#" className="btn btn-primary me-5">
Learn more
</a>
<button
onClick={addFavorite}
className="btn btn-primary"
type="button"
>
<i className="fa-regular fa-bookmark"></i>
</button>
</div>
</div>
</div>
</>
);
};
45 changes: 45 additions & 0 deletions src/components/Favorites.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import useGlobalReducer from "../hooks/useGlobalReducer.jsx";
export const Favorites = () => {
const { store, dispatch } = useGlobalReducer();

const favorites = store.favoriteList || [];

const removeFavorite = (name) => {
dispatch({
type: "remove_favorite",
payload: { name: item },
});
};

return (
<>
<div className="dropdown">
<button
className="btn btn-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Favorites ({favorites.length})
</button>

<ul className="dropdown-menu">
{favorites.map((item, index) => (
<li
key={item || index}
className="d-flex justify-content-between align-item-center p-2"
>
<span className="dropdown-item-text">{item}</span>
<button
onClick={() => removeFavorite(item)}
className="btn btn-second btn-sm p-0 m-0"
>
<i className="fa fa-trash"></i>
</button>
</li>
))}
</ul>
</div>
</>
);
};
40 changes: 24 additions & 16 deletions src/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import React from "react";
import { Link } from "react-router-dom";
import starWarsLogo from "../assets/img/iconosw.png";
import { Favorites } from "./Favorites";

export const Navbar = () => {
// ... el resto del componente

return (
<nav className="navbar navbar-light bg-light">
<div className="container">
<Link to="/">
<span className="navbar-brand mb-0 h1">React Boilerplate</span>
</Link>
<div className="ml-auto">
<Link to="/demo">
<button className="btn btn-primary">Check the Context in action</button>
</Link>
</div>
</div>
</nav>
);
};
export const Navbar = () => {
return (
<nav className="navbar navbar-light bg-light">
<div className="container">
<Link to="/">
<img
src={starWarsLogo}
alt="Star Wars Logo"
style={{ height: "100px", marginRight: "10px" }}
/>
</Link>
<div className="ml-auto">
<Link to="/demo">
<Favorites />
</Link>
</div>
</div>
</nav>
);
};
3 changes: 3 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.card {
width: 18rem;
}
Loading