diff --git a/backend/src/db/migrations/20260129154500-grant-public-permissions.js b/backend/src/db/migrations/20260129154500-grant-public-permissions.js new file mode 100644 index 0000000..f0e5ec2 --- /dev/null +++ b/backend/src/db/migrations/20260129154500-grant-public-permissions.js @@ -0,0 +1,64 @@ +const { v4: uuid } = require("uuid"); + +module.exports = { + async up(queryInterface) { + const createdAt = new Date(); + const updatedAt = new Date(); + + const [roles] = await queryInterface.sequelize.query( + "SELECT id FROM roles WHERE name = 'Public' LIMIT 1" + ); + + if (!roles || roles.length === 0) return; + + const publicRoleId = roles[0].id; + + const entities = [ + "abouts", "sliders", "projects", "achievements", "news", "reels", "partners" + ]; + + const permissionsToGrant = []; + + for (const entity of entities) { + const [perms] = await queryInterface.sequelize.query( + `SELECT id FROM permissions WHERE name = 'READ_${entity.toUpperCase()}' LIMIT 1` + ); + if (perms && perms.length > 0) { + permissionsToGrant.push({ + createdAt, + updatedAt, + roles_permissionsId: publicRoleId, + permissionId: perms[0].id + }); + } + } + + // Grant CREATE_CONTACT_MESSAGES + const [contactPerms] = await queryInterface.sequelize.query( + "SELECT id FROM permissions WHERE name = 'CREATE_CONTACT_MESSAGES' LIMIT 1" + ); + if (contactPerms && contactPerms.length > 0) { + permissionsToGrant.push({ + createdAt, + updatedAt, + roles_permissionsId: publicRoleId, + permissionId: contactPerms[0].id + }); + } + + if (permissionsToGrant.length > 0) { + // Use a for loop to avoid bulkInsert issues with existing records + for (const item of permissionsToGrant) { + try { + await queryInterface.bulkInsert("rolesPermissionsPermissions", [item]); + } catch (e) { + // Ignore duplicates + } + } + } + }, + + async down(queryInterface) { + // Optional: implement down migration + } +}; diff --git a/backend/src/index.js b/backend/src/index.js index 4cc6289..440b2cb 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,4 +1,3 @@ - const express = require('express'); const cors = require('cors'); const app = express(); @@ -107,21 +106,21 @@ app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoute app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); -app.use('/api/abouts', passport.authenticate('jwt', {session: false}), aboutsRoutes); +app.use('/api/abouts', aboutsRoutes); -app.use('/api/sliders', passport.authenticate('jwt', {session: false}), slidersRoutes); +app.use('/api/sliders', slidersRoutes); -app.use('/api/projects', passport.authenticate('jwt', {session: false}), projectsRoutes); +app.use('/api/projects', projectsRoutes); -app.use('/api/achievements', passport.authenticate('jwt', {session: false}), achievementsRoutes); +app.use('/api/achievements', achievementsRoutes); -app.use('/api/news', passport.authenticate('jwt', {session: false}), newsRoutes); +app.use('/api/news', newsRoutes); -app.use('/api/reels', passport.authenticate('jwt', {session: false}), reelsRoutes); +app.use('/api/reels', reelsRoutes); -app.use('/api/partners', passport.authenticate('jwt', {session: false}), partnersRoutes); +app.use('/api/partners', partnersRoutes); -app.use('/api/contact_messages', passport.authenticate('jwt', {session: false}), contact_messagesRoutes); +app.use('/api/contact_messages', contact_messagesRoutes); app.use( '/api/openai', @@ -167,4 +166,4 @@ db.sequelize.sync().then(function () { }); }); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/frontend/src/components/Landing/AboutUs.tsx b/frontend/src/components/Landing/AboutUs.tsx new file mode 100644 index 0000000..9e39646 --- /dev/null +++ b/frontend/src/components/Landing/AboutUs.tsx @@ -0,0 +1,70 @@ +import React from 'react' +import BaseIcon from '../BaseIcon' +import { mdiEyeOutline, mdiFlagOutline, mdiHeartOutline } from '@mdi/js' + +const AboutUs = () => { + return ( +
+
+
+
+

+ من نحن +

+

+ مؤسسة دار الشفاء الطبية هي مؤسسة خيرية صحية اجتماعية غير ربحية، تأسست في محافظة حضرموت بمدينة سيئون. تسعى المؤسسة لتطوير القطاع الصحي وتقديم الخدمات الطبية النوعية للفئات المحتاجة، من خلال برامج ومشاريع مستدامة وشراكات فاعلة. +

+ +
+
+
+ +
+
+

رؤيتنا

+

الريادة في تقديم الخدمات الطبية والإنسانية المتميزة والمستدامة.

+
+
+ +
+
+ +
+
+

رسالتنا

+

تحسين الوضع الصحي للمجتمع من خلال برامج طبية وإنسانية بجودة عالية واحترافية.

+
+
+ +
+
+ +
+
+

قيمنا

+

الإخلاص، الشفافية، الإتقان، والعمل الجماعي.

+
+
+
+
+ +
+
+ Dar Al Shifa +
+
+
+15
+
عاماً من العطاء
+
+
+
+
+
+ ) +} + +export default AboutUs diff --git a/frontend/src/components/Landing/Achievements.tsx b/frontend/src/components/Landing/Achievements.tsx new file mode 100644 index 0000000..ea108df --- /dev/null +++ b/frontend/src/components/Landing/Achievements.tsx @@ -0,0 +1,31 @@ +import React from 'react' + +const stats = [ + { label: 'مستفيد سنوياً', value: '50,000+' }, + { label: 'عملية جراحية', value: '1,200+' }, + { label: 'مخيم طبي', value: '85' }, + { label: 'شريك نجاح', value: '40+' }, +] + +const Achievements = () => { + return ( +
+
+
+ {stats.map((stat, index) => ( +
+
+ {stat.value} +
+
+ {stat.label} +
+
+ ))} +
+
+
+ ) +} + +export default Achievements diff --git a/frontend/src/components/Landing/Contact.tsx b/frontend/src/components/Landing/Contact.tsx new file mode 100644 index 0000000..2c76677 --- /dev/null +++ b/frontend/src/components/Landing/Contact.tsx @@ -0,0 +1,160 @@ +import React, { useState } from 'react' +import { mdiPhone, mdiEmail, mdiMapMarker } from '@mdi/js' +import BaseIcon from '../BaseIcon' +import axios from 'axios' +import { toast } from 'react-toastify' + +const Contact = () => { + const [formData, setFormData] = useState({ + name: '', + email: '', + subject: '', + message: '', + }) + const [loading, setLoading] = useState(false) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + try { + await axios.post('/contact_messages', { data: formData }) + toast.success('تم إرسال رسالتك بنجاح!') + setFormData({ name: '', email: '', subject: '', message: '' }) + } catch (error) { + console.error(error) + toast.error('حدث خطأ أثناء إرسال الرسالة. يرجى المحاولة مرة أخرى.') + } finally { + setLoading(false) + } + } + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ ...formData, [e.target.name]: e.target.value }) + } + + return ( +
+
+

+ تواصل معنا +

+ +
+ {/* Contact Info */} +
+
+

معلومات التواصل

+ +
+
+ +
+
+

العنوان

+

حضرموت - سيئون - الشارع العام

+
+
+ +
+
+ +
+
+

الهاتف

+

+967 5 440000

+
+
+ +
+
+ +
+
+

البريد الإلكتروني

+

info@daralshifa.org

+
+
+
+ + {/* Google Maps Placeholder */} +
+ +
+
+ + {/* Contact Form */} +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+
+
+
+
+ ) +} + +export default Contact diff --git a/frontend/src/components/Landing/Footer.tsx b/frontend/src/components/Landing/Footer.tsx new file mode 100644 index 0000000..dea29dd --- /dev/null +++ b/frontend/src/components/Landing/Footer.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import Link from 'next/link' + +const Footer = () => { + return ( + + ) +} + +export default Footer diff --git a/frontend/src/components/Landing/Header.tsx b/frontend/src/components/Landing/Header.tsx new file mode 100644 index 0000000..b457d72 --- /dev/null +++ b/frontend/src/components/Landing/Header.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react' +import Link from 'next/link' +import { mdiMenu, mdiClose } from '@mdi/js' +import BaseIcon from '../BaseIcon' + +const Header = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false) + + const menuItems = [ + { label: 'الرئيسية', href: '/' }, + { label: 'من نحن', href: '#about' }, + { label: 'المشاريع', href: '#projects' }, + { label: 'الأخبار', href: '#news' }, + { label: 'تواصل معنا', href: '#contact' }, + ] + + return ( + + ) +} + +export default Header diff --git a/frontend/src/components/Landing/Hero.tsx b/frontend/src/components/Landing/Hero.tsx new file mode 100644 index 0000000..b8e2764 --- /dev/null +++ b/frontend/src/components/Landing/Hero.tsx @@ -0,0 +1,88 @@ +import React, { useState, useEffect } from 'react' +import BaseButton from '../BaseButton' + +const slides = [ + { + image: 'https://images.pexels.com/photos/263402/pexels-photo-263402.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + title: 'رعاية طبية متميزة لمجتمعنا', + description: 'نحن في مؤسسة دار الشفاء نسعى لتقديم أفضل الخدمات الطبية والإنسانية لأبناء محافظة حضرموت.', + }, + { + image: 'https://images.pexels.com/photos/236380/pexels-photo-236380.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + title: 'برامج صحية رائدة', + description: 'ننفذ مشاريع طبية نوعية تهدف إلى تحسين جودة الحياة وتوفير العلاج للمحتاجين.', + }, + { + image: 'https://images.pexels.com/photos/127873/pexels-photo-127873.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + title: 'شراكات من أجل الخير', + description: 'نعمل جنباً إلى جنب مع شركاء النجاح لتحقيق رؤيتنا في بناء مجتمع صحي ومعافى.', + }, +] + +const Hero = () => { + const [currentSlide, setCurrentSlide] = useState(0) + + useEffect(() => { + const timer = setInterval(() => { + setCurrentSlide((prev) => (prev + 1) % slides.length) + }, 5000) + return () => clearInterval(timer) + }, []) + + return ( +
+ {slides.map((slide, index) => ( +
+
+
+
+
+
+

+ {slide.title} +

+

+ {slide.description} +

+
+ + +
+
+
+
+ ))} +
+ {slides.map((_, index) => ( +
+
+ ) +} + +export default Hero diff --git a/frontend/src/components/Landing/News.tsx b/frontend/src/components/Landing/News.tsx new file mode 100644 index 0000000..c432055 --- /dev/null +++ b/frontend/src/components/Landing/News.tsx @@ -0,0 +1,67 @@ +import React from 'react' + +const newsItems = [ + { + date: '25 يناير 2026', + title: 'اختتام القافلة الطبية الجراحية في وادي حضرموت', + image: 'https://images.pexels.com/photos/1170979/pexels-photo-1170979.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + }, + { + date: '18 يناير 2026', + title: 'توقيع اتفاقية تعاون مع مستشفى سيئون العام', + image: 'https://images.pexels.com/photos/356040/pexels-photo-356040.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + }, + { + date: '10 يناير 2026', + title: 'توزيع مستلزمات طبية للمراكز الصحية الريفية', + image: 'https://images.pexels.com/photos/3184418/pexels-photo-3184418.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + }, +] + +const News = () => { + return ( +
+
+
+
+

+ آخر الأخبار +

+

تابع أحدث أنشطة وفعاليات المؤسسة

+
+ +
+ +
+ {newsItems.map((item, index) => ( +
+
+ {item.title} +
+ {item.date} +
+
+ +
+ ))} +
+
+
+ ) +} + +export default News diff --git a/frontend/src/components/Landing/Partners.tsx b/frontend/src/components/Landing/Partners.tsx new file mode 100644 index 0000000..f71be24 --- /dev/null +++ b/frontend/src/components/Landing/Partners.tsx @@ -0,0 +1,45 @@ +import React from 'react' + +const partners = [ + 'شركة النفط اليمنية', + 'مجموعة هائل سعيد أنعم', + 'منظمة الصحة العالمية', + 'اليونيسيف', + 'مركز الملك سلمان', + 'الهلال الأحمر الإماراتي', + 'مؤسسة صلة للتنمية', + 'مؤسسة العون للتنمية', +] + +const Partners = () => { + return ( +
+
+

شركاء النجاح

+
+ {[...partners, ...partners].map((partner, index) => ( +
+ {partner} +
+ ))} +
+
+ +
+ ) +} + +export default Partners diff --git a/frontend/src/components/Landing/Projects.tsx b/frontend/src/components/Landing/Projects.tsx new file mode 100644 index 0000000..639bdce --- /dev/null +++ b/frontend/src/components/Landing/Projects.tsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react' +import { mdiChevronRight, mdiChevronLeft } from '@mdi/js' +import BaseIcon from '../BaseIcon' + +const projects = [ + { + title: 'برنامج العمليات الجراحية', + description: 'تنفيذ عمليات جراحية نوعية للمرضى المعسرين في تخصصات مختلفة تشمل الجراحة العامة وجراحة العظام والعيون.', + image: 'https://images.pexels.com/photos/2324449/pexels-photo-2324449.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + }, + { + title: 'توزيع الأدوية والمستلزمات الطبية', + description: 'توفير الأدوية الضرورية والمزمنة للمرضى المحتاجين عبر صيدلية المؤسسة الخيرية والمخيمات الطبية.', + image: 'https://images.pexels.com/photos/3652103/pexels-photo-3652103.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + }, + { + title: 'العيادات التخصصية المجانية', + description: 'استضافة أطباء استشاريين في مختلف التخصصات لتقديم الاستشارات الطبية والفحوصات المجانية.', + image: 'https://images.pexels.com/photos/4021775/pexels-photo-4021775.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1', + }, +] + +const Projects = () => { + const [currentIndex, setCurrentIndex] = useState(0) + + const next = () => setCurrentIndex((prev) => (prev + 1) % projects.length) + const prev = () => setCurrentIndex((prev) => (prev - 1 + projects.length) % projects.length) + + return ( +
+
+

+ مشاريعنا الرائدة +

+ +
+
+
+ مشروع متميز +

+ {projects[currentIndex].title} +

+

+ {projects[currentIndex].description} +

+
+ + +
+
+
+ {projects[currentIndex].title} +
+
+
+
+
+ ) +} + +export default Projects diff --git a/frontend/src/components/Landing/Reels.tsx b/frontend/src/components/Landing/Reels.tsx new file mode 100644 index 0000000..6fce0f5 --- /dev/null +++ b/frontend/src/components/Landing/Reels.tsx @@ -0,0 +1,44 @@ +import React from 'react' + +const reels = [ + { id: 1, thumbnail: 'https://images.pexels.com/photos/5214958/pexels-photo-5214958.jpeg?auto=compress&cs=tinysrgb&w=400&h=600&dpr=1', title: 'عمليات العيون' }, + { id: 2, thumbnail: 'https://images.pexels.com/photos/5215024/pexels-photo-5215024.jpeg?auto=compress&cs=tinysrgb&w=400&h=600&dpr=1', title: 'مخيم الأمل' }, + { id: 3, thumbnail: 'https://images.pexels.com/photos/5327656/pexels-photo-5327656.jpeg?auto=compress&cs=tinysrgb&w=400&h=600&dpr=1', title: 'توزيع الأدوية' }, + { id: 4, thumbnail: 'https://images.pexels.com/photos/5215016/pexels-photo-5215016.jpeg?auto=compress&cs=tinysrgb&w=400&h=600&dpr=1', title: 'خدمات المختبر' }, +] + +const Reels = () => { + return ( +
+
+

+ أنشطتنا في فيديو +

+ +
+ {reels.map((reel) => ( +
+ {reel.title} +
+
+ {reel.title} +
+
+
+
+
+
+
+
+ ))} +
+
+
+ ) +} + +export default Reels diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 6548433..9870a06 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index a774203..9bf8357 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -149,7 +149,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { setStepsEnabled(false); }; - const title = 'App Draft' + const title = 'Dar Al-Shifa Medical Foundation' const description = "Professional responsive website for Dar Al-Shifa Medical Foundation with updated content and modern design." const url = "https://flatlogic.com/" const image = "https://project-screens.s3.amazonaws.com/screenshots/37946/app-hero-20260129-153829.png" @@ -180,6 +180,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { + + + @@ -198,4 +201,4 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ) } -export default appWithTranslation(MyApp); +export default appWithTranslation(MyApp); \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index d27d82e..008afed 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,46 @@ +import React from 'react' +import type { ReactElement } from 'react' +import Head from 'next/head' +import LayoutGuest from '../layouts/Guest' +import Header from '../components/Landing/Header' +import Hero from '../components/Landing/Hero' +import AboutUs from '../components/Landing/AboutUs' +import Projects from '../components/Landing/Projects' +import Achievements from '../components/Landing/Achievements' +import News from '../components/Landing/News' +import Reels from '../components/Landing/Reels' +import Partners from '../components/Landing/Partners' +import Contact from '../components/Landing/Contact' +import Footer from '../components/Landing/Footer' -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; -import Head from 'next/head'; -import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; -import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; -import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; - - -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'App Draft' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } - }; - +export default function LandingPage() { return ( -
+
- {getPageTitle('Starter Page')} + مؤسسة دار الشفاء الطبية | حضرموت - سيئون + - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - - - -
+
+ +
+
{/* Spacer for fixed header */} + + + + + + + +
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+ +
- ); + ) } -Starter.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - +LandingPage.getLayout = function getLayout(page: ReactElement) { + return {page} +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 8ea29b7..2292888 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -52,6 +52,19 @@ module.exports = { green:{ text: '#45B26B', }, + medicalGreen: { + 50: '#f0fdf4', + 100: '#dcfce7', + 200: '#bbf7d0', + 300: '#86efac', + 400: '#4ade80', + 500: '#22c55e', + 600: '#16a34a', + 700: '#15803d', + 800: '#166534', + 900: '#14532d', + 950: '#052e16', + }, 'pavitra': { 'blue': '#0162FD', 'green': '#00B448', @@ -86,8 +99,8 @@ module.exports = { primaryText: '#FFFFFF', }, fontFamily: { - sans: ['Nunito Sans', 'sans-serif'], - + sans: ['Cairo', 'Nunito Sans', 'sans-serif'], + cairo: ['Cairo', 'sans-serif'], }, borderRadius: { '3xl': '2rem', @@ -128,4 +141,4 @@ module.exports = { ); }), ], -} +} \ No newline at end of file