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",
"key": "Public.about.stats.clients",
"tr": "Mutlu Müşteri",
"en": "Happy Clients"
"tr": "Mutlu Şubeler",
"en": "Happy Branches"
},
{
"resourceName": "Platform",
"key": "Public.about.stats.users",
"tr": "Mutlu Kullanıcılar",
"en": "Happy Users"
},
{
"resourceName": "Platform",

View file

@ -369,6 +369,15 @@
"Abouts": [
{
"stats": [
{
"Icon": "FaUser",
"Value": "740\u002B",
"LabelKey": "Public.about.stats.users",
"UseCounter": true,
"CounterEnd": "740\u002B",
"CounterSuffix": "\u002B",
"CounterDuration": 3000
},
{
"icon": "FaUsers",
"value": "310+",
@ -380,7 +389,7 @@
},
{
"icon": "FaAward",
"value": "20",
"value": "25",
"labelKey": "Public.about.stats.experience",
"useCounter": true,
"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 navigationIcon from '@/proxy/menus/navigation-icon.config'
import { AboutDto } from '@/proxy/about/models'
@ -102,6 +103,88 @@ function resolveLocalizedValue(
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(
about: AboutDto | undefined,
translate: (key: string) => string,
@ -213,7 +296,7 @@ function buildAboutContent(
}
}
const About: React.FC = () => {
const About: FC = () => {
const { translate } = useLocalization()
const { setLang } = useStoreActions((actions) => actions.locale)
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) {
return null
}
@ -736,7 +819,7 @@ const About: React.FC = () => {
{/* Stats Section */}
<div className="py-10 bg-white">
<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) => {
const IconComponent = navigationIcon[stat.icon || '']
@ -754,7 +837,9 @@ const About: React.FC = () => {
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>
</SelectableBlock>