Profile image eklendi

This commit is contained in:
Sedat Öztürk 2025-06-20 23:17:38 +03:00
parent c6db98522e
commit ef309d0e3f
6 changed files with 189 additions and 41 deletions

View file

@ -525,14 +525,14 @@
{
"resourceName": "Platform",
"key": "App.Blog",
"en": "Blog",
"tr": "Blog"
"en": "Blog Management",
"tr": "Blog Yönetimi"
},
{
"resourceName": "Platform",
"key": "App.Forum",
"en": "Forum",
"tr": "Forum"
"en": "Forum Management",
"tr": "Forum Yönetimi"
},
{
"resourceName": "Platform",

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -90,10 +90,10 @@ const LoginWithTenant: React.FC = () => {
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center py-24 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full">
<div className="bg-white rounded-lg shadow-xl p-8">
<div className="text-center mb-8">
<div className="text-center mb-4">
<div className="inline-flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-4">
<LogIn className="h-8 w-8 text-white" />
</div>

View file

@ -1,8 +1,8 @@
import React from 'react';
import { useAuthStore } from '../store/authStore';
import { User, Mail, Calendar, Shield } from 'lucide-react';
import { format } from 'date-fns';
import { tr } from 'date-fns/locale';
import React from "react";
import { useAuthStore } from "../store/authStore";
import { User, Mail, Calendar, Shield } from "lucide-react";
import { format } from "date-fns";
import { tr } from "date-fns/locale";
const Profile: React.FC = () => {
const { user } = useAuthStore();
@ -28,6 +28,10 @@ const Profile: React.FC = () => {
src={user.avatar}
alt={user.name}
className="w-24 h-24 rounded-full object-cover border-4 border-gray-200"
onError={(e) => {
e.currentTarget.onerror = null; // sonsuz döngüyü önlemek için
e.currentTarget.src = "/img/default-profile.png"; // bu senin varsayılan avatar görselin
}}
/>
) : (
<div className="w-24 h-24 bg-blue-500 rounded-full flex items-center justify-center text-white text-3xl font-bold">
@ -35,7 +39,9 @@ const Profile: React.FC = () => {
</div>
)}
<div className="ml-6">
<h2 className="text-2xl font-semibold text-gray-900">{user.fullName || user.name}</h2>
<h2 className="text-2xl font-semibold text-gray-900">
{user.fullName || user.name}
</h2>
<p className="text-gray-600">@{user.userName}</p>
</div>
</div>
@ -66,8 +72,10 @@ const Profile: React.FC = () => {
<p className="text-sm text-gray-500">Kayıt Tarihi</p>
<p className="font-medium">
{user.creationTime
? format(new Date(user.creationTime), 'dd MMMM yyyy', { locale: tr })
: 'Bilinmiyor'}
? format(new Date(user.creationTime), "dd MMMM yyyy", {
locale: tr,
})
: "Bilinmiyor"}
</p>
</div>
</div>
@ -96,17 +104,30 @@ const Profile: React.FC = () => {
</div>
<div className="mt-8 pt-8 border-t border-gray-200">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Hesap Durumu</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Hesap Durumu
</h3>
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 rounded-full ${user.isActive ? 'bg-green-500' : 'bg-red-500'}`}></div>
<span className={`font-medium ${user.isActive ? 'text-green-700' : 'text-red-700'}`}>
{user.isActive ? 'Aktif' : 'Pasif'}
<div
className={`w-3 h-3 rounded-full ${
user.isActive ? "bg-green-500" : "bg-red-500"
}`}
></div>
<span
className={`font-medium ${
user.isActive ? "text-green-700" : "text-red-700"
}`}
>
{user.isActive ? "Aktif" : "Pasif"}
</span>
</div>
{user.lastLoginTime && (
<p className="text-sm text-gray-500 mt-2">
Son giriş: {format(new Date(user.lastLoginTime), 'dd MMMM yyyy HH:mm', { locale: tr })}
Son giriş:{" "}
{format(new Date(user.lastLoginTime), "dd MMMM yyyy HH:mm", {
locale: tr,
})}
</p>
)}
</div>

View file

@ -0,0 +1,42 @@
import { apiClient } from "./config";
export interface LanguageText {
id: string;
name: string;
slug: string;
description: string;
icon?: string;
topicCount: number;
postCount: number;
lastPost?: {
id: string;
title: string;
author: string;
createdAt: string;
};
order: number;
isLocked: boolean;
}
export interface LanguageParams {
cultureName?: string;
}
export interface PaginatedResponse<T> {
items: T[];
totalCount: number;
pageNumber: number;
pageSize: number;
totalPages: number;
}
class LanguageService {
async getLanguageTextByCultureName(cultureName: string): Promise<PaginatedResponse<LanguageText>> {
const response = await apiClient.get<PaginatedResponse<LanguageText>>(
`/api/app/language/language-by-culture-name/${cultureName}`,
);
return response.data;
}
}
export const languageService = new LanguageService();

View file

@ -31,11 +31,11 @@ import THead from '@/components/ui/Table/THead'
import TBody from '@/components/ui/Table/TBody'
import Td from '@/components/ui/Table/Td'
import { SelectBoxOption } from '@/shared/types'
import { CheckBox } from 'devextreme-react'
import { Checkbox } from '@/components/ui'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ConfirmDialog } from '@/components/shared'
import { useStoreState } from '@/store/store'
const validationSchema = Yup.object().shape({
title: Yup.string().required(),
@ -67,9 +67,10 @@ const BlogManagement = () => {
const [categoryModalVisible, setCategoryModalVisible] = useState(false)
const [editingPost, setEditingPost] = useState<BlogPost | null>(null)
const [editingCategory, setEditingCategory] = useState<BlogCategory | null>(null)
const { texts } = useStoreState((state) => state.abpConfig)
const categoryItems = categories?.map((cat) => ({
value: cat.id,
label: cat.name,
label: texts?.Platform[cat.name] + ' (' + cat.name + ')',
}))
useEffect(() => {
@ -388,7 +389,7 @@ const BlogManagement = () => {
</div>
{activeTab === 'posts' ? (
<Table compact>
<Table compact >
<THead>
<Tr>
<Th>{translate('::blog.posts.post.title')}</Th>
@ -415,9 +416,9 @@ const BlogManagement = () => {
) : (
posts.map((post) => (
<Tr key={post.id}>
<Td className="font-medium">{post.title}</Td>
<Td>{texts?.Platform[post.title]}</Td>
<Td>{post.slug}</Td>
<Td>{post.category?.name}</Td>
<Td>{texts?.Platform[post.category?.name]}</Td>
<Td>{post.author?.name}</Td>
<Td>
{post.publishedAt
@ -425,7 +426,11 @@ const BlogManagement = () => {
: '-'}
</Td>
<Td>
<Switcher className="switcher-sm" checked={post.isPublished} onChange={() => handlePublish(post)} />
<Switcher
className="switcher-sm"
checked={post.isPublished}
onChange={() => handlePublish(post)}
/>
</Td>
<Td>
<div className="flex gap-2">
@ -452,6 +457,7 @@ const BlogManagement = () => {
<Th>{translate('::blog.posts.categories.slug')}</Th>
<Th>{translate('::blog.posts.categories.description')}</Th>
<Th>{translate('::blog.posts.categories.count')}</Th>
<Th>{translate('::blog.posts.categories.order')}</Th>
<Th>{translate('::blog.posts.categories.status')}</Th>
<Th>
<Button
@ -475,10 +481,11 @@ const BlogManagement = () => {
) : (
categories.map((category) => (
<Tr key={category.id}>
<Td className="font-medium">{category.name}</Td>
<Td>{texts?.Platform[category.name]}</Td>
<Td>{category.slug}</Td>
<Td>{category.description}</Td>
<Td>{texts?.Platform[category.description!!]}</Td>
<Td>{category.postCount}</Td>
<Td>{category.displayOrder}</Td>
<Td>
<Tag
className={
@ -540,7 +547,27 @@ const BlogManagement = () => {
invalid={errors.title && touched.title}
errorMessage={errors.title}
>
<Field type="text" name="title" component={Input} autoFocus={true} />
<Field name="title">
{({ field, form }: FieldProps<SelectBoxOption>) => {
const options = texts?.Platform
? Object.entries(texts.Platform).map(([key, value]) => ({
value: key,
label: value + ' (' + key + ')',
}))
: []
return (
<Select
field={field}
form={form}
options={options}
isClearable={true}
value={options.find((opt) => opt.value === field.value)}
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
/>
)
}}
</Field>
</FormItem>
<FormItem
@ -558,7 +585,27 @@ const BlogManagement = () => {
invalid={errors.summary && touched.summary}
errorMessage={errors.summary}
>
<Field name="summary" component={Input} />
<Field name="summary">
{({ field, form }: FieldProps<SelectBoxOption>) => {
const options = texts?.Platform
? Object.entries(texts.Platform).map(([key, value]) => ({
value: key,
label: value + ' (' + key + ')',
}))
: []
return (
<Select
field={field}
form={form}
options={options}
isClearable={true}
value={options.find((opt) => opt.value === field.value)}
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
/>
)
}}
</Field>
</FormItem>
<FormItem
@ -662,7 +709,27 @@ const BlogManagement = () => {
invalid={errors.name && touched.name}
errorMessage={errors.name}
>
<Field autoFocus={true} type="text" name="name" component={Input} />
<Field name="name">
{({ field, form }: FieldProps<SelectBoxOption>) => {
const options = texts?.Platform
? Object.entries(texts.Platform).map(([key, value]) => ({
value: key,
label: value + ' (' + key + ')',
}))
: []
return (
<Select
field={field}
form={form}
options={options}
isClearable={true}
value={options.find((opt) => opt.value === field.value)}
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
/>
)
}}
</Field>
</FormItem>
<FormItem
@ -675,14 +742,32 @@ const BlogManagement = () => {
</FormItem>
<FormItem
label="Açıklama"
asterisk
label={translate('::blog.posts.categories.description')}
invalid={errors.description && touched.description}
errorMessage={errors.description}
>
<Field
name={translate('::blog.posts.categories.description')}
component={Input}
/>
<Field name="description">
{({ field, form }: FieldProps<SelectBoxOption>) => {
const options = texts?.Platform
? Object.entries(texts.Platform).map(([key, value]) => ({
value: key,
label: value + ' (' + key + ')',
}))
: []
return (
<Select
field={field}
form={form}
options={options}
isClearable={true}
value={options.find((opt) => opt.value === field.value)}
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
/>
)
}}
</Field>
</FormItem>
<FormItem label={translate('::blog.posts.categories.icon')}>