Hero Slider

سنقوم الآن ببناء Banner / Hero Slider System احترافي 100%
بنية صحيحة في Strapi + كود React كامل + Redux + Swiper
وقابل للتوسع مستقبلاً.

سنبنيه وكأننا نبني متجر Production حقيقي 👌


🏗 أولًا: إعداد Strapi بطريقة احترافية

✅ أنشئ Collection Type باسم:

banners

📦 الحقول داخل banners

1️⃣ Basic Content

FieldType
titleShort text
subtitleShort text
descriptionRich text
buttonTextShort text
buttonLinkShort text

2️⃣ Images

FieldType
backgroundLightMedia (Single)
backgroundDarkMedia (Single)
sideImageMedia (Single)

3️⃣ Animated Icons (Optional)

FieldType
icon1Media (Single)
icon2Media (Single)
icon3Media (Single)

4️⃣ Control Fields (احترافي جدًا)

FieldType
locationEnumeration (homepage, offers, category)
orderInteger
isActiveBoolean
startDateDateTime (اختياري)
endDateDateTime (اختياري)

🧠 لماذا هذه الحقول مهمة؟

  • location → يمكن استخدام نفس البانر في أكثر من صفحة
  • order → ترتيب يدوي
  • isActive → تعطيل بدون حذف
  • startDate/endDate → جدولة زمنية

🔥 هذا مستوى احترافي جدًا.


🌐 API الاحترافي

/api/banners?filters[location][$eq]=homepage
&filters[isActive][$eq]=true
&sort=order:asc
&populate=*

🏗 ثانيًا: Redux Slice احترافي

📁 store/bannerSlice.js

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import api from "../api";export const getBanners = createAsyncThunk(
"banners/getBanners",
async (location = "homepage") => { const { data } = await api.get(
`/banners?filters[location][$eq]=${location}&filters[isActive][$eq]=true&sort=order:asc&populate=*`
); return data.data;
}
);const bannerSlice = createSlice({
name: "banners",
initialState: {
data: [],
isLoading: false,
error: null,
},
extraReducers: (builder) => {
builder
.addCase(getBanners.pending, (state) => {
state.isLoading = true;
})
.addCase(getBanners.fulfilled, (state, action) => {
state.data = action.payload;
state.isLoading = false;
})
.addCase(getBanners.rejected, (state, action) => {
state.error = action.error.message;
state.isLoading = false;
});
},
});export const bannersReducer = bannerSlice.reducer;

🏗 ثالثًا: HeroSlider Component احترافي

📁 components/HeroSlider.jsx

import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination, Autoplay, EffectFade } from "swiper/modules";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/effect-fade";import { useEffect, useContext } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getBanners } from "../store/bannerSlice";
import { ThemeContext } from "../context/ThemeContext";
import { Link } from "react-router-dom";function HeroSlider() { const dispatch = useDispatch();
const { data: banners } = useSelector(state => state.banners);
const { theme } = useContext(ThemeContext); useEffect(() => {
dispatch(getBanners("homepage"));
}, [dispatch]); return (
<div className="hero-wrapper"> <Swiper
modules={[Navigation, Pagination, Autoplay, EffectFade]}
navigation
pagination={{ clickable: true }}
autoplay={{ delay: 5000 }}
effect="fade"
loop
> {banners.map(banner => { const bgImage =
theme === "dark"
? banner.attributes.backgroundDark?.data?.attributes?.url
: banner.attributes.backgroundLight?.data?.attributes?.url; return (
<SwiperSlide key={banner.id}> <div
className="hero-slide"
style={{
backgroundImage: `url(${import.meta.env.VITE_API_URL}${bgImage})`
}}
> <div className="container">
<div className="row align-items-center"> <div className="col-lg-6 hero-text"> <h5 className="subtitle">
{banner.attributes.subtitle}
</h5> <h1 className="title">
{banner.attributes.title}
</h1> <p className="description">
{banner.attributes.description}
</p> {banner.attributes.buttonText && (
<Link
to={banner.attributes.buttonLink}
className="btn btn-primary mt-3"
>
{banner.attributes.buttonText}
</Link>
)} </div> <div className="col-lg-6 text-center">
<img
src={`${import.meta.env.VITE_API_URL}${banner.attributes.sideImage?.data?.attributes?.url}`}
className="img-fluid side-image"
alt=""
/>
</div> </div>
</div> </div> </SwiperSlide>
);
})} </Swiper> </div>
);
}export default HeroSlider;

🎨 CSS احترافي

.hero-wrapper {
height: 85vh;
}.hero-slide {
height: 85vh;
background-size: cover;
background-position: center;
display: flex;
align-items: center;
position: relative;
}.hero-text {
color: var(--bs-body-color);
}.title {
font-size: 3rem;
font-weight: 700;
}.subtitle {
color: var(--bs-primary);
}.side-image {
animation: float 4s ease-in-out infinite;
}@keyframes float {
0% { transform: translateY(0); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0); }
}

🧠 لماذا هذا النظام احترافي؟

✔ Collection مستقل
✔ قابل للاستخدام في أي صفحة
✔ يدعم Dark/Light
✔ يدعم جدولة مستقبلًا
✔ ترتيب يدوي
✔ قابل للتوسع
✔ API نظيف
✔ متوافق مع Redux


🚀 مستوى أعلى (اختياري)

يمكننا الآن إضافة:

  • ✨ Text animation عند تغير slide
  • 🌈 Gradient overlay ديناميكي
  • 🧠 جدولة startDate / endDate من React
  • 🔥 Parallax
  • 🎥 Video banner