About güncellemesi

This commit is contained in:
Sedat Öztürk 2026-05-26 11:56:05 +03:00
parent 277dbd907f
commit 0e95c7df7b
3 changed files with 108 additions and 8 deletions

View file

@ -6903,8 +6903,14 @@
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "Public.about.stats.clients", "key": "Public.about.stats.clients",
"tr": "Mutlu Müşteri", "tr": "Mutlu Şubeler",
"en": "Happy Clients" "en": "Happy Branches"
},
{
"resourceName": "Platform",
"key": "Public.about.stats.users",
"tr": "Mutlu Kullanıcılar",
"en": "Happy Users"
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",

View file

@ -369,6 +369,15 @@
"Abouts": [ "Abouts": [
{ {
"stats": [ "stats": [
{
"Icon": "FaUser",
"Value": "740\u002B",
"LabelKey": "Public.about.stats.users",
"UseCounter": true,
"CounterEnd": "740\u002B",
"CounterSuffix": "\u002B",
"CounterDuration": 3000
},
{ {
"icon": "FaUsers", "icon": "FaUsers",
"value": "310+", "value": "310+",
@ -380,7 +389,7 @@
}, },
{ {
"icon": "FaAward", "icon": "FaAward",
"value": "20", "value": "25",
"labelKey": "Public.about.stats.experience", "labelKey": "Public.about.stats.experience",
"useCounter": true, "useCounter": true,
"counterEnd": "20", "counterEnd": "20",

View file

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import type { FC } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import navigationIcon from '@/proxy/menus/navigation-icon.config' import navigationIcon from '@/proxy/menus/navigation-icon.config'
import { AboutDto } from '@/proxy/about/models' import { AboutDto } from '@/proxy/about/models'
@ -102,6 +103,88 @@ function resolveLocalizedValue(
return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue
} }
function getCounterParts(value?: string, suffix?: string) {
const sourceValue = value || '0'
const numericMatch = sourceValue.replace(',', '.').match(/\d+(?:\.\d+)?/)
if (!numericMatch) {
return null
}
const endValue = Number(numericMatch[0])
if (!Number.isFinite(endValue)) {
return null
}
const prefix = sourceValue.slice(0, numericMatch.index)
const detectedSuffix = sourceValue.slice((numericMatch.index || 0) + numericMatch[0].length)
return {
endValue,
prefix,
suffix: suffix ?? detectedSuffix,
hasDecimals: numericMatch[0].includes('.'),
}
}
const AnimatedStatValue: FC<{ stat: AboutStatContent }> = ({ stat }) => {
const counterParts = useMemo(
() => getCounterParts(stat.counterEnd || stat.value, stat.counterSuffix),
[stat.counterEnd, stat.counterSuffix, stat.value],
)
const [currentValue, setCurrentValue] = useState(0)
useEffect(() => {
if (!stat.useCounter || !counterParts) {
return
}
let animationFrameId = 0
let startTime: number | null = null
const duration = Math.max(stat.counterDuration || 2000, 0)
const animate = (timestamp: number) => {
if (startTime === null) {
startTime = timestamp
}
const elapsed = timestamp - startTime
const progress = duration === 0 ? 1 : Math.min(elapsed / duration, 1)
const easedProgress = 1 - Math.pow(1 - progress, 3)
setCurrentValue(counterParts.endValue * easedProgress)
if (progress < 1) {
animationFrameId = requestAnimationFrame(animate)
}
}
setCurrentValue(0)
animationFrameId = requestAnimationFrame(animate)
return () => {
cancelAnimationFrame(animationFrameId)
}
}, [counterParts, stat.counterDuration, stat.useCounter])
if (!stat.useCounter || !counterParts) {
return <>{stat.value}</>
}
const formattedValue = counterParts.hasDecimals
? currentValue.toFixed(1)
: Math.floor(currentValue).toString()
return (
<>
{counterParts.prefix}
{formattedValue}
{counterParts.suffix}
</>
)
}
function buildAboutContent( function buildAboutContent(
about: AboutDto | undefined, about: AboutDto | undefined,
translate: (key: string) => string, translate: (key: string) => string,
@ -213,7 +296,7 @@ function buildAboutContent(
} }
} }
const About: React.FC = () => { const About: FC = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const { setLang } = useStoreActions((actions) => actions.locale) const { setLang } = useStoreActions((actions) => actions.locale)
const { getConfig } = useStoreActions((actions) => actions.abpConfig) const { getConfig } = useStoreActions((actions) => actions.abpConfig)
@ -372,7 +455,7 @@ const About: React.FC = () => {
}) })
} }
const selectedSelection: DesignerSelection | null = React.useMemo(() => { const selectedSelection: DesignerSelection | null = useMemo(() => {
if (!content || !selectedBlockId) { if (!content || !selectedBlockId) {
return null return null
} }
@ -736,7 +819,7 @@ const About: React.FC = () => {
{/* Stats Section */} {/* Stats Section */}
<div className="py-10 bg-white"> <div className="py-10 bg-white">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8"> <div className="grid grid-cols-1 md:grid-cols-5 gap-8">
{content?.stats.map((stat, index) => { {content?.stats.map((stat, index) => {
const IconComponent = navigationIcon[stat.icon || ''] const IconComponent = navigationIcon[stat.icon || '']
@ -754,7 +837,9 @@ const About: React.FC = () => {
className={`w-12 h-12 mx-auto mb-4 ${getIconColor(index)}`} className={`w-12 h-12 mx-auto mb-4 ${getIconColor(index)}`}
/> />
)} )}
<div className={stat.valueStyleClass || 'text-4xl font-bold text-gray-900 mb-2'}>{stat.value}</div> <div className={stat.valueStyleClass || 'text-4xl font-bold text-gray-900 mb-2'}>
<AnimatedStatValue stat={stat} />
</div>
<div className={stat.labelStyleClass || 'text-gray-600'}>{stat.label}</div> <div className={stat.labelStyleClass || 'text-gray-600'}>{stat.label}</div>
</div> </div>
</SelectableBlock> </SelectableBlock>