diff --git a/apps/lms/app/apple-icon.png b/apps/lms/app/apple-icon.png new file mode 100644 index 0000000..f93df67 Binary files /dev/null and b/apps/lms/app/apple-icon.png differ diff --git a/apps/lms/app/favicon.ico b/apps/lms/app/favicon.ico index 0e0e0c6..d8e4b35 100644 Binary files a/apps/lms/app/favicon.ico and b/apps/lms/app/favicon.ico differ diff --git a/apps/lms/app/icon0.svg b/apps/lms/app/icon0.svg new file mode 100644 index 0000000..a091f47 --- /dev/null +++ b/apps/lms/app/icon0.svg @@ -0,0 +1,17 @@ +RealFaviconGeneratorhttps://realfavicongenerator.net \ No newline at end of file diff --git a/apps/lms/app/icon1.png b/apps/lms/app/icon1.png new file mode 100644 index 0000000..d990e64 Binary files /dev/null and b/apps/lms/app/icon1.png differ diff --git a/apps/lms/app/manifest.json b/apps/lms/app/manifest.json new file mode 100644 index 0000000..4b7eb0b --- /dev/null +++ b/apps/lms/app/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "BeeWise", + "short_name": "BeeWise", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/lms/next.config.ts b/apps/lms/next.config.ts index 445d54e..b60f79e 100644 --- a/apps/lms/next.config.ts +++ b/apps/lms/next.config.ts @@ -38,14 +38,20 @@ const nextConfig: NextConfig = { ], }, async rewrites() { + if (!apiBaseUrl) { + return []; + } + + const formattedBaseUrl = apiBaseUrl.replace(/\/$/, ""); + return [ { source: "/api/ai/:path*", - destination: `${apiBaseUrl}/api/ai/:path*`, + destination: `${formattedBaseUrl}/api/ai/:path*`, }, { source: "/api/:path*", - destination: `${apiBaseUrl}/:path*`, + destination: `${formattedBaseUrl}/:path*`, }, ]; }, diff --git a/apps/sale/app/apple-icon.png b/apps/sale/app/apple-icon.png new file mode 100644 index 0000000..f93df67 Binary files /dev/null and b/apps/sale/app/apple-icon.png differ diff --git a/apps/sale/app/favicon.ico b/apps/sale/app/favicon.ico index 0e0e0c6..d8e4b35 100644 Binary files a/apps/sale/app/favicon.ico and b/apps/sale/app/favicon.ico differ diff --git a/apps/sale/app/icon0.svg b/apps/sale/app/icon0.svg new file mode 100644 index 0000000..a091f47 --- /dev/null +++ b/apps/sale/app/icon0.svg @@ -0,0 +1,17 @@ +RealFaviconGeneratorhttps://realfavicongenerator.net \ No newline at end of file diff --git a/apps/sale/app/icon1.png b/apps/sale/app/icon1.png new file mode 100644 index 0000000..d990e64 Binary files /dev/null and b/apps/sale/app/icon1.png differ diff --git a/apps/sale/app/manifest.json b/apps/sale/app/manifest.json new file mode 100644 index 0000000..4b7eb0b --- /dev/null +++ b/apps/sale/app/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "BeeWise", + "short_name": "BeeWise", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/sale/features/auth/components/AuthLayout.tsx b/apps/sale/features/auth/components/AuthLayout.tsx index af3bb29..e0c1c59 100644 --- a/apps/sale/features/auth/components/AuthLayout.tsx +++ b/apps/sale/features/auth/components/AuthLayout.tsx @@ -41,29 +41,27 @@ export function AuthLayout({ children, variant = "login" }: AuthLayoutProps) { className="flex items-center gap-3" aria-label="BeeWise - Về trang chủ" > -
+
BeeWise Logo
- - Cộng Đồng Gia Sư -
- "Gia nhập BeeWise,
+ "Gia nhập BeeWise, +
Kiến tạo tương lai."

diff --git a/apps/sale/features/landing/components/FeatureShowcaseSection.tsx b/apps/sale/features/landing/components/FeatureShowcaseSection.tsx index e9fb6f9..a2bfb0c 100644 --- a/apps/sale/features/landing/components/FeatureShowcaseSection.tsx +++ b/apps/sale/features/landing/components/FeatureShowcaseSection.tsx @@ -69,7 +69,7 @@ const SHOWCASE_ITEMS: ShowcaseItem[] = [ title: "Gia Sư Được Xác Thực", subtitle: "Hồ sơ minh bạch, uy tín tuyệt đối", description: - "Mọi gia sư trên BeeWise đều phải hoàn thành quy trình xác thực đa tầng gồm kiểm tra thông tin, bằng cấp chuyên môn, chứng chỉ quốc tế và phỏng vấn trực tiếp trước khi nhận lớp.", + "Mọi gia sư trên BeeWise đều phải hoàn thành quy trình xác thực đa tầng gồm kiểm tra thông tin, bằng cấp chuyên môn, chứng chỉ quốc tế và phỏng vấn trước khi nhận lớp.", benefits: [ "100% hồ sơ được kiểm duyệt thực tế", "Bằng cấp, chứng chỉ rõ ràng, công khai", diff --git a/apps/sale/features/landing/components/HeroSection.tsx b/apps/sale/features/landing/components/HeroSection.tsx index 947258b..3ab88a6 100644 --- a/apps/sale/features/landing/components/HeroSection.tsx +++ b/apps/sale/features/landing/components/HeroSection.tsx @@ -6,10 +6,12 @@ import Link from "next/link"; import { HeroMotion } from "@workspace/ui/components/HeroMotion"; import { HeroCarousel } from "./HeroCarousel"; import { LightningIcon } from "@phosphor-icons/react"; +import { FunnelIcon } from "lucide-react"; export function HeroSection() { const router = useRouter(); const [query, setQuery] = useState(""); + const [isFocused, setIsFocused] = useState(false); const handleSearch = () => { if (query.trim()) { @@ -69,28 +71,58 @@ export function HeroSection() { }} aria-hidden="true" /> -

- setQuery(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") handleSearch(); - }} - /> +
+
+ setIsFocused(true)} + onBlur={() => setIsFocused(false)} + onChange={(e) => setQuery(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") handleSearch(); + }} + /> + + {/* Running placeholder marquee for mobile when input is empty & not focused */} + {!query && !isFocused && ( + + )} +
+
-
- - Tìm kiếm thủ công - +
+
+ Hoặc: + + Xem danh sách gia sư + +
+ +
+ Gợi ý: + {["Toán 12", "IELTS", "Tiếng Anh"].map((tag) => ( + + ))} +
diff --git a/apps/sale/features/landing/components/VideoIntroSection.tsx b/apps/sale/features/landing/components/VideoIntroSection.tsx index c8473d7..872cdb6 100644 --- a/apps/sale/features/landing/components/VideoIntroSection.tsx +++ b/apps/sale/features/landing/components/VideoIntroSection.tsx @@ -96,7 +96,7 @@ export function VideoIntroSection() {
diff --git a/apps/sale/features/tutor-guide/components/TutorGuideFeatures.tsx b/apps/sale/features/tutor-guide/components/TutorGuideFeatures.tsx index 6d5e2ea..8839a34 100644 --- a/apps/sale/features/tutor-guide/components/TutorGuideFeatures.tsx +++ b/apps/sale/features/tutor-guide/components/TutorGuideFeatures.tsx @@ -1,6 +1,5 @@ import Image from "next/image"; import { - ChalkboardTeacherIcon, HeadsetIcon, MagicWandIcon, WalletIcon, @@ -10,88 +9,92 @@ const SUPPORT_ITEMS = [ { icon: WalletIcon, title: "Thu nhập rõ ràng", - text: "Mức học phí do bạn đưa ra, Beewise giúp bạn quản lý qua từng buổi học.", + text: "Bạn chủ động đề xuất học phí. BeeWise ghi nhận và quản lý thu nhập theo từng buổi.", }, { icon: HeadsetIcon, - title: "Được hỗ trợ từ Beewise team", - text: "Cố vấn hỗ trợ lịch học thử, trao đổi kỳ vọng và các vấn đề phát sinh.", + title: "Hỗ trợ khi cần", + text: "Cố vấn đồng hành trong buổi học thử, thống nhất kỳ vọng và xử lý tình huống phát sinh.", }, { icon: MagicWandIcon, - title: "Bạn không có nhiều thời gian tóm tắt bài?", - text: "Beewise sẽ giúp bạn tóm tắt buổi học và tạo sinh bài tập", + title: "Nhẹ việc sau buổi học", + text: "BeeWise hỗ trợ tạo tóm tắt và bài tập để bạn có thêm thời gian chuẩn bị buổi tiếp theo.", }, ]; export function TutorGuideFeatures() { return (
+
diff --git a/apps/sale/features/tutor-guide/components/TutorGuideFeedback.tsx b/apps/sale/features/tutor-guide/components/TutorGuideFeedback.tsx index 9f2496c..762e0e9 100644 --- a/apps/sale/features/tutor-guide/components/TutorGuideFeedback.tsx +++ b/apps/sale/features/tutor-guide/components/TutorGuideFeedback.tsx @@ -1,212 +1,207 @@ -"use client"; +import { QuotesIcon, StarIcon } from "@phosphor-icons/react/dist/ssr"; -import { Star, Quotes } from "@phosphor-icons/react"; +interface TutorFeedbackItem { + id: string; + name: string; + subject: string; + avatar: string; + avatarClassName: string; + rating: number; + quote: string; + sessions: string; +} const TUTOR_FEEDBACKS = [ { id: "1", name: "Thầy Nguyễn Hoàng Anh", - subject: "Gia sư Toán • 4 năm kinh nghiệm", + subject: "Gia sư Toán, 4 năm kinh nghiệm", avatar: "HA", - avatarBg: "bg-blue-500", + avatarClassName: "bg-accent text-primary", rating: 5, - highlight: "Không còn mất thời gian tìm lớp", quote: "Trước đây mình phải đăng bài khắp nơi, mất cả tuần mới tìm được 1 lớp. Từ khi dùng BeeWise, học viên chủ động tìm đến mình dựa trên hồ sơ đã xác thực. Tiết kiệm rất nhiều công sức.", - sessions: "38 buổi dạy / tháng", - income: "12.000.000đ / tháng", + sessions: "38 buổi mỗi tháng", }, { id: "2", name: "Cô Trần Mai Linh", - subject: "Gia sư Tiếng Anh • 6 năm kinh nghiệm", + subject: "Gia sư Tiếng Anh, 6 năm kinh nghiệm", avatar: "ML", - avatarBg: "bg-emerald-500", + avatarClassName: "bg-secondary text-white", rating: 5, quote: "Học viên được ghép qua AI thực sự phù hợp với phương pháp dạy của mình. Không còn cảnh học thử rồi nghỉ nữa. Lịch dạy ổn định hơn, thu nhập cũng đoán trước được.", - sessions: "22 buổi dạy / tháng", - income: "8.800.000đ / tháng", + sessions: "22 buổi mỗi tháng", }, { id: "3", name: "Thầy Lê Minh Tuấn", - subject: "Gia sư Lý • Giáo viên THPT", + subject: "Gia sư Vật lý, giáo viên THPT", avatar: "MT", - avatarBg: "bg-violet-500", + avatarClassName: "bg-primary text-white", rating: 5, quote: "Phụ huynh đọc hồ sơ xong là tin ngay, không cần mất thời gian giới thiệu bản thân lại từ đầu. Quy trình xác thực bằng cấp của BeeWise tạo ra sự khác biệt rất lớn so với các nền tảng khác.", - sessions: "16 buổi dạy / tháng", - income: "7.200.000đ / tháng", + sessions: "16 buổi mỗi tháng", }, { id: "4", name: "Cô Phạm Thu Hà", - subject: "Gia sư Hóa • Thủ khoa ĐH KHTN", + subject: "Gia sư Hóa học, thủ khoa ĐH KHTN", avatar: "TH", - avatarBg: "bg-rose-500", + avatarClassName: "bg-highlight text-primary", rating: 5, quote: "Có lần học viên đột ngột xin nghỉ giữa chừng, mình không biết xử lý thế nào. Đội ngũ BeeWise hỗ trợ ngay trong ngày, giải quyết ổn thỏa cho cả hai bên. Rất chuyên nghiệp.", - sessions: "28 buổi dạy / tháng", - income: "9.600.000đ / tháng", + sessions: "28 buổi mỗi tháng", }, -]; +] satisfies TutorFeedbackItem[]; + +const CARD_LAYOUT_CLASSES = [ + "md:col-span-2 lg:col-span-5 lg:row-span-2 lg:min-h-[34rem]", + "md:col-span-1 lg:col-span-7 lg:min-h-[16rem]", + "md:col-span-1 lg:col-span-4 lg:min-h-[17rem]", + "md:col-span-2 lg:col-span-3 lg:min-h-[17rem]", +] as const; + +const CARD_TONE_CLASSES = [ + "border-primary bg-primary text-primary-foreground", + "border-primary/10 bg-white text-foreground", + "border-primary/10 bg-white text-foreground", + "border-accent bg-accent text-accent-foreground", +] as const; export function TutorGuideFeedback() { return (
- {/* Ambient glows */} -
-
- -
- {/* Section header */} -
- - Gia sư nói gì về BeeWise - +
); diff --git a/apps/sale/features/tutor-guide/components/TutorGuideHero.tsx b/apps/sale/features/tutor-guide/components/TutorGuideHero.tsx index ca143bd..8506585 100644 --- a/apps/sale/features/tutor-guide/components/TutorGuideHero.tsx +++ b/apps/sale/features/tutor-guide/components/TutorGuideHero.tsx @@ -1,100 +1,116 @@ "use client"; -import Image from "next/image"; +import { useRef } from "react"; import Link from "next/link"; -import { motion, useReducedMotion } from "motion/react"; import { + ArrowDownIcon, ArrowRightIcon, CheckCircleIcon, - ShieldCheckIcon, } from "@phosphor-icons/react"; +import { TutorGuideHeroVisual } from "./TutorGuideHeroVisual"; +import { useTutorGuideHeroMotion } from "../hooks/useTutorGuideHeroMotion"; -const PROOFS = [ - "Hồ sơ được xác thực trước khi hiển thị", - "Chủ động chọn học viên phù hợp", - "Có cố vấn hỗ trợ trong quá trình kết nối", +const TRUST_POINTS = [ + "Miễn phí tạo hồ sơ", + "Xét duyệt minh bạch", + "Chủ động lịch dạy", ]; export function TutorGuideHero() { - const reduceMotion = useReducedMotion(); + const heroRef = useRef(null); + useTutorGuideHeroMotion(heroRef); return (
); diff --git a/apps/sale/features/tutor-guide/components/TutorGuideHeroVisual.tsx b/apps/sale/features/tutor-guide/components/TutorGuideHeroVisual.tsx new file mode 100644 index 0000000..9be5a81 --- /dev/null +++ b/apps/sale/features/tutor-guide/components/TutorGuideHeroVisual.tsx @@ -0,0 +1,30 @@ +import Image from "next/image"; + +export function TutorGuideHeroVisual() { + return ( +
+
+ +
+ ); +} diff --git a/apps/sale/features/tutor-guide/components/TutorGuideSteps.tsx b/apps/sale/features/tutor-guide/components/TutorGuideSteps.tsx index 98de5dd..2ed1b25 100644 --- a/apps/sale/features/tutor-guide/components/TutorGuideSteps.tsx +++ b/apps/sale/features/tutor-guide/components/TutorGuideSteps.tsx @@ -1,26 +1,25 @@ import { CheckCircleIcon, PlayIcon, - SparkleIcon, VideoCameraIcon, } from "@phosphor-icons/react/dist/ssr"; const STEPS = [ { title: "Hoàn thiện hồ sơ", - body: "Chia sẻ môn dạy, kinh nghiệm, khu vực, lịch trống và cách bạn tổ chức một buổi học.", + description: "Bổ sung chuyên môn, kinh nghiệm và lịch có thể nhận lớp.", }, { title: "Tải lên giấy tờ", - body: "Gửi giấy tờ cần thiết để đội ngũ BeeWise kiểm tra và phản hồi nếu cần bổ sung.", + description: "Xác thực danh tính, bằng cấp và chứng chỉ liên quan.", }, { - title: "Phỏng vấn cùng trợ lý Beewise AI", - body: "Hoàn thành buổi phỏng vấn ngắn với trợ lý AI để chia sẻ chi tiết về chuyên môn và phong cách giảng dạy.", + title: "Phỏng vấn cùng trợ lý BeeWise AI", + description: "Trao đổi ngắn để BeeWise hiểu cách bạn giảng dạy.", }, { title: "Trở thành Gia sư", - body: "Sau khi hồ sơ và phỏng vấn được duyệt, bạn đã có thể nhận các yêu cầu kết nối phù hợp.", + description: "Hoàn tất xét duyệt và sẵn sàng nhận lớp phù hợp.", }, ]; @@ -44,8 +43,8 @@ export function TutorGuideSteps() {
-
- Hướng dẫn cho "new Bee" +
+ Hướng dẫn dành cho gia sư mới

- -
-
@@ -122,23 +116,31 @@ export function TutorGuideSteps() {
    {STEPS.map((step, index) => (
  1. -
    - 0{index + 1} +
    + + {String(index + 1).padStart(2, "0")} + + {index < STEPS.length - 1 && ( +
    -
    -

    +
    +

    {step.title}

    -

    - {step.body} +

    + {step.description}

  2. diff --git a/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx b/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx index 512c50f..4d8a5d4 100644 --- a/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx +++ b/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx @@ -131,7 +131,7 @@ export function TutorGuideTrust() {
    BeeWise Sticker, +) { + useGSAP( + () => { + const root = scope.current; + if (!root) return; + + const media = gsap.matchMedia(); + media.add( + { + reduceMotion: "(prefers-reduced-motion: reduce)", + desktop: "(min-width: 1024px)", + }, + (context) => { + const { reduceMotion, desktop } = context.conditions as { + reduceMotion: boolean; + desktop: boolean; + }; + if (reduceMotion) return; + + const timeline = gsap.timeline({ + defaults: { duration: 0.75, ease: "power3.out" }, + }); + + timeline + .from("[data-hero-eyebrow]", { autoAlpha: 0, y: 16 }) + .from( + "[data-hero-line]", + { yPercent: 105, rotate: 1.5, stagger: 0.11 }, + "-=0.45", + ) + .from( + "[data-hero-copy]", + { autoAlpha: 0, y: 18, stagger: 0.08 }, + "-=0.4", + ) + .from( + "[data-hero-visual]", + { + autoAlpha: 0, + clipPath: "inset(12% 10% 12% 10% round 2rem)", + scale: 0.96, + duration: 1, + }, + "-=0.75", + ); + + if (desktop) { + gsap.to("[data-hero-media]", { + yPercent: 7, + ease: "none", + scrollTrigger: { + trigger: root, + start: "top top", + end: "bottom top", + scrub: 0.6, + }, + }); + } + }, + ); + + return () => media.revert(); + }, + { scope }, + ); +} diff --git a/apps/sale/features/tutor-list/components/TutorListController.tsx b/apps/sale/features/tutor-list/components/TutorListController.tsx index 7410a97..6898f3a 100644 --- a/apps/sale/features/tutor-list/components/TutorListController.tsx +++ b/apps/sale/features/tutor-list/components/TutorListController.tsx @@ -83,7 +83,7 @@ export function TutorListController({

    Tìm Kiếm Gia Sư diff --git a/apps/sale/next.config.ts b/apps/sale/next.config.ts index d85b0c0..610f7b0 100644 --- a/apps/sale/next.config.ts +++ b/apps/sale/next.config.ts @@ -46,14 +46,20 @@ const nextConfig: NextConfig = { ], }, async rewrites() { + if (!apiBaseUrl) { + return []; + } + + const formattedBaseUrl = apiBaseUrl.replace(/\/$/, ""); + return [ { source: "/api/ai/:path*", - destination: `${apiBaseUrl}/api/ai/:path*`, + destination: `${formattedBaseUrl}/api/ai/:path*`, }, { source: "/api/:path*", - destination: `${apiBaseUrl}/:path*`, + destination: `${formattedBaseUrl}/:path*`, }, ]; }, diff --git a/apps/staff/next.config.ts b/apps/staff/next.config.ts index e221881..212df77 100644 --- a/apps/staff/next.config.ts +++ b/apps/staff/next.config.ts @@ -38,10 +38,16 @@ const nextConfig: NextConfig = { ], }, async rewrites() { + if (!apiBaseUrl) { + return []; + } + + const formattedBaseUrl = apiBaseUrl.replace(/\/$/, ""); + return [ { source: "/api/:path*", - destination: `${apiBaseUrl}/:path*`, + destination: `${formattedBaseUrl}/:path*`, }, ]; }, diff --git a/packages/ui/components/layout/Footer.tsx b/packages/ui/components/layout/Footer.tsx index bfae5b3..76cf985 100644 --- a/packages/ui/components/layout/Footer.tsx +++ b/packages/ui/components/layout/Footer.tsx @@ -1,66 +1,95 @@ import Link from "next/link"; import Image from "next/image"; -const FOOTER_LINKS = [ +const QUICK_LINKS = [ + { label: "Gia sư 1:1", href: "/tutors" }, + { label: "Tìm lớp", href: "/classes" }, + { label: "Trở thành gia sư", href: "/tutor-guide" }, + { label: "Về chúng tôi", href: "/about-us" }, +]; + +const LEGAL_LINKS = [ { label: "Chính Sách Bảo Mật", href: "/privacy" }, { label: "Điều Khoản Sử Dụng", href: "/terms" }, ]; export function Footer() { return ( -