diff --git a/wab/.eslintrc.cjs b/wab/.eslintrc.cjs new file mode 100644 index 000000000..d6c953795 --- /dev/null +++ b/wab/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/wab/.gitignore b/wab/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/wab/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/wab/137f588a-3ab4-43c8-8678-8b5ee0288bd3.zip b/wab/137f588a-3ab4-43c8-8678-8b5ee0288bd3.zip new file mode 100644 index 000000000..0fe243627 Binary files /dev/null and b/wab/137f588a-3ab4-43c8-8678-8b5ee0288bd3.zip differ diff --git a/wab/README.md b/wab/README.md new file mode 100644 index 000000000..5c898bfd5 --- /dev/null +++ b/wab/README.md @@ -0,0 +1,8 @@ +# Magic Patterns - Vite Template + +This code was generated by [Magic Patterns](https://magicpatterns.com) for this design: [Source Design](https://www.magicpatterns.com/c/n8t6ftyeqwzmbvhlpr27qb) + +## Getting Started + +1. Run `npm install` +2. Run `npm run dev` diff --git a/wab/index.html b/wab/index.html new file mode 100644 index 000000000..3073c4c15 --- /dev/null +++ b/wab/index.html @@ -0,0 +1,13 @@ + + + + + + + Cambodia Immigration Portal - Fork - Fork - Fork + + +
+ + + diff --git a/wab/package.json b/wab/package.json new file mode 100644 index 000000000..b0e952906 --- /dev/null +++ b/wab/package.json @@ -0,0 +1,34 @@ +{ + "name": "magic-patterns-vite-template", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "npx vite", + "build": "npx vite build", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "preview": "npx vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "@emotion/react": "^11.13.3", + "lucide-react": "0.522.0" + }, + "devDependencies": { + "@types/node": "^20.11.18", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^5.54.0", + "@typescript-eslint/parser": "^5.54.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.50.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.1", + "typescript": "^5.5.4", + "vite": "^5.2.0", + "tailwindcss": "3.4.17", + "autoprefixer": "latest", + "postcss": "latest" + } +} \ No newline at end of file diff --git a/wab/postcss.config.js b/wab/postcss.config.js new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/wab/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/wab/public/image-1.png b/wab/public/image-1.png new file mode 100644 index 000000000..2b302fa63 Binary files /dev/null and b/wab/public/image-1.png differ diff --git a/wab/public/image-2.png b/wab/public/image-2.png new file mode 100644 index 000000000..9cd7f94de Binary files /dev/null and b/wab/public/image-2.png differ diff --git a/wab/public/image-3.png b/wab/public/image-3.png new file mode 100644 index 000000000..1510a90b2 Binary files /dev/null and b/wab/public/image-3.png differ diff --git a/wab/public/image-4.png b/wab/public/image-4.png new file mode 100644 index 000000000..788a6b39e Binary files /dev/null and b/wab/public/image-4.png differ diff --git a/wab/public/image.png b/wab/public/image.png new file mode 100644 index 000000000..153053b74 Binary files /dev/null and b/wab/public/image.png differ diff --git a/wab/public/logo-new.png b/wab/public/logo-new.png new file mode 100644 index 000000000..8b0603a45 Binary files /dev/null and b/wab/public/logo-new.png differ diff --git a/wab/src/App.tsx b/wab/src/App.tsx new file mode 100644 index 000000000..5002216e8 --- /dev/null +++ b/wab/src/App.tsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { GovernmentBar } from './components/GovernmentBar'; +import { Header } from './components/Header'; +import { AnnouncementBanner } from './components/AnnouncementBanner'; +import { FeaturedNewsSection } from './components/FeaturedNewsSection'; +import { ServiceCards } from './components/ServiceCards'; +import { SmartGateSection } from './components/SmartGateSection'; +import { NewsSection } from './components/NewsSection'; +import { Footer } from './components/Footer'; +import { LoginOverlay } from './pages/LoginPage'; +import { DashboardPage } from './pages/DashboardPage'; +import { ComplaintFormPage } from './pages/ComplaintFormPage'; +import { QuestionFormPage } from './pages/QuestionFormPage'; +import { LanguageProvider } from './lib/LanguageContext'; +function AppContent() { + const [showLogin, setShowLogin] = useState(true); + const [currentPage, setCurrentPage] = useState< + 'home' | 'dashboard' | 'complaint' | 'question'>( + 'home'); + const handleGuestLogin = () => { + setCurrentPage('dashboard'); + setShowLogin(false); + }; + if (currentPage === 'complaint') { + return setCurrentPage('dashboard')} />; + } + if (currentPage === 'question') { + return setCurrentPage('dashboard')} />; + } + if (currentPage === 'dashboard') { + return ( + setCurrentPage('complaint')} + onNavigateToQuestion={() => setCurrentPage('question')} />); + + + } + return ( +
+ setShowLogin(true)} /> +
setShowLogin(true)} /> + +
+ + + + +
+
+ + {/* Login Overlay */} + {showLogin && + setShowLogin(false)} + onGuestLogin={handleGuestLogin} /> + + } +
); + +} +export function App() { + return ( + + + ); + +} \ No newline at end of file diff --git a/wab/src/components/AnnouncementBanner.tsx b/wab/src/components/AnnouncementBanner.tsx new file mode 100644 index 000000000..84962ddbb --- /dev/null +++ b/wab/src/components/AnnouncementBanner.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ChevronRight } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +export function AnnouncementBanner() { + const { t } = useLanguage(); + return ( +
+ + + + + {t('banner_earrival')}{' '} + + {t('banner_submission_free')} + + + + +
); + +} \ No newline at end of file diff --git a/wab/src/components/FeaturedNewsSection.tsx b/wab/src/components/FeaturedNewsSection.tsx new file mode 100644 index 000000000..f740fb5d0 --- /dev/null +++ b/wab/src/components/FeaturedNewsSection.tsx @@ -0,0 +1,248 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +export function FeaturedNewsSection() { + const { t } = useLanguage(); + const [currentSlide, setCurrentSlide] = useState(0); + const [isTransitioning, setIsTransitioning] = useState(false); + const slides = [ + { + id: 1, + image: "/image.png", + + title: + 'ប្រជុំពិភាក្សាការងារពាក់ព័ន្ធប្រព័ន្ធម៉ែហានិភ័យអ្នកដំណើរតាមផ្លូវអាកាស GTAS', + description: + 'ប្រជុំពិភាក្សាការងារពាក់ព័ន្ធប្រព័ន្ធម៉ែហានិភ័យអ្នកដំណើរតាមផ្លូវអាកាស GTAS', + date: 'នៅព្រឹកថ្ងៃប្រហស្បតិ៍ ទី៨ ខែមករា ឆ្នាំ២០២៦ នេះ ឯកឧត្តម ឧត្តមសេនីយ៍ឯក សុខ វាសនា អគ្គនាយកអន្តោប្រវេសន៍...' + }, + { + id: 2, + image: "/image-1.png", + + title: + 'កិច្ចប្រជុំតាមដានខ្មែរនភាពការងារបង្រ្គាបបទល្មើសជោគជ័យតាមប្រព័ន្ធបច្ចេកវិទ្យា...', + description: + 'កិច្ចប្រជុំតាមដានខ្មែរនភាពការងារបង្រ្គាបបទល្មើសជោគជ័យតាមប្រព័ន្ធបច្ចេកវិទ្យា (Online Scams)', + date: 'នៅព្រឹកថ្ងៃអង្គារ ទី៦ ខែមករា ឆ្នាំ២០២៦ នេះ ឯកឧត្តម ឧត្តមសេនីយ៍ឯក សុខ វាសនា អគ្គនាយកអន្តោប្រវេសន៍...' + }, + { + id: 3, + image: "/image-2.png", + + title: + 'ពិធីបិទព្រឹត្តិការណ៍ប្រកួតកីឡាមហាបាដន់ ពានរង្វាន់ឯកឧត្តមអគ្គសេនីយ៍ឯក ស សុខា...', + description: + 'ពិធីបិទព្រឹត្តិការណ៍ប្រកួតកីឡាមហាបាដន់ ពានរង្វាន់ឯកឧត្តមអគ្គសេនីយ៍ឯក ស សុខា ដើម្បីអបអរសាទរវិជ្ជមានមួយ ពាមករា', + date: 'នៅសៀលថ្ងៃចន្ទ ទី៥ ខែមករា ឆ្នាំ២០២៦ នេះ ឯកឧត្តម ឧត្តមសេនីយ៍ឯក សុខ វាសនា អគ្គ...' + }, + { + id: 4, + image: "/image-3.png", + + title: + 'វឌ្ឍនភាពការងារត្រួតពិនិត្យវត្តមានស្នាក់នៅរបស់ជនបរទេសនៅក្នុងព្រះរាជាណាចក្រ...', + description: + 'វឌ្ឍនភាពការងារត្រួតពិនិត្យវត្តមានស្នាក់នៅរបស់ជនបរទេសនៅក្នុងព្រះរាជាណាចក្រកម្ពុជា', + date: 'នៅព្រឹកថ្ងៃសុក្រ ទី៩ ខែមករា ឆ្នាំ២០២៦ នេះ ឯកឧត្តម ឧត្តមសេនីយ៍ឯក សុខ វាសនា អគ្គនាយកអន្តោប្រវេសន៍ ព្រមទាំង...' + }]; + + const goToSlide = useCallback( + (index: number) => { + if (isTransitioning) return; + setIsTransitioning(true); + setCurrentSlide(index); + setTimeout(() => setIsTransitioning(false), 600); + }, + [isTransitioning] + ); + const nextSlide = useCallback(() => { + goToSlide((currentSlide + 1) % slides.length); + }, [currentSlide, slides.length, goToSlide]); + const prevSlide = useCallback(() => { + goToSlide((currentSlide - 1 + slides.length) % slides.length); + }, [currentSlide, slides.length, goToSlide]); + // Auto-rotate every 3 seconds + useEffect(() => { + const timer = setInterval(() => { + nextSlide(); + }, 3000); + return () => clearInterval(timer); + }, [nextSlide]); + return ( +
+
+
+ {/* Left Column: News Carousel */} +
+ {/* All slides stacked, only current one visible */} + {slides.map((slide, idx) => +
+ +
+
+ )} + + {/* Content Overlay */} +
+

+ + {slides[currentSlide].title} +

+

+ {slides[currentSlide].description} +

+

+ {slides[currentSlide].date} +

+
+ + {/* Navigation Arrows */} + + + + {/* Pagination Dots */} +
+ {slides.map((_, idx) => +
+ + {/* Progress Bar */} +
+
+ +
+
+ + {/* Right Column: Profile Card */} +
+ {/* Orange Header Curve */} +
+
+ + {/* Profile Image */} +
+
+ Director General + +
+
+ + {/* Text Content */} +
+

+ សារស្វាគមន៍របស់ឯកឧត្តម +

+

+ ឯកឧត្តម ឧត្តមសេនីយ៍ឯក សុខ វាសនា +

+ +

+ សូមស្វាគមន៍ចំពោះការចូលទស្សនាគេហទំព័រអគ្គនាយកដ្ឋាន អន្តោប្រវេសន៍! + អ្នកអានជាទីគោរ! ដើម្បីឆ្លើតទៅសម្រេចបានកម្មវត្ថុស័យឆ្នាំ២០៤០ + ដែលកម្ពុជាជាយផ្សេទមានចំណុលខ្ពស់ រជ្ជកាលបានដាក់ចេញនូវក្រុម + ខណ្ឌនយោបាយ យុទ្ធសាស្រ្តបញ្ញាពេលណ ដំណាក់កាលទី១... +

+ + + + Read More + +
+ + {/* Background Pattern (Subtle) */} +
+
+
+
+
+ + {/* Orange Announcement Banner - Ticker */} +
+ +
+ {/* Label Tag */} + + + ព័ត៌មានថ្មី + + + {/* Scrolling Ticker */} +
+
+ + + នៅក្នុងឆ្នាំ២០២៦ចាប់ពីខែកុម្ភះនេះទៅថ្នាក់ដឹងនាំនៃព្រះរាជាណាចក្រកម្ពុជាបានចេញសេចក្តីថ្លែងការណ័នឹងជំរាប់ដល់ជនជាតិបរទេសទាំងអស់ដែលរស់នៅកម្ពុជា + ដែលមានប្រវត្តរស់នៅកម្ពុជា២ឆ្នាំឡើងនិងមានសិទ្ធិស្នើរសុំចូលជាសញ្ជាត្តិកម្ពុជា + ៕ ពត៏មានបន្ថែមអាចទំនាក់ទំនងមកកាន់គេហទំព័រផ្លូវការរបស់ + អគ្គនាយកដ្ឋានអន្តោប្រវេសន៍ ។ + + + នៅក្នុងឆ្នាំ២០២៦ចាប់ពីខែកុម្ភះនេះទៅថ្នាក់ដឹងនាំនៃព្រះរាជាណាចក្រកម្ពុជាបានចេញសេចក្តីថ្លែងការណ័នឹងជំរាប់ដល់ជនជាតិបរទេសទាំងអស់ដែលរស់នៅកម្ពុជា + ដែលមានប្រវត្តរស់នៅកម្ពុជា២ឆ្នាំឡើងនិងមានសិទ្ធិស្នើរសុំចូលជាសញ្ជាត្តិកម្ពុជា + ៕ ពត៏មានបន្ថែមអាចទំនាក់ទំនងមកកាន់គេហទំព័រផ្លូវការរបស់ + អគ្គនាយកដ្ឋានអន្តោប្រវេសន៍ ។ + +
+
+
+
+
); + +} \ No newline at end of file diff --git a/wab/src/components/Footer.tsx b/wab/src/components/Footer.tsx new file mode 100644 index 000000000..85e571eb0 --- /dev/null +++ b/wab/src/components/Footer.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { MapPin, Phone, Mail, Facebook, Twitter, Youtube, Globe } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +export function Footer() { + const { + t + } = useLanguage(); + return
+
+
+ {/* Column 1: About */} +
+
+ General Department of Immigration Seal +
+

+ {t('department_name').split(' ').slice(0, 2).join(' ')} +
+ {t('department_name').split(' ').slice(2).join(' ')} +

+
+
+

+ {t('footer_desc')} +

+ +
+ + {/* Column 2: Quick Links */} +
+

+ {t('quick_links')} +

+
    + {[t('nav_about'), t('service_visa_title'), t('nav_legal'), t('nav_services'), t('nav_news'), t('nav_contact')].map((item) =>
  • + + + {item} + +
  • )} +
+
+ + {/* Column 3: Services */} +
+

+ {t('e_services')} +

+
    + {['e-Visa Application', 'Foreigners Present in Cambodia System', 'Non-Immigrant Visa Extension', 'Exit Visa', 'Refugee Travel Document'].map((item) =>
  • + + + {item} + +
  • )} +
+
+ + {/* Column 4: Contact */} +
+

+ {t('contact_info')} +

+
    +
  • + + + No. 322, Russian Federation Blvd,
    + Phnom Penh, Cambodia +
    +
  • +
  • + + +855 23 890 380 +
  • +
  • + + info@immigration.gov.kh +
  • +
  • + + www.immigration.gov.kh +
  • +
+
+
+ + {/* Bottom Bar */} + +
+
; +} \ No newline at end of file diff --git a/wab/src/components/GovernmentBar.tsx b/wab/src/components/GovernmentBar.tsx new file mode 100644 index 000000000..ed5bc91de --- /dev/null +++ b/wab/src/components/GovernmentBar.tsx @@ -0,0 +1,125 @@ +import React, { useState } from 'react'; +import { ChevronDown, Globe, User, Check } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +import { Language } from '../lib/translations'; +interface GovernmentBarProps { + onLoginClick?: () => void; +} +export function GovernmentBar({ onLoginClick }: GovernmentBarProps) { + const { language, setLanguage, t } = useLanguage(); + const [isLangMenuOpen, setIsLangMenuOpen] = useState(false); + const languages: { + code: Language; + label: string; + flag: string; + }[] = [ + { + code: 'en', + label: 'English', + flag: '🇬🇧' + }, + { + code: 'km', + label: 'ភាសាខ្មែរ', + flag: '🇰🇭' + }, + { + code: 'zh', + label: '中文', + flag: 'https://firebasestorage.googleapis.com/v0/b/egdi-ecosystem.appspot.com/o/eligibleNationalities%2Fchina.png?alt=media&token=094e6a2b-6af6-430d-9f51-5c74b70882e8' + }]; + + const currentLang = languages.find((l) => l.code === language) || languages[0]; + return ( +
+ +
+
+ + + {t('official_website')} + + + Official Gov Portal + + +
+ + + {isLangMenuOpen && + <> +
setIsLangMenuOpen(false)}> +
+
+ {languages.map((lang) => + + )} +
+ + } +
+
+ + +
+
); + +} \ No newline at end of file diff --git a/wab/src/components/Header.tsx b/wab/src/components/Header.tsx new file mode 100644 index 000000000..32bbb5b6d --- /dev/null +++ b/wab/src/components/Header.tsx @@ -0,0 +1,419 @@ +import React, { useState } from 'react'; +import { Menu, X, User, ChevronRight, ChevronDown, Check } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +import { Language } from '../lib/translations'; +interface HeaderProps { + onLoginClick?: () => void; +} +export function Header({ onLoginClick }: HeaderProps) { + const { t, language, setLanguage } = useLanguage(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [expandedMobileMenu, setExpandedMobileMenu] = useState( + null + ); + const [isLangMenuOpen, setIsLangMenuOpen] = useState(false); + const languages: { + code: Language; + label: string; + flag: string; + }[] = [ + { + code: 'en', + label: 'English', + flag: '🇬🇧' + }, + { + code: 'km', + label: 'ភាសាខ្មែរ', + flag: '🇰🇭' + }, + { + code: 'zh', + label: '中文', + flag: 'https://firebasestorage.googleapis.com/v0/b/egdi-ecosystem.appspot.com/o/eligibleNationalities%2Fchina.png?alt=media&token=094e6a2b-6af6-430d-9f51-5c74b70882e8' + }]; + + const currentLang = languages.find((l) => l.code === language) || languages[0]; + const navItems = [ + { + label: t('nav_home'), + href: '/', + active: true + }, + { + label: t('nav_about'), + href: '#', + active: false, + children: [ + { + label: t('about_dg_message'), + href: '#' + }, + { + label: t('about_general_dept'), + href: '#' + }, + { + label: t('about_infrastructure'), + href: '#' + }, + { + label: t('about_leaders'), + href: '#' + }, + { + label: t('about_admin_dept'), + href: '#' + }, + { + label: t('about_non_immigrant_dept'), + href: '#' + }, + { + label: t('about_immigrant_refugee_dept'), + href: '#' + }, + { + label: t('about_immigrant_investor_dept'), + href: '#' + }, + { + label: t('about_gateway_1'), + href: '#' + }, + { + label: t('about_gateway_2'), + href: '#' + }, + { + label: t('about_investigation_dept'), + href: '#' + }, + { + label: t('about_it_dept'), + href: '#' + }, + { + label: t('about_uyfc'), + href: '#' + }, + { + label: t('about_gender'), + href: '#' + }, + { + label: t('about_sport'), + href: '#' + }] + + }, + { + label: t('nav_services'), + href: '#', + active: false, + children: [ + { + label: t('service_visa_extension'), + href: '#' + }, + { + label: t('service_residence_permit'), + href: '#' + }, + { + label: t('service_foreign_passport'), + href: '#' + }, + { + label: t('service_citizenship'), + href: '#' + }, + { + label: t('nav_contact'), + href: '#' + }] + + }, + { + label: t('nav_news'), + href: '/news', + active: false + }, + { + label: t('nav_digital_library'), + href: '#', + active: false, + children: [ + { + label: t('digital_announcement'), + href: '#' + }, + { + label: t('digital_official_doc'), + href: '#' + }] + + }]; + + const toggleMobileSubmenu = (label: string) => { + if (expandedMobileMenu === label) { + setExpandedMobileMenu(null); + } else { + setExpandedMobileMenu(label); + } + }; + return ( +
+ + {/* Overlay for better text readability */} +
+
+ + {/* Top Branding Area */} +
+
+
+ General Department of Immigration Seal + +
+

+ + អគ្គនាយកដ្ឋានអន្តោប្រវេសន៍ +

+

+ + General Department of Immigration +

+
+
+ +
+ +
+
+
+ + {/* Scrolling Marquee Ticker */} +
+ + {/* Desktop Navigation Bar */} +
+
+
+ + + + {/* Language Selector */} +
+ + + {isLangMenuOpen && +
+ {languages.map((lang) => + + )} +
+ } +
+
+
+
+ + {/* Mobile Navigation Dropdown */} + {isMenuOpen && +
+ +
+ } +
); + +} \ No newline at end of file diff --git a/wab/src/components/HeroSection.tsx b/wab/src/components/HeroSection.tsx new file mode 100644 index 000000000..568093be6 --- /dev/null +++ b/wab/src/components/HeroSection.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { ArrowRight, ChevronRight } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +export function HeroSection() { + const { + t + } = useLanguage(); + return
+ {/* Background Image Placeholder with Gradient */} +
+ {/* Abstract shapes to simulate Angkor Wat silhouette */} +
+
+
+ + {/* Overlay Gradient */} +
+ + {/* Content */} +
+
+
+ + + {t('hero_official_portal')} + +
+ +

+ {t('hero_welcome')}
+ + {t('hero_immigration_services')} + +

+ +

+ {t('hero_description')} +

+ +
+ + +
+
+
+ + {/* Bottom decorative bar */} +
+
; +} \ No newline at end of file diff --git a/wab/src/components/NewsSection.tsx b/wab/src/components/NewsSection.tsx new file mode 100644 index 000000000..de2888be5 --- /dev/null +++ b/wab/src/components/NewsSection.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { Calendar, ArrowRight, Tag } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +export function NewsSection() { + const { + t + } = useLanguage(); + const news = [{ + id: 1, + title: 'New Visa Regulations for 2024 Announced', + date: 'October 15, 2023', + category: 'Policy', + excerpt: 'The General Department of Immigration has released updated guidelines for long-term visa extensions effective from January 1st.', + image: 'bg-[#1B2A4A]/10' + }, { + id: 2, + title: 'Smart Gate Expansion at Phnom Penh International Airport', + date: 'September 28, 2023', + category: 'Technology', + excerpt: 'Ten additional automated border control gates have been installed to facilitate faster processing for eligible travelers.', + image: 'bg-[#D4841C]/10' + }, { + id: 3, + title: 'Department Launches New Online Service Portal', + date: 'September 10, 2023', + category: 'Service', + excerpt: 'Citizens and foreigners can now access 15 different immigration services through the newly upgraded digital platform.', + image: 'bg-gray-200' + }]; + return
+
+
+
+

+ {t('news_title')} +

+

{t('news_subtitle')}

+
+ + {t('view_all_news')} + +
+ +
+ {news.map((item) =>
+ {/* Image Placeholder */} +
+
+ News Image +
+
+ + {item.category} +
+
+ +
+
+ + {item.date} +
+ +

+ {item.title} +

+ +

+ {item.excerpt} +

+ + + {t('read_full_article')}{' '} + + +
+
)} +
+
+
; +} \ No newline at end of file diff --git a/wab/src/components/ServiceCards.tsx b/wab/src/components/ServiceCards.tsx new file mode 100644 index 000000000..8c2c8c8c4 --- /dev/null +++ b/wab/src/components/ServiceCards.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { FileText, Smartphone, Briefcase, AlertTriangle, Plane, Phone, ArrowRight, UserPlus } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +export function ServiceCards() { + const { + t + } = useLanguage(); + const services = [{ + title: t('service_visa_title'), + description: t('service_visa_desc'), + icon: FileText, + link: '#' + }, { + title: t('service_smartgate_title'), + description: t('service_smartgate_desc'), + icon: Smartphone, + link: '#' + }, { + title: t('service_work_title'), + description: t('service_work_desc'), + icon: Briefcase, + link: '#' + }, { + title: t('service_citizenship_title'), + description: t('service_citizenship_desc'), + icon: UserPlus, + link: '#' + }, { + title: t('service_advisory_title'), + description: t('service_advisory_desc'), + icon: AlertTriangle, + link: '#' + }, { + title: t('service_arrival_title'), + description: t('service_arrival_desc'), + icon: Plane, + link: '#' + }, { + title: t('service_contact_title'), + description: t('service_contact_desc'), + icon: Phone, + link: '#' + }]; + return
+
+
+

+ {t('services_title')} +

+
+

{t('services_subtitle')}

+
+ + +
+
; +} \ No newline at end of file diff --git a/wab/src/components/SmartGateSection.tsx b/wab/src/components/SmartGateSection.tsx new file mode 100644 index 000000000..1d3799925 --- /dev/null +++ b/wab/src/components/SmartGateSection.tsx @@ -0,0 +1,188 @@ +import React from 'react'; +import { CheckCircle2 } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +type Region = 'ASEAN' | 'Asia' | 'Americas' | 'Oceania' | 'Europe'; +type Country = { + name: string; + flag: string; +}; +const countryData: Record = { + ASEAN: [ + { + name: 'Cambodia', + flag: '🇰🇭' + }, + { + name: 'Indonesia', + flag: '🇮🇩' + }, + { + name: 'Malaysia', + flag: '🇲🇾' + }, + { + name: 'Singapore', + flag: '🇸🇬' + }, + { + name: 'Laos', + flag: '🇱🇦' + }, + { + name: 'Philippines', + flag: '🇵🇭' + }, + { + name: 'Vietnam', + flag: '🇻🇳' + }, + { + name: 'Thailand', + flag: '🇹🇭' + }, + { + name: 'Myanmar', + flag: '🇲🇲' + }, + { + name: 'Brunei Darussalam', + flag: '🇧🇳' + }], + + Asia: [ + { + name: 'China', + flag: '🇨🇳' + }, + { + name: 'Japan', + flag: '🇯🇵' + }, + { + name: 'Macao SAR', + flag: '🇲🇴' + }, + { + name: 'Republic of Korea (South Korea)', + flag: '🇰🇷' + }, + { + name: 'Hong Kong SAR', + flag: '🇭🇰' + }], + + Americas: [ + { + name: 'USA', + flag: '🇺🇸' + }], + + Oceania: [ + { + name: 'Australia', + flag: '🇦🇺' + }], + + Europe: [ + { + name: 'France', + flag: '🇫🇷' + }, + { + name: 'Germany', + flag: '🇩🇪' + }, + { + name: 'United Kingdom', + flag: '🇬🇧' + }] + +}; +export function SmartGateSection() { + const { t } = useLanguage(); + const getRegionName = (region: Region) => { + switch (region) { + case 'ASEAN': + return t('region_asean'); + case 'Asia': + return t('region_asia'); + case 'Americas': + return t('region_americas'); + case 'Oceania': + return t('region_oceania'); + case 'Europe': + return t('region_europe'); + default: + return region; + } + }; + const RegionBlock = ({ + region, + className = '' + + + + }: {region: Region;className?: string;}) => +
+

+ {getRegionName(region)} +

+
+ {countryData[region].map((country) => +
+ + + + {country.flag} + + + {country.name} + +
+ )} +
+
; + + return ( +
+
+ {/* Header */} +
+

+ {t('smartgate_title')} +

+
+ + {/* Content Wrapper */} +
+ {/* Top Row: ASEAN + Asia */} +
+ {/* ASEAN takes 2/3 width on large screens */} + + + {/* Asia takes 1/3 width on large screens */} + +
+ + {/* Bottom Row: Americas + Oceania + Europe */} +
+ + + +
+ + {/* Footer Note */} +
+ + {t('smartgate_requirement')} +
+
+
+
); + +} \ No newline at end of file diff --git a/wab/src/index.css b/wab/src/index.css new file mode 100644 index 000000000..809a753f9 --- /dev/null +++ b/wab/src/index.css @@ -0,0 +1,20 @@ +/* URL IMPORTS (SUCH AS FONT IMPORTS) SHOULD BE KEPT ABOVE TAILWIND IMPORTS - DO NOT DELETE THIS COMMENT */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Moul&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Battambang:wght@300;400;700&display=swap'); + +/* PLEASE NOTE: THESE TAILWIND IMPORTS SHOULD NEVER BE DELETED - DO NOT DELETE THIS COMMENT */ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + +/* DO NOT DELETE THESE TAILWIND IMPORTS, OTHERWISE THE STYLING WILL NOT RENDER AT ALL - DO NOT DELETE THIS COMMENT */ + +@keyframes marquee { + 0% { + transform: translateX(0%); + } + 100% { + transform: translateX(-50%); + } +} diff --git a/wab/src/index.tsx b/wab/src/index.tsx new file mode 100644 index 000000000..bdf8733fb --- /dev/null +++ b/wab/src/index.tsx @@ -0,0 +1,6 @@ +import './index.css'; +import "./index.css"; +import React from "react"; +import { render } from "react-dom"; +import { App } from "./App"; +render(, document.getElementById("root")); \ No newline at end of file diff --git a/wab/src/lib/LanguageContext.tsx b/wab/src/lib/LanguageContext.tsx new file mode 100644 index 000000000..f5d962c28 --- /dev/null +++ b/wab/src/lib/LanguageContext.tsx @@ -0,0 +1,37 @@ +import React, { useState, createContext, useContext } from 'react'; +import { translations, Language } from './translations'; +interface LanguageContextType { + language: Language; + setLanguage: (lang: Language) => void; + t: (key: keyof typeof translations) => string; +} +const LanguageContext = createContext(undefined); +export function LanguageProvider({ + children + + +}: {children: ReactNode;}) { + const [language, setLanguage] = useState('km'); + const t = (key: keyof typeof translations) => { + const translation = translations[key]; + if (!translation) { + console.warn(`Translation key "${key}" not found.`); + return key; + } + return translation[language] || translation['en']; + }; + return + {children} + ; +} +export function useLanguage() { + const context = useContext(LanguageContext); + if (context === undefined) { + throw new Error('useLanguage must be used within a LanguageProvider'); + } + return context; +} \ No newline at end of file diff --git a/wab/src/lib/translations.ts b/wab/src/lib/translations.ts new file mode 100644 index 000000000..a756243cd --- /dev/null +++ b/wab/src/lib/translations.ts @@ -0,0 +1,442 @@ +export type Language = 'en' | 'km' | 'zh'; + +export const translations = { + // Common + official_website: { + en: 'An official website of the Kingdom of Cambodia', + km: 'គេហទំព័រផ្លូវការនៃព្រះរាជាណាចក្រកម្ពុជា', + zh: '柬埔寨王国官方网站' + }, + sign_in: { + en: 'Sign In', + km: 'ចូលគណនី', + zh: '登录' + }, + department_name: { + en: 'General Department of Immigration', + km: 'អគ្គនាយកដ្ឋានអន្តោប្រវេសន៍', + zh: '移民总局' + }, + + // Navigation + nav_home: { + en: 'Home', + km: 'ទំព័រដើម', + zh: '首页' + }, + nav_services: { + en: 'Services', + km: 'សេវាកម្ម', + zh: '服务' + }, + nav_news: { + en: 'News', + km: 'ព័ត៌មាន', + zh: '新闻' + }, + nav_legal: { + en: 'Legal Documents', + km: 'ឯកសារផ្លូវច្បាប់', + zh: '法律文件' + }, + nav_about: { + en: 'About Us', + km: 'អំពីយើង', + zh: '关于我们' + }, + nav_contact: { + en: 'Contact Us', + km: 'ទំនាក់ទំនង', + zh: '联系我们' + }, + nav_digital_library: { + en: 'Digital Library', + km: 'បណ្ណាល័យឌីជីថល', + zh: '数字图书馆' + }, + + // About Dropdown + about_dg_message: { + en: "Director General's Welcome Message", + km: 'សារស្វាគមន៍របស់អគ្គនាយក', + zh: '总干事致辞' + }, + about_general_dept: { + en: 'General Department', + km: 'អគ្គនាយកដ្ឋាន', + zh: '总局' + }, + about_infrastructure: { + en: 'Structure', + km: 'រចនាសម្ព័ន្ធ', + zh: '组织结构' + }, + about_leaders: { + en: 'Leaders', + km: 'ថ្នាក់ដឹកនាំ', + zh: '领导层' + }, + about_admin_dept: { + en: 'General Administration Dept', + km: 'នាយកដ្ឋានរដ្ឋបាលសរុប', + zh: '综合行政部' + }, + about_non_immigrant_dept: { + en: 'Non-Immigrant Foreign Dept', + km: 'នាយកដ្ឋានជនបរទេសមិនមែនអន្តោប្រវេសន្ត', + zh: '非移民外国人管理部' + }, + about_immigrant_refugee_dept: { + en: 'Immigrant & Refugee Dept', + km: 'នាយកដ្ឋានជនបរទេសអន្តោប្រវេសន្ត និងជនភៀសខ្លួន', + zh: '移民与难民部' + }, + about_immigrant_investor_dept: { + en: 'Immigrant & Private Investor Dept', + km: 'នាយកដ្ឋានជនបរទេសអន្តោប្រវេសន្តនិងជាអ្នកវិនិយោគឯកជន', + zh: '移民与私人投资者部' + }, + about_gateway_1: { + en: 'Gateway Dept 1', + km: 'នាយកដ្ឋានច្រកទ្វារទី១', + zh: '第一口岸部' + }, + about_gateway_2: { + en: 'Gateway Dept 2', + km: 'នាយកដ្ឋានច្រកទ្វារទី២', + zh: '第二口岸部' + }, + about_investigation_dept: { + en: 'Investigation & Procedure Dept', + km: 'នាយកដ្ឋានស៊ើបអង្កេត និងអនុវត្តនិតិវិធី', + zh: '调查与程序执行部' + }, + about_it_dept: { + en: 'IT Dept', + km: 'នាយកដ្ឋានបច្ចេកវិទ្យាព័ត៌មានអន្តោប្រវេសន៍', + zh: '移民信息技术部' + }, + about_uyfc: { + en: 'Union of Youth Federations of Cambodia', + km: 'សហភាពសហព័ន្ធយុវជនកម្ពុជា', + zh: '柬埔寨青年联合会' + }, + about_gender: { + en: 'Gender', + km: 'យេនឌ័រ', + zh: '性别事务' + }, + about_sport: { + en: 'Sport', + km: 'កីឡា', + zh: '体育' + }, + + // Services Dropdown + service_visa_extension: { + en: 'VISAS AND EXTENSIONS', + km: 'ទិដ្ឋាការ និងការបន្តសុពលភាព', + zh: '签证与延期' + }, + service_residence_permit: { + en: 'RESIDENCE PERMIT', + km: 'ប័ណ្ណស្នាក់នៅ', + zh: '居留许可' + }, + service_foreign_passport: { + en: 'FOREIGN PASSPORT SERVICES', + km: 'សេវាធ្វើលិខិតឆ្លងដែនជនបរទេស', + zh: '外籍办理护照' + }, + service_citizenship: { + en: 'CAMBODIAN CITIZENSHIP APPLICATION', + km: 'ពាក្យសុំសញ្ជាតិខ្មែរសម្រាប់ជនបរទេស', + zh: '外籍申请加入柬埔寨国籍' + }, + + // Digital Library Dropdown + digital_announcement: { + en: 'Announcement', + km: 'សេចក្តីប្រកាស', + zh: '公告' + }, + digital_official_doc: { + en: 'Official Document', + km: 'ឯកសារផ្លូវការ', + zh: '官方文件' + }, + + // Announcement Banner + banner_earrival: { + en: 'All travelers can submit the Cambodia e-Arrival within 7 days before or on arrival.', + km: 'អ្នកធ្វើដំណើរទាំងអស់អាចដាក់ Cambodia e-Arrival ក្នុងរយៈពេល ៧ ថ្ងៃមុនពេលមកដល់ ឬនៅពេលមកដល់។', + zh: '所有旅客可在抵达前7天内或抵达时提交柬埔寨电子入境卡。' + }, + banner_submission_free: { + en: 'Submission is Free.', + km: 'ការដាក់ពាក្យគឺឥតគិតថ្លៃ។', + zh: '提交免费。' + }, + + // Hero Section + hero_official_portal: { + en: 'Official Government Portal', + km: 'ច្រកទ្វាររដ្ឋាភិបាលផ្លូវការ', + zh: '官方政府门户' + }, + hero_welcome: { + en: 'Welcome to Cambodia', + km: 'សូមស្វាគមន៍មកកាន់កម្ពុជា', + zh: '欢迎来到柬埔寨' + }, + hero_immigration_services: { + en: 'Immigration Services', + km: 'សេវាអន្តោប្រវេសន៍', + zh: '移民服务' + }, + hero_description: { + en: 'Facilitating safe travel, managing immigration, and serving the Kingdom with integrity and efficiency.', + km: 'សម្របសម្រួលការធ្វើដំណើរប្រកបដោយសុវត្ថិភាព គ្រប់គ្រងអន្តោប្រវេសន៍ និងបម្រើព្រះរាជាណាចក្រដោយសុចរិតភាព និងប្រសិទ្ធភាព។', + zh: '促进安全旅行,管理移民事务,以诚信和高效服务王国。' + }, + hero_apply_visa: { + en: 'Apply for Visa', + km: 'ស្នើសុំទិដ្ឋាការ', + zh: '申请签证' + }, + hero_check_status: { + en: 'Check Status', + km: 'ពិនិត្យស្ថានភាព', + zh: '检查状态' + }, + + // Service Cards + services_title: { + en: 'Our Services', + km: 'សេវាកម្មរបស់យើង', + zh: '我们的服务' + }, + services_subtitle: { + en: 'Access essential immigration services quickly and efficiently through our digital platforms.', + km: 'ចូលប្រើសេវាកម្មអន្តោប្រវេសន៍សំខាន់ៗយ៉ាងឆាប់រហ័ស និងប្រកបដោយប្រសិទ្ធភាពតាមរយៈវេទិកាឌីជីថលរបស់យើង។', + zh: '通过我们的数字平台快速高效地访问基本移民服务。' + }, + service_visa_title: { + en: 'Visa Application', + km: 'ពាក្យសុំទិដ្ឋាការ', + zh: '签证申请' + }, + service_visa_desc: { + en: 'Apply for tourist, business, and other visa types online securely.', + km: 'ដាក់ពាក្យសុំទិដ្ឋាការទេសចរណ៍ អាជីវកម្ម និងប្រភេទផ្សេងៗទៀតតាមអ៊ីនធឺណិតដោយសុវត្ថិភាព។', + zh: '在线安全申请旅游、商务和其他类型的签证。' + }, + service_smartgate_title: { + en: 'Smart Gate', + km: 'ច្រកទ្វារឆ្លាតវៃ', + zh: '智能闸门' + }, + service_smartgate_desc: { + en: 'Automated border control system for eligible passport holders.', + km: 'ប្រព័ន្ធត្រួតពិនិត្យព្រំដែនដោយស្វ័យប្រវត្តិសម្រាប់អ្នកកាន់លិខិតឆ្លងដែនដែលមានសិទ្ធិ។', + zh: '适用于符合条件的护照持有者的自动边境控制系统。' + }, + service_work_title: { + en: 'Work Permits', + km: 'ប័ណ្ណការងារ', + zh: '工作许可证' + }, + service_work_desc: { + en: 'Information and application process for foreign employment.', + km: 'ព័ត៌មាន និងដំណើរការដាក់ពាក្យសម្រាប់ការងារបរទេស។', + zh: '外国就业的信息和申请流程。' + }, + service_advisory_title: { + en: 'Travel Advisory', + km: 'ការណែនាំអំពីការធ្វើដំណើរ', + zh: '旅行建议' + }, + service_advisory_desc: { + en: 'Latest updates on travel restrictions and entry requirements.', + km: 'បច្ចុប្បន្នភាពចុងក្រោយស្តីពីការរឹតបន្តឹងការធ្វើដំណើរ និងតម្រូវការចូល។', + zh: '关于旅行限制和入境要求的最新更新。' + }, + service_arrival_title: { + en: 'e-Arrival', + km: 'e-Arrival', + zh: '电子入境卡' + }, + service_arrival_desc: { + en: 'Submit your arrival card digitally before you fly.', + km: 'ដាក់ជូនប័ណ្ណមកដល់របស់អ្នកតាមប្រព័ន្ធឌីជីថលមុនពេលអ្នកហោះហើរ។', + zh: '在飞行前以数字方式提交您的入境卡。' + }, + service_contact_title: { + en: 'Contact Us', + km: 'ទាក់ទង​មក​ពួក​យើង', + zh: '联系我们' + }, + service_contact_desc: { + en: 'Get in touch with our support team for assistance.', + km: 'ទាក់ទងក្រុមគាំទ្ររបស់យើងសម្រាប់ជំនួយ។', + zh: '与我们的支持团队联系以获得帮助。' + }, + service_citizenship_title: { + en: 'Citizenship Application', + km: 'ពាក្យសុំសញ្ជាតិខ្មែរ', + zh: '申请加入柬埔寨国籍' + }, + service_citizenship_desc: { + en: 'Foreign nationals can apply for Cambodian citizenship through the official naturalization process.', + km: 'ជនបរទេសអាចដាក់ពាក្យសុំសញ្ជាតិខ្មែរតាមរយៈដំណើរការផ្លូវការនៃការចូលសញ្ជាតិ។', + zh: '外籍人士可通过官方入籍程序申请加入柬埔寨国籍。' + }, + access_service: { + en: 'Access Service', + km: 'ចូលប្រើសេវាកម្ម', + zh: '访问服务' + }, + + // Smart Gate Section + smartgate_label: { + en: 'Automated Border Control', + km: 'ការត្រួតពិនិត្យព្រំដែនដោយស្វ័យប្រវត្តិ', + zh: '自动边境控制' + }, + smartgate_title: { + en: 'Eligible Passports For Smart Gate At Techo International Airport (KTI)', + km: 'លិខិតឆ្លងដែនដែលមានសិទ្ធិសម្រាប់ច្រកទ្វារឆ្លាតវៃនៅអាកាសយានដ្ឋានអន្តរជាតិតេជោ (KTI)', + zh: '德崇国际机场 (KTI) 智能闸门适用护照' + }, + smartgate_desc: { + en: 'Citizens of the following countries can use the automated Smart Gates for faster immigration clearance.', + km: 'ពលរដ្ឋនៃប្រទេសខាងក្រោមអាចប្រើប្រាស់ច្រកទ្វារឆ្លាតវៃដោយស្វ័យប្រវត្តិសម្រាប់ការត្រួតពិនិត្យអន្តោប្រវេសន៍លឿនជាងមុន។', + zh: '以下国家的公民可以使用自动智能闸门进行更快的移民通关。' + }, + region_asean: { + en: 'ASEAN', + km: 'អាស៊ាន', + zh: '东盟' + }, + region_asia: { + en: 'Asia', + km: 'អាស៊ី', + zh: '亚洲' + }, + region_americas: { + en: 'Americas', + km: 'អាមេរិក', + zh: '美洲' + }, + region_oceania: { + en: 'Oceania', + km: 'អូសេអានី', + zh: '大洋洲' + }, + region_europe: { + en: 'Europe', + km: 'អឺរ៉ុប', + zh: '欧洲' + }, + smartgate_requirement: { + en: 'Valid e-Passport required for Smart Gate usage', + km: 'តម្រូវឱ្យមានលិខិតឆ្លងដែនអេឡិចត្រូនិកដែលមានសុពលភាពសម្រាប់ការប្រើប្រាស់ច្រកទ្វារឆ្លាតវៃ', + zh: '使用智能闸门需要有效的电子护照' + }, + + // News Section + news_title: { + en: 'Latest News & Updates', + km: 'ព័ត៌មាន & បច្ចុប្បន្នភាពចុងក្រោយ', + zh: '最新新闻与动态' + }, + news_subtitle: { + en: 'Stay informed about immigration policies and announcements.', + km: 'ទទួលបានព័ត៌មានអំពីគោលនយោបាយអន្តោប្រវេសន៍ និងសេចក្តីប្រកាស។', + zh: '随时了解移民政策和公告。' + }, + view_all_news: { + en: 'View All News', + km: 'មើលព័ត៌មានទាំងអស់', + zh: '查看所有新闻' + }, + read_full_article: { + en: 'Read Full Article', + km: 'អានអត្ថបទពេញ', + zh: '阅读全文' + }, + + // Footer + footer_desc: { + en: 'The General Department of Immigration is responsible for managing the flow of foreign nationals entering and exiting the Kingdom of Cambodia.', + km: 'អគ្គនាយកដ្ឋានអន្តោប្រវេសន៍ទទួលខុសត្រូវក្នុងការគ្រប់គ្រងលំហូរជនបរទេសដែលចូល និងចេញពីព្រះរាជាណាចក្រកម្ពុជា។', + zh: '移民总局负责管理进出柬埔寨王国的外国国民流动。' + }, + quick_links: { + en: 'Quick Links', + km: 'តំណភ្ជាប់រហ័ស', + zh: '快速链接' + }, + e_services: { + en: 'E-Services', + km: 'សេវាអេឡិចត្រូនិក', + zh: '电子服务' + }, + contact_info: { + en: 'Contact Info', + km: 'ព័ត៌មានទំនាក់ទំនង', + zh: '联系信息' + }, + rights_reserved: { + en: 'All Rights Reserved.', + km: 'រក្សាសិទ្ធិគ្រប់យ៉ាង។', + zh: '保留所有权利。' + }, + privacy_policy: { + en: 'Privacy Policy', + km: 'គោលការណ៍​ភាព​ឯកជន', + zh: '隐私政策' + }, + terms_of_use: { + en: 'Terms of Use', + km: 'លក្ខខណ្ឌនៃការប្រើប្រាស់', + zh: '使用条款' + }, + sitemap: { + en: 'Sitemap', + km: 'ផែនទីគេហទំព័រ', + zh: '网站地图' + }, + + // Login + login_guest: { + en: 'Login as Guest', + km: 'ចូលដោយជាភ្ញៀវ', + zh: '以访客身份登录' + }, + login_gmail: { + en: 'Login with Gmail', + km: 'ចូលជាមួយ Gmail', + zh: '使用 Gmail 登录' + }, + copyright_kh: { + en: 'Copyright © 2024 Kingdom of Cambodia. All Rights Reserved.', + km: 'រក្សាសិទ្ធិ © 2024 ព្រះរាជាណាចក្រកម្ពុជា រក្សាសិទ្ធិគ្រប់យ៉ាង', + zh: '版权所有 © 2024 柬埔寨王国。保留所有权利。' + }, + version: { + en: 'Version: 1.0.0', + km: 'ជំនាន់: 1.0.0', + zh: '版本: 1.0.0' + }, + get_it_on: { + en: 'GET IT ON', + km: 'GET IT ON', + zh: '获取于' + }, + download_on: { + en: 'Download on the', + km: 'Download on the', + zh: '下载于' + } +}; \ No newline at end of file diff --git a/wab/src/pages/ComplaintFormPage.tsx b/wab/src/pages/ComplaintFormPage.tsx new file mode 100644 index 000000000..31fcc5464 --- /dev/null +++ b/wab/src/pages/ComplaintFormPage.tsx @@ -0,0 +1,278 @@ +import React from 'react'; +import { + X, + Grid3x3, + Bell, + User, + Bold, + Italic, + Underline, + Strikethrough, + List, + ListOrdered, + Type, + AlignLeft, + Paperclip, + ChevronDown } from +'lucide-react'; +interface ComplaintFormPageProps { + onBack: () => void; +} +export function ComplaintFormPage({ onBack }: ComplaintFormPageProps) { + return ( +
+ {/* Top Header (Same as Dashboard) */} +
+
+ + HOTLINE + +
+ +
+
+ 中文 + Chinese + +
+
+ + +
+ +
+
+
+ + {/* Sub-Header */} +
+
+ +

新投诉

+
+ +
+ + {/* Main Content */} +
+
+ {/* Form Header */} +
+

投诉详情

+

+ 我们会优先考虑您的反馈和疑虑。 +
+ 请在下面的表格中填写您的请求或评论,然后单击"提交"。 +

+
+ + {/* Section 1: Comments */} +
+

1.评论

+ + {/* Complaint Type */} +
+
+ + +
+
+ + {/* Rich Text Editor */} +
+ {/* Toolbar */} +
+ + + + +
+ + +
+ + +
+ +
+ {/* Text Area */} +
+ +
+
+ + {/* Attachment */} +
+ +
+
+ +
+
+

+ 插入附件 +

+

+ 附件可以是图片、视频或其他文件(大小不得超过100MB)。 +

+
+
+
+
+ + {/* Section 2: Personal Info */} +
+

2.个人信息

+ + {/* Name Fields */} +
+
+ + + +
+ +
+ + + +
+
+ + {/* Phone Number */} +
+
+
+ 电话号码* +
+
+ +855 +
+
+
+ + + +
+
+ + {/* Gender */} +
+
+ + + +
+
+ + {/* Nationality */} +
+
+ + + +
+
+
+
+
+
); + +} \ No newline at end of file diff --git a/wab/src/pages/DashboardPage.tsx b/wab/src/pages/DashboardPage.tsx new file mode 100644 index 000000000..006788bff --- /dev/null +++ b/wab/src/pages/DashboardPage.tsx @@ -0,0 +1,221 @@ +import React from 'react'; +import { + Search, + Plus, + Grid3x3, + Bell, + User, + MessageSquare, + HelpCircle, + Phone, + Facebook, + Mail, + Package } from +'lucide-react'; +interface DashboardPageProps { + onNavigateToComplaint?: () => void; + onNavigateToQuestion?: () => void; +} +export function DashboardPage({ + onNavigateToComplaint, + onNavigateToQuestion +}: DashboardPageProps) { + return ( +
+ {/* Left Sidebar */} + + + {/* Main Content */} +
+ {/* Header */} +
+
+ + HOTLINE + +
+ +
+
+ 中文 +
+ Chinese + +
+
+ + +
+ +
+
+
+ + {/* Dashboard Content */} +
+
+ {/* Card 1: Post Comment */} +
+ +
+ +
+

+ 发表评论 +

+

+ 对于有关移民程序、系统或官员的任何问题,请提交正式投诉。 +

+
+ + {/* Card 2: Have a Question */} +
+ +
+ +
+

+ 有一个问题? +

+

+ 如果您需要有关任何移民问题的澄清或帮助,请向我们提交问题。 +

+
+ + {/* Card 3: Contact */} +
+

接触

+
+
+
+ +
+ (+855) 68 386 699 +
+
+
+ +
+ (+855) 78 386 699 +
+
+
+ +
+ (+855) 87 386 699 +
+ + +
+
+
+
+
+
); + +} \ No newline at end of file diff --git a/wab/src/pages/LoginPage.tsx b/wab/src/pages/LoginPage.tsx new file mode 100644 index 000000000..7b1c773f0 --- /dev/null +++ b/wab/src/pages/LoginPage.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import { X } from 'lucide-react'; +import { useLanguage } from '../lib/LanguageContext'; +interface LoginOverlayProps { + onClose?: () => void; + onGuestLogin?: () => void; +} +export function LoginOverlay({ + onClose, + onGuestLogin +}: LoginOverlayProps) { + const { + t + } = useLanguage(); + const handleGuestLogin = () => { + if (onGuestLogin) { + onGuestLogin(); + } else if (onClose) { + onClose(); + } + }; + return
+ {/* Backdrop */} +
+ + {/* White Card */} +
+ {/* Close Button */} + + + {/* Content */} +
+ {/* Government Seal */} + General Department of Immigration Seal + + {/* Khmer Title */} +

+ {t('department_name')} +

+ + {/* Divider */} +
+ + {/* Buttons */} +
+ {/* Guest Login Button */} + + + {/* Gmail Login Button */} + +
+ + {/* Copyright */} +
+

{t('copyright_kh')}

+

{t('version')}

+
+ + {/* App Store Badges */} + +
+
+
; +} \ No newline at end of file diff --git a/wab/src/pages/QuestionFormPage.tsx b/wab/src/pages/QuestionFormPage.tsx new file mode 100644 index 000000000..af19571d2 --- /dev/null +++ b/wab/src/pages/QuestionFormPage.tsx @@ -0,0 +1,286 @@ +import React from 'react'; +import { + X, + Grid3x3, + Bell, + User, + Bold, + Italic, + Underline, + Strikethrough, + List, + ListOrdered, + Type, + AlignLeft, + Paperclip, + ChevronDown } from +'lucide-react'; +interface QuestionFormPageProps { + onBack: () => void; +} +export function QuestionFormPage({ onBack }: QuestionFormPageProps) { + return ( +
+ {/* Top Header */} +
+
+ + HOTLINE + +
+
+
+ 中文 + Chinese + +
+
+ + +
+ +
+
+
+ + {/* Sub-Header */} +
+
+ +

新问题

+
+ +
+ + {/* Main Content */} +
+
+ {/* Form Header */} +
+

+ 问题与解答 +

+

+ 在这里您可以找到过去提出的问题的答案。 +
+ 如果您的问题在常见问题解答中没有得到解答,请在下面创建一个问题。 +

+
+ + {/* Section 1: Question */} +
+

1.问题

+ + {/* Question Type */} +
+
+ + +
+
+ + {/* Rich Text Editor */} +
+ +
+
+ {/* Toolbar */} +
+ + + + +
+ + +
+ + +
+ +
+ {/* Text Area */} +
+ +
+
+ + {/* Attachment */} +
+ +
+
+ +
+
+

+ 插入附件 +

+

+ 附件可以是图片、视频或其他文件(大小不得超过100MB)。 +

+
+
+
+
+ + {/* Section 2: Personal Info */} +
+

2.个人信息

+ +
+
+ + + +
+ +
+ + + +
+
+ + {/* Phone Number */} +
+
+
+ 电话号码* +
+
+ +855 +
+
+
+ + + +
+
+ + {/* Gender */} +
+
+ + + +
+
+ + {/* Nationality */} +
+
+ + + +
+
+
+
+
+
); + +} \ No newline at end of file diff --git a/wab/tailwind.config.js b/wab/tailwind.config.js new file mode 100644 index 000000000..2aab84e6c --- /dev/null +++ b/wab/tailwind.config.js @@ -0,0 +1,71 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + './index.html', + './src/**/*.{js,ts,jsx,tsx}' +], + theme: { + extend: { + fontFamily: { + sans: ['Inter', 'Battambang', 'system-ui', 'sans-serif'], + khmer: ['Battambang', 'sans-serif'], + 'khmer-title': ['Moul', 'serif'], + }, + colors: { + navy: { + 50: '#f2f5f9', + 100: '#e1e8f0', + 200: '#c5d3e3', + 300: '#9db5d0', + 400: '#7092ba', + 500: '#4d74a3', + 600: '#385a85', + 700: '#2d486b', + 800: '#1B2A4A', // Primary Navy (darker than 500 usually, but adjusting scale to fit) + 900: '#111a2e', + 950: '#0a0f1a', + }, + // Adjusting the provided navy #1B2A4A to be the 900 or 800 level for better contrast text, + // but prompt asked for it as a base. Let's make a custom scale where 500 is the brand color. + brandNavy: { + 50: '#eff4ff', + 100: '#dbe6fe', + 200: '#bfd3fe', + 300: '#93bbfd', + 400: '#609afa', + 500: '#3b82f6', // standard blue + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1B2A4A', // The requested Navy + 900: '#17223b', + }, + // Let's try a more manual scale centered on the requested colors + customNavy: { + 50: '#f4f6f8', + 100: '#e4e8ed', + 200: '#cdd5df', + 300: '#aab9cc', + 400: '#8098b6', + 500: '#5e7a9e', + 600: '#466082', + 700: '#364b68', + 800: '#1B2A4A', // Using this as the deep primary + 900: '#16223b', + }, + gold: { + 50: '#fbf8f3', + 100: '#f5efe4', + 200: '#ebdcc3', + 300: '#dfc29a', + 400: '#D4841C', // The requested Gold + 500: '#b56f16', + 600: '#8f5610', + 700: '#6b400c', + 800: '#4a2c08', + 900: '#2b1904', + } + } + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/wab/tsconfig.json b/wab/tsconfig.json new file mode 100644 index 000000000..a7fc6fbf2 --- /dev/null +++ b/wab/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/wab/tsconfig.node.json b/wab/tsconfig.node.json new file mode 100644 index 000000000..97ede7ee6 --- /dev/null +++ b/wab/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/wab/vite.config.ts b/wab/vite.config.ts new file mode 100644 index 000000000..5a33944a9 --- /dev/null +++ b/wab/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +})