Skip to content

Commit 679b3e0

Browse files
committed
tags are added to "create" post and "post detail". Now a post can be edited. A bug remains to be handled when saving changes to a post.
1 parent 4bb31ff commit 679b3e0

File tree

12 files changed

+307
-104
lines changed

12 files changed

+307
-104
lines changed

docs/README.es.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Esto levantará el servidor y pondrá en funcionamiento el API REST para que pue
122122

123123
1. Descargue el comprimido del proyecto desde GitHub a su ordenador.
124124

125-
2. Una vez desgcargado el comprimido, abra el proyecto en Visual Studio agregando el directorio del proyecto a su espacio de trabajo.
125+
2. Una vez desarcargado el comprimido, abra el proyecto en Visual Studio agregando el directorio del proyecto a su espacio de trabajo.
126126

127127
### Notas
128128

src/js/components/notifications/notificationsController.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,34 @@ export function notificationsController(container) { // container is ".notificat
2222

2323
container.appendChild(newNotification) // <-- Here we insert our HTML ready to use.
2424

25-
// Obtener todas las notificaciones actuales después de agregar la nueva
2625
let currentNotifications = Array.from(container.querySelectorAll('.notification'));
2726

28-
// Si hay más de 3, elimina las más antiguas
2927
while (currentNotifications.length > 3) {
30-
const oldest = currentNotifications.shift(); // remueve la primera (más vieja)
28+
const oldest = currentNotifications.shift();
3129
removeNotification(oldest);
3230
}
3331

34-
// Control de duración dependiendo del tipo
3532
if (type === 'error') {
36-
// Errores antiguos (excepto el nuevo)
3733
const oldErrors = currentNotifications.filter(n =>
3834
n !== newNotification && n.classList.contains('error')
3935
);
4036

41-
// Los errores antiguos duran 1 segundo
4237
oldErrors.forEach(n => {
4338
setTimeout(() => removeNotification(n), 1000);
4439
});
4540

46-
// El nuevo error dura 5 segundos
4741
setTimeout(() => {
4842
removeNotification(newNotification);
4943
}, 5000);
5044
} else {
51-
// Notificaciones que no son de tipo 'error' también desaparecen a los 5s
5245
setTimeout(() => {
5346
removeNotification(newNotification);
5447
}, 5000);
5548
}
49+
50+
console.log('Error:',message)
5651
};
52+
5753

5854
return {
5955
showNotification

src/js/modules/create-post/createPost.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createPostController } from "../create-post/createPostController.js";
2+
import { buildCreatePostForm } from "./createPostView.js";
23

34
import {notificationsController} from "../../components/notifications/notificationsController.js"
45
import { loaderController } from "../../components/loader/loaderController.js";
@@ -18,6 +19,8 @@ export function initCreatePost() {
1819
const { showNotification } = notificationsController(notifications)
1920
const { show, hide } = loaderController(loader);
2021

22+
createPostForm.innerHTML = buildCreatePostForm()
23+
2124
createPostForm.addEventListener('load-posts-started', () => {
2225
show();
2326
})

src/js/modules/create-post/createPostController.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ export const createPostController = (createPostForm) => {
88

99
event.preventDefault();
1010

11-
const postImage = createPostForm.querySelector('#post-image').value;
11+
const image = createPostForm.querySelector('#post-image').value;
12+
let postImage;
13+
14+
if (!image) {
15+
postImage = '../public/no-image-available.jpg';
16+
}
17+
1218
const postName = createPostForm.querySelector('#post-name').value;
19+
const postTag = createPostForm.querySelector('#post-tag').value;
1320
const postDescription = createPostForm.querySelector('#post-description').value;
1421
const postPrice = createPostForm.querySelector('#post-price').value;
1522
const transactionType = createPostForm.querySelector('input[name="transactionType"]:checked').value;
@@ -19,6 +26,7 @@ export const createPostController = (createPostForm) => {
1926
const postData = {
2027
image: postImage,
2128
name: postName,
29+
tag: postTag,
2230
description: postDescription,
2331
price: postPrice,
2432
isPurchase: isPurchase

src/js/modules/create-post/createPostModel.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ export const createPost = async (postData) => {
55
const response = await fetch("http://localhost:8000/api/posts", {
66
method: "POST",
77
body: JSON.stringify({
8-
photo: postData.image,
8+
image: postData.image,
99
name: postData.name,
10+
tag: postData.tag,
1011
description: postData.description,
1112
price: postData.price,
1213
isPurchase: postData.isPurchase
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { tags } from "../../utils/constants.js";
2+
3+
export const buildCreatePostForm = () => {
4+
5+
const tagOptions = tags.map(tag => {
6+
return `<option value="${tag}">${tag}</option>`;
7+
}).join('');
8+
9+
10+
let createPostFormView = `
11+
12+
<label for="post-image">Photo: </label>
13+
<input type="file" id="post-image" name="photo" accept="image/*">
14+
15+
<label for="post-name">Product Name: </label>
16+
<input required id="post-name" name="post-name" type="text" maxlength="120"
17+
placeholder="Introduce the name of your post." />
18+
19+
<label for="post-description">Description: </label>
20+
<textarea required name="post-description" id="post-description" maxlength="300"
21+
placeholder="Max. Length 300"></textarea>
22+
23+
<label for="post-price">Price: </label>
24+
<input required id="post-price" name="post-price" type="number" min="1" max="9999999" step="0.01"
25+
placeholder="Introduce the price of your product." />
26+
27+
<div>
28+
<label for="purchase">
29+
<input required type="radio" id="purchase" name="transactionType" value="purchase">
30+
Purchase
31+
</label>
32+
33+
<label for="sale">
34+
<input required type="radio" id="sale" name="transactionType" value="sale">
35+
Sale
36+
</label>
37+
</div>
38+
39+
<label for="post-tag">Tag: </label>
40+
<select id="post-tag" name="tag">
41+
${tagOptions}
42+
</select>
43+
44+
<button>Create Post</button>
45+
`;
46+
47+
return createPostFormView
48+
}
Lines changed: 117 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,145 @@
1-
import { postDetailModel, getLoggedInUserInfo, removepost } from "./postDetailModel.js"
2-
import { buildpostDetailView, buildRemovepostButton } from "./postDetailView.js"
1+
import { postDetailModel, getLoggedInUserInfo, removePost, updatePost } from "./postDetailModel.js";
2+
import { buildPostDetailEditableView, buildPostDetailReadOnlyView } from "./postDetailView.js";
3+
34

4-
//===================================================================================================================
55
export const postDetailController = async (postContainer, postId) => {
6+
let postDetail = null;
7+
let user = null;
8+
let isEditing = false;
9+
10+
try {
11+
const event = new CustomEvent("load-posts-started");
12+
postContainer.dispatchEvent(event);
13+
14+
try {
15+
//===================================
16+
postDetail = await postDetailModel(postId);
17+
//===================================
18+
} catch (error) {
19+
dispatchNotification('post-error', error.message);
20+
}
21+
22+
try {
23+
//===================================
24+
user = await getLoggedInUserInfo();
25+
//===================================
26+
} catch (error) {
27+
dispatchNotification('post-error', error.message);
28+
}
629

7-
//-------------------------------------------------------------------------------------------------------------------
8-
const showRemovepostButton = (postId) => {
9-
const removeButton = buildRemovepostButton()
10-
postContainer.appendChild(removeButton)
30+
const isOwner = user.id === postDetail.userId;
1131

12-
removeButton.addEventListener("click", async () => {
13-
if (confirm("Are you sure about deleting the post?")) {
14-
try {
32+
renderReadOnlyView(postDetail, isOwner);
1533

16-
//====================================================
17-
await removepost(postId)
18-
//====================================================
34+
} catch (error) {
35+
dispatchNotification('post-error', error.message);
36+
} finally {
37+
const event = new CustomEvent("load-posts-finished");
38+
postContainer.dispatchEvent(event);
39+
}
1940

20-
dispatchNotification('post-success', {
21-
message: "Post successfully deleted.",
22-
type: 'success'
23-
})
41+
//===================================================================================================================
2442

25-
setTimeout(() => window.location = '/', 7000)
43+
function renderReadOnlyView(post, isOwner) {
44+
isEditing = false;
45+
postContainer.innerHTML = buildPostDetailReadOnlyView(post, isOwner);
2646

27-
} catch (error) {
28-
dispatchNotification('post-error', error.message)
29-
}
30-
}
31-
})
47+
if (isOwner) {
48+
const editPost = postContainer.querySelector("#edit-post")
49+
editPost.addEventListener("click", () => renderEditableView(post));
50+
51+
const deletePost = postContainer.querySelector("#delete-post")
52+
deletePost.addEventListener("click", () => handleDelete(post.id));
53+
}
3254
}
3355

34-
//-------------------------------------------------------------------------------------------------------------------
35-
try {
56+
//------------------------------------------------------------------------
57+
function renderEditableView(post) {
58+
isEditing = true;
59+
postContainer.innerHTML = buildPostDetailEditableView(post);
3660

37-
//----------------------------------------------------
38-
const event = new CustomEvent("load-posts-started");
39-
postContainer.dispatchEvent(event);
40-
//----------------------------------------------------
61+
const saveEdit = postContainer.querySelector("#save-changes")
62+
saveEdit.addEventListener("click", (event) => {
63+
event.preventDefault();
4164

42-
//====================================================
43-
const postDetail = await postDetailModel(postId)
44-
//====================================================
65+
handleSave(post);
66+
});
4567

46-
postContainer.innerHTML = buildpostDetailView(postDetail)
68+
const cancelEdit = postContainer.querySelector("#cancel-edit")
69+
cancelEdit.addEventListener("click", (event) => {
70+
event.preventDefault();
4771

48-
try {
49-
//====================================================
50-
const user = await getLoggedInUserInfo()
51-
//====================================================
52-
if (user?.id === postDetail?.userId) {
53-
showRemovepostButton(postId)
72+
renderReadOnlyView(postDetail, true);
73+
});
74+
}
75+
76+
//------------------------------------------------------------------------
77+
async function handleDelete(postId) {
78+
if (confirm("Are you sure about deleting the post?")) {
79+
try {
80+
//===================================
81+
await removePost(postId);
82+
//===================================
83+
84+
dispatchNotification('post-success', {
85+
message: "Post successfully deleted.",
86+
type: 'success'
87+
});
88+
} catch (error) {
89+
dispatchNotification('post-error', error.message);
5490
}
55-
} catch (error) {
56-
dispatchNotification('post-error', error.message)
5791
}
92+
}
5893

59-
} catch (error) {
94+
//------------------------------------------------------------------------
95+
async function handleSave(post) {
96+
const editPostForm = postContainer.querySelector('#editPostForm');
6097

61-
dispatchNotification('post-error', error.message)
98+
const name = editPostForm.querySelector('#post-name').value;
99+
const description = editPostForm.querySelector('#post-description').value;
100+
const price = editPostForm.querySelector('#post-price').value;
101+
const tag = editPostForm.querySelector('#edit-tag').value;
102+
const isPurchase = editPostForm.querySelector('input[name="transactionType"]:checked').value === 'purchase';
62103

63-
} finally {
104+
const fileInput = editPostForm.querySelector('#post-image');
105+
const newImage = fileInput.files[0];
106+
107+
post.name = name;
108+
post.description = description;
109+
post.price = price;
110+
post.tag = tag;
111+
post.isPurchase = isPurchase;
112+
113+
if (newImage) {
114+
post.photo = newImage;
115+
}
64116

65-
//----------------------------------------------------
66-
const event = new CustomEvent("load-posts-finished")
67-
postContainer.dispatchEvent(event)
68-
//----------------------------------------------------
117+
try {
118+
//===================================
119+
const updatedPost = await updatePost(post.id, post);
120+
//===================================
121+
122+
postDetail = updatedPost; // actualizar info local
123+
dispatchNotification('post-success', {
124+
message: "Post successfully updated.",
125+
type: 'success'
126+
});
127+
128+
renderReadOnlyView(updatedPost, true);
129+
130+
} catch (error) {
131+
dispatchNotification('post-error', error.message);
132+
}
69133
}
70134

71-
//-------------------------------------------------------------------------------------------------------------------
135+
//------------------------------------------------------------------------
72136
function dispatchNotification(eventType, message) {
73-
const event = new CustomEvent(eventType, {
74-
detail: message
75-
})
137+
const event = new CustomEvent(eventType, { detail: message });
76138

77139
if (message.type === 'success') {
78-
setTimeout(() => window.location = '/', 7000)
140+
setTimeout(() => window.location = '/index.html', 7000);
79141
}
80142

81-
postContainer.dispatchEvent(event)
143+
postContainer.dispatchEvent(event);
82144
}
83-
}
145+
};

0 commit comments

Comments
 (0)