feed back
This commit is contained in:
@@ -2,6 +2,7 @@ VITE_TOKEN_NAME = 'dmail_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'dmail_refresh_token'
|
||||
|
||||
VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
|
||||
# VITE_DANAK_BASE_URL ='http://192.168.1.112:4001'
|
||||
|
||||
VITE_SOCKET_URL = 'wss://dmail-api.danakcorp.com/email'
|
||||
# VITE_SOCKET_URL = 'ws://192.168.1.106:4000/email'
|
||||
@@ -9,6 +10,8 @@ VITE_SOCKET_URL = 'wss://dmail-api.danakcorp.com/email'
|
||||
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.117:4000'
|
||||
|
||||
VITE_HELP_URL = 'https://help.danakcorp.com'
|
||||
|
||||
|
||||
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext';
|
||||
import SocketManagment from './lib/SocketManagment';
|
||||
import NajvaToken from './components/NajvaToken';
|
||||
import { ThemeProvider } from './contexts/ThemeContext';
|
||||
import ReportBug from '@/pages/guide/ReportBug';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -131,6 +132,7 @@ const App: FC = () => {
|
||||
<Main />
|
||||
<SocketManagment />
|
||||
<NajvaToken />
|
||||
<ReportBug />
|
||||
</EmailWebSocketProvider>
|
||||
) : isLogin === 'isNotLogin' ? (
|
||||
<Login />
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
@@ -33,7 +33,7 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
||||
|
||||
{
|
||||
props.isHeader && props.title_header &&
|
||||
<div onClick={props.close} className='pb-6 border-b border-white border-opacity-20'>
|
||||
<div onClick={props.close} className='pb-6 border-b border-border border-opacity-20'>
|
||||
<div className='h-[5px] w-[200px] mx-auto bg-[#D1D3D7] rounded-full mb-4 xl:hidden'></div>
|
||||
<HeaderModal close={props.close} label={props.title_header} />
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,10 @@ export interface RowActionItem {
|
||||
interface RowActionsDropdownProps {
|
||||
actions: RowActionItem[];
|
||||
className?: string;
|
||||
trigger?: React.ReactNode;
|
||||
}
|
||||
|
||||
const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, className = '' }) => {
|
||||
const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, className = '', trigger }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [position, setPosition] = useState({ top: 0, left: 0 })
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
@@ -102,8 +103,12 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, classN
|
||||
ref={buttonRef}
|
||||
onClick={handleToggle}
|
||||
className="mt-2 hover:bg-gray-100 dark:hover:bg-[#2d2d30] rounded-full transition-colors"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
{trigger ? trigger : (
|
||||
<More size={16} color={getIconColor('primary')} className="rotate-90" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{renderDropdown()}
|
||||
|
||||
@@ -19,20 +19,22 @@ const SwitchComponent = (props: Props) => {
|
||||
<Switch checked={props.active} onChange={handleChange} as={Fragment}>
|
||||
{({ checked }) => (
|
||||
<button
|
||||
className={`${checked ? 'bg-primary' : 'bg-gray-200'
|
||||
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
||||
className={`${checked
|
||||
? 'bg-primary dark:bg-primary/80'
|
||||
: 'bg-gray-300 dark:bg-gray-600'
|
||||
} relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200`}
|
||||
>
|
||||
<span className="sr-only">Enable notifications</span>
|
||||
<span
|
||||
className={`${checked ? 'translate-x-6' : 'translate-x-1'
|
||||
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
|
||||
} inline-block h-4 w-4 transform rounded-full bg-white dark:bg-gray-100 shadow-sm transition-all duration-200`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</Switch>
|
||||
{
|
||||
props.label &&
|
||||
<div className='text-sm'>
|
||||
<div className='text-sm text-gray-700 dark:text-gray-300'>
|
||||
: {props.label}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -519,3 +519,8 @@ strong {
|
||||
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(7416%)
|
||||
hue-rotate(296deg) brightness(109%) contrast(97%);
|
||||
}
|
||||
|
||||
.filterBlue {
|
||||
filter: brightness(0) saturate(100%) invert(9%) sepia(100%) saturate(7246%)
|
||||
hue-rotate(236deg) brightness(111%) contrast(112%);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import DefaulModal from '@/components/DefaulModal'
|
||||
import Textarea from '@/components/Textarea'
|
||||
import Button from '@/components/Button'
|
||||
import { useSharedStore } from '@/shared/store/sharedStore'
|
||||
import { useSendFeedback } from './hooks/useFeedbackData'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { ErrorType } from '@/helpers/types'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
|
||||
|
||||
// Import images
|
||||
import sad1 from '@/assets/images/sad1.png'
|
||||
import sad2 from '@/assets/images/sad2.png'
|
||||
import sad3 from '@/assets/images/sad3.png'
|
||||
import smile from '@/assets/images/smile.png'
|
||||
import smile2 from '@/assets/images/smile2.png'
|
||||
import smile3 from '@/assets/images/smile3.png'
|
||||
import { useGetProfile } from '../profile/hooks/useProfileData'
|
||||
|
||||
const ReportBug: React.FC = () => {
|
||||
const { openReportBug, setOpenReportBug } = useSharedStore()
|
||||
const [description, setDescription] = useState('')
|
||||
const [feeling, setFeeling] = useState<string | null>(null)
|
||||
const { data } = useGetProfile()
|
||||
const { mutate: sendReportBug, isPending } = useSendFeedback()
|
||||
const { theme } = useTheme()
|
||||
|
||||
const isValid = useMemo(() => description.trim().length > 0, [description])
|
||||
|
||||
const getRatingFromFeeling = (feeling: string | null): number => {
|
||||
if (!feeling) return 0
|
||||
|
||||
const feelingMap: Record<string, number> = {
|
||||
'angry': 1, // sad1
|
||||
'crying': 2, // sad2
|
||||
'sad': 3, // sad3
|
||||
'neutral': 4, // smile
|
||||
'smile': 5, // smile2
|
||||
'love': 6, // smile3
|
||||
}
|
||||
|
||||
return feelingMap[feeling] || 0
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!isValid) return
|
||||
|
||||
sendReportBug({
|
||||
content: description,
|
||||
rating: getRatingFromFeeling(feeling),
|
||||
serviceId: import.meta.env.VITE_SERVICE_ID,
|
||||
email: data?.data?.user?.emailAddress,
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
toast('گزارش با موفقیت ارسال شد', 'success')
|
||||
setDescription('')
|
||||
setFeeling(null)
|
||||
setOpenReportBug(false)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const feelings = [
|
||||
{ id: 'angry', image: sad1, alt: 'ناراضی' },
|
||||
{ id: 'crying', image: sad2, alt: 'ناراضی' },
|
||||
{ id: 'sad', image: sad3, alt: 'ناراضی' },
|
||||
{ id: 'neutral', image: smile, alt: 'خنثی' },
|
||||
{ id: 'smile', image: smile2, alt: 'راضی' },
|
||||
{ id: 'love', image: smile3, alt: 'راضی' },
|
||||
]
|
||||
|
||||
return (
|
||||
<DefaulModal open={openReportBug} close={() => setOpenReportBug(false)} isHeader title_header='گزارش اشکالات' width={500}>
|
||||
<div className='space-y-6 mt-4'>
|
||||
<div className='text-xs text-muted-foreground leading-5'>
|
||||
لطفاً در صورت مشاهده هرگونه اشکال یا باگ در سایت، از طریق این فرم گزارش دهید. همچنین میتوانید بازخورد، نظر یا پیشنهادات خود را درباره سایت با ما به اشتراک بگذارید. اطلاعات دقیق شما به ما کمک میکند مشکلات را سریعتر برطرف کنیم و کیفیت سایت را بهبود بخشیم.
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
label='متن گزارش'
|
||||
placeholder='اینجا بنویسید...'
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className='flex justify-start'>
|
||||
<div className='flex w-full justify-between gap-4'>
|
||||
{feelings.map((f, idx) => (
|
||||
<div key={f.id} className='flex flex-col items-center gap-1'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setFeeling(f.id)}
|
||||
|
||||
>
|
||||
<img
|
||||
src={f.image}
|
||||
alt={f.alt}
|
||||
className={`w-8 object-contain ${feeling === f.id ? 'filterBlue' : theme === 'dark' ? 'filterWhite' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
{idx === 0 && (
|
||||
<div className='text-xs mt-2 text-muted-foreground'>ناراضی</div>
|
||||
)}
|
||||
{idx === feelings.length - 1 && (
|
||||
<div className='text-xs mt-2 text-muted-foreground'>راضی</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='text-[11px] text-muted-foreground leading-6'>
|
||||
با ادامه و ارسال، شما تأیید میکنید که «دیمیل» میتواند پاسخها و اطلاعات حساب شما را بهمنظور بهبود خدمات جمعآوری و استفاده کند. برای جزئیات بیشتر لطفاً به <span className='text-blue-500'>قوانین و شرایط و سیاست حریم خصوصی</span> مراجعه کنید.
|
||||
</div>
|
||||
|
||||
<div className='w-full flex justify-end'>
|
||||
<Button onClick={handleSubmit} disabled={!isValid} loading={isPending} className='px-10 w-fit'>ارسال</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReportBug
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { sendFeedback } from "../service/Service";
|
||||
import { FeedbackType } from "../types/Types";
|
||||
|
||||
export const useSendFeedback = () => {
|
||||
return useMutation({
|
||||
mutationFn: (params: FeedbackType) => sendFeedback(params),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from "@/config/axiosDanak";
|
||||
import { FeedbackType } from "../types/Types";
|
||||
|
||||
export const sendFeedback = async (params: FeedbackType) => {
|
||||
const { data } = await axios.post("/danak-services/feedback", params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export type FeedbackType = {
|
||||
content: string;
|
||||
rating: number;
|
||||
serviceId: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
};
|
||||
+18
-3
@@ -1,6 +1,6 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import Input from '../components/Input'
|
||||
import { ArrowDown2, CloseCircle, HambergerMenu, Logout, ProfileCircle, SearchNormal, Setting2 } from 'iconsax-react'
|
||||
import { ArrowDown2, CloseCircle, HambergerMenu, Logout, MessageQuestion, ProfileCircle, SearchNormal, Setting2 } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Notifications from '../pages/notification/Notification'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
@@ -13,6 +13,7 @@ import { useGetProfile } from '@/pages/profile/hooks/useProfileData'
|
||||
import { useGetInbox } from '@/pages/received/hooks/useEmailData'
|
||||
import ThemeToggle from '@/components/ThemeToggle'
|
||||
import { getIconColor } from '@/utils/colorUtils'
|
||||
import RowActionsDropdown from '@/components/RowActionsDropdown'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
@@ -30,7 +31,7 @@ const Header: FC = () => {
|
||||
})
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
const { setOpenSidebar, openSidebar, setOpenReportBug } = useSharedStore()
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearch(value)
|
||||
@@ -269,9 +270,23 @@ const Header: FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Notifications />
|
||||
<RowActionsDropdown
|
||||
actions={[
|
||||
{
|
||||
label: 'راهنما',
|
||||
onClick: () => window.location.href = import.meta.env.VITE_HELP_URL + `/${import.meta.env.VITE_SERVICE_ID}`,
|
||||
},
|
||||
{
|
||||
label: 'گزارش اشکالات',
|
||||
onClick: () => setOpenReportBug(true),
|
||||
},
|
||||
]}
|
||||
trigger={<MessageQuestion size={20} color={getIconColor('primary')} />}
|
||||
/>
|
||||
|
||||
|
||||
<ThemeToggle />
|
||||
<Notifications />
|
||||
|
||||
{
|
||||
data && (
|
||||
|
||||
@@ -6,6 +6,8 @@ export const useSharedStore = create<SharedStoreType>((set) => ({
|
||||
setOpenSidebar: (value) => set({ openSidebar: value }),
|
||||
openNewMessage: false,
|
||||
setOpenNewMessage: (value) => set({ openNewMessage: value }),
|
||||
openReportBug: false,
|
||||
setOpenReportBug: (value) => set({ openReportBug: value }),
|
||||
draftData: null,
|
||||
setDraftData: (draft) => set({ draftData: draft }),
|
||||
editingDraftId: null,
|
||||
|
||||
@@ -5,6 +5,8 @@ export type SharedStoreType = {
|
||||
setOpenSidebar: (value: boolean) => void;
|
||||
openNewMessage: boolean;
|
||||
setOpenNewMessage: (value: boolean) => void;
|
||||
openReportBug: boolean;
|
||||
setOpenReportBug: (value: boolean) => void;
|
||||
draftData: DraftMessage | null;
|
||||
setDraftData: (draft: DraftMessage | null) => void;
|
||||
editingDraftId: number | null;
|
||||
|
||||
Reference in New Issue
Block a user