Skip to content

Commit

Permalink
Merge branch 'dev' into bugfix/139/responsive-website
Browse files Browse the repository at this point in the history
  • Loading branch information
JulienBenoit7 authored Dec 12, 2024
2 parents 7bf2b35 + 0cbbed1 commit c0b5d70
Show file tree
Hide file tree
Showing 34 changed files with 2,446 additions and 67 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
JWT_SECRET_KEY = "My_secret-key"
POSTGRES_PASSWORD=example
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
JWT_SECRET_KEY=defaultvalue
JWT_SECRET_KEY=defaultvalue
POSTGRES_PASSWORD=example
8 changes: 8 additions & 0 deletions .github/workflows/frontend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ jobs:
context: "{{defaultContext}}:backend"
file: Dockerfile.prod
tags: ${{ secrets.DOCKERHUB_USERNAME }}/wildrent-backend:latest, ${{ secrets.DOCKERHUB_USERNAME }}/wildrent-backend:${{ github.sha }}
- name: Build and push img
uses: docker/build-push-action@v4
with:
push: true
context: "{{defaultContext}}:img"
file: Dockerfile.prod
tags: ${{ secrets.DOCKERHUB_USERNAME }}/wildrent-img:latest, ${{ secrets.DOCKERHUB_USERNAME }}/wildrent-img:${{ github.sha }}

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.env
img/node_modules
frontend/src/generated/graphql-types.ts
10 changes: 6 additions & 4 deletions backend/package-lock.json

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

2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"dependencies": {
"@apollo/server": "^4.10.2",
"@playwright/test": "^1.45.1",
"@types/jsonwebtoken": "^9.0.6",
"argon2": "^0.40.3",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
Expand All @@ -33,6 +32,7 @@
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.7",
"@types/set-cookie-parser": "^2.4.7"
}
}
2 changes: 1 addition & 1 deletion backend/src/entities/reservation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export enum ReservationStatus {
}

@ObjectType() //typeGraphQl
@Entity() //typeORM
@Entity() //typeORM
export class Reservation extends BaseEntity {
@Field()
@PrimaryGeneratedColumn()
Expand Down
7 changes: 2 additions & 5 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,16 @@ const start = async () => {
],
authChecker: ({ context }: { context: Context }, roles) => {
console.log("roles for this query/mutation ", roles);
// Check user
if (!context.email) {
// No user, restrict access
return false;
}

// Check '@Authorized()'

if (roles.length === 0) {
// Only authentication required
return true;
}

// Check '@Authorized(...)' roles inclues the role of user

if (roles.includes(context.role)) {
return true;
} else {
Expand Down
23 changes: 23 additions & 0 deletions backend/src/resolvers/ArticleResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class NewArticleInput {
@Field(() => String)
productId: number;
}

@Resolver(Article)
class ArticleResolver {
@Query(() => [Article])
Expand Down Expand Up @@ -51,6 +52,28 @@ class ArticleResolver {
await Article.delete(idToDelete);
return `Product deleted successfully`;
}

@Mutation(() => Article)
async deleteArticleFromReservation(@Arg("articleId") articleId: string) {
const article = await Article.findOne({
where: { id: Number.parseInt(articleId) },
relations: { reservations: true },
});

if (!article) {
throw new Error("Article not found");
}

if (!article.reservations) {
throw new Error("Article is not part of any reservation");
}

article.reservations = [];

await article.save();

return article;
}
}

export default ArticleResolver;
23 changes: 19 additions & 4 deletions backend/src/resolvers/ReservationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,19 @@ class ReservationResolver {
where: { user: { id: context.id } },
relations: ["user", "articles", "articles.product"],
order: {
createdAt: "DESC"
createdAt: "DESC",
},
});
return reservations.map(reservation => {

return reservations.map((reservation) => {
const totalPrice = calculateTotal(reservation.articles);
return { reservation, totalPrice };
});
} else {
return [];
}
}

@Mutation(() => Reservation)
async handleReservation(
@Ctx() context: Context,
Expand Down Expand Up @@ -174,6 +174,21 @@ class ReservationResolver {

return reservation;
}

@Mutation(() => Reservation)
async cancelReservation(@Arg("reservationId") reservationId: string) {
const reservation = await Reservation.findOne({
where: { id: Number.parseInt(reservationId) },
});

if (!reservation) {
throw new Error("Reservation not found");
}
reservation.status = ReservationStatus.Ended;
await reservation.save();

return reservation;
}
}

export default ReservationResolver;
6 changes: 5 additions & 1 deletion frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ module.exports = {
"warn",
{ allowConstantExport: true },
],
quotes: ["error", "double"],
"@typescript-eslint/quotes": ["error", "double"],
eqeqeq: ["error", "always"],
indent: ["error", 2],
"no-unused-vars": "warn",
"no-console": "warn",
"no-trailing-spaces": "error",
},
};
9 changes: 5 additions & 4 deletions frontend/package-lock.json

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

4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "graphql-codegen --config codegen.ts && vite --host",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint . --ext .ts,.tsx",
"preview": "vite preview",
"test": "vitest"
},
Expand All @@ -15,7 +15,7 @@
"@apollo/client": "^3.10.8",
"@testing-library/jest-dom": "^6.4.6",
"antd": "^5.19.0",
"axios": "^1.7.2",
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-responsive": "^10.0.0",
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@ import Login from "./pages/Login";
import SearchPage from "./pages/search/[searchKeywords]";
import Profile from "./pages/Profile";
import { Cart } from "./pages/Cart";
import AdminRouteProtection from "./components/AdminRouteProtection";

const App = () => {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="register" element={<Register />} />
<Route path="admin" element={<Admin />} />
<Route
path="admin"
element={
<AdminRouteProtection>
<Admin />
</AdminRouteProtection>
}
/>
<Route path="profile" element={<Profile />} />
<Route path="login" element={<Login />} />
<Route path="/product/:productId" element={<ProductDescription />} />
<Route path="search" element={<SearchPage />} />
<Route path="search" element={<SearchPage />} />
<Route path="/search/:keyword" element={<SearchPage />} />
<Route path="register" element={<Register />} />
<Route path="cart" element={<Cart />} />
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/AdminRouteProtection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { UserContext } from "./Layout";
import { Role } from "../interface/types";

function AdminRouteProtection({ children }: { children: React.ReactNode }) {
const userInfo = useContext(UserContext);

if (!userInfo.isLoggedIn || userInfo.role !== Role.Admin) {
return <Navigate to="/" replace />;
}

return <>{children}</>;
}

export default AdminRouteProtection;
67 changes: 67 additions & 0 deletions frontend/src/components/ArticleReservationCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Card, Divider } from "antd";
import { ReservationData } from "../interface/types";
import ValidateReservationButton from "./ValidateReservationButton";
import CancelReservationButton from "./CancelReservationButton";
import DeleteArticleReservationButton from "./DeleteArticleReservationButton";

export const ArticleReservationCard = ({
reservationData,
}: {
reservationData: ReservationData;
}) => {
const articles = reservationData.reservation.articles;

return (
<>
{reservationData.reservation.status === "pending" ? (
<Card title={`Détails de votre réservation`} style={{ width: 500 }}>
{articles.map((article) => (
<Card style={{ margin: 20 }} key={article.product?.id}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div style={{ display: "flex", alignItems: "center" }}>
<img
src={article.product?.imgUrl}
alt="product img"
style={{ height: 100, marginRight: 20 }}
/>
<div>
<p style={{ margin: 0, fontWeight: "bold" }}>
{article.product?.name}
</p>
<p style={{ margin: 0 }}>{article.product?.price}</p>
</div>
</div>
<DeleteArticleReservationButton
articleId={article.id}
reservationData={reservationData}
/>
</div>
</Card>
))}
<div style={{ display: "flex", justifyContent: "space-around" }}>
{reservationData.reservation.status === "pending" && (
<ValidateReservationButton
reservation={reservationData.reservation}
/>
)}
{reservationData.reservation.status === "pending" && (
<CancelReservationButton
reservation={reservationData.reservation}
/>
)}
</div>
</Card>
) : (
<h1>Aucune réservation en cours.</h1>
)}

<Divider dashed />
</>
);
};
45 changes: 45 additions & 0 deletions frontend/src/components/CancelReservationButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useMutation } from "@apollo/client";
import { Button, message, Popconfirm } from "antd";
import { CANCEL_RESERVATION } from "../graphql/mutations";
import { Reservation } from "../interface/types";
import {
GetCurrentReservationByUserIdDocument,
GetReservationsByUserIdDocument,
} from "../generated/graphql-types";

function CancelReservationButton({ reservation }: Reservation) {
const [cancelReservation] = useMutation(CANCEL_RESERVATION, {
onCompleted: () => {
message.success("La réservation a bien été annulée.");
},
onError: () => {
message.error("Une erreur est survenue lors de l'annulation.");
},
refetchQueries: [
GetReservationsByUserIdDocument,
GetCurrentReservationByUserIdDocument,
],
});

return (
<>
<Popconfirm
title="Annuler cette réservation ?"
description="La réservation sera définitivement annulée."
okText="Oui"
cancelText="Non"
onConfirm={() =>
cancelReservation({
variables: {
reservationId: reservation.id.toString(),
},
})
}
>
<Button danger>Annuler la réservation</Button>
</Popconfirm>
</>
);
}

export default CancelReservationButton;
Loading

0 comments on commit c0b5d70

Please sign in to comment.