Skip to content

Commit 064542b

Browse files
authored
Reproduce actual website part 3 (#4)
1 parent 22e544f commit 064542b

File tree

9 files changed

+350
-314
lines changed

9 files changed

+350
-314
lines changed

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"printWidth": 80,
2+
"printWidth": 100,
33
"tabWidth": 4,
44
"trailingComma": "es5",
55
"useTabs": false,

package-lock.json

Lines changed: 238 additions & 238 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/icon/check.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/icon/x.svg

Lines changed: 3 additions & 0 deletions
Loading

src/components/Footer.astro

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import { SITE_NAME } from '../config.js'
88
99
const baseYear = 2024
1010
const currentYear = new Date().getFullYear()
11-
const yearString =
12-
currentYear === baseYear
13-
? baseYear.toString()
14-
: `${baseYear}-${currentYear}`
11+
const yearString = currentYear === baseYear ? baseYear.toString() : `${baseYear}-${currentYear}`
1512
---
1613

1714
<footer>
@@ -21,11 +18,7 @@ const yearString =
2118
</div>
2219
<ul>
2320
<li title="GitHub">
24-
<a
25-
href="https://github.com/TheSmartCooking"
26-
target="_blank"
27-
rel="noopener noreferrer"
28-
>
21+
<a href="https://github.com/TheSmartCooking" target="_blank" rel="noopener noreferrer">
2922
<IconGitHub />
3023
</a>
3124
</li>

src/components/RecipeCard.astro

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
---
22
import '../styles/global.css'
33
4-
const {
5-
author_name,
6-
cook_time,
7-
number_of_reviews,
8-
picture_id,
9-
recipe_status,
10-
title,
11-
} = Astro.props
4+
const { author_name, cook_time, number_of_reviews, picture_id, recipe_status, title } = Astro.props
125
136
function randint(min: number, max: number) {
147
return Math.floor(Math.random() * (max - min + 1) + min)
@@ -17,10 +10,7 @@ function randint(min: number, max: number) {
1710

1811
<Fragment>
1912
<div class="recipe-polaroid">
20-
<div
21-
class="card-body"
22-
style=`background-image: url("recipe/${picture_id}.png");`
23-
>
13+
<div class="card-body" style=`background-image: url("recipe/${picture_id}.png");`>
2414
<div class="card-top">
2515
<span class="card-text cook-time">⏱️ {cook_time}</span>
2616
<span class="card-text recipe-status">{recipe_status}</span>

src/components/SearchBar.astro

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,21 @@ const { placeholder } = Astro.props
3636
</style>
3737

3838
<script type="module" is:inline>
39+
const input = document.querySelector('.search-bar input')
3940
let debounceTimeout
4041

4142
function handleSearch(input) {
4243
const query = input.value.trim().toLowerCase()
4344
const products = document.querySelectorAll('.recipe-polaroid')
4445

4546
products.forEach((product) => {
46-
const name =
47-
product.querySelector('h1')?.textContent.toLowerCase() || ''
48-
product.style.display =
49-
query && name.includes(query) ? 'flex' : query ? 'none' : 'flex'
47+
const name = product.querySelector('h1')?.textContent.toLowerCase() || ''
48+
product.style.display = query && name.includes(query) ? 'flex' : query ? 'none' : 'flex'
5049
})
5150
}
5251

53-
function initSearchBar() {
54-
const input = document.querySelector('.search-bar input')
55-
56-
input.addEventListener('input', () => {
57-
clearTimeout(debounceTimeout)
58-
debounceTimeout = setTimeout(() => handleSearch(input), 500)
59-
})
60-
}
61-
62-
if (document.readyState === 'loading') {
63-
document.addEventListener('DOMContentLoaded', initSearchBar)
64-
} else {
65-
initSearchBar()
66-
}
52+
input.addEventListener('input', () => {
53+
clearTimeout(debounceTimeout)
54+
debounceTimeout = setTimeout(() => handleSearch(input), 500)
55+
})
6756
</script>

src/pages/authentication.astro

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
---
22
import '../styles/global.css'
33
import Main from '../layouts/Main.astro'
4-
import IconShowPassword from '../assets/icon/eye.svg'
4+
import IconCheck from '../assets/icon/check.svg'
55
import IconHidePassword from '../assets/icon/eye-off.svg'
6+
import IconShowPassword from '../assets/icon/eye.svg'
7+
import IconX from '../assets/icon/x.svg'
68
---
79

810
<Main title="Authentication">
@@ -11,23 +13,13 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
1113
<h2>Login</h2>
1214
<form action="#">
1315
<div class="form-field">
14-
<input
15-
type="email"
16-
id="login-email"
17-
name="login-email"
18-
required
19-
/>
16+
<input type="email" id="login-email" name="login-email" required />
2017
<label for="login-email">E-mail</label>
2118
</div>
2219

2320
<div class="form-field">
2421
<div class="input-with-icon">
25-
<input
26-
type="password"
27-
id="login-password"
28-
name="login-password"
29-
required
30-
/>
22+
<input type="password" id="login-password" name="login-password" required />
3123
<label for="login-password">Password</label>
3224
<div class="icons" title="Toggle password visibility">
3325
<IconShowPassword class="show-password" />
@@ -47,22 +39,12 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
4739
<h2>Register</h2>
4840
<form action="#">
4941
<div class="form-field">
50-
<input
51-
type="text"
52-
id="register-username"
53-
name="register-username"
54-
required
55-
/>
42+
<input type="text" id="register-username" name="register-username" required />
5643
<label for="register-username">Username</label>
5744
</div>
5845

5946
<div class="form-field">
60-
<input
61-
type="email"
62-
id="register-email"
63-
name="register-email"
64-
required
65-
/>
47+
<input type="email" id="register-email" name="register-email" required />
6648
<label for="register-email">E-mail</label>
6749
</div>
6850

@@ -86,18 +68,52 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
8668
<div class="input-with-icon">
8769
<input
8870
type="password"
89-
id="register-password-password"
90-
name="register-password-password"
71+
id="register-password-confirm"
72+
name="register-password-confirm"
9173
required
9274
/>
93-
<label for="register-password-password">Password</label>
75+
<label for="register-password-confirm">Password confirmation</label>
9476
<div class="icons" title="Toggle password visibility">
9577
<IconShowPassword class="show-password" />
9678
<IconHidePassword class="hide-password" />
9779
</div>
9880
</div>
9981
</div>
10082

83+
<div id="password-requirements">
84+
<h3>Password requirements</h3>
85+
<ul>
86+
<li>
87+
<IconCheck class="password-requirement-check" />
88+
<IconX class="password-requirement-x" />
89+
<span>At least 12 characters long.</span>
90+
</li>
91+
<li>
92+
<IconCheck class="password-requirement-check" />
93+
<IconX class="password-requirement-x" />
94+
<span> Contains at least one uppercase letter (A-Z). </span>
95+
</li>
96+
<li>
97+
<IconCheck class="password-requirement-check" />
98+
<IconX class="password-requirement-x" />
99+
<span> Contains at least one lowercase letter (a-z). </span>
100+
</li>
101+
<li>
102+
<IconCheck class="password-requirement-check" />
103+
<IconX class="password-requirement-x" />
104+
<span>Contains at least one digit (0-9).</span>
105+
</li>
106+
<li>
107+
<IconCheck class="password-requirement-check" />
108+
<IconX class="password-requirement-x" />
109+
<span>
110+
Contains at least one special character (any non-alphanumeric
111+
character).
112+
</span>
113+
</li>
114+
</ul>
115+
</div>
116+
101117
<button type="submit">Register</button>
102118
</form>
103119

@@ -139,6 +155,16 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
139155
width: clamp(320px, 100%, 500px);
140156
}
141157

158+
ul {
159+
list-style: none;
160+
}
161+
162+
ul > li {
163+
display: flex;
164+
gap: var(--spacing-small);
165+
align-items: center;
166+
}
167+
142168
.flip-link {
143169
font-weight: bold;
144170
}
@@ -181,7 +207,8 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
181207
border-bottom-color: var(--color-secondary);
182208
}
183209

184-
.hide-password {
210+
.hide-password,
211+
.password-requirement-check {
185212
display: none;
186213
}
187214

@@ -197,6 +224,20 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
197224
display: flex;
198225
}
199226

227+
.password-requirement-check,
228+
.password-requirement-x {
229+
width: 1.75rem;
230+
min-width: 1.75rem;
231+
}
232+
233+
.password-requirement-check {
234+
color: var(--color-success);
235+
}
236+
237+
.password-requirement-x {
238+
color: var(--color-error);
239+
}
240+
200241
#authentication {
201242
align-items: center;
202243
display: flex;
@@ -223,6 +264,7 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
223264
const registerSection = document.getElementById('register')
224265
const loginLink = document.getElementById('register-link')
225266
const registerLink = document.getElementById('login-link')
267+
const passwordInput = document.getElementById('register-password')
226268

227269
// Maintain label position on filled inputs
228270
document.querySelectorAll('.form-field input').forEach((input) => {
@@ -259,9 +301,7 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
259301
const hideIcons = section.querySelectorAll('.hide-password')
260302

261303
const toggle = () => {
262-
const isHidden = [...passwordInputs].some(
263-
(input) => input.type === 'password'
264-
)
304+
const isHidden = [...passwordInputs].some((input) => input.type === 'password')
265305
passwordInputs.forEach((input) => {
266306
input.type = isHidden ? 'text' : 'password'
267307
})
@@ -275,4 +315,24 @@ import IconHidePassword from '../assets/icon/eye-off.svg'
275315

276316
iconContainer.addEventListener('click', toggle)
277317
})
318+
319+
passwordInput.addEventListener('input', () => {
320+
const password = passwordInput.value
321+
const requirements = [
322+
{ regex: /.{12,}/, index: 0 },
323+
{ regex: /[A-Z]/, index: 1 },
324+
{ regex: /[a-z]/, index: 2 },
325+
{ regex: /\d/, index: 3 },
326+
{ regex: /[^a-zA-Z0-9]/, index: 4 },
327+
]
328+
329+
requirements.forEach(({ regex, index }) => {
330+
const isValid = regex.test(password)
331+
const checkIcon = document.querySelectorAll('.password-requirement-check')[index]
332+
const xIcon = document.querySelectorAll('.password-requirement-x')[index]
333+
334+
checkIcon.style.display = isValid ? 'inline' : 'none'
335+
xIcon.style.display = isValid ? 'none' : 'inline'
336+
})
337+
})
278338
</script>

src/styles/global.css

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
--color-primary: light-dark(#66af71, #2b422f);
99
--color-secondary: light-dark(#527f59, #3f8549);
1010
--color-error: red;
11-
--color-success: green;
11+
--color-success: var(--color-secondary);
1212

1313
/*
1414
Define spacing sizes for margins, paddings, and gaps.
@@ -34,9 +34,7 @@
3434
--polaroid-height: 107mm;
3535
--polaroid-width: 88mm;
3636
--polaroid-photo-size: calc(var(--polaroid-width) - 9mm);
37-
--polaroid-margin-top: calc(
38-
(var(--polaroid-width) - var(--polaroid-photo-size)) / 2
39-
);
37+
--polaroid-margin-top: calc((var(--polaroid-width) - var(--polaroid-photo-size)) / 2);
4038
}
4139

4240
* {

0 commit comments

Comments
 (0)