From 282019870d1ca300c6ee90970a7f91a0ed86e322 Mon Sep 17 00:00:00 2001 From: krizzn65 Date: Mon, 16 Feb 2026 11:53:56 +0700 Subject: [PATCH] perbaikan header --- frontend/package-lock.json | 153 ++++++++++- frontend/package.json | 2 + frontend/src/components/Hero.jsx | 110 ++++---- frontend/src/components/Navbar.jsx | 80 +++--- frontend/src/components/button.jsx | 53 ++++ frontend/src/components/sky-toggle.jsx | 251 ++++++++++++++++++ .../src/components/ui/expandable-tabs.jsx | 20 +- frontend/src/index.css | 65 +++++ frontend/src/pages/LandingPage.jsx | 4 +- 9 files changed, 629 insertions(+), 109 deletions(-) create mode 100644 frontend/src/components/button.jsx create mode 100644 frontend/src/components/sky-toggle.jsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9dc8f8f..78a876b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@iconify/react": "^6.0.2", "axios": "^1.13.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -17,6 +18,7 @@ "react-dom": "^18.2.0", "react-icons": "^5.5.0", "react-router-dom": "^6.8.1", + "styled-components": "^6.3.9", "tailwind-merge": "^3.4.1", "tailwindcss": "^3.4.19", "tailwindcss-animate": "^1.0.7", @@ -347,6 +349,12 @@ "license": "MIT", "optional": true }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -981,6 +989,27 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify/react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-6.0.2.tgz", + "integrity": "sha512-SMmC2sactfpJD427WJEDN6PMyznTFMhByK9yLW0gOTtnjzzbsi/Ke/XqsumsavFPwNiXs8jSiYeZTmLCLwO+Fg==", + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1501,6 +1530,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/stylis": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", + "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", @@ -1794,6 +1829,15 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001769", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", @@ -1959,6 +2003,26 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1975,7 +2039,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -3750,6 +3813,12 @@ "semver": "bin/semver.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3795,6 +3864,88 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.9.tgz", + "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@emotion/unitless": "0.10.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.6", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index c4aeb24..6053edc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@iconify/react": "^6.0.2", "axios": "^1.13.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -19,6 +20,7 @@ "react-dom": "^18.2.0", "react-icons": "^5.5.0", "react-router-dom": "^6.8.1", + "styled-components": "^6.3.9", "tailwind-merge": "^3.4.1", "tailwindcss": "^3.4.19", "tailwindcss-animate": "^1.0.7", diff --git a/frontend/src/components/Hero.jsx b/frontend/src/components/Hero.jsx index 0194702..4593712 100644 --- a/frontend/src/components/Hero.jsx +++ b/frontend/src/components/Hero.jsx @@ -1,9 +1,12 @@ import React from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; +import { Component as ReportButton } from './button'; +import { Icon } from '@iconify/react'; import { motion } from 'framer-motion'; import { fadeIn, slideUp, slideLeft, slideRight } from '../utils/motionVariants'; const Hero = ({ heroData }) => { + const navigate = useNavigate(); const defaultHero = { title: 'Aman Bicara, Aman Melapor', subtitle: 'Satgas PPKPT Politeknik Negeri Jember', @@ -46,11 +49,11 @@ const Hero = ({ heroData }) => { /> -
-
+
+
{/* Left Content */} { transition={{ delay: 0.2 }} > { { - {hero.description} + {hero.description ? ( + hero.description + ) : ( + <> + Kami siap mendengar dan membantu Anda dengan{' '} + profesionalisme dan{' '} + kerahasiaan terjamin.{' '} + Setiap laporan akan ditangani dengan{' '} + empati dan{' '} + seksama. + + )} - { whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > - Butuh Bantuan Darurat - + } + title="Butuh Bantuan Darurat" + size="sm" + className="rounded-full bg-red-600 hover:bg-red-700 border-0" + gradientLight={{ from: "from-red-600", via: "via-red-600", to: "to-red-600" }} + gradientDark={{ from: "from-red-600", via: "via-red-600", to: "to-red-600" }} + onClick={() => window.open('https://wa.me/6281234567890', '_blank')} + /> + { whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > - - Buat Laporan - + } + title="Buat Laporan" + size="sm" + className="rounded-full bg-[#191970] hover:bg-blue-900 border-0" + gradientLight={{ from: "from-[#191970]", via: "via-[#191970]", to: "to-[#191970]" }} + gradientDark={{ from: "from-[#191970]", via: "via-[#191970]", to: "to-[#191970]" }} + onClick={() => navigate('/artikel')} + /> {/* Trust Indicators */} - -
-
- 100% Rahasia -
-
-
- Profesional -
- -
-
- 24/7 Support -
-
{/* Right Content - Logo & Branding */} @@ -173,7 +173,7 @@ const Hero = ({ heroData }) => { header gambar @@ -181,26 +181,12 @@ const Hero = ({ heroData }) => {
+ + {/* Brand Stats Banner - Glassmorphic Light Design */} +
- {/* Scroll Indicator */} - - - Scroll ke bawah -
-
-
-
-
+ ); }; diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 2f1cc05..0d1c33c 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -5,7 +5,7 @@ import { Link, useNavigate, useLocation } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { useAuth } from '../hooks/useAuth'; import { fadeIn, slideDown } from '../utils/motionVariants'; -import ThemeToggle from './ThemeToggle'; +import Switch from './sky-toggle'; import LoginModal from './LoginModal'; @@ -47,33 +47,39 @@ const Navbar = () => { useEffect(() => { + let ticking = false; const handleScroll = () => { - // Logic for switching navbar type (Standard vs Expandable) - // Switch when approaching 'about' section or scrolled past 1st screen - const aboutSection = document.getElementById('about'); - const threshold = aboutSection ? aboutSection.offsetTop - 400 : window.innerHeight - 200; - setIsScrolled(window.scrollY > threshold); + if (!ticking) { + requestAnimationFrame(() => { + // Logic for switching navbar type (Standard vs Expandable) + const aboutSection = document.getElementById('about'); + const threshold = aboutSection ? aboutSection.offsetTop - 400 : window.innerHeight - 200; + setIsScrolled(window.scrollY > threshold); - // ScrollSpy Logic - const sections = navLinks.map(link => link.href.substring(1)); - let currentSection = ""; + // ScrollSpy Logic + const sections = navLinks.map(link => link.href.substring(1)); + let currentSection = ""; - for (const section of sections) { - const element = document.getElementById(section); - if (element) { - const rect = element.getBoundingClientRect(); - if (rect.top <= 150 && rect.bottom >= 150) { - currentSection = "#" + section; + for (const section of sections) { + const element = document.getElementById(section); + if (element) { + const rect = element.getBoundingClientRect(); + if (rect.top <= 150 && rect.bottom >= 150) { + currentSection = "#" + section; + } + } } - } - } - if (currentSection && currentSection !== activeLink) { - setActiveLink(currentSection); + if (currentSection && currentSection !== activeLink) { + setActiveLink(currentSection); + } + ticking = false; + }); + ticking = true; } }; - window.addEventListener('scroll', handleScroll); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); }, [navLinks, activeLink]); @@ -103,15 +109,15 @@ const Navbar = () => { return ( <> - + {isScrolled ? ( { }} trailingElement={
- + {isAuthenticated ? ( <> @@ -144,7 +150,7 @@ const Navbar = () => { ) : ( @@ -156,11 +162,11 @@ const Navbar = () => { ) : (
@@ -199,7 +205,7 @@ const Navbar = () => { {/* Right Section - Auth & Theme */}
- + {isAuthenticated ? ( <> @@ -219,7 +225,7 @@ const Navbar = () => { ) : ( diff --git a/frontend/src/components/button.jsx b/frontend/src/components/button.jsx new file mode 100644 index 0000000..a29ee8e --- /dev/null +++ b/frontend/src/components/button.jsx @@ -0,0 +1,53 @@ +import React from "react"; + +export const Component = ({ + icon, + title, + subtitle, + size = "md", + className = "", + ...props +}) => { + const sizes = { + sm: "px-10 py-4 rounded-full text-base", + md: "p-4 rounded-2xl", + lg: "p-6 rounded-3xl", + }; + + return ( + + ); +}; diff --git a/frontend/src/components/sky-toggle.jsx b/frontend/src/components/sky-toggle.jsx new file mode 100644 index 0000000..eb95dcb --- /dev/null +++ b/frontend/src/components/sky-toggle.jsx @@ -0,0 +1,251 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useTheme } from '../contexts/ThemeContext'; + +const Switch = () => { + const { toggleTheme, isDark } = useTheme(); + + const handleChange = () => { + toggleTheme(); + }; + + return ( + +