recive email + save draft and list draft + skeleton email detail

This commit is contained in:
hamid zarghami
2025-07-10 11:06:36 +03:30
parent 4258f57662
commit df6e74c632
14 changed files with 540 additions and 228 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
VITE_TOKEN_NAME = 'dmail_token'
VITE_REFRESH_TOKEN_NAME = 'dmail_refresh_token'
VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
# VITE_BASE_URL = 'http://192.168.1.134:4000'
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
VITE_BASE_URL = 'http://192.168.1.134:4000'
# VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
+24 -91
View File
@@ -1,91 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 961.6 222.5" style="enable-background:new 0 0 961.6 222.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1D1D1B;}
</style>
<g>
<g>
<g>
<g>
<path class="st0" d="M246,180.4h-8.7v24.3h-3.6v-24.3H225v-2.9h21V180.4z"/>
<path class="st0" d="M279.1,205c-2.7,0-5-0.9-6.7-2.7s-2.6-4.2-2.6-7.2v-0.6c0-2,0.4-3.8,1.1-5.4s1.8-2.8,3.2-3.7
s2.9-1.3,4.5-1.3c2.6,0,4.7,0.9,6.1,2.6c1.5,1.7,2.2,4.2,2.2,7.4v1.4h-13.7c0,2,0.6,3.6,1.7,4.8s2.5,1.8,4.2,1.8
c1.2,0,2.3-0.2,3.1-0.7s1.6-1.2,2.2-2l2.1,1.6C285,203.7,282.5,205,279.1,205z M278.7,186.9c-1.4,0-2.6,0.5-3.5,1.5
s-1.5,2.4-1.8,4.3h10.1v-0.3c-0.1-1.8-0.6-3.1-1.4-4.1C281.3,187.4,280.1,186.9,278.7,186.9z"/>
<path class="st0" d="M320.4,202.2c1.2,0,2.3-0.4,3.2-1.1c0.9-0.7,1.4-1.7,1.5-2.8h3.3c-0.1,1.2-0.5,2.3-1.2,3.3
s-1.7,1.9-2.9,2.5c-1.2,0.6-2.5,0.9-3.9,0.9c-2.8,0-4.9-0.9-6.6-2.8c-1.6-1.8-2.4-4.3-2.4-7.5v-0.6c0-2,0.4-3.7,1.1-5.2
s1.8-2.7,3.1-3.6c1.4-0.8,2.9-1.3,4.8-1.3c2.3,0,4.1,0.7,5.6,2c1.5,1.4,2.3,3.1,2.4,5.3h-3.3c-0.1-1.3-0.6-2.4-1.5-3.2
s-2-1.3-3.3-1.3c-1.8,0-3.1,0.6-4.1,1.9s-1.4,3.1-1.4,5.5v0.7c0,2.3,0.5,4.1,1.4,5.4C317.2,201.6,318.6,202.2,320.4,202.2z"/>
<path class="st0" d="M357,186.9c1.5-1.9,3.5-2.8,6-2.8c4.3,0,6.4,2.4,6.5,7.2v13.4H366v-13.4c0-1.5-0.3-2.5-1-3.2s-1.7-1-3.1-1
c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.4v-28.7h3.5V186.9z"/>
<path class="st0" d="M399.1,184.5l0.1,2.5c1.5-1.9,3.6-2.9,6.1-2.9c4.3,0,6.4,2.4,6.5,7.2v13.4h-3.5v-13.4c0-1.5-0.3-2.5-1-3.2
s-1.7-1-3.1-1c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M437.3,194.4c0-2,0.4-3.8,1.2-5.3c0.8-1.6,1.9-2.8,3.2-3.7s3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9
s2.5,4.4,2.5,7.6v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7c-1.4,0.9-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9s-2.5-4.4-2.5-7.6v-0.2
H437.3z M440.8,194.8c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2c1.8,0,3.1-0.7,4.2-2.1s1.6-3.3,1.6-5.8c0-2.2-0.5-4-1.6-5.4
s-2.5-2.1-4.2-2.1s-3.1,0.7-4.1,2C441.3,190.3,440.8,192.3,440.8,194.8z"/>
<path class="st0" d="M485.1,204.7h-3.5V176h3.5V204.7z"/>
<path class="st0" d="M511,194.4c0-2,0.4-3.8,1.2-5.3c0.8-1.5,1.9-2.8,3.2-3.7c1.4-0.9,3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9
s2.5,4.4,2.5,7.6v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7s-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9c-1.7-1.9-2.5-4.4-2.5-7.6v-0.2
H511z M514.4,194.8c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2s3.2-0.7,4.2-2.1c1-1.4,1.5-3.3,1.5-5.8c0-2.2-0.5-4-1.6-5.4
s-2.5-2.1-4.2-2.1s-3.1,0.7-4.1,2C515,190.3,514.4,192.3,514.4,194.8z"/>
<path class="st0" d="M554.1,194.4c0-3.2,0.7-5.7,2.2-7.5s3.4-2.8,5.8-2.8c2.5,0,4.4,0.9,5.8,2.6l0.2-2.2h3.2v19.7
c0,2.6-0.8,4.7-2.3,6.2c-1.6,1.5-3.6,2.3-6.2,2.3c-1.5,0-2.9-0.3-4.3-0.9c-1.4-0.6-2.5-1.5-3.2-2.6l1.8-2.1
c1.5,1.8,3.3,2.7,5.4,2.7c1.7,0,3-0.5,3.9-1.4s1.4-2.3,1.4-4v-1.7c-1.4,1.6-3.3,2.4-5.7,2.4s-4.3-1-5.7-2.9
S554.1,197.7,554.1,194.4z M557.6,194.8c0,2.3,0.5,4.1,1.4,5.4c0.9,1.3,2.2,2,3.9,2c2.2,0,3.8-1,4.8-3V190
c-1.1-1.9-2.6-2.9-4.8-2.9c-1.7,0-3,0.7-3.9,2C558.1,190.3,557.6,192.2,557.6,194.8z"/>
<path class="st0" d="M604.3,199.6l4.7-15.1h3.7l-8.1,23.3c-1.3,3.4-3.3,5-6,5l-0.7-0.1l-1.3-0.2v-2.8l0.9,0.1
c1.2,0,2.1-0.2,2.7-0.7c0.7-0.5,1.2-1.3,1.6-2.6l0.8-2.1l-7.2-20h3.8L604.3,199.6z"/>
<path class="st0" d="M688.3,196c-0.3,2.9-1.4,5.1-3.2,6.7c-1.8,1.6-4.2,2.3-7.1,2.3c-3.2,0-5.8-1.2-7.7-3.5s-2.9-5.4-2.9-9.2
v-2.6c0-2.5,0.5-4.8,1.4-6.7s2.2-3.4,3.8-4.4s3.6-1.5,5.8-1.5c2.9,0,5.2,0.8,6.9,2.4c1.7,1.6,2.8,3.8,3,6.7h-3.6
c-0.3-2.2-1-3.7-2-4.7s-2.5-1.5-4.4-1.5c-2.3,0-4.1,0.8-5.4,2.5c-1.3,1.7-2,4.1-2,7.2v2.6c0,3,0.6,5.3,1.8,7s3,2.6,5.2,2.6
c2,0,3.5-0.5,4.6-1.4s1.8-2.5,2.1-4.7h3.7V196z"/>
<path class="st0" d="M713,194.4c0-2,0.4-3.8,1.2-5.3s1.9-2.8,3.2-3.7c1.4-0.9,3-1.3,4.8-1.3c2.8,0,5,1,6.7,2.9s2.5,4.4,2.5,7.6
v0.2c0,2-0.4,3.7-1.1,5.3s-1.8,2.8-3.2,3.7s-3,1.3-4.8,1.3c-2.7,0-5-1-6.7-2.9s-2.5-4.4-2.5-7.6v-0.2H713z M716.5,194.8
c0,2.2,0.5,4,1.6,5.4c1,1.4,2.4,2,4.2,2s3.2-0.7,4.2-2.1c1-1.4,1.5-3.3,1.5-5.8c0-2.2-0.5-4-1.6-5.4s-2.5-2.1-4.2-2.1
s-3.1,0.7-4.1,2C717,190.3,716.5,192.3,716.5,194.8z"/>
<path class="st0" d="M760.3,184.5l0.1,2.2c1.5-1.7,3.5-2.6,6-2.6c2.8,0,4.8,1.1,5.8,3.2c0.7-1,1.5-1.8,2.6-2.4
c1.1-0.6,2.4-0.9,3.8-0.9c4.4,0,6.7,2.3,6.8,7v13.5h-3.5v-13.3c0-1.4-0.3-2.5-1-3.2s-1.8-1.1-3.3-1.1c-1.3,0-2.3,0.4-3.2,1.1
c-0.8,0.8-1.3,1.8-1.5,3.1v13.4h-3.5v-13.2c0-2.9-1.4-4.4-4.3-4.4c-2.3,0-3.8,1-4.7,2.9v14.8h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M828.9,194.8c0,3.1-0.7,5.6-2.1,7.4c-1.4,1.9-3.3,2.8-5.7,2.8c-2.5,0-4.4-0.8-5.8-2.3v9.7h-3.5v-28h3.2
l0.2,2.2c1.4-1.7,3.4-2.6,5.9-2.6c2.4,0,4.3,0.9,5.8,2.7c1.4,1.8,2.1,4.4,2.1,7.6v0.5H828.9z M825.5,194.4
c0-2.3-0.5-4.1-1.5-5.4c-1-1.3-2.3-2-4-2c-2.1,0-3.7,0.9-4.7,2.8v9.7c1,1.8,2.6,2.8,4.7,2.8c1.7,0,3-0.7,4-2
S825.5,197,825.5,194.4z"/>
<path class="st0" d="M867,204.7c-0.2-0.4-0.4-1.1-0.5-2.1c-1.6,1.7-3.5,2.5-5.8,2.5c-2,0-3.6-0.6-4.9-1.7s-1.9-2.6-1.9-4.3
c0-2.1,0.8-3.7,2.4-4.9c1.6-1.2,3.9-1.7,6.8-1.7h3.4v-1.7c0-1.2-0.4-2.2-1.1-2.9c-0.7-0.7-1.8-1.1-3.2-1.1
c-1.2,0-2.3,0.3-3.1,0.9c-0.8,0.6-1.3,1.4-1.3,2.3h-3.5c0-1,0.4-2,1.1-2.9c0.7-0.9,1.7-1.7,2.9-2.2s2.6-0.8,4-0.8
c2.3,0,4.2,0.6,5.5,1.7c1.3,1.2,2,2.8,2.1,4.8v9.3c0,1.9,0.2,3.3,0.7,4.4v0.3H867V204.7z M861.3,202c1.1,0,2.1-0.3,3.1-0.8
c1-0.6,1.7-1.3,2.1-2.2v-4.1h-2.7c-4.2,0-6.4,1.2-6.4,3.7c0,1.1,0.4,1.9,1.1,2.5C859.2,201.7,860.2,202,861.3,202z"/>
<path class="st0" d="M899.9,184.5l0.1,2.5c1.5-1.9,3.6-2.9,6.1-2.9c4.3,0,6.4,2.4,6.5,7.2v13.4h-3.5v-13.4c0-1.5-0.3-2.5-1-3.2
s-1.7-1-3.1-1c-1.1,0-2.1,0.3-3,0.9s-1.5,1.4-2,2.4v14.4h-3.5v-20.2h3.4V184.5z"/>
<path class="st0" d="M945.6,199.6l4.7-15.1h3.7l-8.1,23.3c-1.3,3.4-3.3,5-6,5l-0.7-0.1l-1.3-0.2v-2.8l0.9,0.1
c1.2,0,2.1-0.2,2.7-0.7c0.7-0.5,1.2-1.3,1.6-2.6l0.8-2.1l-7.2-20h3.8L945.6,199.6z"/>
</g>
</g>
<path class="st0" d="M221.6,148.7V44.1h29.5c9.1,0,17.1,2,24.1,6s12.4,9.7,16.2,17.2c3.8,7.4,5.7,15.9,5.8,25.6v6.7
c0,9.9-1.9,18.5-5.7,25.9c-3.8,7.4-9.2,13.1-16.3,17.1c-7.1,4-15.3,6-24.7,6.1L221.6,148.7L221.6,148.7z M235.4,55.4v82h14.5
c10.6,0,18.9-3.3,24.8-9.9c5.9-6.6,8.9-16,8.9-28.3v-6.1c0-11.9-2.8-21.1-8.4-27.7s-13.5-9.9-23.7-10L235.4,55.4L235.4,55.4z"/>
<path class="st0" d="M627.1,148.7h-13.9l-52.7-80.6v80.6h-13.9V44.1h13.9l52.8,81v-81H627v104.6H627.1z"/>
<path class="st0" d="M900.9,100.1l-12.8,13.3v35.3h-13.8V44.1h13.8v51.7l46.5-51.7h16.7l-41.2,46.2l44.4,58.4H938L900.9,100.1z"/>
<polygon class="st0" points="423.2,61.1 455.1,148.7 469.2,148.7 429.2,44.1 417.1,44.1 377.2,148.7 391.4,148.7 "/>
<polygon class="st0" points="750.6,61.1 782.6,148.7 796.7,148.7 756.7,44.1 744.6,44.1 704.7,148.7 718.9,148.7 "/>
</g>
<path class="st0" d="M132.1,140.4c0,0-26.1,25.2-48,46.5v28.9c-2.2,1.7,77.6-75.4,77.6-75.4L84.1,59v31L132.1,140.4z"/>
<g>
<polygon class="st0" points="83.7,157.5 6.5,75.1 7.5,74.2 84.7,156.6 "/>
<polygon class="st0" points="83.7,149.8 13.6,75.2 14.6,74.3 84.6,148.9 "/>
<polygon class="st0" points="83.7,142.1 20.7,75.4 21.7,74.4 84.6,141.1 "/>
<polygon class="st0" points="83.6,134.3 27.8,75.5 28.7,74.6 84.6,133.4 "/>
<polygon class="st0" points="83.6,126.6 34.9,75.6 35.8,74.7 84.6,125.7 "/>
</g>
<g>
<polygon class="st0" points="8,75.5 7.1,74.5 83.7,0 84.6,0.9 "/>
<polygon class="st0" points="15.2,75.5 14.3,74.6 83.6,7 84.5,7.9 "/>
<polygon class="st0" points="22.4,75.6 21.4,74.7 83.6,14 84.5,14.9 "/>
<polygon class="st0" points="29.5,75.6 28.6,74.7 83.5,21 84.4,22 "/>
<polygon class="st0" points="36.7,75.7 35.7,74.8 83.5,28 84.4,29 "/>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.89 595.28">
<!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
<defs>
<style>
.st0 {
fill: #231f20;
}
</style>
</defs>
<path d="M366.35,300.52v12.08h-29.98v-12.08h29.98Z"/>
<path d="M237.82,332.6h-9.97v-3.26c0-1.08.22-1.9.67-2.44.45-.54,1.11-.82,1.98-.82.47,0,.88.13,1.24.4.36.26.64.62.83,1.08.15-.54.44-.96.86-1.28.42-.31.93-.47,1.51-.47.89,0,1.6.29,2.11.87s.77,1.4.77,2.46v3.46ZM232.1,331.29v-1.98c0-.58-.14-1.04-.43-1.38-.29-.34-.68-.52-1.17-.52-.55,0-.95.16-1.2.48-.25.32-.37.81-.37,1.46v1.94h3.18ZM233.16,331.29h3.59v-2.17c0-.61-.16-1.09-.48-1.45-.32-.35-.75-.53-1.31-.53-1.2,0-1.8.65-1.8,1.96v2.19Z"/>
<path d="M232.85,320.33l-5-2.6v-1.49l6.25,3.44h3.72v1.31h-3.72l-6.25,3.44v-1.51l5-2.59Z"/>
<path d="M237.58,310.17h-9.99v-2.82c0-.87.19-1.64.58-2.31.38-.67.93-1.18,1.64-1.55.71-.36,1.52-.55,2.44-.55h.64c.94,0,1.77.18,2.48.55s1.25.88,1.63,1.56c.38.67.57,1.46.58,2.36v2.77ZM228.67,308.85h7.84v-1.39c0-1.02-.32-1.81-.95-2.37-.63-.57-1.53-.85-2.7-.85h-.58c-1.14,0-2.02.27-2.65.8-.63.53-.95,1.29-.96,2.27v1.54Z"/>
<path d="M237.58,282.61v1.32l-7.7,5.03h7.7v1.32h-9.99v-1.32l7.74-5.04h-7.74v-1.31h9.99Z"/>
<path d="M232.93,267.02l1.27,1.22h3.38v1.32h-9.99v-1.32h4.94l-4.94-4.44v-1.59l4.41,3.93,5.58-4.24v1.58l-4.65,3.54Z"/>
<polygon points="229.21 296.84 237.58 293.79 237.58 292.44 227.59 296.27 227.59 297.42 237.58 301.24 237.58 299.88 229.21 296.84"/>
<polygon points="229.21 276.17 237.58 273.12 237.58 271.77 227.59 275.6 227.59 276.75 237.58 280.57 237.58 279.21 229.21 276.17"/>
<path class="st0" d="M414.84,314.81h.3l18.96-52.91h19.32v71.79h-14.81v-49.01l-.3-.05-18.25,49.06h-10.04l-18.25-49.16-.3.05v49.11h-14.8v-71.79h19.42l18.76,52.91Z"/>
<path class="st0" d="M553.87,333.71h-15.05v-71.79h15.05v71.79Z"/>
<path class="st0" d="M580.35,322.65h34.83v11.05h-50.42v-71.79h15.6v60.75Z"/>
<polygon class="st0" points="503.2 261.9 486.88 261.9 460.07 333.69 476.72 333.69 482.07 318.21 482.09 318.21 486.02 306.92 486 306.92 490.09 295.23 490.09 295.23 490.09 295.23 494.85 281.62 495.17 281.62 502.88 303.78 502.88 303.78 502.88 303.78 503.96 306.92 503.96 306.92 509.79 323.79 509.79 323.79 513.25 333.69 529.9 333.69 503.2 261.9"/>
<path d="M290.8,261.9c-8.76,0-27.37.05-27.79.07,8.69,8.7,25.29,25.04,35.12,34.83-1.03,1.03-2.08,2.08-3.19,3.19-3.01,3.01-5.04,3.73-8.08.69-12.46-12.46-38.65-38.71-38.75-38.78h-5.28v71.79s11.97-.02,12.57-.02v-46.95c.06-.22,4.3,3.95,9.51,9.14l-.45,37.85c.65,0,18.09.02,25.65-.01,1.88,0,3.78-.11,5.65-.31,16.3-1.82,29.74-15.62,31.21-32.01,1.93-21.53-14.52-39.48-36.16-39.48ZM289.13,273.48c9.28-.86,17.69,3.92,21.54,10.81-1.8,1.8-3.48,3.47-5.12,5.1-4.95-4.8-13.48-13.06-16.42-15.91ZM291.13,321.97c-2.92.05-5.84.05-8.76.04h0s-7.68,0-7.68,0v-16.34c2.08,2.08,4,4.01,5.57,5.56,6.21,6.16,15.69,3.55,21.9-2.58,4.29-4.24,12.61-12.65,13.24-13.24.93,14.06-10.11,26.33-24.28,26.58Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

+2 -2
View File
@@ -85,7 +85,7 @@ const Table = <T extends RowDataType>({
if (data.length === 0) {
return (
<div className={`bg-white ${roundedClass} py-6 md:py-8 text-center text-gray-500`}>
<div className={`bg-white ${roundedClass} text-center text-gray-500`}>
{noDataMessage}
</div>
);
@@ -155,7 +155,7 @@ const Table = <T extends RowDataType>({
if (data.length === 0) {
return (
<tr>
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 py-6 md:py-8 text-center text-gray-500">
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 pb-6 md:pb-8 text-center text-gray-500">
{noDataMessage}
</td>
</tr>
+108 -60
View File
@@ -1,9 +1,9 @@
import { FC, useState, useEffect } from 'react'
import { FC, useState, useEffect, useRef } from 'react'
import { CloseCircle } from 'iconsax-react'
import { useTranslation } from 'react-i18next'
import { useSharedStore } from '@/shared/store/sharedStore'
import { useAuthStore } from '../../pages/auth/store/AuthStore'
import { useSendEmail, useSaveDraft } from '../../pages/received/hooks/useEmailData'
import { useSendEmail } from '../../pages/received/hooks/useEmailData'
import { SendEmailDto } from '../../pages/received/types/Types'
import Input from '../Input'
import ReactQuill from 'react-quill-new';
@@ -11,6 +11,7 @@ import Button from '../Button'
import Select from '../Select'
import UploadButton from '../UploadButton'
import { toast } from '../Toast'
import { ErrorType } from '@/helpers/types'
const NewMessage: FC = () => {
@@ -28,10 +29,15 @@ const NewMessage: FC = () => {
const [subject, setSubject] = useState('')
const [priority, setPriority] = useState('')
// Loading states
const [isSending, setIsSending] = useState(false)
const [isSavingDraft, setIsSavingDraft] = useState(false)
// Auto-add email functionality
const typingTimeoutRef = useRef<number | null>(null)
// API hooks
const sendEmailMutation = useSendEmail()
const saveDraftMutation = useSaveDraft()
useEffect(() => {
if (openNewMessage) {
@@ -70,63 +76,92 @@ const NewMessage: FC = () => {
return emailRegex.test(email)
}
const addEmailToList = (email: string) => {
if (!isValidEmail(email)) {
return
}
if (toEmails.includes(email)) {
toast('این ایمیل قبلاً اضافه شده است', 'error')
return
}
setToEmails([...toEmails, email])
setTo('')
}
const handleToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value
setTo(newValue)
// Clear existing timeout
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current)
}
// Set new timeout to auto-add email after user stops typing
if (newValue.trim()) {
typingTimeoutRef.current = window.setTimeout(() => {
if (isValidEmail(newValue.trim())) {
addEmailToList(newValue.trim())
}
}, 1500) // Wait 1.5 seconds after user stops typing
}
}
const handleToBlur = () => {
// Clear timeout when input loses focus
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current)
}
// Auto-add email if valid when input loses focus
if (to.trim() && isValidEmail(to.trim())) {
addEmailToList(to.trim())
}
}
const handleToKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && to.trim()) {
e.preventDefault()
const email = to.trim()
if (!isValidEmail(email)) {
toast('لطفا یک ایمیل معتبر وارد کنید', 'error')
return
// Clear timeout when Enter is pressed
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current)
}
if (toEmails.includes(email)) {
toast('این ایمیل قبلاً اضافه شده است', 'error')
return
}
setToEmails([...toEmails, email])
setTo('')
addEmailToList(to.trim())
}
}
// Clean up timeout on component unmount
useEffect(() => {
return () => {
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current)
}
}
}, [])
const removeEmail = (emailToRemove: string) => {
setToEmails(toEmails.filter(email => email !== emailToRemove))
}
const resetForm = () => {
setTo('')
setToEmails([])
setSubject('')
setValue('')
setPriority('')
}
const handleSend = async () => {
if (toEmails.length === 0 || !subject) {
const handleEmailAction = async (isDraft: boolean = false) => {
if (!isDraft && (toEmails.length === 0 || !subject)) {
toast('لطفا تمام فیلدهای مورد نیاز را پر کنید', 'error')
return
}
const emailData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: parseEmailAddresses(toEmails),
subject,
html: value,
text: value.replace(/<[^>]*>/g, ''), // Strip HTML for text version
}
try {
await sendEmailMutation.mutateAsync(emailData)
toast('ایمیل با موفقیت ارسال شد', 'success')
handleClose()
// Reset form
setTo('')
setToEmails([])
setSubject('')
setValue('')
setPriority('')
} catch {
toast('خطا در ارسال ایمیل', 'error')
}
}
const handleSaveDraft = async () => {
if (toEmails.length === 0 && !subject && !value) {
if (isDraft && toEmails.length === 0 && !subject && !value) {
toast('حداقل یکی از فیلدها باید پر باشد', 'error')
return
}
@@ -136,25 +171,37 @@ const NewMessage: FC = () => {
to: parseEmailAddresses(toEmails),
subject,
html: value,
text: value.replace(/<[^>]*>/g, ''),
isDraft: true,
text: value.replace(/<[^>]*>/g, ''), // Strip HTML for text version
...(isDraft && { isDraft: true, uploadOnly: true }),
}
try {
await saveDraftMutation.mutateAsync(emailData)
toast('پیش‌نویس ذخیره شد', 'success')
handleClose()
// Reset form
setTo('')
setToEmails([])
setSubject('')
setValue('')
setPriority('')
} catch {
toast('خطا در ذخیره پیش‌نویس', 'error')
// Set appropriate loading state
if (isDraft) {
setIsSavingDraft(true)
} else {
setIsSending(true)
}
await sendEmailMutation.mutateAsync(emailData, {
onSuccess: (data) => {
toast(data.message, 'success')
handleClose()
resetForm()
},
onError: (error: ErrorType) => {
toast(error.response?.data?.error.message[0], 'error')
},
onSettled: () => {
// Reset loading states
setIsSending(false)
setIsSavingDraft(false)
}
})
}
const handleSend = () => handleEmailAction(false)
const handleSaveDraft = () => handleEmailAction(true)
const priorityOptions = [
{ value: 'high', label: 'بالا' },
{ value: 'medium', label: 'متوسط' },
@@ -170,7 +217,7 @@ const NewMessage: FC = () => {
}`}
onClick={handleClose}
/>
<div className={`fixed left-2 right-2 bottom-2 md:left-4 md:right-4 md:bottom-4 xl:left-8 xl:right-auto xl:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
<div className={`fixed left-2 right-2 bottom-2 md:left-4 md:right-4 md:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
}`}>
{/* Header */}
<div className='flex justify-between items-center mb-6 md:mb-8'>
@@ -209,7 +256,8 @@ const NewMessage: FC = () => {
{/* Input Field */}
<input
value={to}
onChange={(e) => setTo(e.target.value)}
onChange={handleToChange}
onBlur={handleToBlur}
onKeyDown={handleToKeyDown}
placeholder={toEmails.length === 0 ? "ایمیل را وارد کرده و Enter بزنید" : ""}
className="flex-1 min-w-32 bg-transparent outline-none"
@@ -263,13 +311,13 @@ const NewMessage: FC = () => {
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('new_message.draft')}
onClick={handleSaveDraft}
loading={saveDraftMutation.isPending}
loading={isSavingDraft}
/>
<Button
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
label={t('new_message.send')}
onClick={handleSend}
loading={sendEmailMutation.isPending}
loading={isSending}
/>
</div>
</div>
+14
View File
@@ -247,3 +247,17 @@ textarea::placeholder {
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
display: none;
}
/* انیمیشن چرخش معکوس */
@keyframes spin-reverse {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
.animate-spin-reverse {
animation: spin-reverse 1s linear infinite;
}
+98 -43
View File
@@ -5,42 +5,55 @@ import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2, Trash, Edit } from 'iconsax-react';
import { RowActionItem } from '../../components/RowActionsDropdown';
interface Message extends Record<string, unknown> {
id: string;
title: string;
sender: string;
date: string;
read: boolean;
}
import { useNavigate } from 'react-router-dom';
import { useGetDrafts, useDeleteDraft } from './hooks/useDraftData';
import { DraftMessage } from './types/DraftTypes';
import { formatDate } from '@/config/func';
const List: FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const [isLoading] = useState(false);
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<FilterValues>({});
const messageData: Message[] = [
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
];
const { data: draftData, isLoading, refetch, isFetching } = useGetDrafts({
page: currentPage,
limit: 10,
...filters
});
const columns: ColumnType<Message>[] = [
const deleteMutation = useDeleteDraft();
const [selectedMessages, setSelectedMessages] = useState<DraftMessage[]>([]);
const columns: ColumnType<DraftMessage>[] = [
{
key: 'title',
title: 'عنوان پیام',
render: (item) => (
<div className="flex items-center">
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
<span className="truncate">{item.title}</span>
title: 'title',
render: () => (
<div className="text-[#0038FF]">
پیش نویس
</div>
)
},
{
key: 'sender',
title: 'فرستنده',
key: 'subject',
title: 'عنوان پیام',
render: (item) => (
<span className="truncate">{item.sender}</span>
<div className="flex items-center">
<span className="truncate">{item.subject || 'بدون موضوع'}</span>
</div>
)
},
{
key: 'to',
title: 'گیرنده',
render: (item) => (
<span className="truncate">
{item.to && item.to.length > 0
? (item.to[0].name || item.to[0].address)
: 'بدون گیرنده'
}
</span>
)
},
{
@@ -48,87 +61,129 @@ const List: FC = () => {
title: 'تاریخ',
align: 'center',
render: (item) => (
<span className="text-xs md:text-sm">{item.date}</span>
<span>{formatDate(item.date)}</span>
)
},
];
const tableActions = (
<div className='flex items-center gap-2 md:gap-4'>
<Refresh2 size={18} color='black' className="cursor-pointer" />
<Refresh2
size={18}
color='black'
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
onClick={() => refetch()}
/>
<More size={18} color='black' className='rotate-90 cursor-pointer' />
</div>
);
const selectedActions = (
<>
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
<Edit size={18} color='black' onClick={() => handleEditSelected()} className="cursor-pointer" />
<Trash
size={18}
color='black'
onClick={() => handleDeleteSelected()}
className="cursor-pointer"
/>
<Edit
size={18}
color='black'
onClick={() => handleEditSelected()}
className="cursor-pointer"
/>
</>
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
setFilters(newFilters);
setCurrentPage(1); // Reset to first page when filters change
};
const handleSelectionChange = (selectedRows: Message[]) => {
const handleSelectionChange = (selectedRows: DraftMessage[]) => {
setSelectedMessages(selectedRows);
};
const handleDeleteSelected = () => {
console.log('حذف پیام‌های انتخاب شده:', selectedMessages.map(m => m.id));
// اینجا منطق حذف را پیاده‌سازی کنید
selectedMessages.forEach(message => {
deleteMutation.mutate(message.id.toString());
});
setSelectedMessages([]);
};
const handleEditSelected = () => {
console.log('ویرایش پیام‌های انتخاب شده:', selectedMessages.map(m => m.id));
// اینجا منطق ویرایش را پیاده‌سازی کنید
if (selectedMessages.length === 1) {
// Navigate to edit page for single draft
navigate(`/draft/edit/${selectedMessages[0].id}`);
} else {
console.log('ویرایش چندین پیش‌نویس انتخاب شده:', selectedMessages.map(m => m.id));
}
};
const getRowActions = (message: Message): RowActionItem[] => [
const handleDeleteDraft = (messageId: number) => {
deleteMutation.mutate(messageId.toString());
};
const handleEditDraft = (messageId: number) => {
navigate(`/draft/edit/${messageId}`);
};
const getRowActions = (message: DraftMessage): RowActionItem[] => [
{
label: 'ویرایش',
icon: <Edit size={16} color="black" />,
onClick: () => console.log('ویرایش پیام', message.id),
onClick: () => handleEditDraft(message.id),
},
{
label: 'حذف',
icon: <Trash size={16} color="red" />,
onClick: () => console.log('حذف پیام', message.id),
onClick: () => handleDeleteDraft(message.id),
},
];
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
const messages = draftData?.data?.draftsMail || [];
const pager = draftData?.data?.pager;
return (
<div className='mt-2 md:mt-4 px-2 md:px-0'>
<h1 className="text-lg mb-4 md:mb-0">{t('draft.title')}</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('draft.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('draft.to_date') },
{ type: 'date', name: 'dateFrom', placeholder: t('draft.from_date') },
{ type: 'date', name: 'dateTo', placeholder: t('draft.to_date') },
{ type: 'input', name: 'search', placeholder: t('draft.search') }
]}
onChange={handleFilterChange}
searchField="search"
/>
<Table<Message>
<Table<DraftMessage>
columns={columns}
data={messageData}
data={messages}
isLoading={isLoading}
showHeader={false}
actions={tableActions}
selectedActions={selectedActions}
onSelectionChange={handleSelectionChange}
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
onRowClick={(id) => navigate(`/draft/edit/${id}`)}
noDataMessage={
<div className="flex flex-col items-center py-6">
<div className="flex flex-col items-center ">
<InfoCircle size={32} className="text-gray-300 mb-2" />
<div className="text-sm">هیچ پیشنویسی یافت نشد</div>
</div>
}
selectable={true}
rowActions={getRowActions}
pagination={pager ? {
totalPages: pager.totalPages,
currentPage: pager.page,
onPageChange: handlePageChange
} : undefined}
/>
</div>
)
+45
View File
@@ -0,0 +1,45 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as api from '../service/DraftService';
import { MessageListQueryDto, DraftUpdateDto } from '../types/DraftTypes';
export const useGetDrafts = (query: MessageListQueryDto) => {
return useQuery({
queryKey: ['drafts', query],
queryFn: () => api.getDrafts(query),
staleTime: 1000 * 60 * 5, // 5 minutes
});
};
export const useGetDraftDetail = (messageId: string, enabled: boolean = true) => {
return useQuery({
queryKey: ['draft', messageId],
queryFn: () => api.getDraftDetail(messageId),
enabled: !!messageId && enabled,
staleTime: 1000 * 60 * 10, // 10 minutes
});
};
export const useDeleteDraft = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (messageId: string) => api.deleteDraft(messageId),
onSuccess: () => {
// Invalidate drafts queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['drafts'] });
},
});
};
export const useEditDraft = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ messageId, draftData }: { messageId: string; draftData: DraftUpdateDto }) =>
api.editDraft(messageId, draftData),
onSuccess: () => {
// Invalidate drafts queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['drafts'] });
},
});
};
+37
View File
@@ -0,0 +1,37 @@
import axios from "../../../config/axios";
import {
MessageListQueryDto,
InboxResponse,
InboxMessage,
DraftUpdateDto,
} from "../types/DraftTypes";
export const getDrafts = async (
query: MessageListQueryDto
): Promise<InboxResponse> => {
const { data } = await axios.get(`/email/messages/drafts`, { params: query });
return data;
};
export const deleteDraft = async (messageId: string): Promise<void> => {
const { data } = await axios.delete(`/email/messages/drafts/${messageId}`);
return data;
};
export const editDraft = async (
messageId: string,
draftData: DraftUpdateDto
): Promise<void> => {
const { data } = await axios.put(
`/email/messages/drafts/${messageId}`,
draftData
);
return data;
};
export const getDraftDetail = async (
messageId: string
): Promise<InboxMessage> => {
const { data } = await axios.get(`/email/messages/drafts/${messageId}`);
return data.data.message;
};
+107
View File
@@ -0,0 +1,107 @@
export interface EmailRecipientDto {
name?: string;
address: string;
}
export interface EmailHeaderDto {
key: string;
value: string;
}
export interface EmailAttachmentDto {
filename?: string;
contentType?: string;
encoding?: string;
contentTransferEncoding?: string;
contentDisposition?: "inline" | "attachment";
content: string;
cid?: string;
}
export interface MessageListQueryDto {
page?: number;
limit?: number;
order?: string;
search?: string;
from?: string;
subject?: string;
dateFrom?: string;
dateTo?: string;
hasAttachment?: boolean;
isRead?: boolean;
isFlagged?: boolean;
}
export interface DraftMessage extends Record<string, unknown> {
id: number;
mailbox: string;
thread: string;
from: {
address: string;
name: string;
};
to: Array<{
address: string;
name: string;
}>;
cc: Array<{
address: string;
name: string;
}>;
bcc: Array<{
address: string;
name: string;
}>;
messageId: string;
subject: string;
date: string;
idate: string;
intro: string;
attachments: boolean;
attachmentsList: EmailAttachmentDto[];
size: number;
seen: boolean;
deleted: boolean;
flagged: boolean;
draft: boolean;
answered: boolean;
forwarded: boolean;
references: string[];
contentType: {
value: string;
params: {
boundary: string;
};
};
}
export type InboxMessage = DraftMessage;
export interface InboxPager {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean;
nextPage: boolean;
}
export interface InboxResponse {
statusCode: number;
success: boolean;
data: {
pager: InboxPager;
draftsMail: DraftMessage[];
};
}
export interface DraftUpdateDto {
from?: EmailRecipientDto;
to?: EmailRecipientDto[];
cc?: EmailRecipientDto[];
bcc?: EmailRecipientDto[];
subject?: string;
text?: string;
html?: string;
attachments?: EmailAttachmentDto[];
}
@@ -0,0 +1,67 @@
import { FC } from 'react'
const SkeletonDetail: FC = () => {
return (
<div className='bg-white rounded-4xl p-8'>
<div className='animate-pulse'>
{/* Header Skeleton */}
<div className='h-6 bg-gray-200 rounded w-48 mb-6'></div>
{/* Subject and Tags Section */}
<div className='mt-6 flex xl:flex-row flex-col justify-between xl:items-center'>
<div className='flex xl:flex-row flex-col-reverse gap-4 xl:items-center'>
<div className='flex gap-1 items-center'>
<div className='h-4 bg-gray-200 rounded w-12 hidden xl:block'></div>
<div className='h-5 bg-gray-200 rounded w-40'></div>
</div>
<div className='flex gap-2'>
<div className='h-6 xl:h-10 bg-gray-200 rounded-full w-20'></div>
<div className='h-6 xl:h-10 bg-gray-200 rounded-full w-24'></div>
</div>
</div>
<div className='h-4 bg-gray-200 rounded w-24'></div>
</div>
{/* Sender Info */}
<div className='mt-6 flex gap-4 items-center'>
<div className='size-10 bg-gray-200 rounded-full'></div>
<div className='h-4 bg-gray-200 rounded w-32'></div>
</div>
{/* Email Content */}
<div className='xl:mt-8 mt-3 space-y-3'>
<div className='h-4 bg-gray-200 rounded w-full'></div>
<div className='h-4 bg-gray-200 rounded w-full'></div>
<div className='h-4 bg-gray-200 rounded w-3/4'></div>
<div className='h-4 bg-gray-200 rounded w-full'></div>
<div className='h-4 bg-gray-200 rounded w-2/3'></div>
</div>
{/* Attachments Section */}
<div className='mt-10'>
<div className='h-4 bg-gray-200 rounded w-20 mb-4'></div>
<div className='flex border-b border-gray-200 pb-10 mt-4 flex-wrap gap-2.5 items-center'>
<div className='h-10 bg-gray-200 rounded-full w-36'></div>
</div>
</div>
{/* Action Buttons */}
<div className='flex mt-9 justify-end'>
<div className='flex gap-5'>
<div className='size-6 bg-gray-200 rounded'></div>
<div className='flex gap-2 items-center border-r border-gray-200 pr-5'>
<div className='w-[17px] h-[17px] bg-gray-200 rounded'></div>
<div className='h-4 bg-gray-200 rounded w-12'></div>
</div>
<div className='flex gap-2 items-center border-r border-gray-200 pr-5'>
<div className='w-[17px] h-[17px] bg-gray-200 rounded'></div>
<div className='h-4 bg-gray-200 rounded w-16'></div>
</div>
</div>
</div>
</div>
</div>
)
}
export default SkeletonDetail
+18 -24
View File
@@ -9,11 +9,12 @@ import AnswerIcon from '@/assets/images/answer.svg'
import Header from './Components/Header'
import ReactQuill from 'react-quill-new';
import Select from '@/components/Select'
import { useGetMessageDetail, useSendEmail, useSaveDraft } from './hooks/useEmailData'
import { useGetMessageDetail, useSendEmail } from './hooks/useEmailData'
import { useAuthStore } from '../auth/store/AuthStore'
import { SendEmailDto } from './types/Types'
import { toast } from '@/components/Toast'
import { formatDate } from '@/config/func'
import SkeletonDetail from './Components/SkeletonDetail'
const DetailEmail: FC = () => {
@@ -27,7 +28,7 @@ const DetailEmail: FC = () => {
// API hooks
const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', !!id)
const sendEmailMutation = useSendEmail()
const saveDraftMutation = useSaveDraft()
// const saveDraftMutation = useSaveDraft()
const priorityOptions = [
{ value: 'high', label: 'بالا' },
@@ -80,21 +81,21 @@ const DetailEmail: FC = () => {
return
}
const draftData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
subject: `Re: ${messageDetail.subject}`,
html: value,
text: value.replace(/<[^>]*>/g, ''),
isDraft: true,
reference: {
inReplyTo: messageDetail.messageId,
originalMessageId: messageDetail.id.toString()
}
}
// const draftData: SendEmailDto = {
// from: { address: userEmail || 'sender@example.com' },
// to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
// subject: `Re: ${messageDetail.subject}`,
// html: value,
// text: value.replace(/<[^>]*>/g, ''),
// isDraft: true,
// reference: {
// inReplyTo: messageDetail.messageId,
// originalMessageId: messageDetail.id.toString()
// }
// }
try {
await saveDraftMutation.mutateAsync(draftData)
// await saveDraftMutation.mutateAsync(draftData)
toast('پیش‌نویس ذخیره شد', 'success')
} catch {
toast('خطا در ذخیره پیش‌نویس', 'error')
@@ -108,14 +109,7 @@ const DetailEmail: FC = () => {
if (isLoading) {
return (
<div className='bg-white rounded-4xl p-8'>
<div className='animate-pulse'>
<div className='h-6 bg-gray-200 rounded mb-4'></div>
<div className='h-4 bg-gray-200 rounded mb-2'></div>
<div className='h-4 bg-gray-200 rounded mb-2'></div>
<div className='h-20 bg-gray-200 rounded'></div>
</div>
</div>
<SkeletonDetail />
)
}
@@ -247,7 +241,7 @@ const DetailEmail: FC = () => {
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('new_message.draft')}
onClick={handleSaveDraft}
loading={saveDraftMutation.isPending}
// loading={saveDraftMutation.isPending}
/>
<Button
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
+2 -2
View File
@@ -16,7 +16,7 @@ const List: FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<FilterValues>({});
const { data: inboxData, isLoading, refetch } = useGetInbox({
const { data: inboxData, isLoading, refetch, isFetching } = useGetInbox({
page: currentPage,
limit: 10,
...filters
@@ -57,7 +57,7 @@ const List: FC = () => {
<Refresh2
size={18}
color='black'
className="cursor-pointer"
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
onClick={() => refetch()}
/>
<More size={18} color='black' className='rotate-90 cursor-pointer' />
@@ -14,6 +14,14 @@ export const useSaveDraft = () => {
});
};
export const useGetDraft = (query: MessageListQueryDto) => {
return useQuery({
queryKey: ['draft', query],
queryFn: () => api.getDraft(query),
staleTime: 1000 * 60 * 5, // 5 minutes
});
};
export const useGetInbox = (query: MessageListQueryDto) => {
return useQuery({
queryKey: ['inbox', query],
+8 -4
View File
@@ -13,10 +13,14 @@ export const sendEmail = async (params: SendEmailDto) => {
};
export const saveDraft = async (params: SendEmailDto) => {
const { data } = await axios.post(`/email/draft`, {
...params,
isDraft: true,
});
const { data } = await axios.post(`/email/messages/drafts`, params);
return data;
};
export const getDraft = async (
query: MessageListQueryDto
): Promise<InboxResponse> => {
const { data } = await axios.get(`/email/messages/drafts`, { params: query });
return data;
};