Profile image eklendi
This commit is contained in:
parent
c6db98522e
commit
ef309d0e3f
6 changed files with 189 additions and 41 deletions
|
|
@ -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",
|
||||
|
|
|
|||
BIN
company/public/img/default-profile.png
Normal file
BIN
company/public/img/default-profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
42
company/src/services/api/language.service.ts
Normal file
42
company/src/services/api/language.service.ts
Normal 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();
|
||||
|
|
@ -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')}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue