About güncellemesi
This commit is contained in:
parent
277dbd907f
commit
0e95c7df7b
3 changed files with 108 additions and 8 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue