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",
|
"resourceName": "Platform",
|
||||||
"key": "App.Blog",
|
"key": "App.Blog",
|
||||||
"en": "Blog",
|
"en": "Blog Management",
|
||||||
"tr": "Blog"
|
"tr": "Blog Yönetimi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Forum",
|
"key": "App.Forum",
|
||||||
"en": "Forum",
|
"en": "Forum Management",
|
||||||
"tr": "Forum"
|
"tr": "Forum Yönetimi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"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 (
|
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="max-w-md w-full">
|
||||||
<div className="bg-white rounded-lg shadow-xl p-8">
|
<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">
|
<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" />
|
<LogIn className="h-8 w-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from "../store/authStore";
|
||||||
import { User, Mail, Calendar, Shield } from 'lucide-react';
|
import { User, Mail, Calendar, Shield } from "lucide-react";
|
||||||
import { format } from 'date-fns';
|
import { format } from "date-fns";
|
||||||
import { tr } from 'date-fns/locale';
|
import { tr } from "date-fns/locale";
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
const Profile: React.FC = () => {
|
||||||
const { user } = useAuthStore();
|
const { user } = useAuthStore();
|
||||||
|
|
@ -28,6 +28,10 @@ const Profile: React.FC = () => {
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
className="w-24 h-24 rounded-full object-cover border-4 border-gray-200"
|
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">
|
<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>
|
||||||
)}
|
)}
|
||||||
<div className="ml-6">
|
<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>
|
<p className="text-gray-600">@{user.userName}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,8 +72,10 @@ const Profile: React.FC = () => {
|
||||||
<p className="text-sm text-gray-500">Kayıt Tarihi</p>
|
<p className="text-sm text-gray-500">Kayıt Tarihi</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{user.creationTime
|
{user.creationTime
|
||||||
? format(new Date(user.creationTime), 'dd MMMM yyyy', { locale: tr })
|
? format(new Date(user.creationTime), "dd MMMM yyyy", {
|
||||||
: 'Bilinmiyor'}
|
locale: tr,
|
||||||
|
})
|
||||||
|
: "Bilinmiyor"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -96,17 +104,30 @@ const Profile: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 pt-8 border-t border-gray-200">
|
<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="flex items-center space-x-2">
|
||||||
<div className={`w-3 h-3 rounded-full ${user.isActive ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
<div
|
||||||
<span className={`font-medium ${user.isActive ? 'text-green-700' : 'text-red-700'}`}>
|
className={`w-3 h-3 rounded-full ${
|
||||||
{user.isActive ? 'Aktif' : 'Pasif'}
|
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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user.lastLoginTime && (
|
{user.lastLoginTime && (
|
||||||
<p className="text-sm text-gray-500 mt-2">
|
<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>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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 TBody from '@/components/ui/Table/TBody'
|
||||||
import Td from '@/components/ui/Table/Td'
|
import Td from '@/components/ui/Table/Td'
|
||||||
import { SelectBoxOption } from '@/shared/types'
|
import { SelectBoxOption } from '@/shared/types'
|
||||||
import { CheckBox } from 'devextreme-react'
|
|
||||||
import { Checkbox } from '@/components/ui'
|
import { Checkbox } from '@/components/ui'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { ConfirmDialog } from '@/components/shared'
|
import { ConfirmDialog } from '@/components/shared'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
title: Yup.string().required(),
|
title: Yup.string().required(),
|
||||||
|
|
@ -67,9 +67,10 @@ const BlogManagement = () => {
|
||||||
const [categoryModalVisible, setCategoryModalVisible] = useState(false)
|
const [categoryModalVisible, setCategoryModalVisible] = useState(false)
|
||||||
const [editingPost, setEditingPost] = useState<BlogPost | null>(null)
|
const [editingPost, setEditingPost] = useState<BlogPost | null>(null)
|
||||||
const [editingCategory, setEditingCategory] = useState<BlogCategory | null>(null)
|
const [editingCategory, setEditingCategory] = useState<BlogCategory | null>(null)
|
||||||
|
const { texts } = useStoreState((state) => state.abpConfig)
|
||||||
const categoryItems = categories?.map((cat) => ({
|
const categoryItems = categories?.map((cat) => ({
|
||||||
value: cat.id,
|
value: cat.id,
|
||||||
label: cat.name,
|
label: texts?.Platform[cat.name] + ' (' + cat.name + ')',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -388,7 +389,7 @@ const BlogManagement = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeTab === 'posts' ? (
|
{activeTab === 'posts' ? (
|
||||||
<Table compact>
|
<Table compact >
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>{translate('::blog.posts.post.title')}</Th>
|
<Th>{translate('::blog.posts.post.title')}</Th>
|
||||||
|
|
@ -415,9 +416,9 @@ const BlogManagement = () => {
|
||||||
) : (
|
) : (
|
||||||
posts.map((post) => (
|
posts.map((post) => (
|
||||||
<Tr key={post.id}>
|
<Tr key={post.id}>
|
||||||
<Td className="font-medium">{post.title}</Td>
|
<Td>{texts?.Platform[post.title]}</Td>
|
||||||
<Td>{post.slug}</Td>
|
<Td>{post.slug}</Td>
|
||||||
<Td>{post.category?.name}</Td>
|
<Td>{texts?.Platform[post.category?.name]}</Td>
|
||||||
<Td>{post.author?.name}</Td>
|
<Td>{post.author?.name}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{post.publishedAt
|
{post.publishedAt
|
||||||
|
|
@ -425,7 +426,11 @@ const BlogManagement = () => {
|
||||||
: '-'}
|
: '-'}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Switcher className="switcher-sm" checked={post.isPublished} onChange={() => handlePublish(post)} />
|
<Switcher
|
||||||
|
className="switcher-sm"
|
||||||
|
checked={post.isPublished}
|
||||||
|
onChange={() => handlePublish(post)}
|
||||||
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|
@ -452,6 +457,7 @@ const BlogManagement = () => {
|
||||||
<Th>{translate('::blog.posts.categories.slug')}</Th>
|
<Th>{translate('::blog.posts.categories.slug')}</Th>
|
||||||
<Th>{translate('::blog.posts.categories.description')}</Th>
|
<Th>{translate('::blog.posts.categories.description')}</Th>
|
||||||
<Th>{translate('::blog.posts.categories.count')}</Th>
|
<Th>{translate('::blog.posts.categories.count')}</Th>
|
||||||
|
<Th>{translate('::blog.posts.categories.order')}</Th>
|
||||||
<Th>{translate('::blog.posts.categories.status')}</Th>
|
<Th>{translate('::blog.posts.categories.status')}</Th>
|
||||||
<Th>
|
<Th>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -475,10 +481,11 @@ const BlogManagement = () => {
|
||||||
) : (
|
) : (
|
||||||
categories.map((category) => (
|
categories.map((category) => (
|
||||||
<Tr key={category.id}>
|
<Tr key={category.id}>
|
||||||
<Td className="font-medium">{category.name}</Td>
|
<Td>{texts?.Platform[category.name]}</Td>
|
||||||
<Td>{category.slug}</Td>
|
<Td>{category.slug}</Td>
|
||||||
<Td>{category.description}</Td>
|
<Td>{texts?.Platform[category.description!!]}</Td>
|
||||||
<Td>{category.postCount}</Td>
|
<Td>{category.postCount}</Td>
|
||||||
|
<Td>{category.displayOrder}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Tag
|
<Tag
|
||||||
className={
|
className={
|
||||||
|
|
@ -540,7 +547,27 @@ const BlogManagement = () => {
|
||||||
invalid={errors.title && touched.title}
|
invalid={errors.title && touched.title}
|
||||||
errorMessage={errors.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>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|
@ -558,7 +585,27 @@ const BlogManagement = () => {
|
||||||
invalid={errors.summary && touched.summary}
|
invalid={errors.summary && touched.summary}
|
||||||
errorMessage={errors.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>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|
@ -662,7 +709,27 @@ const BlogManagement = () => {
|
||||||
invalid={errors.name && touched.name}
|
invalid={errors.name && touched.name}
|
||||||
errorMessage={errors.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>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|
@ -675,14 +742,32 @@ const BlogManagement = () => {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label="Açıklama"
|
asterisk
|
||||||
|
label={translate('::blog.posts.categories.description')}
|
||||||
invalid={errors.description && touched.description}
|
invalid={errors.description && touched.description}
|
||||||
errorMessage={errors.description}
|
errorMessage={errors.description}
|
||||||
>
|
>
|
||||||
<Field
|
<Field name="description">
|
||||||
name={translate('::blog.posts.categories.description')}
|
{({ field, form }: FieldProps<SelectBoxOption>) => {
|
||||||
component={Input}
|
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>
|
||||||
|
|
||||||
<FormItem label={translate('::blog.posts.categories.icon')}>
|
<FormItem label={translate('::blog.posts.categories.icon')}>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue