diff --git a/apps/web/src/features/landing/landing-config.ts b/apps/web/src/features/landing/landing-config.ts
index 74cf8ec..99cc1d8 100644
--- a/apps/web/src/features/landing/landing-config.ts
+++ b/apps/web/src/features/landing/landing-config.ts
@@ -151,7 +151,7 @@ export const HEADING_WEIGHTS = ['600', '700', '750', '800', '900'] as const;
const text = (fa: string, en: string): LocaleText => ({ fa, en });
export const defaultLandingConfig: LandingConfig = {
- brand: { name: 'LingoSpeak', mark: 'LS', logo: '' },
+ brand: { name: 'کال آرزو', mark: 'کا', logo: '' },
header: {
sticky: true,
background: '#ffffff',
@@ -166,20 +166,20 @@ export const defaultLandingConfig: LandingConfig = {
signUp: text('ثبتنام', 'Sign up free'),
},
hero: {
- eyebrow: text('کلاس خصوصی آنلاین با مدرسهای تأییدشده', 'Private online lessons with verified teachers'),
- title: text('زبان را برای حرفزدن یاد بگیر، نه فقط برای امتحان.', 'Learn a language to speak it, not just to pass a test.'),
+ eyebrow: text('کال آرزو؛ مدرسهی مکالمه با آرزو', 'Kal Arzoo — real conversation with Arezoo'),
+ title: text('زبان را برای حرف زدن واقعی یاد بگیرید', 'Learn a language for real conversations'),
description: text(
- 'سطحت را رایگان بسنج، مدرسی متناسب با هدفت انتخاب کن و با برنامهای که با زمان تو جور است، جلو برو.',
- 'Check your level for free, pick a teacher who fits your goal, and follow a plan that works with your schedule.',
+ 'با آموزشهای کاربردی و همراهی آرزو، از حفظ کردن فاصله بگیر و با اعتمادبهنفس حرف بزن؛ در موقعیتهایی که واقعاً برایت مهماند.',
+ 'Move beyond memorisation with practical lessons and Arezoo’s guidance, so you can speak with confidence in situations that matter.',
),
- primaryButton: { label: text('انتخاب زبان', 'Choose a language'), href: '/languages' },
+ primaryButton: { label: text('مشاهده دورهها', 'Explore courses'), href: '/courses' },
secondaryButton: { label: text('تعیین سطح رایگان', 'Free placement test'), href: '/placement' },
- image: '/images/lingospeak-student.png',
- imageAlt: text('زبانآموز در حال یادگیری با لپتاپ', 'A language learner studying with a laptop'),
- imageSide: 'right',
+ image: '/images/arzoo-hero.png',
+ imageAlt: text('آرزو، بنیانگذار و مدرس کال آرزو در فضای آموزش', 'Arezoo, founder and instructor of Kal Arzoo'),
+ imageSide: 'left',
overlay: 0,
founderName: text('آرزو احمدی', 'Arezoo Ahmadi'),
- founderTitle: text('بنیانگذار و مدرس ارشد لینگواسپیک', 'Founder & lead teacher at LingoSpeak'),
+ founderTitle: text('بنیانگذار و مدرس کال آرزو', 'Founder & lead teacher at Kal Arzoo'),
quote: text(
'«زبان را یاد میگیریم که حرف بزنیم، نه فقط که امتحان بدهیم.»',
'“We learn a language to speak it, not just to pass a test.”',
@@ -326,8 +326,8 @@ export const defaultLandingConfig: LandingConfig = {
},
footer: {
description: text(
- 'LingoSpeak؛ کلاس خصوصی آنلاین زبان با مدرسهای تأییدشده و برنامه متناسب با هدف تو.',
- 'LingoSpeak: private online language lessons with verified teachers and a plan built around your goal.',
+ 'کال آرزو؛ یادگیری زبان برای حرف زدن واقعی، با همراهی آرزو.',
+ 'Kal Arzoo: practical language learning for real conversations, guided by Arezoo.',
),
columns: [
{
@@ -343,7 +343,6 @@ export const defaultLandingConfig: LandingConfig = {
links: [
{ label: text('مدرسها', 'Teachers'), href: '/teachers' },
{ label: text('مقالات', 'Articles'), href: '/blog' },
- { label: text('درباره ما', 'About us'), href: '/about' },
],
},
],
@@ -354,20 +353,20 @@ export const defaultLandingConfig: LandingConfig = {
},
sections: [
{ id: 'hero', type: 'hero', label: text('هیرو', 'Hero'), visible: true, style: {} },
- { id: 'about', type: 'about', label: text('درباره آرزو', 'About Arezoo'), visible: true, style: {} },
+ { id: 'about', type: 'about', label: text('درباره آرزو', 'About Arezoo'), visible: false, style: {} },
{ id: 'languages', type: 'languages', label: text('زبانها', 'Languages'), visible: true, style: {} },
{ id: 'benefits', type: 'benefits', label: text('مزیتها', 'Benefits'), visible: true, style: {} },
{ id: 'placement', type: 'placement', label: text('تعیین سطح', 'Placement'), visible: true, style: {} },
{ id: 'courses', type: 'courses', label: text('دورهها', 'Courses'), visible: true, style: {} },
- { id: 'blog', type: 'blog', label: text('مقالات', 'Articles'), visible: true, style: {} },
+ { id: 'blog', type: 'blog', label: text('مقالات', 'Articles'), visible: false, style: {} },
{ id: 'faq', type: 'faq', label: text('سؤالات متداول', 'FAQ'), visible: true, style: {} },
{ id: 'finalCta', type: 'finalCta', label: text('دعوت نهایی', 'Final CTA'), visible: true, style: {} },
],
theme: {
- primary: '#6d4aff',
- secondary: '#c4b5fd',
- button: '#6d4aff',
- background: '#fbfbfe',
+ primary: '#7046d8',
+ secondary: '#c7b4ff',
+ button: '#7046d8',
+ background: '#fcfbff',
radius: '24px',
shadow: '0 18px 55px rgba(48, 31, 112, 0.09)',
containerWidth: '1240px',
diff --git a/apps/web/src/features/panel/components/online-class-toast.tsx b/apps/web/src/features/panel/components/online-class-toast.tsx
new file mode 100644
index 0000000..628f1d9
--- /dev/null
+++ b/apps/web/src/features/panel/components/online-class-toast.tsx
@@ -0,0 +1,47 @@
+'use client';
+
+import { useEffect, useMemo, useState } from 'react';
+import { ExternalLink, Video, X } from 'lucide-react';
+import { useQuery } from '@tanstack/react-query';
+import { api } from '@/shared/services/api';
+import { useTranslations } from '@/components/shared/locale-provider';
+import { localized } from '@/lib/i18n';
+import { cn } from '@/shared/components/ui/cn';
+
+type Booking = { id: string; startsAt: string; endsAt?: string | null; status: string; meetingUrl?: string | null; teacher?: { nameFa?: string; nameEn?: string } };
+
+export function OnlineClassToast() {
+ const { locale } = useTranslations();
+ const [dismissed, setDismissed] = useState
(null);
+ const bookings = useQuery({ queryKey: ['bookings'], queryFn: () => api('/bookings/me'), staleTime: 30_000, refetchInterval: 60_000 });
+ const now = Date.now();
+ const live = useMemo(() => (bookings.data ?? []).find((item) => {
+ const start = new Date(item.startsAt).getTime();
+ const end = item.endsAt ? new Date(item.endsAt).getTime() : start + 60 * 60 * 1000;
+ return item.status === 'CONFIRMED' && Boolean(item.meetingUrl) && start <= now && now < end;
+ }), [bookings.data, now]);
+ const key = live ? `${live.id}:${live.startsAt}:${live.endsAt ?? ''}:${live.meetingUrl}` : null;
+
+ useEffect(() => {
+ if (!key) return;
+ setDismissed(window.localStorage.getItem(`live-class-toast:${key}`));
+ }, [key]);
+
+ if (!live || !key || dismissed === key) return null;
+ const teacher = localized({ fa: live.teacher?.nameFa, en: live.teacher?.nameEn }, locale) || (locale === 'en' ? 'Your instructor' : 'مدرس شما');
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/features/panel/components/panel-shell.tsx b/apps/web/src/features/panel/components/panel-shell.tsx
index d755fdf..3e06182 100644
--- a/apps/web/src/features/panel/components/panel-shell.tsx
+++ b/apps/web/src/features/panel/components/panel-shell.tsx
@@ -47,7 +47,8 @@ import { clearAuthSession } from '@/shared/services/api';
import { featureFlags } from '@/config';
import { cn } from '@/shared/components/ui/cn';
import { formatNumber } from '@/lib/money';
-import { useNotifications } from '../hooks/use-notifications';
+import { useMarkAllNotificationsRead, useMarkNotificationRead, useNotifications } from '../hooks/use-notifications';
+import { OnlineClassToast } from './online-class-toast';
export type NavItem = {
href: string;
@@ -152,6 +153,9 @@ export function PanelShell({ items, children }: { title?: string; items: NavItem
const permissions = Array.isArray(me.data?.permissions) ? me.data.permissions : [];
const isAdmin = roles.includes('ADMIN');
const notifications = useNotifications(Boolean(me.data) && !adminMode);
+ const markNotificationRead = useMarkNotificationRead();
+ const markAllNotificationsRead = useMarkAllNotificationsRead();
+ const [notificationsOpen, setNotificationsOpen] = useState(false);
const adminSummary = useQuery({
queryKey: ['admin-dashboard'],
queryFn: () => api('/admin/dashboard'),
@@ -399,8 +403,10 @@ export function PanelShell({ items, children }: { title?: string; items: NavItem
)}
{notificationsHref && (
-
+
+ {notificationsOpen && (
+
+
+ {english ? 'Notifications' : 'اعلانها'}
+ { markAllNotificationsRead.mutate(); setNotificationsOpen(false); }} className="text-xs font-bold text-primary">{english ? 'View all' : 'مشاهده همه'}
+
+
+ {(notifications.data ?? []).slice(0, 6).map((item) => (
+
{ if (!item.readAt) markNotificationRead.mutate(item.id); setNotificationsOpen(false); }} className="flex gap-3 rounded-xl px-3 py-3 text-start hover:bg-canvas">
+
+
{localized({ fa: item.titleFa, en: item.titleEn }, locale)}{localized({ fa: item.bodyFa, en: item.bodyEn }, locale)}
+
+ ))}
+ {!notifications.data?.length &&
{english ? 'You are all caught up.' : 'اعلان تازهای ندارید.'}
}
+
+
+ )}
+