Merge branch 'main' of http://45.93.137.91:3000/Rahaf/REXNT
All checks were successful
Build frontend / build (push) Successful in 29s

This commit is contained in:
2026-01-13 20:09:51 +03:00
8 changed files with 203 additions and 63 deletions

View File

@ -0,0 +1,32 @@
name: Build frontend
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v2
with:
repository: Rahaf/REXNT
github-server-url: http://45.93.137.91:3000
- name: Use Node.js 22.13.1
uses: actions/setup-node@v1
with:
node-version: 24.x
- name: Install dependencies
run: npm install
- name: Generate build
run: npm run build
- name: Copy to Nginx root
run: |
sudo rm -rf /var/www/html/*
sudo cp -r dist/* /var/www/html/
sudo chown -R www-data:www-data /var/www/html/
sudo systemctl reload nginx

Binary file not shown.

View File

@ -1,12 +1,16 @@
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useState, useRef } from "react";
import styled from 'styled-components'; import styled from "styled-components";
import { useTranslation } from 'react-i18next'; import { useTranslation } from "react-i18next";
import REXNT from "../../../assets/Images/REXNT.png";
import TPSlogo from "../../../assets/Images/TPS-logo.png";
import NSC from "../../../assets/Images/NSC.png";
import LOGO from "../../../assets/Images/LOGO.png";
export default function EngineeringHeroFlowbite() { export default function EngineeringHeroFlowbite() {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const defaultConfig = { const defaultConfig = {
main_title: "عندما تطلب الرؤية مستشارًا, ويحتاج المخطط منفذًا استراتيجيًا…نكون القرار بالقيادة من الرؤية حتى التسليم", main_title:
"عندما تطلب الرؤية مستشارًا, ويحتاج المخطط منفذًا استراتيجيًا…نكون القرار بالقيادة من الرؤية حتى التسليم",
subtitle: subtitle:
"حلول متكاملة تشمل التصميم، التنفيذ، التشغيل، والصيانة\nللمشاريع الصناعية والمدنية، وفق أحدث المعايير والتقنيات", "حلول متكاملة تشمل التصميم، التنفيذ، التشغيل، والصيانة\nللمشاريع الصناعية والمدنية، وفق أحدث المعايير والتقنيات",
primary_color: "#e67e22", primary_color: "#e67e22",
@ -20,8 +24,8 @@ export default function EngineeringHeroFlowbite() {
const [config, setConfig] = useState({ const [config, setConfig] = useState({
...defaultConfig, ...defaultConfig,
main_title: t ? t('hero.main_title') : defaultConfig.main_title, main_title: t ? t("hero.main_title") : defaultConfig.main_title,
subtitle: t ? t('hero.subtitle') : defaultConfig.subtitle, subtitle: t ? t("hero.subtitle") : defaultConfig.subtitle,
}); });
const [isMounted, setIsMounted] = useState(false); const [isMounted, setIsMounted] = useState(false);
@ -34,7 +38,8 @@ export default function EngineeringHeroFlowbite() {
const link = document.createElement("link"); const link = document.createElement("link");
link.id = id; link.id = id;
link.rel = "stylesheet"; link.rel = "stylesheet";
link.href = "https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800&display=swap"; link.href =
"https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800&display=swap";
document.head.appendChild(link); document.head.appendChild(link);
} }
}, []); }, []);
@ -46,23 +51,23 @@ export default function EngineeringHeroFlowbite() {
useEffect(() => { useEffect(() => {
const updateFromI18n = () => { const updateFromI18n = () => {
const lang = i18n.language || 'en'; const lang = i18n.language || "en";
let main = t('hero.main_title'); let main = t("hero.main_title");
if (lang && lang.startsWith('en')) { if (lang && lang.startsWith("en")) {
main = main.replace(/[—–]/g, ' - '); main = main.replace(/[—–]/g, " - ");
} }
setConfig((c) => ({ setConfig((c) => ({
...c, ...c,
main_title: main, main_title: main,
subtitle: t('hero.subtitle'), subtitle: t("hero.subtitle"),
})); }));
document.documentElement.lang = lang; document.documentElement.lang = lang;
document.documentElement.dir = lang.startsWith('ar') ? 'rtl' : 'ltr'; document.documentElement.dir = lang.startsWith("ar") ? "rtl" : "ltr";
}; };
updateFromI18n(); updateFromI18n();
i18n.on && i18n.on('languageChanged', updateFromI18n); i18n.on && i18n.on("languageChanged", updateFromI18n);
return () => { return () => {
i18n.off && i18n.off('languageChanged', updateFromI18n); i18n.off && i18n.off("languageChanged", updateFromI18n);
}; };
}, [i18n, t]); }, [i18n, t]);
@ -70,23 +75,34 @@ export default function EngineeringHeroFlowbite() {
const main = mainTitleRef.current; const main = mainTitleRef.current;
const sub = subtitleRef.current; const sub = subtitleRef.current;
const baseFontStack = "Arial, sans-serif"; const baseFontStack = "Arial, sans-serif";
const font = (config.font_family || defaultConfig.font_family) + ", " + baseFontStack; const font =
(config.font_family || defaultConfig.font_family) + ", " + baseFontStack;
const baseSize = config.font_size || defaultConfig.font_size; const baseSize = config.font_size || defaultConfig.font_size;
const applyStyles = () => { const applyStyles = () => {
const lang = i18n.language || 'en'; const lang = i18n.language || "en";
const isArabic = lang.startsWith('ar'); const isArabic = lang.startsWith("ar");
const width = typeof window !== "undefined" ? window.innerWidth : 1024; const width = typeof window !== "undefined" ? window.innerWidth : 1024;
const responsiveBase = width <= 400 ? baseSize * 0.78 : width <= 640 ? baseSize * 0.88 : width <= 1024 ? baseSize * 0.96 : baseSize; const responsiveBase =
width <= 400
? baseSize * 0.78
: width <= 640
? baseSize * 0.88
: width <= 1024
? baseSize * 0.96
: baseSize;
const headingMultiplier = width <= 640 ? 2.6 : 3.2; const headingMultiplier = width <= 640 ? 2.6 : 3.2;
const subtitleMultiplier = width <= 640 ? 0.95 : 1.1; const subtitleMultiplier = width <= 640 ? 0.95 : 1.1;
const mainText = (config.main_title || defaultConfig.main_title).replace(/\s+/g, " ").trim(); const mainText = (config.main_title || defaultConfig.main_title)
.replace(/\s+/g, " ")
.trim();
const mainTextLength = mainText.length; const mainTextLength = mainText.length;
const lengthFactor = mainTextLength > 100 ? 0.78 : mainTextLength > 70 ? 0.86 : 1.0; const lengthFactor =
mainTextLength > 100 ? 0.78 : mainTextLength > 70 ? 0.86 : 1.0;
const maxFont = 68; const maxFont = 68;
const minFont = Math.max( Math.round(responsiveBase * 1.6), 14 ); const minFont = Math.max(Math.round(responsiveBase * 1.6), 14);
const root = document.documentElement; const root = document.documentElement;
root.style.setProperty('--base', `${responsiveBase}px`); root.style.setProperty("--base", `${responsiveBase}px`);
if (main) { if (main) {
const headingText = (config.main_title || defaultConfig.main_title) const headingText = (config.main_title || defaultConfig.main_title)
@ -96,12 +112,20 @@ export default function EngineeringHeroFlowbite() {
if (headingText.length === 1) { if (headingText.length === 1) {
main.textContent = headingText[0]; main.textContent = headingText[0];
} else { } else {
main.innerHTML = `<span style="display:block;line-height:1.02">${headingText[0]}</span><span style="display:block;font-weight:700;opacity:0.95;margin-top:6px">${headingText main.innerHTML = `<span style="display:block;line-height:1.02">${
headingText[0]
}</span><span style="display:block;font-weight:700;opacity:0.95;margin-top:6px">${headingText
.slice(1) .slice(1)
.join("<br/>")}</span>`; .join("<br/>")}</span>`;
} }
main.style.fontFamily = font; main.style.fontFamily = font;
const computedSize = Math.min(Math.max(Math.round(responsiveBase * headingMultiplier * lengthFactor), minFont), maxFont); const computedSize = Math.min(
Math.max(
Math.round(responsiveBase * headingMultiplier * lengthFactor),
minFont
),
maxFont
);
main.style.fontSize = `${computedSize}px`; main.style.fontSize = `${computedSize}px`;
main.style.color = config.text_color || defaultConfig.text_color; main.style.color = config.text_color || defaultConfig.text_color;
main.style.fontWeight = 800; main.style.fontWeight = 800;
@ -131,10 +155,22 @@ export default function EngineeringHeroFlowbite() {
sub.style.display = "block"; sub.style.display = "block";
} }
root.style.setProperty("--ehb-primary", config.primary_color || defaultConfig.primary_color); root.style.setProperty(
root.style.setProperty("--ehb-background", config.background_color || defaultConfig.background_color); "--ehb-primary",
root.style.setProperty("--ehb-surface", config.secondary_surface || defaultConfig.secondary_surface); config.primary_color || defaultConfig.primary_color
root.style.setProperty("--ehb-action", config.secondary_action || defaultConfig.secondary_action); );
root.style.setProperty(
"--ehb-background",
config.background_color || defaultConfig.background_color
);
root.style.setProperty(
"--ehb-surface",
config.secondary_surface || defaultConfig.secondary_surface
);
root.style.setProperty(
"--ehb-action",
config.secondary_action || defaultConfig.secondary_action
);
}; };
applyStyles(); applyStyles();
@ -155,20 +191,37 @@ export default function EngineeringHeroFlowbite() {
const OrangeActionButton = ({ onClick }) => { const OrangeActionButton = ({ onClick }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<button className="button" onClick={onClick} aria-label={t('hero.action')}> <button
{t('hero.action')} className="button"
<svg className="icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden> onClick={onClick}
<path fillRule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm4.28 10.28a.75.75 0 000-1.06l-3-3a.75.75 0 10-1.06 1.06l1.72 1.72H8.25a.75.75 0 000 1.5h5.69l-1.72 1.72a.75.75 0 101.06 1.06l3-3z" clipRule="evenodd" /> aria-label={t("hero.action")}
>
{t("hero.action")}
<svg
className="icon"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden
>
<path
fillRule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm4.28 10.28a.75.75 0 000-1.06l-3-3a.75.75 0 10-1.06 1.06l1.72 1.72H8.25a.75.75 0 000 1.5h5.69l-1.72 1.72a.75.75 0 101.06 1.06l3-3z"
clipRule="evenodd"
/>
</svg> </svg>
</button> </button>
</StyledWrapper> </StyledWrapper>
); );
}; };
const isArabic = i18n.language && i18n.language.startsWith('ar'); const isArabic = i18n.language && i18n.language.startsWith("ar");
return ( return (
<div dir={isArabic ? "rtl" : "ltr"} className="h-screen w-full overflow-hidden content-container" style={{ background: "transparent" }}> <div
dir={isArabic ? "rtl" : "ltr"}
className="h-screen w-full overflow-hidden content-container"
style={{ background: "transparent" }}
>
<style>{` <style>{`
:root { --ehb-primary: #e67e22; --ehb-background: #000000; --ehb-surface: #95a5a6; --ehb-action: #34495e; --base: 16px } :root { --ehb-primary: #e67e22; --ehb-background: #000000; --ehb-surface: #95a5a6; --ehb-action: #34495e; --base: 16px }
@ -328,25 +381,57 @@ export default function EngineeringHeroFlowbite() {
<div className={`hero-section ${isMounted ? "is-mounted" : ""}`}> <div className={`hero-section ${isMounted ? "is-mounted" : ""}`}>
<div className="hero-overlay" /> <div className="hero-overlay" />
<div className={`hero-layout ${isArabic ? '' : 'layout-ltr'}`}> <div className={`hero-layout ${isArabic ? "" : "layout-ltr"}`}>
<div className="hero-left"> <div className="hero-left">
<img src="src/assets/REXNT.png" alt="REXNT" /> <img src={REXNT} alt="REXNT" />
<div className="partner-logos" aria-hidden> <div className="partner-logos" aria-hidden>
<div className="partner-bubble" style={{ animationDelay: "0ms" }} aria-label="شريك TPS"> <div
<img src="src/assets/TPS-logo.png" alt="TPS" /> className="partner-bubble"
style={{ animationDelay: "0ms" }}
aria-label="شريك TPS"
>
<img src={TPSlogo} alt="TPS" />
</div> </div>
<div className="partner-bubble" style={{ animationDelay: "180ms" }} aria-label="شريك NSC"> <div
<img src="src/assets/NSC.png" alt="NSC" /> className="partner-bubble"
style={{ animationDelay: "180ms" }}
aria-label="شريك NSC"
>
<img src={NSC} alt="NSC" />
</div> </div>
<div className="partner-bubble" style={{ animationDelay: "360ms" }} aria-label="شريك LOGO"> <div
<img src="src/assets/LOGO.png" alt="LOGO" /> className="partner-bubble"
style={{ animationDelay: "360ms" }}
aria-label="شريك LOGO"
>
<img src={LOGO} alt="LOGO" />
</div> </div>
</div> </div>
</div> </div>
<div className={`hero-content ${isArabic ? 'content-rtl' : 'content-ltr'}`}> <div
<h1 ref={mainTitleRef} className="hero-title text-white" style={{ fontWeight: 800, marginBottom: 16, textShadow: "2px 2px 10px rgba(0,0,0,0.6)" }} /> className={`hero-content ${
<p ref={subtitleRef} className="hero-subtitle text-white" style={{ maxWidth: 800, textShadow: "1px 1px 6px rgba(0,0,0,0.45)", lineHeight: 1.6 }} /> isArabic ? "content-rtl" : "content-ltr"
}`}
>
<h1
ref={mainTitleRef}
className="hero-title text-white"
style={{
fontWeight: 800,
marginBottom: 16,
textShadow: "2px 2px 10px rgba(0,0,0,0.6)",
}}
/>
<p
ref={subtitleRef}
className="hero-subtitle text-white"
style={{
maxWidth: 800,
textShadow: "1px 1px 6px rgba(0,0,0,0.45)",
lineHeight: 1.6,
}}
/>
<div className="action-wrapper" style={{ marginTop: 18 }}> <div className="action-wrapper" style={{ marginTop: 18 }}>
<OrangeActionButton onClick={goToDepartments} /> <OrangeActionButton onClick={goToDepartments} />
</div> </div>
@ -379,11 +464,22 @@ const StyledWrapper = styled.div`
cursor: pointer; cursor: pointer;
} }
.icon { width: 24px; height: 24px; transition: all 0.3s ease-in-out; } .icon {
width: 24px;
height: 24px;
transition: all 0.3s ease-in-out;
}
.button:hover { transform: scale(1.05); border-color: #fff9; } .button:hover {
.button:hover .icon { transform: translate(4px); } transform: scale(1.05);
.button:hover::before { animation: shine 1.5s ease-out infinite; } border-color: #fff9;
}
.button:hover .icon {
transform: translate(4px);
}
.button:hover::before {
animation: shine 1.5s ease-out infinite;
}
.button::before { .button::before {
content: ""; content: "";
@ -402,8 +498,14 @@ const StyledWrapper = styled.div`
} }
@keyframes shine { @keyframes shine {
0% { left: -100px; } 0% {
60% { left: 100%; } left: -100px;
to { left: 100%; } }
60% {
left: 100%;
}
to {
left: 100%;
}
} }
`; `;

BIN
src/assets/Images/LOGO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
src/assets/Images/NSC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
src/assets/Images/REXNT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@ -1,19 +1,25 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
css: { css: {
postcss: './postcss.config.js', postcss: "./postcss.config.js",
}, },
build: { build: {
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {
'react-vendor': ['react', 'react-dom'], "react-vendor": ["react", "react-dom"],
'animation-vendor': ['three', 'styled-components', 'framer-motion'], "animation-vendor": ["three", "styled-components", "framer-motion"],
} },
} assetFileNames: ({ name }) => {
} if (name && name.match(/\.(png|jpe?g|svg|gif|webp)$/)) {
} return "assets/images/[name][extname]";
}) }
return "assets/[name]-[hash][extname]";
},
},
},
},
});