Skip to content

Commit 48b76d8

Browse files
committed
refactor: clean up core helpers
1 parent c8b52b7 commit 48b76d8

6 files changed

Lines changed: 82 additions & 39 deletions

File tree

components/Loader/Loader.tsx

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
import React, { useEffect, useState, useRef } from 'react';
22
import * as Styled from './Loader.styles';
33

4+
const BOOT_SEQUENCE = [
5+
'[0.000000] Linux version 6.8.11-amd64',
6+
'[0.018392] Command line: BOOT_IMAGE=/boot/vmlinuz root=/dev/sda1 quiet',
7+
'[ OK ] Started Load Kernel Modules.',
8+
'[ OK ] Mounted /home.',
9+
'[ OK ] Started udev Kernel Device Manager.',
10+
'[ OK ] Started Network Manager.',
11+
'[ OK ] Started Accounts Service.',
12+
'[ OK ] Started D-Bus System Message Bus.',
13+
'[ OK ] Started Light Display Manager.',
14+
'[ OK ] Reached target Graphical Interface.',
15+
'Starting Kali GNU/Linux...',
16+
'Initializing xfce4-session...',
17+
'Loading profile: zis3c',
18+
'Starting display manager...',
19+
];
20+
421
export interface Props {
522
isOnScreen: boolean;
623
loadingDuration: number;
@@ -19,24 +36,6 @@ const Loader = ({
1936
const [bootLines, setBootLines] = useState<string[]>([]);
2037
const bootRef = useRef<HTMLDivElement>(null);
2138

22-
// eslint-disable-next-line react-hooks/exhaustive-deps
23-
const bootSequence = [
24-
'[0.000000] Linux version 6.8.11-amd64',
25-
'[0.018392] Command line: BOOT_IMAGE=/boot/vmlinuz root=/dev/sda1 quiet',
26-
'[ OK ] Started Load Kernel Modules.',
27-
'[ OK ] Mounted /home.',
28-
'[ OK ] Started udev Kernel Device Manager.',
29-
'[ OK ] Started Network Manager.',
30-
'[ OK ] Started Accounts Service.',
31-
'[ OK ] Started D-Bus System Message Bus.',
32-
'[ OK ] Started Light Display Manager.',
33-
'[ OK ] Reached target Graphical Interface.',
34-
'Starting Kali GNU/Linux...',
35-
'Initializing xfce4-session...',
36-
'Loading profile: zis3c',
37-
'Starting display manager...'
38-
];
39-
4039
useEffect(() => {
4140
if (!isOnScreen) return;
4241

@@ -47,10 +46,10 @@ const Loader = ({
4746
window.addEventListener('keydown', handleSkip);
4847
window.addEventListener('click', handleSkip);
4948

50-
const lineDelay = (loadingDuration - 400) / bootSequence.length;
49+
const lineDelay = (loadingDuration - 400) / BOOT_SEQUENCE.length;
5150
const timeouts: NodeJS.Timeout[] = [];
5251

53-
bootSequence.forEach((line, index) => {
52+
BOOT_SEQUENCE.forEach((line, index) => {
5453
const t = setTimeout(() => {
5554
setBootLines((prev) => [...prev, line]);
5655
if (bootRef.current) {
@@ -70,8 +69,7 @@ const Loader = ({
7069
window.removeEventListener('click', handleSkip);
7170
timeouts.forEach(clearTimeout);
7271
};
73-
// eslint-disable-next-line react-hooks/exhaustive-deps
74-
}, [isOnScreen]);
72+
}, [isOnScreen, loadingDuration, onBootComplete]);
7573

7674
if (!isOnScreen) return <></>;
7775

@@ -83,7 +81,7 @@ const Loader = ({
8381
const rest = line.slice(8);
8482
return (
8583
<Styled.BootLine key={index}>
86-
[ <Styled.OkToken>OK</Styled.OkToken> ]{rest}
84+
[ <Styled.OkToken>OK</Styled.OkToken> ]{rest}
8785
</Styled.BootLine>
8886
);
8987
}

frontend-rest-client/rest/news.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ export { getLatestNews };
1717
* @returns {object} - promise with news articles
1818
*/
1919
const getLatestNews = (): Promise<AxiosResponse<INewsApiResponse>> => {
20+
if (!process.env.NEWS_URL_QUERY || !process.env.NEWS_API_KEY) {
21+
return Promise.reject(
22+
new Error('News API is not configured for this environment')
23+
);
24+
}
25+
2026
return withRetry(
2127
() =>
2228
makeRequest<INewsApiResponse>({

hooks/useHover.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from 'react';
1+
import { useCallback, useEffect, useState } from 'react';
22

33
/**
44
* Custom hook to detect whether the mouse is hovering an element.
@@ -10,16 +10,15 @@ import { useEffect, useState } from 'react';
1010
export default function useHover(ref: React.RefObject<HTMLElement>): boolean {
1111
const [isHovering, setIsHovering] = useState(false);
1212

13-
const on = () => setIsHovering(true);
14-
const off = () => setIsHovering(false);
13+
const on = useCallback(() => setIsHovering(true), []);
14+
const off = useCallback(() => setIsHovering(false), []);
1515

1616
useEffect(() => {
17-
if (!ref.current) {
17+
const node = ref.current;
18+
if (!node) {
1819
return;
1920
}
2021

21-
const node = ref.current;
22-
2322
node.addEventListener('mouseenter', on);
2423
node.addEventListener('mousemove', on);
2524
node.addEventListener('mouseleave', off);
@@ -29,9 +28,7 @@ export default function useHover(ref: React.RefObject<HTMLElement>): boolean {
2928
node.removeEventListener('mousemove', on);
3029
node.removeEventListener('mouseleave', off);
3130
};
32-
33-
// eslint-disable-next-line react-hooks/exhaustive-deps
34-
}, []);
31+
}, [off, on, ref]);
3532

3633
return isHovering;
3734
}

pages/api/contact/index.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { NextApiRequest, NextApiResponse } from 'next';
22
import nc from 'next-connect';
33
import { sendEmail } from '../../../backend/controllers/contactsController';
44
import { onError } from '../../../middleware/onError';
5-
import { rateLimiter, validateContactBody } from '../../../middleware/rateLimit';
5+
import {
6+
rateLimiter,
7+
validateContactBody,
8+
} from '../../../middleware/rateLimit';
69
import { withCorrelationId } from '../../../middleware/requestId';
710
import { setSecurityHeaders } from '../../../middleware/securityHeaders';
811
import { logger } from '../../../utils/logger';
@@ -27,6 +30,34 @@ const ALLOWED_ORIGINS = [
2730
'https://me.zis3c.dev',
2831
].filter(Boolean);
2932

33+
const parseRequestOrigin = (
34+
value: string | string[] | undefined
35+
): string | null => {
36+
if (!value) {
37+
return null;
38+
}
39+
40+
const rawValue = Array.isArray(value) ? value[0] : value;
41+
42+
try {
43+
return new URL(rawValue).origin;
44+
} catch {
45+
return null;
46+
}
47+
};
48+
49+
export const isAllowedContactRequest = (req: NextApiRequest): boolean => {
50+
const requestOrigin =
51+
parseRequestOrigin(req.headers.origin) ??
52+
parseRequestOrigin(req.headers.referer);
53+
54+
if (!requestOrigin) {
55+
return true;
56+
}
57+
58+
return ALLOWED_ORIGINS.includes(requestOrigin);
59+
};
60+
3061
handler.post(async (req, res) => {
3162
// Attach correlation ID
3263
const correlationId = withCorrelationId(req, res);
@@ -35,8 +66,10 @@ handler.post(async (req, res) => {
3566
setSecurityHeaders(req, res);
3667

3768
// CSRF: reject cross-origin POSTs without valid origin
38-
const origin = req.headers.origin;
39-
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
69+
const origin =
70+
parseRequestOrigin(req.headers.origin) ??
71+
parseRequestOrigin(req.headers.referer);
72+
if (origin && !isAllowedContactRequest(req)) {
4073
logger.warn('Blocked cross-origin request', { origin, correlationId });
4174
return res.status(403).json({
4275
success: false,

store/action-creators/news-action-creators.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import { logger } from '../../utils/logger';
1414
*/
1515
export const loadLatestNews = () => {
1616
return async (dispatch: Dispatch<NewsReducerAction>): Promise<void> => {
17+
if (!process.env.NEWS_URL_QUERY || !process.env.NEWS_API_KEY) {
18+
return;
19+
}
20+
1721
dispatch({ type: NewsReducerActionTypes.LOAD_NEWS_ARTICLES });
1822
try {
1923
const { data } = await getLatestNews();

utils/apiFeatures.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2-
type MongooseQuery = any;
1+
type QueryLike = {
2+
find: (filter: Record<string, unknown>) => QueryLike;
3+
sort: (sortBy: string) => QueryLike;
4+
select: (fields: string) => QueryLike;
5+
skip: (count: number) => QueryLike;
6+
limit: (count: number) => QueryLike;
7+
};
38

49
/**
510
* APIfeatures class, enhances mongoose query with additional filtering, sorting, limiting and pagination functionality
@@ -12,10 +17,10 @@ export class APIfeatures {
1217
queryString: {
1318
[key: string]: string;
1419
};
15-
query: MongooseQuery;
20+
query: QueryLike;
1621

1722
constructor(
18-
query: MongooseQuery,
23+
query: QueryLike,
1924
queryString: {
2025
[key: string]: string;
2126
}

0 commit comments

Comments
 (0)