This repository was archived by the owner on May 20, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
363 lines (338 loc) · 18.5 KB
/
Copy pathindex.html
File metadata and controls
363 lines (338 loc) · 18.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
<html>
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin="" />
<link rel="stylesheet" as="style" onload="this.rel='stylesheet'"
href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans%3Awght%40400%3B500%3B700%3B900&family=Spline+Sans%3Awght%40400%3B500%3B700" />
<title>User Profile - GenAI for Frontend Developers</title>
<meta name="description"
content="Google Stitch, a GenAI tool, helps frontend developers in quick prototyping and development." />
<meta name="keywords"
content="GenAI, frontend development, web applications, Google Stitch, AI tools, web development" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Custom stylesheet -->
<link rel="stylesheet" href="./style.css">
<!-- Google Icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons&icon_names=search,menu&display=block">
<!-- Jasmine - Testing framework -->
<!-- Jasmine core CSS and JS -->
<link rel="stylesheet" href="https://unpkg.com/jasmine-core/lib/jasmine-core/jasmine.css">
<script src="https://unpkg.com/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script src="https://unpkg.com/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
<script src="https://unpkg.com/jasmine-core/lib/jasmine-core/boot0.js"></script>
<script src="https://unpkg.com/jasmine-core/lib/jasmine-core/boot1.js"></script>
<script>
window.addEventListener('DOMContentLoaded', () => {
describe("User Profile Page Tests", function () {
it("should display the brand name ConnectHub", function () {
const brand = document.querySelector('.brand__name');
expect(brand.textContent.trim()).toBe("ConnectHub");
});
it("should toggle the navigation menu when nav__button is clicked", function () {
const menuButton = document.querySelector('.nav__button');
const nav = document.querySelector('.nav');
const initialState = nav.classList.contains('nav--active');
menuButton.click();
expect(nav.classList.contains('nav--active')).toBe(!initialState);
menuButton.click();
expect(nav.classList.contains('nav--active')).toBe(initialState);
});
it("should toggle follow button text", function () {
const followButton = document.querySelector('.follow-user-button');
const original = followButton.textContent.trim();
followButton.click();
expect(followButton.textContent.trim()).not.toBe(original);
followButton.click();
expect(followButton.textContent.trim()).toBe(original);
});
it("should display user profile information correctly", function () {
const userName = document.querySelector('.user__name');
const userId = document.querySelector('.user__id');
const userJoiningDate = document.querySelector('.user__joining-date');
expect(userName.textContent.trim()).toBe("Ethan Carter");
expect(userId.textContent.trim()).toBe("@ethan.c");
expect(userJoiningDate.textContent.trim()).toBe("Joined in 2018");
});
});
});
</script>
</head>
<body>
<div class="relative viewport">
<header class="header">
<div class="brand">
<div class="brand__logo">
<svg class="icon" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 6H42L36 24L42 42H6L12 24L6 6Z" fill="currentColor"></path>
</svg>
</div>
<h2 class="brand__name text-[#0d141c] text-lg font-bold leading-tight tracking-[-0.015em]">ConnectHub</h2>
</div>
<div class="nav">
<a class="nav__link" href="#">Home</a>
<a class="nav__link" href="#">Explore</a>
<a class="nav__link" href="#">Notifications</a>
<a class="nav__link" href="#">Messages</a>
<span class="nav__button" tabindex="1" role="button" aria-label="Open Menu"><span class="icon material-icons">menu</span></span>
</div>
<label class="search-bar">
<div class="search-bar__icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor"
viewBox="0 0 256 256">
<path
d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z">
</path>
</svg>
</div>
<input placeholder="Search" class="search-bar__input" value="" />
</label>
<div class="avatar">
<img class="avatar__image icon" src="./assets/avatar.png" alt="User profile">
</div>
</header>
<main class="main">
<div class="user-section">
<div class="user">
<div class="user-data">
<img class="user__image" src="./assets/user-image.png" alt="user image">
</div>
<div class="user-data">
<p class="user__name">Ethan Carter</p>
<p class="user__id">@ethan.c</p>
<p class="user__joining-date">Joined in 2018</p>
</div>
</div>
<button class="follow-user-button"><span>Follow</span></button>
</div>
<aside class="tabs">
<a class="tab tab--active" href="#">Posts</a>
<a class="tab" href="#">Replies</a>
<a class="tab" href="#">Media</a>
<a class="tab" href="#">Likes</a>
</aside>
<section class="tab-content">
<article class="post" tabindex="0">
<div class="post__text-section">
<p class="post__statistics">1d · 120 impressions · 15 comments</p>
<p class="post__content">Just completed a course on Full Stack Web Development on Coursera! Highly recommend it to anyone interested in building web applications. #webdev #fullstack</p>
<p class="post__author">Ethan Carter</p>
</div>
<div class="post__image-section">
<img class="post__image" src="./assets/post-1.png" alt="post title">
</div>
</article>
<article class="post" tabindex="0">
<div class="post__text-section">
<p class="post__statistics">2d · 150 impressions · 20 comments</p>
<p class="post__content">Completed a course on Java Programming Fundamentals on Coursera. Excited to apply these new skills! #java #programming</p>
<p class="post__author">Ethan Carter</p>
</div>
<div class="post__image-section">
<img class="post__image" src="./assets/post-2.png" alt="post title">
</div>
</article>
<article class="post" tabindex="0">
<div class="post__text-section">
<p class="post__statistics">3d · 100 impressions · 10 comments</p>
<p class="post__content">Finished a course on Advanced Java Concepts on Coursera. Ready to dive deeper into Java development! #java #advanced</p>
<p class="post__author">Ethan Carter</p>
</div>
<div class="post__image-section">
<img class="post__image" src="./assets/post-3.png" alt="post title">
</div>
</article>
</section>
</main>
</div>
<script>
// Ensure the menu button toggles the navigation menu
const menuButton = document.querySelector('.nav__button');
const nav = document.querySelector('.nav');
// Add click as well as keydown event listeners for accessibility
menuButton.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); // Prevent default action
nav.classList.toggle('nav--active');
// change the .nav__button icon
menuButton.firstChild.textContent = nav.classList.contains('nav--active') ? "close" : "menu";
}
});
menuButton.addEventListener('click', () => {
nav.classList.toggle('nav--active');
// change the .nav__button icon
menuButton.firstChild.textContent = nav.classList.contains('nav--active') ? "close" : "menu";
});
// Close navigation when focus moves outside nav links and button, or Escape key is pressed
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && nav.classList.contains('nav--active')) {
nav.classList.remove('nav--active');
menuButton.firstChild.textContent = "menu";
}
});
const navLinks = nav.querySelectorAll('.nav__link');
const focusableNavElements = [...navLinks, menuButton];
document.addEventListener('focusin', (event) => {
if (nav.classList.contains('nav--active')) {
const isInNav = focusableNavElements.some(el => el === event.target);
if (!isInNav) {
nav.classList.remove('nav--active');
menuButton.firstChild.textContent = "menu";
}
}
});
// Close navigation when clicking outside the nav
document.addEventListener('click', (event) => {
if (nav.classList.contains('nav--active') && !nav.contains(event.target) && !menuButton.contains(event.target)) {
nav.classList.remove('nav--active');
menuButton.firstChild.textContent = "menu";
}
});
// JavaScript to handle the follow button state
const followButton = document.querySelector('.follow-user-button');
followButton.addEventListener('click', () => {
if (followButton.classList.contains('following')) {
followButton.innerHTML = '<span>Follow</span>';
followButton.classList.remove('following');
} else {
followButton.innerHTML = '<span>Unfollow</span>';
followButton.classList.add('following');
}
});
// Toggle active class for tabs such that the page does not reload
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', (event) => {
event.preventDefault(); // Prevent default anchor behavior
tabs.forEach(t => t.classList.remove('tab--active'));
tab.classList.add('tab--active');
renderTabContent(tab);
});
});
// Add keyboard accessibility for tabs
tabs.forEach(tab => {
tab.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); // Prevent default action
tabs.forEach(t => t.classList.remove('tab--active'));
tab.classList.add('tab--active');
renderTabContent(tab);
}
});
});
// Function to render tab content based on the active tab
function renderTabContent(activeTab) {
const tabContent = document.querySelector('.tab-content');
tabContent.innerHTML = ''; // Clear existing content
switch (activeTab.textContent.trim()) {
case 'Posts':
tabContent.innerHTML = `
<article class="post" tabindex="0">
<div class="post__text-section">
<p class="post__statistics">1d · 120 impressions · 15 comments</p>
<p class="post__content">Just completed a course on Full Stack Web Development on Coursera! Highly recommend it to anyone interested in building web applications. #webdev #fullstack</p>
<p class="post__author">Ethan Carter</p>
</div>
<div class="post__image-section">
<img class="post__image" src="./assets/post-1.png" alt="post title">
</div>
</article>
<article class="post" tabindex="0">
<div class="post__text-section">
<p class="post__statistics">2d · 150 impressions · 20 comments</p>
<p class="post__content">Completed a course on Java Programming Fundamentals on Coursera. Excited to apply these new skills! #java #programming</p>
<p class="post__author">Ethan Carter</p>
</div>
<div class="post__image-section">
<img class="post__image" src="./assets/post-2.png" alt="post title">
</div>
</article>
<article class="post" tabindex="0">
<div class="post__text-section">
<p class="post__statistics">3d · 100 impressions · 10 comments</p>
<p class="post__content">Finished a course on Advanced Java Concepts on Coursera. Ready to dive deeper into Java development! #java #advanced</p>
<p class="post__author">Ethan Carter</p>
</div>
<div class="post__image-section">
<img class="post__image" src="./assets/post-3.png" alt="post title">
</div>
</article>
`;
break;
case 'Replies':
tabContent.innerHTML = `
<p class="no-content">No replies yet.</p>
`;
break;
case 'Media':
tabContent.innerHTML = `
<p class="no-content">No media shared yet.</p>
`;
break;
case 'Likes':
tabContent.innerHTML = `
<p class="no-content">No likes yet.</p>
`;
break;
default:
tabContent.innerHTML = `
<p class="no-content">Select a tab to view content.</p>
`;
}
}
// upon clicking the user image, a modal should open where user should be able to edit their profile
const userImage = document.querySelector('.user__image');
userImage.addEventListener('click', () => {
let userNameElement = document.querySelector(".user-data .user__name");
let userImageElement = document.querySelector(".user-data .user__image");
// Create a modal for editing profile
const modal = document.createElement('div');
modal.classList.add('modal');
modal.innerHTML = `
<div class="modal__content">
<h2 class="modal__title">Edit Profile</h2>
<form class="modal__form" id="edit-profile-form">
<div class="modal__form-field">
<label class="modal__form-field__label" for="user_name">Name:</label>
<input class="modal__form-field__input" type="text" id="user_name" name="user_name" value="${userNameElement.textContent}">
</div>
<div class="modal__form-field">
<label class="modal__form-field__label" for="user_profile_picture">Profile Picture:</label>
<input class="modal__form-field__input" type="file" id="user_profile_picture" name="user_profile_picture" accept="image/*">
</div>
<button class="modal__button" type="submit">Save</button>
</form>
<button class="modal__icon-button" aria-label="Close User Profile Modal"><span class="icon material-icons">close</span></button>
</div>
`;
document.body.appendChild(modal);
modal.classList.add('modal--open');
// Save button should update the userNameElement and userImageElement
const saveButton = modal.querySelector(".modal__button");
saveButton.addEventListener('click', (event) => {
event.preventDefault();
const userNameInput = modal.querySelector("#user_name");
const userImageInput = modal.querySelector("#user_profile_picture");
if(userNameInput.value.trim().length > 1) {
userNameElement.textContent = userNameInput.value;
}
if(userImageInput.files[0]) {
const file = userImageInput.files[0];
const reader = new FileReader();
reader.onload = function(e) {
userImageElement.src = e.target.result;
};
reader.readAsDataURL(file);
}
modal.classList.remove('modal--open');
document.body.removeChild(modal);
});
// Close modal on clicking close button
const closeButton = modal.querySelector('.modal__icon-button');
closeButton.addEventListener('click', () => {
modal.classList.remove('modal--open');
document.body.removeChild(modal);
});
});
</script>
</body>
</html>