cards should be stored in DB (postgredb). index.js import 'dotenv/config'; 
import express from 'express';
import cors from 'cors';
import { cards } from './cards.js';
import { pool } from './db.js';
import 'dotenv/config';

const app = express();
const PORT = 5000;

app.use(cors());
app.use(express.json());
app.use('/img', express.static('img'));

app.post('/login', async (req, res) => {
    const { username, password } = req.body;

    try {
        const userCheck = await pool.query('SELECT * FROM users WHERE username = $1', [username]);
        if (userCheck.rows.length === 0) {
            return res.status(401).json({ message: 'Invalid username or password' });
        }

        const user = userCheck.rows[0];

        if (user.password !== password) {
            return res.status(401).json({ message: 'Invalid username or password' });
        }

        res.status(200).json({ message: 'Login successful' });
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Server error' });
    }
});
app.get('/api/cards', (req, res) => {
    const { search } = req.query;

    if (search) {
        const searchLower = search.toLowerCase();
        const filteredCards = cards.filter((cardObj) => {
            const title = cardObj.card.text.heading.toLowerCase();
            const description = cardObj.card.text.description.toLowerCase();
            return (
                title.includes(searchLower) || description.includes(searchLower)
            );
        });

        return res.status(200).json(filteredCards);
    }

    res.status(200).json(cards);
});

app.post('/signup', async (req, res) => {
    const { username, password, repeatPassword, firstName, lastName, age } = req.body;

    const errors = {};

    if (username.length < 3) {
        errors.username = 'Username must contain 3 symbols or more';
    }
    if (password.length < 4  || !/\d/.test(password) || !/[a-zA-Z]/.test(password)) {
        errors.password = 'Password must contain at least 1 number, 1 letter, and be 4 symbols or more';
    }
    if (password !== repeatPassword) {
        errors.repeatPassword = 'Passwords do not match';
    }
    if (firstName.length < 3) {
        errors.firstName = 'First name must contain 3 symbols or more';
    }
    if (lastName.length < 3) {
        errors.lastName = 'Last name must contain 3 symbols or more';
    }
    if (isNaN(age) || age <= 0) {
        errors.age = 'Age must be a number and cannot be zero or negative';
    }

    if (Object.keys(errors).length > 0) {
        return res.status(400).json({ errors });
    }

    try {
        const userCheck = await pool.query('SELECT * FROM users WHERE username = $1', [username]);
        if (userCheck.rows.length > 0) {
            return res.status(400).json({ errors: { username: 'Username already exists' } });
        }

        const newUser = await pool.query(
            'INSERT INTO users (username, password, first_name, last_name, age) VALUES ($1, $2, $3, $4, $5) RETURNING *',
            [username, password, firstName, lastName, age]
        );

        res.status(201).json({ message: 'User registered successfully', user: newUser.rows[0] });
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Server error' });
    }
});
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
}); cards.js export const cards = [
    {
        id: 1,
        card: {
            image: {
                alt: 'Spring Boot Logo',
                src: 'http://localhost:5000/img/spring-boot.svg'
            },
            text: {
                heading: 'Spring Boot',
                description:
                    'Provides core support for dependency injection, transaction management, web apps, data access, messaging, and more.'
            }
        }
    },
    {
        id: 2,
        card: {
            image: {
                alt: 'Spring Framework Logo',
                src: 'http://localhost:5000/img/spring-framework.svg'
            },
            text: {
                heading: 'Spring Framework',
                description:
                    'Takes an opinionated view of building Spring applications and gets you up and running as quickly as possible.'
            }
        }
    },
    {
        id: 3,
        card: {
            image: {
                alt: 'Spring Data Logo',
                src: 'http://localhost:5000/img/spring-data.svg'
            },
            text: {
                heading: 'Spring Data',
                description:
                    'Provides a consistent approach to data access - relational, non-relational, map-reduce, and beyond.'
            }
        }
    },
    {
        id: 4,
        card: {
            image: {
                alt: 'Spring Cloud Logo',
                src: 'http://localhost:5000/img/spring-cloud.svg'
            },
            text: {
                heading: 'Spring Cloud',
                description:
                    'Provides a set of tools for common patterns in distributed systems. Useful for building and deploying microservices.'
            }
        }
    },
    {
        id: 5,
        card: {
            image: {
                alt: 'Spring Cloud Data Flow Logo',
                src: 'http://localhost:5000/img/spring-data-flow.svg'
            },
            text: {
                heading: 'Spring Cloud Data Flow',
                description:
                    'Provides an orchestration service for composable data microservice applications on modern runtimes.'
            }
        }
    },
    {
        id: 6,
        card: {
            image: {
                alt: 'Spring Security Logo',
                src: 'http://localhost:5000/img/spring-security.svg'
            },
            text: {
                heading: 'Spring Security',
                description:
                    'Protects your application with comprehensive and extensible authentication and authorization support.'
            }
        }
    }
];
 db.js import pg from 'pg';
const { Pool } = pg;

export const pool = new Pool({
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    password: process.env.DB_PASSWORD,
    port: process.env.DB_PORT,
});

pool.connect((err) => {
    if (err) {
        console.error('Error connecting to PostgreSQL', err);
    } else {
        console.log('Connected to PostgreSQL');
    }
}); card.jsx import React from 'react';
import PropTypes from 'prop-types';
import style from './Card.module.css';

export const Card = ({ cardObj }) => {
    return (
        <div className={style.MainCardOutline}>
            <div className={style.main__card}>
                <img
                    className={style.main__card_image}
                    src={cardObj.card.image.src}
                    alt={cardObj.card.image.alt}
                />
                <div>
                    <h3>{cardObj.card.text.heading}</h3>
                    <p>{cardObj.card.text.description}</p>
                </div>
            </div>
        </div>
    );
};

Card.propTypes = {
    cardObj: PropTypes.shape({
        card: PropTypes.shape({
            cardClass: PropTypes.string.isRequired,
            image: PropTypes.shape({
                imageClass: PropTypes.string.isRequired,
                src: PropTypes.string.isRequired,
                alt: PropTypes.string.isRequired
            }).isRequired,
            text: PropTypes.shape({
                heading: PropTypes.string.isRequired,
                description: PropTypes.string.isRequired
            }).isRequired
        }).isRequired
    }).isRequired
}; cardSection.jsx import React, { useEffect, useContext, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCards } from '../../../Redux/actions';
import { SearchInput } from '../SearchInput/SearchInput';
import { Card } from '../Card/Card';
import { ThemeContext } from '../../../Selector/ThemeContext';
import classNames from 'classnames';
import style from './CardSection.module.css';

export function CardSection() {
    const dispatch = useDispatch();
    const { cards, error } = useSelector((state) => state.cards);
    const { theme } = useContext(ThemeContext);

    const [searchValue, setSearchValue] = useState('');

    useEffect(() => {
        const loadCards = async () => {
            await dispatch(fetchCards(searchValue));
        };
        loadCards();
    }, [dispatch, searchValue]);

    const handleSearchChange = (value) => {
        setSearchValue(value);
    };

    let content;

    if (error) {
        content = <p>Error: {error}</p>;
    } else if (cards.length > 0) {
        content = cards.map((cardObj) => (
            <Card key={cardObj.id} cardObj={cardObj} />
        ));
    } else {
        content = <p>No results found</p>;
    }

    return (
        <div>
            <SearchInput value={searchValue} onSearch={handleSearchChange} />
            <div
                className={classNames(style.cardSection, {
                    [style.darkTheme]: theme === 'dark'
                })}
            >
                {content}
            </div>
        </div>
    );
}
 SearchInput.jsx import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ThemeContext } from '../../../Selector/ThemeContext';
import classNames from 'classnames';
import style from './SearchInput.module.css';

export const SearchInput = ({ value, onSearch }) => {
    const { theme } = useContext(ThemeContext);
    const [localValue, setLocalValue] = useState(value);
    const [debouncedValue, setDebouncedValue] = useState(localValue);
    const DEBOUNCE_DELAY = 300;

    useEffect(() => {
        const handler = setTimeout(() => {
            setDebouncedValue(localValue);
        }, DEBOUNCE_DELAY);

        return () => {
            clearTimeout(handler);
        };
    }, [localValue]);

    useEffect(() => {
        onSearch(debouncedValue);
    }, [debouncedValue, onSearch]);

    const handleSearchChange = (event) => {
        const newValue = event.target.value;
        setLocalValue(newValue);
    };

    return (
        <div
            className={classNames(style.searchContainer, {
                [style.darkTheme]: theme === 'dark'
            })}
        >
            <input
                type="text"
                className={style.searchInput}
                placeholder="Поиск..."
                value={localValue}
                onChange={handleSearchChange}
            />
        </div>
    );
};

SearchInput.propTypes = {
    value: PropTypes.string.isRequired,
    onSearch: PropTypes.func.isRequired
};