Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
E
el3ab-Player
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
el3ab-Player
Commits
932ff403
Commit
932ff403
authored
May 26, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dd
parent
0c0bcb97
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
292 additions
and
558 deletions
+292
-558
AppShell.tsx
src/components/layout/AppShell.tsx
+6
-10
BottomNav.tsx
src/components/layout/BottomNav.tsx
+9
-23
Header.tsx
src/components/layout/Header.tsx
+21
-28
PageTransition.tsx
src/components/layout/PageTransition.tsx
+1
-1
index.css
src/index.css
+4
-0
HomePage.tsx
src/pages/HomePage.tsx
+88
-103
PlayPage.tsx
src/pages/PlayPage.tsx
+48
-72
ProfilePage.tsx
src/pages/ProfilePage.tsx
+115
-321
No files found.
src/components/layout/AppShell.tsx
View file @
932ff403
import
{
Outlet
}
from
'react-router-dom'
import
{
Header
}
from
'./Header'
import
{
BottomNav
}
from
'./BottomNav'
import
{
DecorativeBackground
}
from
'./DecorativeBackground'
import
{
ToastContainer
}
from
'../ui/ToastContainer'
import
{
usePresence
}
from
'../../hooks/usePresence'
import
{
useNotifications
}
from
'../../hooks/useNotifications'
...
...
@@ -11,15 +10,12 @@ export function AppShell() {
useNotifications
()
return
(
<
div
className=
"relative flex flex-col min-h-dvh overflow-hidden"
>
<
DecorativeBackground
/>
<
div
className=
"relative z-10 flex flex-col min-h-dvh"
>
<
Header
/>
<
main
className=
"flex-1 pb-28 overflow-y-auto"
>
<
Outlet
/>
</
main
>
<
BottomNav
/>
</
div
>
<
div
className=
"flex flex-col min-h-dvh bg-background"
>
<
Header
/>
<
main
className=
"flex-1 overflow-y-auto"
>
<
Outlet
/>
</
main
>
<
BottomNav
/>
<
ToastContainer
/>
</
div
>
)
...
...
src/components/layout/BottomNav.tsx
View file @
932ff403
import
{
motion
}
from
'framer-motion'
import
{
Home
,
Gamepad2
,
Trophy
,
Users
,
User
}
from
'lucide-react'
import
{
useLocation
,
useNavigate
}
from
'react-router-dom'
...
...
@@ -15,38 +14,25 @@ export function BottomNav() {
const
navigate
=
useNavigate
()
return
(
<
nav
className=
"fixed bottom-0 left-0 right-0 z-50 bg-
surface-1 border-t border-border
"
>
<
div
className=
"flex items-
center justify-around px-4 py-2
max-w-[480px] mx-auto"
>
<
nav
className=
"fixed bottom-0 left-0 right-0 z-50 bg-
background border-t border-border safe-area-pb
"
>
<
div
className=
"flex items-
stretch justify-around
max-w-[480px] mx-auto"
>
{
NAV_ITEMS
.
map
((
item
)
=>
{
const
isActive
=
location
.
pathname
===
item
.
path
const
Icon
=
item
.
icon
return
(
<
motion
.
button
<
button
key=
{
item
.
path
}
onClick=
{
()
=>
navigate
(
item
.
path
)
}
className=
"relative flex flex-col items-center gap-1 py-1 min-w-[44px] min-h-[44px] justify-center"
whileTap=
{
{
scale
:
0.9
}
}
transition=
{
{
duration
:
0.15
}
}
className=
{
`flex flex-col items-center justify-center gap-0.5 py-2 px-3 min-w-[56px] min-h-[56px] transition-colors ${
isActive ? 'text-gold' : 'text-text-muted'
}`
}
>
<
Icon
size=
{
22
}
className=
{
isActive
?
'text-gold'
:
'text-text-muted'
}
strokeWidth=
{
isActive
?
2.5
:
2
}
/>
<
span
className=
{
`text-[11px] font-semibold ${isActive ? 'text-gold' : 'text-text-muted'}`
}
>
<
Icon
size=
{
22
}
strokeWidth=
{
isActive
?
2.5
:
1.8
}
/>
<
span
className=
{
`text-[10px] ${isActive ? 'font-semibold' : 'font-normal'}`
}
>
{
item
.
label
}
</
span
>
{
isActive
&&
(
<
motion
.
div
className=
"absolute -bottom-2 w-6 h-[3px] rounded-full bg-gold"
layoutId=
"nav-indicator"
transition=
{
{
duration
:
0.2
}
}
/>
)
}
</
motion
.
button
>
</
button
>
)
})
}
</
div
>
...
...
src/components/layout/Header.tsx
View file @
932ff403
import
{
motion
}
from
'framer-motion'
import
{
Bell
,
Coins
,
Gem
}
from
'lucide-react'
import
{
useNotificationStore
}
from
'../../stores/notificationStore'
import
{
useAuthStore
}
from
'../../stores/authStore'
...
...
@@ -10,55 +9,49 @@ export function Header() {
const
navigate
=
useNavigate
()
return
(
<
header
className=
"sticky top-0 z-50 bg-
surface-1/95 backdrop-blur-sm
border-b border-border"
>
<
div
className=
"
app-container flex items-center justify-between h-14
"
>
<
header
className=
"sticky top-0 z-50 bg-
background
border-b border-border"
>
<
div
className=
"
flex items-center justify-between h-14 px-4 max-w-[1440px] mx-auto
"
>
{
/* Logo */
}
<
div
className=
"flex items-center gap-
2
"
>
<
span
className=
"text-
lg font-bold text-gold tracking-wide
"
>
<
div
className=
"flex items-center gap-
3
"
>
<
span
className=
"text-
xl font-bold tracking-tight text-text-primary
"
>
EL3AB
</
span
>
{
profile
&&
(
<
div
className=
"flex items-center justify-center w-7 h-7 rounded-full bg-surface-2 border border-border"
>
<
span
className=
"text-[11px] font-bold text-gold"
>
{
profile
.
level
||
1
}
</
span
>
</
div
>
<
span
className=
"text-xs font-medium text-text-muted bg-surface-2 px-2 py-0.5 rounded-[var(--radius-tiny)]"
>
Lv.
{
profile
.
level
||
1
}
</
span
>
)
}
</
div
>
{
/* R
esources + Bell
*/
}
<
div
className=
"flex items-center gap-
3
"
>
{
/* R
ight: currency + notifications
*/
}
<
div
className=
"flex items-center gap-
2
"
>
{
profile
&&
(
<>
<
div
className=
"flex items-center gap-1.5 px-
3 py-1.5 rounded-[var(--radius-tiny)] bg-surface-2 border border-border
"
>
<
div
className=
"flex items-center gap-1.5 px-
2.5 py-1.5 bg-surface-2 rounded-[var(--radius-tiny)]
"
>
<
Coins
size=
{
14
}
className=
"text-gold"
/>
<
span
className=
"text-xs font-semibold text-text-primary"
>
{
profile
.
coins
}
</
span
>
<
span
className=
"text-xs font-semibold text-text-primary
tabular-nums
"
>
{
profile
.
coins
}
</
span
>
</
div
>
{
profile
.
gems
>
0
&&
(
<
div
className=
"flex items-center gap-1.5 px-
3 py-1.5 rounded-[var(--radius-tiny)] bg-surface-2 border border-border
"
>
{
(
profile
.
gems
??
0
)
>
0
&&
(
<
div
className=
"flex items-center gap-1.5 px-
2.5 py-1.5 bg-surface-2 rounded-[var(--radius-tiny)]
"
>
<
Gem
size=
{
12
}
className=
"text-purple"
/>
<
span
className=
"text-xs font-semibold text-text-primary"
>
{
profile
.
gems
}
</
span
>
<
span
className=
"text-xs font-semibold text-text-primary
tabular-nums
"
>
{
profile
.
gems
}
</
span
>
</
div
>
)
}
</>
)
}
<
motion
.
button
className=
"relative flex items-center justify-center w-10 h-10 rounded-[var(--radius-tiny)]
bg-surface-2 border border-border
"
<
button
className=
"relative flex items-center justify-center w-10 h-10 rounded-[var(--radius-tiny)]
hover:bg-surface-2 transition-colors
"
onClick=
{
()
=>
navigate
(
'/notifications'
)
}
whileTap=
{
{
scale
:
0.9
}
}
transition=
{
{
duration
:
0.15
}
}
>
<
Bell
size=
{
18
}
className=
"text-text-secondary"
/>
<
Bell
size=
{
20
}
className=
"text-text-secondary"
/>
{
unreadCount
>
0
&&
(
<
div
className=
"absolute -top-1 -right-1 w-5 h-5 rounded-full bg-coral flex items-center justify-center"
>
<
span
className=
"text-[10px] font-bold text-white"
>
{
unreadCount
>
9
?
'9+'
:
unreadCount
}
</
span
>
</
div
>
<
span
className=
"absolute top-1 right-1 w-4 h-4 rounded-full bg-coral text-[9px] font-bold text-white flex items-center justify-center"
>
{
unreadCount
>
9
?
'9+'
:
unreadCount
}
</
span
>
)
}
</
motion
.
button
>
</
button
>
</
div
>
</
div
>
</
header
>
...
...
src/components/layout/PageTransition.tsx
View file @
932ff403
...
...
@@ -9,7 +9,7 @@ interface PageTransitionProps {
export
function
PageTransition
({
children
,
className
=
''
}:
PageTransitionProps
)
{
return
(
<
motion
.
div
className=
{
`
app-container py-6 pb-24
${className}`
}
className=
{
`
px-4 py-6 pb-28 max-w-[1440px] mx-auto w-full md:px-6 lg:px-8
${className}`
}
initial=
{
{
opacity
:
0
}
}
animate=
{
{
opacity
:
1
}
}
exit=
{
{
opacity
:
0
}
}
...
...
src/index.css
View file @
932ff403
...
...
@@ -281,3 +281,7 @@ body {
.bg-surface-1
{
background-color
:
var
(
--color-surface-1
);
}
.bg-surface-2
{
background-color
:
var
(
--color-surface-2
);
}
.bg-surface-3
{
background-color
:
var
(
--color-surface-3
);
}
.safe-area-pb
{
padding-bottom
:
env
(
safe-area-inset-bottom
,
0px
);
}
src/pages/HomePage.tsx
View file @
932ff403
import
{
useState
}
from
'react'
import
{
motion
}
from
'framer-motion'
import
{
Play
,
TrendingUp
,
Swords
,
Flame
,
Bot
,
Users
,
Lightbulb
}
from
'lucide-react'
import
{
Play
,
TrendingUp
,
Swords
,
Flame
,
Bot
,
Users
,
Lightbulb
,
Trophy
}
from
'lucide-react'
import
{
useNavigate
}
from
'react-router-dom'
import
{
useAuthStore
}
from
'../stores/authStore'
import
{
PageTransition
}
from
'../components/layout/PageTransition'
...
...
@@ -18,16 +18,6 @@ const dailyTips = [
'فكر في خطة خصمك قبل ان تلعب - لا تركز فقط على هجومك'
,
]
const
stagger
=
{
hidden
:
{},
show
:
{
transition
:
{
staggerChildren
:
0.06
}
},
}
const
fadeUp
=
{
hidden
:
{
opacity
:
0
,
y
:
12
},
show
:
{
opacity
:
1
,
y
:
0
,
transition
:
{
duration
:
0.2
}
},
}
export
function
HomePage
()
{
const
navigate
=
useNavigate
()
const
{
profile
}
=
useAuthStore
()
...
...
@@ -42,6 +32,10 @@ export function HomePage() {
}
}
const
winRate
=
profile
&&
profile
.
total_games_played
>
0
?
Math
.
round
((
profile
.
total_wins
/
profile
.
total_games_played
)
*
100
)
:
0
return
(
<
PageTransition
>
<
DailyRewardModal
...
...
@@ -53,122 +47,113 @@ export function HomePage() {
onClose=
{
()
=>
setShowDailyModal
(
false
)
}
/>
<
motion
.
div
variants=
{
stagger
}
initial=
"hidden"
animate=
"show"
className=
"flex flex-col gap-6"
>
<
div
className=
"flex flex-col gap-8"
>
{
/* Welcome */
}
{
profile
&&
(
<
motion
.
div
variants=
{
fadeUp
}
className=
"flex items-center gap-3"
>
<
div
className=
"w-12 h-12 rounded-[var(--radius-standard)] bg-surface-2 border border-border flex items-center justify-center"
>
<
span
className=
"text-lg font-bold text-gold"
>
{
profile
.
display_name
?.
charAt
(
0
)
||
'L'
}
</
span
>
<
section
className=
"flex items-center gap-4"
>
<
div
className=
"w-14 h-14 rounded-full bg-surface-2 flex items-center justify-center text-xl font-bold text-gold"
>
{
profile
.
display_name
?.
charAt
(
0
)
||
'?'
}
</
div
>
<
div
>
<
h
2
className=
"text-xl font-bold text-text-primary
"
>
{
profile
.
display_name
}
</
h
2
>
<
span
className=
"text-sm text-text-muted"
>
المستوى
{
profile
.
level
}
</
span
>
<
div
className=
"flex-1 min-w-0"
>
<
h
1
className=
"text-xl font-bold text-text-primary truncate
"
>
اهلا
{
profile
.
display_name
}
</
h
1
>
<
p
className=
"text-sm text-text-muted"
>
المستوى
{
profile
.
level
}
·
{
profile
.
elo_blitz
}
تقييم
</
p
>
</
div
>
</
motion
.
div
>
</
section
>
)
}
{
/* P
lay Button
*/
}
<
motion
.
div
variants=
{
fadeUp
}
>
{
/* P
rimary CTA
*/
}
<
section
>
<
motion
.
button
onClick=
{
()
=>
{
playSound
(
'tap'
)
navigate
(
'/play'
)
}
}
className=
"btn-gold w-full py-6 rounded-[var(--radius-hero)] text-2xl font-bold"
whileTap=
{
{
scale
:
0.96
,
y
:
2
}
}
onClick=
{
()
=>
{
playSound
(
'tap'
);
navigate
(
'/play'
)
}
}
className=
"w-full flex items-center justify-center gap-3 py-5 bg-gold rounded-[var(--radius-large)] text-background text-xl font-bold"
whileTap=
{
{
scale
:
0.97
}
}
transition=
{
{
duration
:
0.15
}
}
>
<
Play
size=
{
32
}
fill=
"currentColor"
/>
<
span
>
العب الان
</
span
>
<
Play
size=
{
28
}
fill=
"currentColor"
/>
العب الان
</
motion
.
button
>
</
motion
.
div
>
</
section
>
{
/* Stats */
}
{
/* Stats
row
*/
}
{
profile
&&
(
<
motion
.
div
variants=
{
fadeUp
}
className=
"grid grid-cols-3 gap-3"
>
<
StatCard
icon=
{
<
TrendingUp
size=
{
18
}
className=
"text-gold"
/>
}
value=
{
profile
.
elo_blitz
}
label=
"تقييم"
/
>
<
StatCard
icon=
{
<
Swords
size=
{
18
}
className=
"text-cyan"
/>
}
value=
{
profile
.
total_games_played
}
label=
"مباراة"
/
>
<
StatCard
icon=
{
<
Flame
size=
{
18
}
className=
"text-coral"
/>
}
value=
{
profile
.
win_streak
}
label=
"سلسلة فوز"
/
>
</
motion
.
div
>
<
section
className=
"grid grid-cols-3 gap-3"
>
<
div
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-4 text-center"
>
<
TrendingUp
size=
{
18
}
className=
"text-gold mx-auto mb-1"
/>
<
p
className=
"text-lg font-bold text-text-primary tabular-nums"
>
{
profile
.
elo_blitz
}
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
تقييم
</
p
>
</
div
>
<
div
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-4 text-center"
>
<
Swords
size=
{
18
}
className=
"text-cyan mx-auto mb-1"
/>
<
p
className=
"text-lg font-bold text-text-primary tabular-nums"
>
{
profile
.
total_games_played
}
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
مباراة
</
p
>
</
div
>
<
div
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-4 text-center"
>
<
Flame
size=
{
18
}
className=
"text-coral mx-auto mb-1"
/>
<
p
className=
"text-lg font-bold text-text-primary tabular-nums"
>
{
winRate
}
%
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
نسبة الفوز
</
p
>
</
div
>
</
section
>
)
}
{
/* Quick Actions */
}
<
motion
.
div
variants=
{
fadeUp
}
className=
"grid grid-cols-2 gap-4"
>
<
QuickAction
icon=
{
<
Bot
size=
{
24
}
className=
"text-purple"
/>
}
title=
"العب ضد روبوت"
subtitle=
"تدريب وتحسين"
<
section
className=
"grid grid-cols-2 gap-3"
>
<
button
onClick=
{
()
=>
{
playSound
(
'tap'
);
navigate
(
'/bot-select'
)
}
}
/>
<
QuickAction
icon=
{
<
Users
size=
{
24
}
className=
"text-gold"
/>
}
title=
"تحدى صديق"
subtitle=
"ارسل دعوة"
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-5 flex flex-col items-center gap-3 hover:bg-surface-3 transition-colors"
>
<
Bot
size=
{
28
}
className=
"text-purple"
/>
<
div
className=
"text-center"
>
<
p
className=
"text-sm font-semibold text-text-primary"
>
ضد الروبوت
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
تدريب
</
p
>
</
div
>
</
button
>
<
button
onClick=
{
()
=>
{
playSound
(
'tap'
);
navigate
(
'/friends'
)
}
}
/>
</
motion
.
div
>
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-5 flex flex-col items-center gap-3 hover:bg-surface-3 transition-colors"
>
<
Users
size=
{
28
}
className=
"text-royal-blue"
/>
<
div
className=
"text-center"
>
<
p
className=
"text-sm font-semibold text-text-primary"
>
تحدى صديق
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
دعوة
</
p
>
</
div
>
</
button
>
<
button
onClick=
{
()
=>
{
playSound
(
'tap'
);
navigate
(
'/tournaments'
)
}
}
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-5 flex flex-col items-center gap-3 hover:bg-surface-3 transition-colors"
>
<
Trophy
size=
{
28
}
className=
"text-gold"
/>
<
div
className=
"text-center"
>
<
p
className=
"text-sm font-semibold text-text-primary"
>
البطولات
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
تنافس
</
p
>
</
div
>
</
button
>
<
button
onClick=
{
()
=>
{
playSound
(
'tap'
);
navigate
(
'/leaderboard'
)
}
}
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-5 flex flex-col items-center gap-3 hover:bg-surface-3 transition-colors"
>
<
TrendingUp
size=
{
28
}
className=
"text-cyan"
/>
<
div
className=
"text-center"
>
<
p
className=
"text-sm font-semibold text-text-primary"
>
المتصدرين
</
p
>
<
p
className=
"text-[11px] text-text-muted"
>
ترتيب
</
p
>
</
div
>
</
button
>
</
section
>
{
/* Daily Tip */
}
<
motion
.
div
variants=
{
fadeUp
}
className=
"card
"
>
<
div
className=
"flex items-center gap-2 mb-
3
"
>
<
section
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-4
"
>
<
div
className=
"flex items-center gap-2 mb-
2
"
>
<
Lightbulb
size=
{
16
}
className=
"text-gold"
/>
<
span
className=
"text-sm font-semibold text-
gold
"
>
نصيحة اليوم
</
span
>
<
span
className=
"text-sm font-semibold text-
text-secondary
"
>
نصيحة اليوم
</
span
>
</
div
>
<
p
className=
"text-sm text-text-secondary leading-relaxed"
>
{
todayTip
}
</
p
>
</
motion
.
div
>
</
motion
.
div
>
</
PageTransition
>
)
}
function
StatCard
({
icon
,
value
,
label
}:
{
icon
:
React
.
ReactNode
;
value
:
number
;
label
:
string
})
{
return
(
<
div
className=
"card flex flex-col items-center gap-1 py-3"
>
{
icon
}
<
span
className=
"text-lg font-bold text-text-primary"
>
{
value
}
</
span
>
<
span
className=
"text-[11px] text-text-muted"
>
{
label
}
</
span
>
</
div
>
)
}
function
QuickAction
({
icon
,
title
,
subtitle
,
onClick
}:
{
icon
:
React
.
ReactNode
;
title
:
string
;
subtitle
:
string
;
onClick
:
()
=>
void
})
{
return
(
<
motion
.
button
className=
"card flex flex-col items-center gap-2 py-5 cursor-pointer"
onClick=
{
onClick
}
whileTap=
{
{
scale
:
0.96
}
}
transition=
{
{
duration
:
0.15
}
}
>
{
icon
}
<
div
className=
"text-center"
>
<
p
className=
"text-sm font-semibold text-text-primary"
>
{
title
}
</
p
>
<
p
className=
"text-[11px] text-text-muted mt-0.5"
>
{
subtitle
}
</
p
>
</
section
>
</
div
>
</
motion
.
butt
on
>
</
PageTransiti
on
>
)
}
src/pages/PlayPage.tsx
View file @
932ff403
...
...
@@ -13,16 +13,6 @@ const CATEGORIES = [
{
key
:
'classical'
,
label
:
'كلاسيكي'
,
icon
:
Hourglass
},
]
as
const
const
stagger
=
{
hidden
:
{},
show
:
{
transition
:
{
staggerChildren
:
0.06
}
},
}
const
fadeUp
=
{
hidden
:
{
opacity
:
0
,
y
:
12
},
show
:
{
opacity
:
1
,
y
:
0
,
transition
:
{
duration
:
0.2
}
},
}
export
function
PlayPage
()
{
const
navigate
=
useNavigate
()
const
[
selectedTC
,
setSelectedTC
]
=
useState
<
string
>
(
'blitz_5_0'
)
...
...
@@ -36,68 +26,56 @@ export function PlayPage() {
return
(
<
PageTransition
>
<
motion
.
div
variants=
{
stagger
}
initial=
"hidden"
animate=
"show"
className=
"flex flex-col gap-6"
>
<
div
className=
"flex flex-col gap-8"
>
{
/* Page Title */
}
<
motion
.
div
variants=
{
fadeUp
}
>
<
h1
className=
"text-2xl font-bold text-text-primary"
>
اختر اللعبة
</
h1
>
</
motion
.
div
>
<
h1
className=
"text-2xl font-bold text-text-primary"
>
العب
</
h1
>
{
/* Featured Game: Chess */
}
<
motion
.
div
variants=
{
fadeUp
}
className=
"card border-gold/20"
>
<
div
className=
"flex items-center gap-4"
>
<
div
className=
"w-14 h-14 rounded-[var(--radius-standard)] bg-gold/10 border border-gold/20 flex items-center justify-center"
>
<
span
className=
"text-2xl"
>
♟️
</
span
>
{
/* Game Selection */
}
<
section
className=
"flex flex-col gap-3"
>
{
/* Chess - primary */
}
<
div
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-4 flex items-center gap-4 border border-gold/20"
>
<
div
className=
"w-12 h-12 rounded-[var(--radius-tiny)] bg-gold/10 flex items-center justify-center text-2xl"
>
♟️
</
div
>
<
div
className=
"flex-1"
>
<
span
className=
"text-lg font-bold text-text-primary"
>
{
chessGame
.
nameAr
}
</
span
>
<
p
className=
"text-sm text-text-secondary mt-0.5"
>
العب شطرنج اونلاين ضد لاعبين حقيقيين
</
p
>
<
div
className=
"flex items-center gap-2 mt-2"
>
<
div
className=
"w-2 h-2 rounded-full bg-online animate-pulse-soft"
/>
<
span
className=
"text-xs text-online font-medium"
>
اونلاين
</
span
>
<
p
className=
"font-semibold text-text-primary"
>
{
chessGame
.
nameAr
}
</
p
>
<
div
className=
"flex items-center gap-1.5 mt-1"
>
<
span
className=
"w-2 h-2 rounded-full bg-online"
/>
<
span
className=
"text-xs text-text-muted"
>
متاح الان
</
span
>
</
div
>
</
div
>
</
div
>
</
motion
.
div
>
{
/* Other Games (locked) */
}
<
motion
.
div
variants=
{
fadeUp
}
className=
"grid grid-cols-2 gap-3"
>
{
otherGames
.
map
((
game
)
=>
(
<
div
key=
{
game
.
key
}
className=
"card flex items-center gap-2 py-3 px-3 opacity-50"
>
<
Lock
size=
{
14
}
className=
"text-text-muted"
/>
<
div
>
<
span
className=
"text-sm font-medium text-text-muted block"
>
{
game
.
nameAr
}
</
span
>
<
span
className=
"text-[11px] text-text-muted"
>
قريبا
</
span
>
{
/* Other games - locked row */
}
<
div
className=
"grid grid-cols-4 gap-2"
>
{
otherGames
.
map
((
game
)
=>
(
<
div
key=
{
game
.
key
}
className=
"bg-surface-2 rounded-[var(--radius-tiny)] py-3 flex flex-col items-center gap-1 opacity-40"
>
<
Lock
size=
{
14
}
className=
"text-text-muted"
/>
<
span
className=
"text-[10px] text-text-muted"
>
{
game
.
nameAr
}
</
span
>
</
div
>
</
div
>
))
}
</
motion
.
div
>
))
}
</
div
>
</
section
>
{
/* Time Control */
}
<
motion
.
div
variants=
{
fadeUp
}
className=
"space-y
-4"
>
<
h2
className=
"text-lg font-
bold text-text-primary"
>
نظام
الوقت
</
h2
>
<
section
className=
"flex flex-col gap
-4"
>
<
h2
className=
"text-lg font-
semibold text-text-primary"
>
الوقت
</
h2
>
{
/* Category
T
abs */
}
<
div
className=
"flex gap-2"
>
{
/* Category
t
abs */
}
<
div
className=
"flex gap-2
overflow-x-auto
"
>
{
CATEGORIES
.
map
((
cat
)
=>
{
const
isActive
=
activeCategory
===
cat
.
key
const
Icon
=
cat
.
icon
return
(
<
button
key=
{
cat
.
key
}
className=
{
`flex items-center gap-1.5 px-
4 py-2 rounded-[var(--radius-tiny)] text-sm font-semibold transition-all
${
className=
{
`flex items-center gap-1.5 px-
3 py-2 rounded-[var(--radius-tiny)] text-sm font-medium whitespace-nowrap transition-colors
${
isActive
? 'bg-gold text-background'
: 'bg-surface-2 text-text-muted
border border-border
'
: 'bg-surface-2 text-text-muted'
}`
}
onClick=
{
()
=>
{
playSound
(
'tap'
)
...
...
@@ -112,39 +90,37 @@ export function PlayPage() {
})
}
</
div
>
{
/* Time
Options Grid
*/
}
<
div
className=
"grid grid-cols-3 gap-
3
"
>
{
/* Time
options
*/
}
<
div
className=
"grid grid-cols-3 gap-
2
"
>
{
Object
.
entries
(
TIME_CONTROLS
)
.
filter
(([,
v
])
=>
v
.
category
===
activeCategory
)
.
map
(([
key
,
tc
])
=>
{
const
isSelected
=
selectedTC
===
key
return
(
<
motion
.
button
<
button
key=
{
key
}
className=
{
`py-4
px-3 text-center font-bold rounded-[var(--radius-standard)] border
transition-all ${
className=
{
`py-4
text-center font-semibold rounded-[var(--radius-standard)]
transition-all ${
isSelected
? 'b
order-gold bg-gold/10
text-gold'
: 'b
order-border bg-surface-1 text-text-secondary
'
? 'b
g-gold/10 border-2 border-gold
text-gold'
: 'b
g-surface-2 border border-transparent text-text-secondary hover:bg-surface-3
'
}`
}
whileTap=
{
{
scale
:
0.95
}
}
transition=
{
{
duration
:
0.15
}
}
onClick=
{
()
=>
{
playSound
(
'tap'
)
setSelectedTC
(
key
)
}
}
>
{
tc
.
labelAr
}
</
motion
.
button
>
</
button
>
)
})
}
</
div
>
</
motion
.
div
>
</
section
>
{
/* Action
Button
s */
}
<
motion
.
div
variants=
{
fadeUp
}
className=
"flex flex-col gap-3 mt-2
"
>
{
/* Actions */
}
<
section
className=
"flex flex-col gap-3
"
>
<
motion
.
button
className=
"
btn-gold w-full py-5 rounded-[var(--radius-large)]
text-lg font-bold"
whileTap=
{
{
scale
:
0.9
6
,
y
:
2
}
}
className=
"
w-full flex items-center justify-center gap-3 py-5 bg-gold rounded-[var(--radius-large)] text-background
text-lg font-bold"
whileTap=
{
{
scale
:
0.9
7
}
}
transition=
{
{
duration
:
0.15
}
}
onClick=
{
()
=>
{
playSound
(
'tap'
)
...
...
@@ -152,12 +128,12 @@ export function PlayPage() {
}
}
>
<
Swords
size=
{
22
}
/>
<
span
>
البحث عن خصم
</
span
>
البحث عن خصم
</
motion
.
button
>
<
motion
.
button
className=
"
btn-ghost w-full py-4 rounded-[var(--radius-large)] text-base font-semibold
"
whileTap=
{
{
scale
:
0.9
6
}
}
className=
"
w-full flex items-center justify-center gap-3 py-4 bg-surface-2 border border-border rounded-[var(--radius-large)] text-text-primary text-base font-medium
"
whileTap=
{
{
scale
:
0.9
7
}
}
transition=
{
{
duration
:
0.15
}
}
onClick=
{
()
=>
{
playSound
(
'tap'
)
...
...
@@ -165,10 +141,10 @@ export function PlayPage() {
}
}
>
<
Cpu
size=
{
20
}
className=
"text-purple"
/>
<
span
>
العب ضد الروبوت
</
span
>
ضد الروبوت
</
motion
.
button
>
</
motion
.
div
>
</
motion
.
div
>
</
section
>
</
div
>
</
PageTransition
>
)
}
src/pages/ProfilePage.tsx
View file @
932ff403
import
{
useState
}
from
'react'
import
{
motion
,
AnimatePresence
}
from
'framer-motion'
import
{
Trophy
,
Target
,
Flame
,
TrendingUp
,
Pencil
,
X
,
LogOut
,
S
tar
,
Shield
}
from
'lucide-react'
import
{
Trophy
,
Target
,
Flame
,
TrendingUp
,
Pencil
,
X
,
LogOut
,
S
words
}
from
'lucide-react'
import
{
useAuthStore
}
from
'../stores/authStore'
import
{
useNotificationStore
}
from
'../stores/notificationStore'
import
{
PageTransition
}
from
'../components/layout/PageTransition'
import
{
Button
}
from
'../components/ui/Button'
import
{
supabase
}
from
'../lib/supabase'
const
stagger
=
{
hidden
:
{
opacity
:
0
},
show
:
{
opacity
:
1
,
transition
:
{
staggerChildren
:
0.08
}
},
}
const
item
=
{
hidden
:
{
opacity
:
0
,
y
:
16
,
scale
:
0.93
},
show
:
{
opacity
:
1
,
y
:
0
,
scale
:
1
,
transition
:
{
type
:
'spring'
as
const
,
stiffness
:
380
,
damping
:
22
}
},
}
const
HEX_CLIP
=
'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)'
export
function
ProfilePage
()
{
const
{
user
,
profile
,
setProfile
}
=
useAuthStore
()
const
{
showToast
}
=
useNotificationStore
()
...
...
@@ -78,366 +66,161 @@ export function ProfilePage() {
const
xpPercent
=
Math
.
min
((
currentXpInLevel
/
xpForNextLevel
)
*
100
,
100
)
const
ratings
=
[
{
label
:
'رصاصة'
,
value
:
profile
.
elo_bullet
,
color
:
'#FF5252'
,
borderColor
:
'border-[#FF5252]'
},
{
label
:
'خاطف'
,
value
:
profile
.
elo_blitz
,
color
:
'#FFC83D'
,
borderColor
:
'border-[#FFC83D]'
},
{
label
:
'سريع'
,
value
:
profile
.
elo_rapid
,
color
:
'#00E5CC'
,
borderColor
:
'border-[#00E5CC]'
},
{
label
:
'كلاسيكي'
,
value
:
profile
.
elo_classical
,
color
:
'#B44DFF'
,
borderColor
:
'border-[#B44DFF]'
},
]
const
stats
=
[
{
icon
:
<
TrendingUp
size=
{
20
}
className=
"text-[#B44DFF]"
/>,
value
:
profile
.
total_games_played
,
label
:
'مباريات'
,
ringColor
:
'#B44DFF'
},
{
icon
:
<
Trophy
size=
{
20
}
className=
"text-[#FFC83D]"
/>,
value
:
profile
.
total_wins
,
label
:
'انتصارات'
,
ringColor
:
'#FFC83D'
},
{
icon
:
<
Target
size=
{
20
}
className=
"text-[#00E5CC]"
/>,
value
:
`
${
winRate
}
%`
,
label
:
'نسبة الفوز'
,
ringColor
:
'#00E5CC'
},
{
icon
:
<
Flame
size=
{
20
}
className=
"text-[#FF5252]"
/>,
value
:
profile
.
best_win_streak
,
label
:
'افضل سلسلة'
,
ringColor
:
'#FF5252'
},
{
label
:
'رصاصة'
,
value
:
profile
.
elo_bullet
,
icon
:
'⚡'
},
{
label
:
'خاطف'
,
value
:
profile
.
elo_blitz
,
icon
:
'🔥'
},
{
label
:
'سريع'
,
value
:
profile
.
elo_rapid
,
icon
:
'⏱'
},
{
label
:
'كلاسيكي'
,
value
:
profile
.
elo_classical
,
icon
:
'♟️'
},
]
return
(
<
PageTransition
>
<
motion
.
div
className=
"flex flex-col gap-6"
variants=
{
stagger
}
initial=
"hidden"
animate=
"show"
>
{
/* === PLAYER CARD / NAMEPLATE === */
}
<
motion
.
div
variants=
{
item
}
>
<
div
className=
"relative overflow-hidden rounded-[20px] p-5"
style=
{
{
border
:
'3px solid var(--color-border)'
,
background
:
'linear-gradient(135deg, rgba(255,200,60,0.05) 0%, rgba(180,77,255,0.05) 100%)'
,
boxShadow
:
'inset 0 2px 4px rgba(255,255,255,0.04), inset 0 -2px 6px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.4)'
,
}
}
>
{
/* Decorative corner stars */
}
<
Star
size=
{
24
}
className=
"absolute top-3 left-3 text-[#FFC83D] opacity-[0.08]"
/>
<
Star
size=
{
18
}
className=
"absolute top-5 left-10 text-[#FFC83D] opacity-[0.06]"
/>
<
Star
size=
{
20
}
className=
"absolute bottom-4 right-4 text-[#B44DFF] opacity-[0.07]"
/>
<
Star
size=
{
14
}
className=
"absolute bottom-6 right-10 text-[#B44DFF] opacity-[0.05]"
/>
{
/* Subtle background pattern */
}
<
div
className=
"absolute inset-0 opacity-[0.02]"
style=
{
{
backgroundImage
:
'repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(255,200,60,0.5) 10px, rgba(255,200,60,0.5) 11px)'
,
}
}
/>
<
div
className=
"relative flex flex-col items-center gap-3"
>
{
/* Hexagonal Avatar */
}
<
div
className=
"relative"
>
<
motion
.
div
className=
"flex items-center justify-center"
style=
{
{
width
:
80
,
height
:
80
,
clipPath
:
HEX_CLIP
,
background
:
'linear-gradient(135deg, rgba(255,200,60,0.25), rgba(180,77,255,0.15))'
,
border
:
'none'
,
}
}
initial=
{
{
scale
:
0
,
rotate
:
-
30
}
}
animate=
{
{
scale
:
1
,
rotate
:
0
}
}
transition=
{
{
type
:
'spring'
,
stiffness
:
300
,
damping
:
18
}
}
>
<
div
className=
"flex items-center justify-center"
style=
{
{
width
:
74
,
height
:
74
,
clipPath
:
HEX_CLIP
,
background
:
'linear-gradient(135deg, var(--color-surface-2), var(--color-surface-3))'
,
}
}
>
<
span
className=
"text-3xl font-black text-[#FFC83D]"
>
{
profile
.
display_name
?.
charAt
(
0
)
||
'?'
}
</
span
>
</
div
>
</
motion
.
div
>
{
/* Gold border hex overlay */
}
<
div
className=
"absolute inset-0 pointer-events-none"
style=
{
{
clipPath
:
HEX_CLIP
,
border
:
'3px solid #FFC83D'
,
width
:
80
,
height
:
80
,
}
}
/>
{
/* Level badge */
}
<
motion
.
div
className=
"absolute -bottom-2 left-1/2 -translate-x-1/2 w-7 h-7 rounded-full flex items-center justify-center"
style=
{
{
background
:
'linear-gradient(to bottom, #FFE066, #FFC83D)'
,
border
:
'2px solid var(--color-background)'
,
boxShadow
:
'0 2px 8px rgba(255,200,60,0.4)'
,
}
}
initial=
{
{
scale
:
0
}
}
animate=
{
{
scale
:
1
}
}
transition=
{
{
type
:
'spring'
,
stiffness
:
500
,
damping
:
20
,
delay
:
0.2
}
}
>
<
span
className=
"text-[10px] font-black text-[#0B0E1A]"
>
{
profile
.
level
}
</
span
>
</
motion
.
div
>
</
div
>
{
/* Player name + username */
}
<
div
className=
"text-center mt-1"
>
<
h1
className=
"text-2xl font-black tracking-tight"
>
{
profile
.
display_name
}
</
h1
>
<
p
className=
"text-sm text-text-muted font-bold"
>
@
{
profile
.
username
}
</
p
>
{
profile
.
bio
&&
(
<
p
className=
"text-xs text-text-secondary mt-1.5 max-w-[240px] mx-auto line-clamp-2 leading-relaxed"
>
{
profile
.
bio
}
</
p
>
)
}
</
div
>
{
/* XP Progress bar */
}
<
div
className=
"w-full mt-2"
>
<
div
className=
"flex items-center justify-between mb-1.5"
>
<
span
className=
"text-xs font-black text-text-secondary"
>
المستوى
{
profile
.
level
}
</
span
>
<
span
className=
"text-xs font-black text-[#FFC83D]"
>
XP
{
currentXpInLevel
}
</
span
>
</
div
>
<
div
className=
"w-full overflow-hidden"
style=
{
{
height
:
16
,
borderRadius
:
10
,
border
:
'2px solid var(--color-border)'
,
background
:
'var(--color-surface-3)'
,
boxShadow
:
'inset 0 2px 4px rgba(0,0,0,0.4)'
,
}
}
>
<
motion
.
div
className=
"h-full relative"
style=
{
{
borderRadius
:
8
,
background
:
'linear-gradient(to left, #FFC83D, #FFE066)'
,
backgroundImage
:
'repeating-linear-gradient(90deg, transparent, transparent 8px, rgba(255,255,255,0.1) 8px, rgba(255,255,255,0.1) 9px)'
,
}
}
initial=
{
{
width
:
0
}
}
animate=
{
{
width
:
`${xpPercent}%`
}
}
transition=
{
{
duration
:
1.2
,
ease
:
'easeOut'
,
delay
:
0.4
}
}
/>
</
div
>
</
div
>
</
div
>
<
div
className=
"flex flex-col gap-8"
>
{
/* Profile Header */
}
<
section
className=
"flex flex-col items-center gap-4"
>
{
/* Avatar */
}
<
div
className=
"w-20 h-20 rounded-full bg-surface-2 border-2 border-border flex items-center justify-center"
>
<
span
className=
"text-3xl font-bold text-gold"
>
{
profile
.
display_name
?.
charAt
(
0
)
||
'?'
}
</
span
>
</
div
>
</
motion
.
div
>
{
/* === RATINGS SECTION === */
}
<
motion
.
div
variants=
{
item
}
className=
"flex flex-col gap-3"
>
{
/* Section header */
}
<
div
className=
"flex items-center gap-3"
>
<
h2
className=
"text-lg font-black"
>
التقييمات
</
h2
>
<
div
className=
"flex-1 h-[2px]"
style=
{
{
background
:
'linear-gradient(to left, transparent, rgba(255,200,60,0.4))'
}
}
/>
{
/* Name + username */
}
<
div
className=
"text-center"
>
<
h1
className=
"text-2xl font-bold text-text-primary"
>
{
profile
.
display_name
}
</
h1
>
<
p
className=
"text-sm text-text-muted mt-0.5"
>
@
{
profile
.
username
}
</
p
>
{
profile
.
bio
&&
(
<
p
className=
"text-sm text-text-secondary mt-2 max-w-[280px]"
>
{
profile
.
bio
}
</
p
>
)
}
</
div
>
{
/* 2x2 Rating grid */
}
<
div
className=
"grid grid-cols-2 gap-3"
>
{
ratings
.
map
((
rating
,
i
)
=>
(
{
/* XP Bar */
}
<
div
className=
"w-full max-w-[300px]"
>
<
div
className=
"flex items-center justify-between mb-1.5"
>
<
span
className=
"text-xs text-text-muted"
>
المستوى
{
profile
.
level
}
</
span
>
<
span
className=
"text-xs text-text-muted tabular-nums"
>
{
currentXpInLevel
}
/
{
xpForNextLevel
}
XP
</
span
>
</
div
>
<
div
className=
"h-2 bg-surface-3 rounded-full overflow-hidden"
>
<
motion
.
div
key=
{
rating
.
label
}
className=
"relative flex flex-col items-center justify-center overflow-hidden"
style=
{
{
height
:
90
,
clipPath
:
HEX_CLIP
,
border
:
`3px solid ${rating.color}`
,
background
:
`linear-gradient(to bottom, ${rating.color}10, var(--color-surface-1))`
,
boxShadow
:
`inset 0 2px 6px rgba(0,0,0,0.3), 0 0 12px ${rating.color}15`
,
}
}
initial=
{
{
opacity
:
0
,
scale
:
0.8
}
}
animate=
{
{
opacity
:
1
,
scale
:
1
}
}
transition=
{
{
type
:
'spring'
,
stiffness
:
400
,
damping
:
22
,
delay
:
0.2
+
i
*
0.07
}
}
>
{
/* Inner hex to create border effect */
}
<
div
className=
"absolute inset-0 flex flex-col items-center justify-center"
style=
{
{
borderTop
:
`4px solid ${rating.color}`
,
}
}
>
<
span
className=
"text-2xl font-black mt-2"
>
{
rating
.
value
}
</
span
>
<
span
className=
"text-[10px] font-bold text-text-muted"
>
{
rating
.
label
}
</
span
>
</
div
>
</
motion
.
div
>
))
}
</
div
>
</
motion
.
div
>
{
/* === STATS SECTION === */
}
<
motion
.
div
variants=
{
item
}
className=
"flex flex-col gap-3"
>
{
/* Section header */
}
<
div
className=
"flex items-center gap-3"
>
<
h2
className=
"text-lg font-black"
>
الاحصائيات
</
h2
>
<
div
className=
"flex-1 h-[2px]"
style=
{
{
background
:
'linear-gradient(to left, transparent, rgba(0,229,204,0.4))'
}
}
/>
className=
"h-full bg-gold rounded-full"
initial=
{
{
width
:
0
}
}
animate=
{
{
width
:
`${xpPercent}%`
}
}
transition=
{
{
duration
:
0.8
,
ease
:
'easeOut'
}
}
/>
</
div
>
</
div
>
</
section
>
{
/* 4 circular medal items */
}
<
div
className=
"flex items-center justify-between gap-2"
>
{
stats
.
map
((
stat
,
i
)
=>
(
<
motion
.
div
key=
{
stat
.
label
}
className=
"flex flex-col items-center gap-1.5"
initial=
{
{
opacity
:
0
,
y
:
12
}
}
animate=
{
{
opacity
:
1
,
y
:
0
}
}
transition=
{
{
type
:
'spring'
,
stiffness
:
400
,
damping
:
22
,
delay
:
0.25
+
i
*
0.07
}
}
>
{
/* Circular badge with ring */
}
<
div
className=
"relative flex items-center justify-center"
style=
{
{
width
:
64
,
height
:
64
,
borderRadius
:
'50%'
,
border
:
`3px solid ${stat.ringColor}`
,
background
:
`linear-gradient(135deg, ${stat.ringColor}12, var(--color-surface-2))`
,
boxShadow
:
`inset 0 2px 6px rgba(0,0,0,0.3), 0 0 8px ${stat.ringColor}15`
,
}
}
>
<
div
className=
"flex flex-col items-center gap-0.5"
>
{
stat
.
icon
}
<
span
className=
"text-xs font-black mt-0.5"
>
{
stat
.
value
}
</
span
>
</
div
>
{
/* Ratings */
}
<
section
>
<
h2
className=
"text-lg font-semibold text-text-primary mb-3"
>
التقييمات
</
h2
>
<
div
className=
"grid grid-cols-2 gap-3"
>
{
ratings
.
map
((
r
)
=>
(
<
div
key=
{
r
.
label
}
className=
"bg-surface-2 rounded-[var(--radius-standard)] p-4 flex items-center gap-3"
>
<
span
className=
"text-xl"
>
{
r
.
icon
}
</
span
>
<
div
>
<
p
className=
"text-lg font-bold text-text-primary tabular-nums"
>
{
r
.
value
}
</
p
>
<
p
className=
"text-xs text-text-muted"
>
{
r
.
label
}
</
p
>
</
div
>
<
span
className=
"text-[9px] text-text-muted font-bold text-center leading-tight"
>
{
stat
.
label
}
</
span
>
</
motion
.
div
>
</
div
>
))
}
</
div
>
</
motion
.
div
>
</
section
>
{
/* Stats */
}
<
section
>
<
h2
className=
"text-lg font-semibold text-text-primary mb-3"
>
الاحصائيات
</
h2
>
<
div
className=
"grid grid-cols-4 gap-2"
>
<
StatItem
icon=
{
<
Swords
size=
{
18
}
className=
"text-text-secondary"
/>
}
value=
{
profile
.
total_games_played
}
label=
"مباريات"
/>
<
StatItem
icon=
{
<
Trophy
size=
{
18
}
className=
"text-gold"
/>
}
value=
{
profile
.
total_wins
}
label=
"فوز"
/>
<
StatItem
icon=
{
<
Target
size=
{
18
}
className=
"text-cyan"
/>
}
value=
{
`${winRate}%`
}
label=
"نسبة"
/>
<
StatItem
icon=
{
<
Flame
size=
{
18
}
className=
"text-coral"
/>
}
value=
{
profile
.
best_win_streak
}
label=
"سلسلة"
/>
</
div
>
</
section
>
{
/* === ACTION BUTTONS === */
}
<
motion
.
div
className=
"flex flex-col items-center gap-3 mt-1"
variants=
{
item
}
>
{
/* Edit profile button */
}
<
motion
.
button
className=
"w-[75%] flex items-center justify-center gap-2.5 py-3.5 rounded-2xl font-black text-sm"
style=
{
{
border
:
'3px solid var(--color-border)'
,
background
:
'var(--color-surface-2)'
,
boxShadow
:
'inset 0 1px 0 rgba(255,255,255,0.04), inset 0 -2px 4px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3)'
,
}
}
whileTap=
{
{
scale
:
0.94
,
y
:
2
}
}
whileHover=
{
{
scale
:
1.02
,
borderColor
:
'rgba(255,200,60,0.5)'
}
}
{
/* Actions */
}
<
section
className=
"flex flex-col gap-3"
>
<
button
onClick=
{
openEdit
}
className=
"w-full flex items-center justify-center gap-2 py-3 bg-surface-2 border border-border rounded-[var(--radius-standard)] text-sm font-medium text-text-primary hover:bg-surface-3 transition-colors"
>
<
Pencil
size=
{
16
}
className=
"text-
[#FFC83D]
"
/>
<
span
>
تعديل الملف الشخصي
</
span
>
</
motion
.
button
>
<
Pencil
size=
{
16
}
className=
"text-
text-secondary
"
/>
تعديل الملف الشخصي
</
button
>
{
/* Logout button */
}
<
motion
.
button
className=
"w-[60%] flex items-center justify-center gap-2 py-2.5 rounded-xl font-bold text-xs"
style=
{
{
border
:
'2px solid rgba(255,82,82,0.4)'
,
background
:
'rgba(255,82,82,0.08)'
,
color
:
'#FF5252'
,
boxShadow
:
'0 2px 8px rgba(255,82,82,0.15)'
,
}
}
whileTap=
{
{
scale
:
0.92
,
y
:
1
}
}
<
button
onClick=
{
handleLogout
}
className=
"w-full flex items-center justify-center gap-2 py-3 rounded-[var(--radius-standard)] text-sm font-medium text-coral hover:bg-coral/10 transition-colors"
>
<
LogOut
size=
{
1
4
}
/>
<
span
>
تسجيل الخروج
</
span
>
</
motion
.
button
>
</
motion
.
div
>
</
motion
.
div
>
<
LogOut
size=
{
1
6
}
/>
تسجيل الخروج
</
button
>
</
section
>
</
div
>
{
/*
=== EDIT MODAL ===
*/
}
{
/*
Edit Modal
*/
}
<
AnimatePresence
>
{
editOpen
&&
(
<
motion
.
div
className=
"fixed inset-0 z-50 flex items-center justify-center px-
6
"
className=
"fixed inset-0 z-50 flex items-center justify-center px-
4
"
initial=
{
{
opacity
:
0
}
}
animate=
{
{
opacity
:
1
}
}
exit=
{
{
opacity
:
0
}
}
transition=
{
{
duration
:
0.2
}
}
>
<
motion
.
div
className=
"absolute inset-0 bg-background/8
5
backdrop-blur-sm"
<
div
className=
"absolute inset-0 bg-background/8
0
backdrop-blur-sm"
onClick=
{
()
=>
setEditOpen
(
false
)
}
initial=
{
{
opacity
:
0
}
}
animate=
{
{
opacity
:
1
}
}
exit=
{
{
opacity
:
0
}
}
/>
<
motion
.
div
className=
"relative w-full max-w-[360px] flex flex-col gap-5 overflow-hidden"
style=
{
{
borderRadius
:
20
,
border
:
'3px solid var(--color-border)'
,
background
:
'var(--color-surface-1)'
,
boxShadow
:
'inset 0 2px 4px rgba(255,255,255,0.03), inset 0 -2px 8px rgba(0,0,0,0.3), 0 16px 48px rgba(0,0,0,0.6)'
,
}
}
initial=
{
{
scale
:
0.85
,
opacity
:
0
,
y
:
30
}
}
animate=
{
{
scale
:
1
,
opacity
:
1
,
y
:
0
}
}
exit=
{
{
scale
:
0.85
,
opacity
:
0
,
y
:
30
}
}
transition=
{
{
type
:
'spring'
,
stiffness
:
400
,
damping
:
22
}
}
className=
"relative w-full max-w-[360px] bg-surface-1 rounded-[var(--radius-hero)] border border-border p-6 flex flex-col gap-5"
style=
{
{
boxShadow
:
'var(--shadow-3)'
}
}
initial=
{
{
scale
:
0.95
,
opacity
:
0
}
}
animate=
{
{
scale
:
1
,
opacity
:
1
}
}
exit=
{
{
scale
:
0.95
,
opacity
:
0
}
}
transition=
{
{
duration
:
0.2
}
}
>
{
/* Ribbon banner header */
}
<
div
className=
"relative flex items-center justify-center py-3"
style=
{
{
background
:
'linear-gradient(to bottom, rgba(255,200,60,0.12), transparent)'
,
borderBottom
:
'2px solid var(--color-border)'
,
clipPath
:
'polygon(0 0, 100% 0, 95% 100%, 5% 100%)'
,
}
}
>
<
Shield
size=
{
18
}
className=
"text-[#FFC83D] ml-2"
/>
<
h2
className=
"text-lg font-black"
>
تعديل الملف
</
h2
>
<
div
className=
"flex items-center justify-between"
>
<
h2
className=
"text-lg font-bold"
>
تعديل الملف
</
h2
>
<
button
onClick=
{
()
=>
setEditOpen
(
false
)
}
className=
"p-2 rounded-[var(--radius-tiny)] hover:bg-surface-2"
>
<
X
size=
{
16
}
className=
"text-text-muted"
/>
</
button
>
</
div
>
{
/* Close button */
}
<
motion
.
button
whileTap=
{
{
scale
:
0.85
}
}
onClick=
{
()
=>
setEditOpen
(
false
)
}
className=
"absolute top-3 left-3 p-2 rounded-xl"
style=
{
{
background
:
'var(--color-surface-3)'
,
border
:
'2px solid var(--color-border)'
,
}
}
>
<
X
size=
{
14
}
className=
"text-text-muted"
/>
</
motion
.
button
>
{
/* Form */
}
<
div
className=
"flex flex-col gap-4 px-5 pb-5"
>
<
div
className=
"flex flex-col gap-4"
>
<
div
className=
"flex flex-col gap-1.5"
>
<
label
className=
"text-xs font-
black
text-text-secondary"
>
الاسم
</
label
>
<
label
className=
"text-xs font-
medium
text-text-secondary"
>
الاسم
</
label
>
<
input
type=
"text"
value=
{
editForm
.
display_name
}
onChange=
{
(
e
)
=>
setEditForm
({
...
editForm
,
display_name
:
e
.
target
.
value
})
}
className=
"px-4 py-3 rounded-xl text-sm font-bold outline-none transition-colors"
style=
{
{
background
:
'var(--color-surface-3)'
,
border
:
'3px solid var(--color-border)'
,
boxShadow
:
'inset 0 2px 4px rgba(0,0,0,0.3)'
,
}
}
className=
"px-4 py-3 bg-surface-2 border border-border rounded-[var(--radius-standard)] text-sm outline-none focus:border-gold/50 transition-colors"
maxLength=
{
30
}
dir=
"rtl"
/>
</
div
>
<
div
className=
"flex flex-col gap-1.5"
>
<
label
className=
"text-xs font-
black
text-text-secondary"
>
نبذة
</
label
>
<
label
className=
"text-xs font-
medium
text-text-secondary"
>
نبذة
</
label
>
<
textarea
value=
{
editForm
.
bio
}
onChange=
{
(
e
)
=>
setEditForm
({
...
editForm
,
bio
:
e
.
target
.
value
})
}
className=
"px-4 py-3 rounded-xl text-sm font-bold outline-none transition-colors resize-none h-20"
style=
{
{
background
:
'var(--color-surface-3)'
,
border
:
'3px solid var(--color-border)'
,
boxShadow
:
'inset 0 2px 4px rgba(0,0,0,0.3)'
,
}
}
className=
"px-4 py-3 bg-surface-2 border border-border rounded-[var(--radius-standard)] text-sm outline-none focus:border-gold/50 transition-colors resize-none h-20"
maxLength=
{
150
}
dir=
"rtl"
/>
</
div
>
<
Button
onClick=
{
handleSave
}
loading=
{
saving
}
disabled=
{
!
editForm
.
display_name
.
trim
()
}
className=
"w-[80%] mx-auto mt-1"
>
حفظ التعديلات
</
Button
>
</
div
>
<
Button
onClick=
{
handleSave
}
loading=
{
saving
}
disabled=
{
!
editForm
.
display_name
.
trim
()
}
className=
"w-full"
>
حفظ التعديلات
</
Button
>
</
motion
.
div
>
</
motion
.
div
>
)
}
...
...
@@ -445,3 +228,14 @@ export function ProfilePage() {
</
PageTransition
>
)
}
function
StatItem
({
icon
,
value
,
label
}:
{
icon
:
React
.
ReactNode
;
value
:
number
|
string
;
label
:
string
})
{
return
(
<
div
className=
"bg-surface-2 rounded-[var(--radius-tiny)] p-3 flex flex-col items-center gap-1"
>
{
icon
}
<
span
className=
"text-sm font-bold text-text-primary tabular-nums"
>
{
value
}
</
span
>
<
span
className=
"text-[10px] text-text-muted"
>
{
label
}
</
span
>
</
div
>
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment